From 63d87b17aa892413dd3e336745b3f446e69c4ede 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 001/549] 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 3869e56521724d0df7eb2b2b6bcc141eb62bb0d1 Mon Sep 17 00:00:00 2001 From: puuu Date: Sat, 21 Aug 2021 19:26:24 +0900 Subject: [PATCH 002/549] 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 ad953f02d11c4b5faa85706a75366324c2b04293 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 23 Aug 2021 10:44:24 +0200 Subject: [PATCH 003/549] 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 12eab6a685..80a1e14ffd 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -46,9 +46,14 @@ void AddressableLight::write_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 9de40c26ebb6f4d312bbbffcb16f1c86fb268a23 Mon Sep 17 00:00:00 2001 From: puuu Date: Tue, 24 Aug 2021 10:18:40 +0900 Subject: [PATCH 004/549] 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 481e0e98f877e186a1dc9c5790c0d512ddd2cec2 Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Mon, 23 Aug 2021 20:20:39 -0500 Subject: [PATCH 005/549] 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 44f8dcfb6e602748a3a2619c84ac9f7cf1177d75 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Mon, 23 Aug 2021 18:26:59 -0700 Subject: [PATCH 006/549] 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 e7404183a026758ca55b2228eecc948b505bdb42 Mon Sep 17 00:00:00 2001 From: mtl010957 Date: Mon, 23 Aug 2021 21:38:59 -0400 Subject: [PATCH 007/549] 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 0a4837c1f02931387ae624c1420491e8905e2037 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 24 Aug 2021 14:26:28 +1200 Subject: [PATCH 008/549] Bump version to 2021.8.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index e25ba7e046..823cbd5924 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.8.0" +__version__ = "2021.8.1" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 877a5fda41f1b47eb3fd3437278b5c7e9c43256a 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 009/549] 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 4937af0cd9311f1e45c149395c5b21ebd8e64d5f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 25 Aug 2021 19:46:55 +1200 Subject: [PATCH 010/549] Bump version to 2021.8.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 823cbd5924..ce3b95b731 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.8.1" +__version__ = "2021.8.2" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 09a6fdf1c7358767909e56ef1bbfb92638119933 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 011/549] Bump version to 2021.9.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 07d0079172..44e3c09870 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.0-dev" +__version__ = "2021.9.0b1" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 7dd40e2014a04a3af560c212815cd77ca604b667 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 012/549] 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 87842e097bcb02893d2dea286f4af7970fa3492f Mon Sep 17 00:00:00 2001 From: poptix Date: Fri, 10 Sep 2021 04:05:25 -0500 Subject: [PATCH 013/549] 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 9821a3442bc7e10654af3b26b6c8785efe9a6de4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 10 Sep 2021 21:34:38 +1200 Subject: [PATCH 014/549] Bump version to 2021.9.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 44e3c09870..100d89594b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.0b1" +__version__ = "2021.9.0b2" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 4eb51ab4d6c48301ff7073aeae4c4ba5c6753faa Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 02:44:39 +0200 Subject: [PATCH 015/549] 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 e92a9d1d9e4b6dbab9f68b570f87cc17a404d27a Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 13 Sep 2021 18:52:53 +0200 Subject: [PATCH 016/549] 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 650f4f6f6e..1a365bc0b0 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_) @@ -700,7 +729,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 @@ -746,15 +775,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_) @@ -767,7 +788,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(); @@ -775,14 +800,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 c4c193b389..33843f384b 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -104,7 +104,7 @@ void APIServer::loop() { std::partition(this->clients_.begin(), this->clients_.end(), [](APIConnection *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()); } // only then delete the pointers, otherwise log routine // would access freed memory diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index aaeee7268a..39741ea7ec 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 91f12a50cf371ce797310aab06b69e7c95d7b066 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 14 Sep 2021 07:13:00 +1200 Subject: [PATCH 017/549] Bump version to 2021.9.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 100d89594b..aa52a28ba8 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.0b2" +__version__ = "2021.9.0b3" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 1b5f11bbee25a3eea1d65b8ba440b8e0bdc7d0d4 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 09:37:11 +0200 Subject: [PATCH 018/549] 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 23ead416d53dc71c764eabcc50b41980a369b03a Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 09:39:18 +0200 Subject: [PATCH 019/549] 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 1a365bc0b0..786fc28d68 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -243,6 +243,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; @@ -291,13 +294,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 460a144ca8be65825e28efbfbebedd65ef0c9935 Mon Sep 17 00:00:00 2001 From: Jas Strong Date: Mon, 13 Sep 2021 00:54:48 -0700 Subject: [PATCH 020/549] 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 39a18fb35831c1fa15f3fc7c56ba79ff8b16d5ef Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 13 Sep 2021 21:16:13 +0200 Subject: [PATCH 021/549] 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 54a279f845..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 = "3.6.0" +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 233783c76c9638d7338ba53dbda50868c4332ea6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 14 Sep 2021 09:53:37 +0200 Subject: [PATCH 022/549] 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 a328fff5a77eae1e03b8b7c73be78312eb880d48 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 14 Sep 2021 11:53:49 +0200 Subject: [PATCH 023/549] 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 a32ad33b4eb885193852029edca361f6ed8529d0 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 024/549] 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 89f2ea572562cc41de5cf36f0f99209614bea0be 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 025/549] 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 5fad38f65f397cb0e8b18841441c40908cd404fb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 14 Sep 2021 23:07:08 +1200 Subject: [PATCH 026/549] Bump version to 2021.9.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index aa52a28ba8..c5c08b74d4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.0b3" +__version__ = "2021.9.0b4" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 3b9d1263229f65b11f21c253b7687d6472f3a20f 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 027/549] 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 c5c08b74d4..587309d213 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 2d79d21c5007fdb874f9d029c3575c2da2429273 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 028/549] 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 efae363739635aaf28e7e62f0aa2e72c302292fc Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 15 Sep 2021 08:48:27 +0200 Subject: [PATCH 029/549] 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 ad5f2cd7481ac625fd995ac541e7d27028f52eeb 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 030/549] 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 50feeb6cad..282900260d 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 b422a63b2a4c066f2ba1e3f65a1c887a757608d2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 15 Sep 2021 19:01:54 +1200 Subject: [PATCH 031/549] Bump version to 2021.9.0b5 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 587309d213..2cd6d05da5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.0b4" +__version__ = "2021.9.0b5" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 0f4a7bf1f548f8fe338e8aee188e93d9da064578 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 16 Sep 2021 09:12:01 +1200 Subject: [PATCH 032/549] Bump version to 2021.9.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 2cd6d05da5..5a5351f1b0 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.0b5" +__version__ = "2021.9.0" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From c78fb90e2f3f89fd2a583296dce115eb46e82585 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Mon, 20 Sep 2021 04:30:41 +1200 Subject: [PATCH 033/549] Fix MQTT discovery for sensor state_class (#2331) --- esphome/components/sensor/sensor.cpp | 2 +- esphome/components/sensor/sensor.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 1dbc1c901a..db81edcc61 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -6,7 +6,7 @@ namespace sensor { static const char *const TAG = "sensor"; -const char *state_class_to_string(StateClass state_class) { +std::string state_class_to_string(StateClass state_class) { switch (state_class) { case STATE_CLASS_MEASUREMENT: return "measurement"; diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 34b8b26a54..209e83d205 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -1,8 +1,8 @@ #pragma once +#include "esphome/components/sensor/filter.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" -#include "esphome/components/sensor/filter.h" namespace esphome { namespace sensor { @@ -13,7 +13,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, state_class_to_string((obj)->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()) { \ @@ -36,7 +36,7 @@ enum StateClass : uint8_t { STATE_CLASS_TOTAL_INCREASING = 2, }; -const char *state_class_to_string(StateClass state_class); +std::string state_class_to_string(StateClass state_class); /** Base-class for all sensors. * From 4c61cf153c8139acf20053fe4c8904fa45953dae Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 19 Sep 2021 18:31:20 +0200 Subject: [PATCH 034/549] 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 945d3910d5..d9574abf7a 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 e3ffecefc098aba2499ce3f86c132486f855dc9b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 19 Sep 2021 18:31:31 +0200 Subject: [PATCH 035/549] 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 d180aee57f8f93dc50cd38ed50dd85bba68246c0 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 19 Sep 2021 18:46:26 +0200 Subject: [PATCH 036/549] 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 7c17e72db42ae6016bf315a7b09368395b38736d Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 00:33:10 +0200 Subject: [PATCH 037/549] 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 786fc28d68..8e6d1bc1c2 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 a0cdb6ec42..aca15f118e 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -5,6 +5,10 @@ #include +#ifdef ARDUINO_ARCH_ESP32 +#include +#endif + namespace esphome { namespace socket { @@ -75,7 +79,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 39741ea7ec..3c225aeb82 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 954b8a0cfff66d54e6a24d16aab5849bfbd6e627 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 20 Sep 2021 14:16:57 +1200 Subject: [PATCH 038/549] Bump version to 2021.9.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 5a5351f1b0..fb7250578c 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.0" +__version__ = "2021.9.1" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 8bda8e5393bcb339120214dc17e8e50fa21f6db7 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 18:58:49 +0200 Subject: [PATCH 039/549] 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 | 138 ++++++++-------------- 7 files changed, 101 insertions(+), 209 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 8e6d1bc1c2..d64cff96c2 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 db81edcc61..0dc0275715 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -18,6 +18,51 @@ std::string 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 209e83d205..85f56eb8b9 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -47,26 +47,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); @@ -93,15 +109,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 @@ -127,35 +134,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 * @@ -163,65 +150,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 ed593544d896df1a830cd7320432ddcde9ddcbec Mon Sep 17 00:00:00 2001 From: Silvio <4004968+s1lvi0@users.noreply.github.com> Date: Wed, 22 Sep 2021 12:03:42 +0200 Subject: [PATCH 040/549] Add support for Daly Smart BMS (#2156) * Add support for Daly Smart BMS * Fix clang-format and python lint * Fix const declaration * Add code owner * Fix malloc with std::vector * Fix with suggestions * Revert "Fix with suggestions" This reverts commit bc618f20cf83e3df903fdbbca8d2529d946264b0. * Fix last commit * Fix Python Lint * Fix typo * Use std::vector instead pointer and fix loop * Fix typo * Add test configuration to test3.yaml * Fix test3.yaml * Fix uart in test3.yaml --- CODEOWNERS | 1 + esphome/components/daly_bms/__init__.py | 27 +++ esphome/components/daly_bms/binary_sensor.py | 49 +++++ esphome/components/daly_bms/daly_bms.cpp | 181 +++++++++++++++++ esphome/components/daly_bms/daly_bms.h | 83 ++++++++ esphome/components/daly_bms/sensor.py | 192 +++++++++++++++++++ esphome/components/daly_bms/text_sensor.py | 39 ++++ tests/test3.yaml | 44 +++++ 8 files changed, 616 insertions(+) create mode 100644 esphome/components/daly_bms/__init__.py create mode 100644 esphome/components/daly_bms/binary_sensor.py create mode 100644 esphome/components/daly_bms/daly_bms.cpp create mode 100644 esphome/components/daly_bms/daly_bms.h create mode 100644 esphome/components/daly_bms/sensor.py create mode 100644 esphome/components/daly_bms/text_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index adf96b7382..2577e0d706 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -39,6 +39,7 @@ esphome/components/coolix/* @glmnet esphome/components/cover/* @esphome/core esphome/components/cs5460a/* @balrog-kun esphome/components/ct_clamp/* @jesserockz +esphome/components/daly_bms/* @s1lvi0 esphome/components/debug/* @OttoWinter esphome/components/dfplayer/* @glmnet esphome/components/dht/* @OttoWinter diff --git a/esphome/components/daly_bms/__init__.py b/esphome/components/daly_bms/__init__.py new file mode 100644 index 0000000000..45b8f98f0c --- /dev/null +++ b/esphome/components/daly_bms/__init__.py @@ -0,0 +1,27 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart +from esphome.const import CONF_ID + +CODEOWNERS = ["@s1lvi0"] +DEPENDENCIES = ["uart"] +AUTO_LOAD = ["sensor", "text_sensor", "binary_sensor"] + +CONF_BMS_DALY_ID = "bms_daly_id" + +daly_bms = cg.esphome_ns.namespace("daly_bms") +DalyBmsComponent = daly_bms.class_( + "DalyBmsComponent", cg.PollingComponent, uart.UARTDevice +) + +CONFIG_SCHEMA = ( + cv.Schema({cv.GenerateID(): cv.declare_id(DalyBmsComponent)}) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.polling_component_schema("30s")) +) + + +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) diff --git a/esphome/components/daly_bms/binary_sensor.py b/esphome/components/daly_bms/binary_sensor.py new file mode 100644 index 0000000000..23330cd945 --- /dev/null +++ b/esphome/components/daly_bms/binary_sensor.py @@ -0,0 +1,49 @@ +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 DalyBmsComponent, CONF_BMS_DALY_ID + +CONF_CHARGING_MOS_ENABLED = "charging_mos_enabled" +CONF_DISCHARGING_MOS_ENABLED = "discharging_mos_enabled" + +TYPES = [ + CONF_CHARGING_MOS_ENABLED, + CONF_DISCHARGING_MOS_ENABLED, +] + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent), + cv.Optional( + CONF_CHARGING_MOS_ENABLED + ): binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor), + } + ), + cv.Optional( + CONF_DISCHARGING_MOS_ENABLED + ): binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor), + } + ), + } + ).extend(cv.COMPONENT_SCHEMA) +) + + +async def setup_conf(config, key, hub): + if key in config: + conf = config[key] + sens = cg.new_Pvariable(conf[CONF_ID]) + await binary_sensor.register_binary_sensor(sens, conf) + cg.add(getattr(hub, f"set_{key}_binary_sensor")(sens)) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_BMS_DALY_ID]) + for key in TYPES: + await setup_conf(config, key, hub) diff --git a/esphome/components/daly_bms/daly_bms.cpp b/esphome/components/daly_bms/daly_bms.cpp new file mode 100644 index 0000000000..19e8f12e1c --- /dev/null +++ b/esphome/components/daly_bms/daly_bms.cpp @@ -0,0 +1,181 @@ +#include "daly_bms.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace daly_bms { + +static const char *const TAG = "daly_bms"; + +static const uint8_t DALY_FRAME_SIZE = 13; +static const uint8_t DALY_TEMPERATURE_OFFSET = 40; +static const uint16_t DALY_CURRENT_OFFSET = 30000; + +static const uint8_t DALY_REQUEST_BATTERY_LEVEL = 0x90; +static const uint8_t DALY_REQUEST_MIN_MAX_VOLTAGE = 0x91; +static const uint8_t DALY_REQUEST_MIN_MAX_TEMPERATURE = 0x92; +static const uint8_t DALY_REQUEST_MOS = 0x93; +static const uint8_t DALY_REQUEST_STATUS = 0x94; +static const uint8_t DALY_REQUEST_TEMPERATURE = 0x96; + +void DalyBmsComponent::setup() {} + +void DalyBmsComponent::dump_config() { + ESP_LOGCONFIG(TAG, "Daly BMS:"); + this->check_uart_settings(9600); +} + +void DalyBmsComponent::update() { + this->request_data(DALY_REQUEST_BATTERY_LEVEL); + this->request_data(DALY_REQUEST_MIN_MAX_VOLTAGE); + this->request_data(DALY_REQUEST_MIN_MAX_TEMPERATURE); + this->request_data(DALY_REQUEST_MOS); + this->request_data(DALY_REQUEST_STATUS); + this->request_data(DALY_REQUEST_TEMPERATURE); + + std::vector get_battery_level_data; + int available_data = this->available(); + if (available_data >= DALY_FRAME_SIZE) { + get_battery_level_data.resize(available_data); + this->read_array(get_battery_level_data.data(), available_data); + this->decode_data(get_battery_level_data); + } +} + +float DalyBmsComponent::get_setup_priority() const { return setup_priority::DATA; } + +void DalyBmsComponent::request_data(uint8_t data_id) { + uint8_t request_message[DALY_FRAME_SIZE]; + + request_message[0] = 0xA5; // Start Flag + request_message[1] = 0x80; // Communication Module Address + request_message[2] = data_id; // Data ID + request_message[3] = 0x08; // Data Length (Fixed) + request_message[4] = 0x00; // Empty Data + request_message[5] = 0x00; // | + request_message[6] = 0x00; // | + request_message[7] = 0x00; // | + request_message[8] = 0x00; // | + request_message[9] = 0x00; // | + request_message[10] = 0x00; // | + request_message[11] = 0x00; // Empty Data + request_message[12] = (uint8_t)(request_message[0] + request_message[1] + request_message[2] + + request_message[3]); // Checksum (Lower byte of the other bytes sum) + + this->write_array(request_message, sizeof(request_message)); + this->flush(); +} + +void DalyBmsComponent::decode_data(std::vector data) { + auto it = data.begin(); + + while ((it = std::find(it, data.end(), 0xA5)) != data.end()) { + if (data.end() - it >= DALY_FRAME_SIZE && it[1] == 0x01) { + uint8_t checksum; + int sum = 0; + for (int i = 0; i < 12; i++) { + sum += it[i]; + } + checksum = sum; + + if (checksum == it[12]) { + switch (it[2]) { + case DALY_REQUEST_BATTERY_LEVEL: + if (this->voltage_sensor_) { + this->voltage_sensor_->publish_state((float) encode_uint16(it[4], it[5]) / 10); + } + if (this->current_sensor_) { + this->current_sensor_->publish_state(((float) (encode_uint16(it[8], it[9]) - DALY_CURRENT_OFFSET) / 10)); + } + if (this->battery_level_sensor_) { + this->battery_level_sensor_->publish_state((float) encode_uint16(it[10], it[11]) / 10); + } + break; + + case DALY_REQUEST_MIN_MAX_VOLTAGE: + if (this->max_cell_voltage_) { + this->max_cell_voltage_->publish_state((float) encode_uint16(it[4], it[5]) / 1000); + } + if (this->max_cell_voltage_number_) { + this->max_cell_voltage_number_->publish_state(it[6]); + } + if (this->min_cell_voltage_) { + this->min_cell_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); + } + if (this->min_cell_voltage_number_) { + this->min_cell_voltage_number_->publish_state(it[9]); + } + break; + + case DALY_REQUEST_MIN_MAX_TEMPERATURE: + if (this->max_temperature_) { + this->max_temperature_->publish_state(it[4] - DALY_TEMPERATURE_OFFSET); + } + if (this->max_temperature_probe_number_) { + this->max_temperature_probe_number_->publish_state(it[5]); + } + if (this->min_temperature_) { + this->min_temperature_->publish_state(it[6] - DALY_TEMPERATURE_OFFSET); + } + if (this->min_temperature_probe_number_) { + this->min_temperature_probe_number_->publish_state(it[7]); + } + break; + + case DALY_REQUEST_MOS: + if (this->status_text_sensor_ != nullptr) { + switch (it[4]) { + case 0: + this->status_text_sensor_->publish_state("Stationary"); + break; + case 1: + this->status_text_sensor_->publish_state("Charging"); + break; + case 2: + this->status_text_sensor_->publish_state("Discharging"); + break; + default: + break; + } + } + if (this->charging_mos_enabled_) { + this->charging_mos_enabled_->publish_state(it[5]); + } + if (this->discharging_mos_enabled_) { + this->discharging_mos_enabled_->publish_state(it[6]); + } + if (this->remaining_capacity_) { + this->remaining_capacity_->publish_state((float) encode_uint32(it[8], it[9], it[10], it[11]) / 1000); + } + break; + + case DALY_REQUEST_STATUS: + if (this->cells_number_) { + this->cells_number_->publish_state(it[4]); + } + break; + + case DALY_REQUEST_TEMPERATURE: + if (it[4] == 1) { + if (this->temperature_1_sensor_) { + this->temperature_1_sensor_->publish_state(it[5] - DALY_TEMPERATURE_OFFSET); + } + if (this->temperature_2_sensor_) { + this->temperature_2_sensor_->publish_state(it[6] - DALY_TEMPERATURE_OFFSET); + } + } + break; + + default: + break; + } + } + std::advance(it, DALY_FRAME_SIZE); + } else { + std::advance(it, 1); + } + } +} + +} // namespace daly_bms +} // namespace esphome diff --git a/esphome/components/daly_bms/daly_bms.h b/esphome/components/daly_bms/daly_bms.h new file mode 100644 index 0000000000..e4f48776dd --- /dev/null +++ b/esphome/components/daly_bms/daly_bms.h @@ -0,0 +1,83 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace daly_bms { + +class DalyBmsComponent : public PollingComponent, public uart::UARTDevice { + public: + DalyBmsComponent() = default; + + // SENSORS + 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_battery_level_sensor(sensor::Sensor *battery_level_sensor) { battery_level_sensor_ = battery_level_sensor; } + void set_max_cell_voltage_sensor(sensor::Sensor *max_cell_voltage) { max_cell_voltage_ = max_cell_voltage; } + void set_max_cell_voltage_number_sensor(sensor::Sensor *max_cell_voltage_number) { + max_cell_voltage_number_ = max_cell_voltage_number; + } + void set_min_cell_voltage_sensor(sensor::Sensor *min_cell_voltage) { min_cell_voltage_ = min_cell_voltage; } + void set_min_cell_voltage_number_sensor(sensor::Sensor *min_cell_voltage_number) { + min_cell_voltage_number_ = min_cell_voltage_number; + } + void set_max_temperature_sensor(sensor::Sensor *max_temperature) { max_temperature_ = max_temperature; } + void set_max_temperature_probe_number_sensor(sensor::Sensor *max_temperature_probe_number) { + max_temperature_probe_number_ = max_temperature_probe_number; + } + void set_min_temperature_sensor(sensor::Sensor *min_temperature) { min_temperature_ = min_temperature; } + void set_min_temperature_probe_number_sensor(sensor::Sensor *min_temperature_probe_number) { + min_temperature_probe_number_ = min_temperature_probe_number; + } + void set_remaining_capacity_sensor(sensor::Sensor *remaining_capacity) { remaining_capacity_ = remaining_capacity; } + void set_cells_number_sensor(sensor::Sensor *cells_number) { cells_number_ = cells_number; } + void set_temperature_1_sensor(sensor::Sensor *temperature_1_sensor) { temperature_1_sensor_ = temperature_1_sensor; } + void set_temperature_2_sensor(sensor::Sensor *temperature_2_sensor) { temperature_2_sensor_ = temperature_2_sensor; } + // TEXT_SENSORS + void set_status_text_sensor(text_sensor::TextSensor *status_text_sensor) { status_text_sensor_ = status_text_sensor; } + // BINARY_SENSORS + void set_charging_mos_enabled_binary_sensor(binary_sensor::BinarySensor *charging_mos_enabled) { + charging_mos_enabled_ = charging_mos_enabled; + } + void set_discharging_mos_enabled_binary_sensor(binary_sensor::BinarySensor *discharging_mos_enabled) { + discharging_mos_enabled_ = discharging_mos_enabled; + } + + void setup() override; + void dump_config() override; + void update() override; + + float get_setup_priority() const override; + + protected: + void request_data(uint8_t data_id); + void decode_data(std::vector data); + + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *battery_level_sensor_{nullptr}; + sensor::Sensor *max_cell_voltage_{nullptr}; + sensor::Sensor *max_cell_voltage_number_{nullptr}; + sensor::Sensor *min_cell_voltage_{nullptr}; + sensor::Sensor *min_cell_voltage_number_{nullptr}; + sensor::Sensor *max_temperature_{nullptr}; + sensor::Sensor *max_temperature_probe_number_{nullptr}; + sensor::Sensor *min_temperature_{nullptr}; + sensor::Sensor *min_temperature_probe_number_{nullptr}; + sensor::Sensor *remaining_capacity_{nullptr}; + sensor::Sensor *cells_number_{nullptr}; + sensor::Sensor *temperature_1_sensor_{nullptr}; + sensor::Sensor *temperature_2_sensor_{nullptr}; + + text_sensor::TextSensor *status_text_sensor_{nullptr}; + + binary_sensor::BinarySensor *charging_mos_enabled_{nullptr}; + binary_sensor::BinarySensor *discharging_mos_enabled_{nullptr}; +}; + +} // namespace daly_bms +} // namespace esphome diff --git a/esphome/components/daly_bms/sensor.py b/esphome/components/daly_bms/sensor.py new file mode 100644 index 0000000000..1d0ee89914 --- /dev/null +++ b/esphome/components/daly_bms/sensor.py @@ -0,0 +1,192 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_VOLTAGE, + CONF_CURRENT, + CONF_BATTERY_LEVEL, + CONF_MAX_TEMPERATURE, + CONF_MIN_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_NONE, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_PERCENT, + UNIT_CELSIUS, + UNIT_EMPTY, + ICON_FLASH, + ICON_PERCENT, + ICON_COUNTER, + ICON_THERMOMETER, + ICON_GAUGE, +) +from . import DalyBmsComponent, CONF_BMS_DALY_ID + +CONF_MAX_CELL_VOLTAGE = "max_cell_voltage" +CONF_MAX_CELL_VOLTAGE_NUMBER = "max_cell_voltage_number" +CONF_MIN_CELL_VOLTAGE = "min_cell_voltage" +CONF_MIN_CELL_VOLTAGE_NUMBER = "min_cell_voltage_number" +CONF_MAX_TEMPERATURE_PROBE_NUMBER = "max_temperature_probe_number" +CONF_MIN_TEMPERATURE_PROBE_NUMBER = "min_temperature_probe_number" +CONF_CELLS_NUMBER = "cells_number" + +CONF_REMAINING_CAPACITY = "remaining_capacity" +CONF_TEMPERATURE_1 = "temperature_1" +CONF_TEMPERATURE_2 = "temperature_2" + +ICON_CURRENT_DC = "mdi:current-dc" +ICON_BATTERY_OUTLINE = "mdi:battery-outline" +ICON_THERMOMETER_CHEVRON_UP = "mdi:thermometer-chevron-up" +ICON_THERMOMETER_CHEVRON_DOWN = "mdi:thermometer-chevron-down" +ICON_CAR_BATTERY = "mdi:car-battery" + +UNIT_AMPERE_HOUR = "Ah" + +TYPES = [ + CONF_VOLTAGE, + CONF_CURRENT, + CONF_BATTERY_LEVEL, + CONF_MAX_CELL_VOLTAGE, + CONF_MAX_CELL_VOLTAGE_NUMBER, + CONF_MIN_CELL_VOLTAGE, + CONF_MIN_CELL_VOLTAGE_NUMBER, + CONF_MAX_TEMPERATURE, + CONF_MAX_TEMPERATURE_PROBE_NUMBER, + CONF_MIN_TEMPERATURE, + CONF_MIN_TEMPERATURE_PROBE_NUMBER, + CONF_CELLS_NUMBER, + CONF_REMAINING_CAPACITY, + CONF_TEMPERATURE_1, + CONF_TEMPERATURE_2, +] + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, + ICON_FLASH, + 1, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + UNIT_AMPERE, + ICON_CURRENT_DC, + 1, + DEVICE_CLASS_CURRENT, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, + ICON_PERCENT, + 1, + DEVICE_CLASS_BATTERY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_MAX_CELL_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, + ICON_FLASH, + 2, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_MAX_CELL_VOLTAGE_NUMBER): sensor.sensor_schema( + UNIT_EMPTY, + ICON_COUNTER, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_MIN_CELL_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, + ICON_FLASH, + 2, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_MIN_CELL_VOLTAGE_NUMBER): sensor.sensor_schema( + UNIT_EMPTY, + ICON_COUNTER, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_MAX_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, + ICON_THERMOMETER_CHEVRON_UP, + 0, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_MAX_TEMPERATURE_PROBE_NUMBER): sensor.sensor_schema( + UNIT_EMPTY, + ICON_COUNTER, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_MIN_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, + ICON_THERMOMETER_CHEVRON_DOWN, + 0, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_MIN_TEMPERATURE_PROBE_NUMBER): sensor.sensor_schema( + UNIT_EMPTY, + ICON_COUNTER, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_REMAINING_CAPACITY): sensor.sensor_schema( + UNIT_AMPERE_HOUR, + ICON_GAUGE, + 2, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELLS_NUMBER): sensor.sensor_schema( + UNIT_EMPTY, + ICON_COUNTER, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_TEMPERATURE_1): sensor.sensor_schema( + UNIT_CELSIUS, + ICON_THERMOMETER, + 0, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE_2): sensor.sensor_schema( + UNIT_CELSIUS, + ICON_THERMOMETER, + 0, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + ), + } + ).extend(cv.COMPONENT_SCHEMA) +) + + +async def setup_conf(config, key, hub): + if key in config: + conf = config[key] + sens = await sensor.new_sensor(conf) + cg.add(getattr(hub, f"set_{key}_sensor")(sens)) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_BMS_DALY_ID]) + for key in TYPES: + await setup_conf(config, key, hub) diff --git a/esphome/components/daly_bms/text_sensor.py b/esphome/components/daly_bms/text_sensor.py new file mode 100644 index 0000000000..de49a0b4b9 --- /dev/null +++ b/esphome/components/daly_bms/text_sensor.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import CONF_ICON, CONF_ID, CONF_STATUS +from . import DalyBmsComponent, CONF_BMS_DALY_ID + +ICON_CAR_BATTERY = "mdi:car-battery" + +TYPES = [ + CONF_STATUS, +] + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent), + cv.Optional(CONF_STATUS): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + cv.Optional(CONF_ICON, default=ICON_CAR_BATTERY): cv.icon, + } + ), + } + ).extend(cv.COMPONENT_SCHEMA) +) + + +async def setup_conf(config, key, hub): + if key in config: + conf = config[key] + sens = cg.new_Pvariable(conf[CONF_ID]) + await text_sensor.register_text_sensor(sens, conf) + cg.add(getattr(hub, f"set_{key}_text_sensor")(sens)) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_BMS_DALY_ID]) + for key in TYPES: + await setup_conf(config, key, hub) diff --git a/tests/test3.yaml b/tests/test3.yaml index de46cec99c..386775749d 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -273,6 +273,37 @@ adalight: sensor: + - platform: daly_bms + voltage: + name: "Battery Voltage" + current: + name: "Battery Current" + battery_level: + name: "Battery Level" + max_cell_voltage: + name: "Max Cell Voltage" + max_cell_voltage_number: + name: "Max Cell Voltage Number" + min_cell_voltage: + name: "Min Cell Voltage" + min_cell_voltage_number: + name: "Min Cell Voltage Number" + max_temperature: + name: "Max Temperature" + max_temperature_probe_number: + name: "Max Temperature Probe Number" + min_temperature: + name: "Min Temperature" + min_temperature_probe_number: + name: "Min Temperature Probe Number" + remaining_capacity: + name: "Remaining Capacity" + cells_number: + name: "Cells Number" + temperature_1: + name: "Temperature 1" + temperature_2: + name: "Temperature 2" - platform: apds9960 type: proximity name: APDS9960 Proximity @@ -621,6 +652,11 @@ mpr121: address: 0x5A binary_sensor: + - platform: daly_bms + charging_mos_enabled: + name: "Charging MOS" + discharging_mos_enabled: + name: "Discharging MOS" - platform: apds9960 direction: up name: APDS9960 Up @@ -701,6 +737,9 @@ status_led: pin: GPIO2 text_sensor: + - platform: daly_bms + status: + name: "BMS Status" - platform: version name: 'ESPHome Version' icon: mdi:icon @@ -1219,3 +1258,8 @@ fingerprint_grow: dsmr: decryption_key: 00112233445566778899aabbccddeeff uart_id: uart6 + +daly_bms: + update_interval: 20s + uart_id: uart1 + From f1364d4af4a72826a79f7427ec17449f5f9061a5 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Wed, 22 Sep 2021 12:12:55 +0200 Subject: [PATCH 041/549] Combine code of xiaomi_miscale and xiaomi_miscale2 (#2266) * Combine xiaomi_miscale and xiaomi_miscale2 * check if message contains impedance * auto detect scale version * remove xiaomi_miscale2 * fix lint errors * Apply suggestions from code review Co-authored-by: Oxan van Leeuwen * Apply suggestions from code review on old code * Fix clang-tidy warnings Co-authored-by: Oxan van Leeuwen --- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 4 +- .../esp32_ble_tracker/esp32_ble_tracker.h | 4 +- esphome/components/xiaomi_miscale/sensor.py | 12 ++ .../xiaomi_miscale/xiaomi_miscale.cpp | 85 +++++++++++-- .../xiaomi_miscale/xiaomi_miscale.h | 6 + esphome/components/xiaomi_miscale2/sensor.py | 60 +--------- .../xiaomi_miscale2/xiaomi_miscale2.cpp | 112 ------------------ .../xiaomi_miscale2/xiaomi_miscale2.h | 40 ------- 8 files changed, 98 insertions(+), 225 deletions(-) delete mode 100644 esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp delete mode 100644 esphome/components/xiaomi_miscale2/xiaomi_miscale2.h diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 5568884b9a..3ca250d52d 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -380,8 +380,8 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const { } return false; } -esp_bt_uuid_t ESPBTUUID::get_uuid() { return this->uuid_; } -std::string ESPBTUUID::to_string() { +esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; } +std::string ESPBTUUID::to_string() const { char sbuf[64]; switch (this->uuid_.len) { case ESP_UUID_LEN_16: diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 40955d39cf..fc5498f91e 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -34,9 +34,9 @@ class ESPBTUUID { bool operator==(const ESPBTUUID &uuid) const; bool operator!=(const ESPBTUUID &uuid) const { return !(*this == uuid); } - esp_bt_uuid_t get_uuid(); + esp_bt_uuid_t get_uuid() const; - std::string to_string(); + std::string to_string() const; protected: esp_bt_uuid_t uuid_; diff --git a/esphome/components/xiaomi_miscale/sensor.py b/esphome/components/xiaomi_miscale/sensor.py index 3a112dfa34..517870cc01 100644 --- a/esphome/components/xiaomi_miscale/sensor.py +++ b/esphome/components/xiaomi_miscale/sensor.py @@ -8,6 +8,9 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_KILOGRAM, ICON_SCALE_BATHROOM, + UNIT_OHM, + CONF_IMPEDANCE, + ICON_OMEGA, ) DEPENDENCIES = ["esp32_ble_tracker"] @@ -28,6 +31,12 @@ CONFIG_SCHEMA = ( accuracy_decimals=2, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_IMPEDANCE): sensor.sensor_schema( + unit_of_measurement=UNIT_OHM, + icon=ICON_OMEGA, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), } ) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) @@ -45,3 +54,6 @@ async def to_code(config): if CONF_WEIGHT in config: sens = await sensor.new_sensor(config[CONF_WEIGHT]) cg.add(var.set_weight(sens)) + if CONF_IMPEDANCE in config: + sens = await sensor.new_sensor(config[CONF_IMPEDANCE]) + cg.add(var.set_impedance(sens)) diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp index 62378de72c..4587045136 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp @@ -11,6 +11,7 @@ static const char *const TAG = "xiaomi_miscale"; void XiaomiMiscale::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi Miscale"); LOG_SENSOR(" ", "Weight", this->weight_); + LOG_SENSOR(" ", "Impedance", this->impedance_); } bool XiaomiMiscale::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { @@ -26,14 +27,22 @@ bool XiaomiMiscale::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!res.has_value()) { continue; } + if (!(parse_message(service_data.data, *res))) { continue; } + if (!(report_results(res, device.address_str()))) { continue; } + if (res->weight.has_value() && this->weight_ != nullptr) this->weight_->publish_state(*res->weight); + + if (res->version == 1 && this->impedance_ != nullptr) { + ESP_LOGW(TAG, "Impedance is only supported on version 2. Your scale was identified as verison 1."); + } else if (res->impedance.has_value() && this->impedance_ != nullptr) + this->impedance_->publish_state(*res->impedance); success = true; } @@ -42,8 +51,14 @@ bool XiaomiMiscale::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { optional XiaomiMiscale::parse_header(const esp32_ble_tracker::ServiceData &service_data) { ParseResult result; - if (!service_data.uuid.contains(0x1D, 0x18)) { - ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes."); + if (service_data.uuid == esp32_ble_tracker::ESPBTUUID::from_uint16(0x181D) && service_data.data.size() == 10) { + result.version = 1; + } else if (service_data.uuid == esp32_ble_tracker::ESPBTUUID::from_uint16(0x181B) && service_data.data.size() == 13) { + result.version = 2; + } else { + ESP_LOGVV(TAG, + "parse_header(): Couldn't identify scale version or data size was not correct. UUID: %s, data_size: %d", + service_data.uuid.to_string().c_str(), service_data.data.size()); return {}; } @@ -51,7 +66,15 @@ optional XiaomiMiscale::parse_header(const esp32_ble_tracker::Servi } bool XiaomiMiscale::parse_message(const std::vector &message, ParseResult &result) { - // example 1d18 a2 6036 e307 07 11 0f1f11 + if (result.version == 1) { + return parse_message_V1(message, result); + } else { + return parse_message_V2(message, result); + } +} + +bool XiaomiMiscale::parse_message_V1(const std::vector &message, ParseResult &result) { + // message size is checked in parse_header // 1-2 Weight (MISCALE 181D) // 3-4 Years (MISCALE 181D) // 5 month (MISCALE 181D) @@ -61,21 +84,56 @@ bool XiaomiMiscale::parse_message(const std::vector &message, ParseResu // 9 second (MISCALE 181D) const uint8_t *data = message.data(); - const int data_length = 10; - - if (message.size() != data_length) { - ESP_LOGVV(TAG, "parse_message(): payload has wrong size (%d)!", message.size()); - return false; - } // weight, 2 bytes, 16-bit unsigned integer, 1 kg const int16_t weight = uint16_t(data[1]) | (uint16_t(data[2]) << 8); if (data[0] == 0x22 || data[0] == 0xa2) result.weight = weight * 0.01f / 2.0f; // unit 'kg' else if (data[0] == 0x12 || data[0] == 0xb2) - result.weight = weight * 0.01f * 0.6; // unit 'jin' + result.weight = weight * 0.01f * 0.6f; // unit 'jin' else if (data[0] == 0x03 || data[0] == 0xb3) - result.weight = weight * 0.01f * 0.453592; // unit 'lbs' + result.weight = weight * 0.01f * 0.453592f; // unit 'lbs' + + return true; +} + +bool XiaomiMiscale::parse_message_V2(const std::vector &message, ParseResult &result) { + // message size is checked in parse_header + // 2-3 Years (MISCALE 2 181B) + // 4 month (MISCALE 2 181B) + // 5 day (MISCALE 2 181B) + // 6 hour (MISCALE 2 181B) + // 7 minute (MISCALE 2 181B) + // 8 second (MISCALE 2 181B) + // 9-10 impedance (MISCALE 2 181B) + // 11-12 weight (MISCALE 2 181B) + + const uint8_t *data = message.data(); + + bool has_impedance = ((data[1] & (1 << 1)) != 0); + bool is_stabilized = ((data[1] & (1 << 5)) != 0); + bool load_removed = ((data[1] & (1 << 7)) != 0); + + if (!is_stabilized || load_removed) { + return false; + } + + // weight, 2 bytes, 16-bit unsigned integer, 1 kg + const int16_t weight = uint16_t(data[11]) | (uint16_t(data[12]) << 8); + if (data[0] == 0x02) + result.weight = weight * 0.01f / 2.0f; // unit 'kg' + else if (data[0] == 0x03) + result.weight = weight * 0.01f * 0.453592f; // unit 'lbs' + + if (has_impedance) { + // impedance, 2 bytes, 16-bit + const int16_t impedance = uint16_t(data[9]) | (uint16_t(data[10]) << 8); + result.impedance = impedance; + + if (impedance == 0 || impedance >= 3000) { + return false; + } + } return true; } @@ -86,11 +144,14 @@ bool XiaomiMiscale::report_results(const optional &result, const st return false; } - ESP_LOGD(TAG, "Got Xiaomi Miscale (%s):", address.c_str()); + ESP_LOGD(TAG, "Got Xiaomi Miscale v%d (%s):", result->version, address.c_str()); if (result->weight.has_value()) { ESP_LOGD(TAG, " Weight: %.2fkg", *result->weight); } + if (result->impedance.has_value()) { + ESP_LOGD(TAG, " Impedance: %.0fohm", *result->impedance); + } return true; } diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.h b/esphome/components/xiaomi_miscale/xiaomi_miscale.h index 409fdeaf93..3c958afc03 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.h +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.h @@ -10,7 +10,9 @@ namespace esphome { namespace xiaomi_miscale { struct ParseResult { + int version; optional weight; + optional impedance; }; class XiaomiMiscale : public Component, public esp32_ble_tracker::ESPBTDeviceListener { @@ -21,13 +23,17 @@ class XiaomiMiscale : public Component, public esp32_ble_tracker::ESPBTDeviceLis void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } void set_weight(sensor::Sensor *weight) { weight_ = weight; } + void set_impedance(sensor::Sensor *impedance) { impedance_ = impedance; } protected: uint64_t address_; sensor::Sensor *weight_{nullptr}; + sensor::Sensor *impedance_{nullptr}; optional parse_header(const esp32_ble_tracker::ServiceData &service_data); bool parse_message(const std::vector &message, ParseResult &result); + bool parse_message_V1(const std::vector &message, ParseResult &result); + bool parse_message_V2(const std::vector &message, ParseResult &result); bool report_results(const optional &result, const std::string &address); }; diff --git a/esphome/components/xiaomi_miscale2/sensor.py b/esphome/components/xiaomi_miscale2/sensor.py index 7cc5984c62..de04e8171e 100644 --- a/esphome/components/xiaomi_miscale2/sensor.py +++ b/esphome/components/xiaomi_miscale2/sensor.py @@ -1,59 +1,5 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import sensor, esp32_ble_tracker -from esphome.const import ( - CONF_MAC_ADDRESS, - CONF_ID, - CONF_WEIGHT, - STATE_CLASS_MEASUREMENT, - UNIT_KILOGRAM, - ICON_SCALE_BATHROOM, - UNIT_OHM, - CONF_IMPEDANCE, - ICON_OMEGA, + +CONFIG_SCHEMA = cv.invalid( + "This platform has been combined into xiaomi_miscale. Use xiaomi_miscale instead." ) - -DEPENDENCIES = ["esp32_ble_tracker"] - -xiaomi_miscale2_ns = cg.esphome_ns.namespace("xiaomi_miscale2") -XiaomiMiscale2 = xiaomi_miscale2_ns.class_( - "XiaomiMiscale2", esp32_ble_tracker.ESPBTDeviceListener, cg.Component -) - -CONFIG_SCHEMA = ( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(XiaomiMiscale2), - cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_WEIGHT): sensor.sensor_schema( - unit_of_measurement=UNIT_KILOGRAM, - icon=ICON_SCALE_BATHROOM, - accuracy_decimals=2, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_IMPEDANCE): sensor.sensor_schema( - unit_of_measurement=UNIT_OHM, - icon=ICON_OMEGA, - accuracy_decimals=0, - state_class=STATE_CLASS_MEASUREMENT, - ), - } - ) - .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) - .extend(cv.COMPONENT_SCHEMA) -) - - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await esp32_ble_tracker.register_ble_device(var, config) - - cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) - - if CONF_WEIGHT in config: - sens = await sensor.new_sensor(config[CONF_WEIGHT]) - cg.add(var.set_weight(sens)) - if CONF_IMPEDANCE in config: - sens = await sensor.new_sensor(config[CONF_IMPEDANCE]) - cg.add(var.set_impedance(sens)) diff --git a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp deleted file mode 100644 index 9ae95c5f97..0000000000 --- a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "xiaomi_miscale2.h" -#include "esphome/core/log.h" - -#ifdef USE_ESP32 - -namespace esphome { -namespace xiaomi_miscale2 { - -static const char *const TAG = "xiaomi_miscale2"; - -void XiaomiMiscale2::dump_config() { - ESP_LOGCONFIG(TAG, "Xiaomi Miscale2"); - LOG_SENSOR(" ", "Weight", this->weight_); - LOG_SENSOR(" ", "Impedance", this->impedance_); -} - -bool XiaomiMiscale2::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { - if (device.address_uint64() != this->address_) { - ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); - return false; - } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); - - bool success = false; - for (auto &service_data : device.get_service_datas()) { - auto res = parse_header(service_data); - if (!res.has_value()) { - continue; - } - if (!(parse_message(service_data.data, *res))) { - continue; - } - if (!(report_results(res, device.address_str()))) { - continue; - } - if (res->weight.has_value() && this->weight_ != nullptr) - this->weight_->publish_state(*res->weight); - if (res->impedance.has_value() && this->impedance_ != nullptr) - this->impedance_->publish_state(*res->impedance); - success = true; - } - - return success; -} - -optional XiaomiMiscale2::parse_header(const esp32_ble_tracker::ServiceData &service_data) { - ParseResult result; - if (!service_data.uuid.contains(0x1B, 0x18)) { - ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes."); - return {}; - } - - return result; -} - -bool XiaomiMiscale2::parse_message(const std::vector &message, ParseResult &result) { - // 2-3 Years (MISCALE 2 181B) - // 4 month (MISCALE 2 181B) - // 5 day (MISCALE 2 181B) - // 6 hour (MISCALE 2 181B) - // 7 minute (MISCALE 2 181B) - // 8 second (MISCALE 2 181B) - // 9-10 impedance (MISCALE 2 181B) - // 11-12 weight (MISCALE 2 181B) - - const uint8_t *data = message.data(); - const int data_length = 13; - - if (message.size() != data_length) { - ESP_LOGVV(TAG, "parse_message(): payload has wrong size (%d)!", message.size()); - return 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); - if (data[0] == 0x02) - result.weight = weight * 0.01f / 2.0f; // unit 'kg' - else if (data[0] == 0x03) - result.weight = weight * 0.01f * 0.453592; // unit 'lbs' - - // impedance, 2 bytes, 16-bit - const int16_t impedance = uint16_t(data[9]) | (uint16_t(data[10]) << 8); - result.impedance = impedance; - - return is_stabilized && !load_removed && impedance != 0 && impedance < 3000; -} - -bool XiaomiMiscale2::report_results(const optional &result, const std::string &address) { - if (!result.has_value()) { - ESP_LOGVV(TAG, "report_results(): no results available."); - return false; - } - - ESP_LOGD(TAG, "Got Xiaomi Miscale2 (%s):", address.c_str()); - - if (result->weight.has_value()) { - ESP_LOGD(TAG, " Weight: %.2fkg", *result->weight); - } - if (result->impedance.has_value()) { - ESP_LOGD(TAG, " Impedance: %.0fohm", *result->impedance); - } - - return true; -} - -} // namespace xiaomi_miscale2 -} // namespace esphome - -#endif diff --git a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.h b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.h deleted file mode 100644 index 9f7ebf6a1f..0000000000 --- a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" - -#ifdef USE_ESP32 - -namespace esphome { -namespace xiaomi_miscale2 { - -struct ParseResult { - optional weight; - optional impedance; -}; - -class XiaomiMiscale2 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { - public: - void set_address(uint64_t address) { address_ = address; }; - - bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; - void dump_config() override; - float get_setup_priority() const override { return setup_priority::DATA; } - void set_weight(sensor::Sensor *weight) { weight_ = weight; } - void set_impedance(sensor::Sensor *impedance) { impedance_ = impedance; } - - protected: - uint64_t address_; - sensor::Sensor *weight_{nullptr}; - sensor::Sensor *impedance_{nullptr}; - - optional parse_header(const esp32_ble_tracker::ServiceData &service_data); - bool parse_message(const std::vector &message, ParseResult &result); - bool report_results(const optional &result, const std::string &address); -}; - -} // namespace xiaomi_miscale2 -} // namespace esphome - -#endif From 9fe7b0887416dd274f4acffa5b8fdfef720e01c9 Mon Sep 17 00:00:00 2001 From: Tommy van der Vorst Date: Wed, 22 Sep 2021 12:39:41 +0200 Subject: [PATCH 042/549] Add support for Waveshare 7.5 inch (C) bichromatic display (black-and-white only for now) (#1844) * Add support for Waveshare 7.5 inch (B) bichromatic display (black-and-white only for now) * Use drawing commands specific to bichromatic displays * Fix inaccurate comment * Fix merge error * Formatting Co-authored-by: Oxan van Leeuwen --- .../components/waveshare_epaper/display.py | 4 + .../waveshare_epaper/waveshare_epaper.cpp | 96 +++++++++++++++++++ .../waveshare_epaper/waveshare_epaper.h | 23 +++++ 3 files changed, 123 insertions(+) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index e825456c36..64f5597a65 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -40,6 +40,9 @@ WaveshareEPaper5P8In = waveshare_epaper_ns.class_( WaveshareEPaper7P5In = waveshare_epaper_ns.class_( "WaveshareEPaper7P5In", WaveshareEPaper ) +WaveshareEPaper7P5InBC = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InBC", WaveshareEPaper +) WaveshareEPaper7P5InV2 = waveshare_epaper_ns.class_( "WaveshareEPaper7P5InV2", WaveshareEPaper ) @@ -66,6 +69,7 @@ MODELS = { "4.20in-bv2": ("b", WaveshareEPaper4P2InBV2), "5.83in": ("b", WaveshareEPaper5P8In), "7.50in": ("b", WaveshareEPaper7P5In), + "7.50in-bc": ("b", WaveshareEPaper7P5InBC), "7.50inv2": ("b", WaveshareEPaper7P5InV2), "2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE), } diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index c32e7d27a0..92fa289cfa 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -1081,6 +1081,102 @@ void WaveshareEPaper7P5InV2::dump_config() { LOG_UPDATE_INTERVAL(this); } +/* 7.50in-bc */ +void WaveshareEPaper7P5InBC::initialize() { + /* The command sequence is similar to the 7P5In display but differs in subtle ways + to allow for faster updates. */ + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x37); + this->data(0x00); + + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0xCF); + this->data(0x08); + + // COMMAND PLL CONTROL + this->command(0x30); + this->data(0x3A); + + // COMMAND VCM_DC_SETTING: all temperature range + this->command(0x82); + this->data(0x28); + + // COMMAND BOOSTER SOFT START + this->command(0x06); + this->data(0xC7); + this->data(0xCC); + this->data(0x15); + + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x77); + + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + + // COMMAND FLASH CONTROL + this->command(0x65); + this->data(0x00); + + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x02); // 640 >> 8 + this->data(0x80); + this->data(0x01); // 384 >> 8 + this->data(0x80); + + // COMMAND FLASH MODE + this->command(0xE5); + this->data(0x03); +} + +void HOT WaveshareEPaper7P5InBC::display() { + // COMMAND DATA START TRANSMISSION 1 + this->command(0x10); + this->start_data_(); + + for (size_t i = 0; i < this->get_buffer_length_(); i++) { + // A line of eight source pixels (each a bit in this byte) + uint8_t eight_pixels = this->buffer_[i]; + + for (uint8_t j = 0; j < 8; j += 2) { + /* For bichromatic displays, each byte represents two pixels. Each nibble encodes a pixel: 0=white, 3=black, + 4=color. Therefore, e.g. 0x44 = two adjacent color pixels, 0x33 is two adjacent black pixels, etc. If you want + to draw using the color pixels, change '0x30' with '0x40' and '0x03' with '0x04' below. */ + uint8_t left_nibble = (eight_pixels & 0x80) ? 0x30 : 0x00; + eight_pixels <<= 1; + uint8_t right_nibble = (eight_pixels & 0x80) ? 0x03 : 0x00; + eight_pixels <<= 1; + this->write_byte(left_nibble | right_nibble); + } + App.feed_wdt(); + } + this->end_data_(); + + // Unlike the 7P5In display, we send the "power on" command here rather than during initialization + // COMMAND POWER ON + this->command(0x04); + + // COMMAND DISPLAY REFRESH + this->command(0x12); +} + +int WaveshareEPaper7P5InBC::get_width_internal() { return 640; } + +int WaveshareEPaper7P5InBC::get_height_internal() { return 384; } + +void WaveshareEPaper7P5InBC::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5in-bc"); + 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); +} + static const uint8_t LUT_SIZE_TTGO_DKE_PART = 153; static const uint8_t PART_UPDATE_LUT_TTGO_DKE[LUT_SIZE_TTGO_DKE_PART] = { diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index f7603c5af0..b50596643d 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -278,6 +278,29 @@ class WaveshareEPaper7P5In : public WaveshareEPaper { int get_height_internal() override; }; +class WaveshareEPaper7P5InBC : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + class WaveshareEPaper7P5InV2 : public WaveshareEPaper { public: void initialize() override; From 8e36e1b92e95a478888a21507def2eaee4bbfc9c Mon Sep 17 00:00:00 2001 From: Stanislav Meduna Date: Wed, 22 Sep 2021 12:43:17 +0200 Subject: [PATCH 043/549] ili9341: use larger SPI transfers (#1628) The original version uses write_byte to tranfer every byte of the display buffer which is quite extensive as every byte needs to be waited for in the SPI driver. This patch prepares transfers in 64-byte chunks. The result is a visible faster redraw of the display. Co-authored-by: Otto winter --- .../components/ili9341/ili9341_display.cpp | 55 +++++++++++++++---- esphome/components/ili9341/ili9341_display.h | 4 ++ 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/esphome/components/ili9341/ili9341_display.cpp b/esphome/components/ili9341/ili9341_display.cpp index b36d05c864..88f8bac272 100644 --- a/esphome/components/ili9341/ili9341_display.cpp +++ b/esphome/components/ili9341/ili9341_display.cpp @@ -93,12 +93,14 @@ void ILI9341Display::display_() { this->start_data_(); uint32_t start_pos = ((this->y_low_ * this->width_) + x_low_); for (uint16_t row = 0; row < h; row++) { - for (uint16_t col = 0; col < w; col++) { - uint32_t pos = start_pos + (row * width_) + col; + uint32_t pos = start_pos + (row * width_); + uint32_t rem = w; - uint16_t color = convert_to_16bit_color_(buffer_[pos]); - this->write_byte(color >> 8); - this->write_byte(color); + while (rem > 0) { + uint32_t sz = buffer_to_transfer_(pos, rem); + this->write_array(transfer_buffer_, 2 * sz); + pos += sz; + rem -= sz; } } this->end_data_(); @@ -140,16 +142,32 @@ void ILI9341Display::fill(Color color) { } void ILI9341Display::fill_internal_(Color color) { + if (color.raw_32 == COLOR_BLACK.raw_32) { + memset(transfer_buffer_, 0, sizeof(transfer_buffer_)); + } else { + uint8_t *dst = transfer_buffer_; + auto color565 = display::ColorUtil::color_to_565(color); + + while (dst < transfer_buffer_ + sizeof(transfer_buffer_)) { + *dst++ = (uint8_t)(color565 >> 8); + *dst++ = (uint8_t) color565; + } + } + + uint32_t rem = this->get_width_internal() * this->get_height_internal(); + this->set_addr_window_(0, 0, this->get_width_internal(), this->get_height_internal()); this->start_data_(); - auto color565 = display::ColorUtil::color_to_565(color); - for (uint32_t i = 0; i < (this->get_width_internal()) * (this->get_height_internal()); i++) { - this->write_byte(color565 >> 8); - this->write_byte(color565); - buffer_[i] = 0; + while (rem > 0) { + size_t sz = rem <= sizeof(transfer_buffer_) ? rem : sizeof(transfer_buffer_); + this->write_array(transfer_buffer_, sz); + rem -= sz; } + this->end_data_(); + + memset(buffer_, 0, (this->get_width_internal()) * (this->get_height_internal())); } void HOT ILI9341Display::draw_absolute_pixel_internal(int x, int y, Color color) { @@ -220,6 +238,23 @@ void ILI9341Display::invert_display_(bool invert) { this->command(invert ? ILI93 int ILI9341Display::get_width_internal() { return this->width_; } int ILI9341Display::get_height_internal() { return this->height_; } +uint32_t ILI9341Display::buffer_to_transfer_(uint32_t pos, uint32_t sz) { + uint8_t *src = buffer_ + pos; + uint8_t *dst = transfer_buffer_; + + if (sz > sizeof(transfer_buffer_) / 2) { + sz = sizeof(transfer_buffer_) / 2; + } + + for (uint32_t i = 0; i < sz; ++i) { + uint16_t color = convert_to_16bit_color_(*src++); + *dst++ = (uint8_t)(color >> 8); + *dst++ = (uint8_t) color; + } + + return sz; +} + // M5Stack display void ILI9341M5Stack::initialize() { this->init_lcd_(INITCMD_M5STACK); diff --git a/esphome/components/ili9341/ili9341_display.h b/esphome/components/ili9341/ili9341_display.h index 2b6ecc6871..d8c90c9d33 100644 --- a/esphome/components/ili9341/ili9341_display.h +++ b/esphome/components/ili9341/ili9341_display.h @@ -71,6 +71,10 @@ class ILI9341Display : public PollingComponent, void start_data_(); void end_data_(); + uint8_t transfer_buffer_[64]; + + uint32_t buffer_to_transfer_(uint32_t pos, uint32_t sz); + GPIOPin *reset_pin_{nullptr}; GPIOPin *led_pin_{nullptr}; GPIOPin *dc_pin_; From 654e31124ea4069545ea2768175552155f2b0d6e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 22 Sep 2021 22:59:03 +1200 Subject: [PATCH 044/549] Correctly invert the float output state (#2368) --- esphome/components/output/float_output.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/esphome/components/output/float_output.cpp b/esphome/components/output/float_output.cpp index 5820a2d7cd..f120f86f1f 100644 --- a/esphome/components/output/float_output.cpp +++ b/esphome/components/output/float_output.cpp @@ -31,14 +31,13 @@ void FloatOutput::set_level(float state) { this->power_.unrequest(); } #endif + + if (!(state == 0.0f && this->zero_means_zero_)) // regardless of min_power_, 0.0 means off + state = (state * (this->max_power_ - this->min_power_)) + this->min_power_; + if (this->is_inverted()) state = 1.0f - state; - if (state == 0.0f && this->zero_means_zero_) { // regardless of min_power_, 0.0 means off - this->write_state(state); - return; - } - float adjusted_value = (state * (this->max_power_ - this->min_power_)) + this->min_power_; - this->write_state(adjusted_value); + this->write_state(state); } void FloatOutput::write_state(bool state) { this->set_level(state != this->inverted_ ? 1.0f : 0.0f); } From b20760c93c3f8874e0d73d515e0e88d04a7c87aa Mon Sep 17 00:00:00 2001 From: Stephen Tierney Date: Wed, 22 Sep 2021 21:24:19 +1000 Subject: [PATCH 045/549] Add support for LTR390 (#1505) * Add support for ltr390 * Fix linting errors * Fix more linting errors * Linting fixes continued * Linting forever * Another one * Fix regression and linting * Fix narrowing conversion * Add test and bugfix * Add codeowners * Update CODEOWNERS * Update sensor defs * Reformatted with black * Fixed device class import * Update CODEOWNERS * Update CODEOWNERS * Adding all config options As requested https://github.com/esphome/esphome/pull/1505#discussion_r597326897 * Moving test to different config file test1.yml runs out of memory * Update according to comments * Add safety clause to reading modes * Fix clang-tidy complaint * Revert change to i2c component * Fix for changes in dev * Revert "Revert change to i2c component" This reverts commit 2810df59e9c05311df6d32149ed79a393676503b. Co-authored-by: Otto winter Co-authored-by: Oxan van Leeuwen --- CODEOWNERS | 1 + esphome/components/i2c/i2c.h | 6 +- esphome/components/ltr390/__init__.py | 0 esphome/components/ltr390/ltr390.cpp | 166 ++++++++++++++++++++++++++ esphome/components/ltr390/ltr390.h | 93 +++++++++++++++ esphome/components/ltr390/sensor.py | 98 +++++++++++++++ tests/test2.yaml | 14 +++ 7 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 esphome/components/ltr390/__init__.py create mode 100644 esphome/components/ltr390/ltr390.cpp create mode 100644 esphome/components/ltr390/ltr390.h create mode 100644 esphome/components/ltr390/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 2577e0d706..92ee989309 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -74,6 +74,7 @@ esphome/components/json/* @OttoWinter esphome/components/ledc/* @OttoWinter esphome/components/light/* @esphome/core esphome/components/logger/* @esphome/core +esphome/components/ltr390/* @sjtrny esphome/components/max7219digit/* @rspaargaren esphome/components/mcp23008/* @jesserockz esphome/components/mcp23017/* @jesserockz diff --git a/esphome/components/i2c/i2c.h b/esphome/components/i2c/i2c.h index 71ab650e97..7ee4cdd811 100644 --- a/esphome/components/i2c/i2c.h +++ b/esphome/components/i2c/i2c.h @@ -13,8 +13,6 @@ namespace i2c { class I2CDevice; class I2CRegister { public: - I2CRegister(I2CDevice *parent, uint8_t a_register) : parent_(parent), register_(a_register) {} - I2CRegister &operator=(uint8_t value); I2CRegister &operator&=(uint8_t value); I2CRegister &operator|=(uint8_t value); @@ -24,6 +22,10 @@ class I2CRegister { uint8_t get() const; protected: + friend class I2CDevice; + + I2CRegister(I2CDevice *parent, uint8_t a_register) : parent_(parent), register_(a_register) {} + I2CDevice *parent_; uint8_t register_; }; diff --git a/esphome/components/ltr390/__init__.py b/esphome/components/ltr390/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/ltr390/ltr390.cpp b/esphome/components/ltr390/ltr390.cpp new file mode 100644 index 0000000000..36f3835724 --- /dev/null +++ b/esphome/components/ltr390/ltr390.cpp @@ -0,0 +1,166 @@ +#include "ltr390.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace ltr390 { + +static const char *const TAG = "ltr390"; + +static const float GAINVALUES[5] = {1.0, 3.0, 6.0, 9.0, 18.0}; +static const float RESOLUTIONVALUE[6] = {4.0, 2.0, 1.0, 0.5, 0.25, 0.125}; +static const uint32_t MODEADDRESSES[2] = {0x0D, 0x10}; + +uint32_t little_endian_bytes_to_int(const uint8_t *buffer, uint8_t num_bytes) { + uint32_t value = 0; + + for (int i = 0; i < num_bytes; i++) { + value <<= 8; + value |= buffer[num_bytes - i - 1]; + } + + return value; +} + +optional LTR390Component::read_sensor_data_(LTR390MODE mode) { + const uint8_t num_bytes = 3; + uint8_t buffer[num_bytes]; + + // Wait until data available + const uint32_t now = millis(); + while (true) { + std::bitset<8> status = this->reg(LTR390_MAIN_STATUS).get(); + bool available = status[3]; + if (available) + break; + + if (millis() - now > 100) { + ESP_LOGW(TAG, "Sensor didn't return any data, aborting"); + return {}; + } + ESP_LOGD(TAG, "Waiting for data"); + delay(2); + } + + if (!this->read_bytes(MODEADDRESSES[mode], buffer, num_bytes)) { + ESP_LOGW(TAG, "Reading data from sensor failed!"); + return {}; + } + + return little_endian_bytes_to_int(buffer, num_bytes); +} + +void LTR390Component::read_als_() { + auto val = this->read_sensor_data_(LTR390_MODE_ALS); + if (!val.has_value()) + return; + uint32_t als = *val; + + if (this->light_sensor_ != nullptr) { + float lux = (0.6 * als) / (GAINVALUES[this->gain_] * RESOLUTIONVALUE[this->res_]) * this->wfac_; + this->light_sensor_->publish_state(lux); + } + + if (this->als_sensor_ != nullptr) { + this->als_sensor_->publish_state(als); + } +} + +void LTR390Component::read_uvs_() { + auto val = this->read_sensor_data_(LTR390_MODE_UVS); + if (!val.has_value()) + return; + uint32_t uv = *val; + + if (this->uvi_sensor_ != nullptr) { + this->uvi_sensor_->publish_state(uv / LTR390_SENSITIVITY * this->wfac_); + } + + if (this->uv_sensor_ != nullptr) { + this->uv_sensor_->publish_state(uv); + } +} + +void LTR390Component::read_mode_(int mode_index) { + // Set mode + LTR390MODE mode = std::get<0>(this->mode_funcs_[mode_index]); + + std::bitset<8> ctrl = this->reg(LTR390_MAIN_CTRL).get(); + ctrl[LTR390_CTRL_MODE] = mode; + this->reg(LTR390_MAIN_CTRL) = ctrl.to_ulong(); + + // After the sensor integration time do the following + this->set_timeout(((uint32_t) RESOLUTIONVALUE[this->res_]) * 100, [this, mode_index]() { + // Read from the sensor + std::get<1>(this->mode_funcs_[mode_index])(); + + // If there are more modes to read then begin the next + // otherwise stop + if (mode_index + 1 < this->mode_funcs_.size()) { + this->read_mode_(mode_index + 1); + } else { + this->reading_ = false; + } + }); +} + +void LTR390Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up ltr390..."); + + // reset + std::bitset<8> ctrl = this->reg(LTR390_MAIN_CTRL).get(); + ctrl[LTR390_CTRL_RST] = true; + this->reg(LTR390_MAIN_CTRL) = ctrl.to_ulong(); + delay(10); + + // Enable + ctrl = this->reg(LTR390_MAIN_CTRL).get(); + ctrl[LTR390_CTRL_EN] = true; + this->reg(LTR390_MAIN_CTRL) = ctrl.to_ulong(); + + // check enabled + ctrl = this->reg(LTR390_MAIN_CTRL).get(); + bool enabled = ctrl[LTR390_CTRL_EN]; + + if (!enabled) { + ESP_LOGW(TAG, "Sensor didn't respond with enabled state"); + this->mark_failed(); + return; + } + + // Set gain + this->reg(LTR390_GAIN) = gain_; + + // Set resolution + uint8_t res = this->reg(LTR390_MEAS_RATE).get(); + // resolution is in bits 5-7 + res &= ~0b01110000; + res |= res << 4; + this->reg(LTR390_MEAS_RATE) = res; + + // Set sensor read state + this->reading_ = false; + + // If we need the light sensor then add to the list + if (this->light_sensor_ != nullptr || this->als_sensor_ != nullptr) { + this->mode_funcs_.emplace_back(LTR390_MODE_ALS, std::bind(<R390Component::read_als_, this)); + } + + // If we need the UV sensor then add to the list + if (this->uvi_sensor_ != nullptr || this->uv_sensor_ != nullptr) { + this->mode_funcs_.emplace_back(LTR390_MODE_UVS, std::bind(<R390Component::read_uvs_, this)); + } +} + +void LTR390Component::dump_config() { LOG_I2C_DEVICE(this); } + +void LTR390Component::update() { + if (!this->reading_ && !mode_funcs_.empty()) { + this->reading_ = true; + this->read_mode_(0); + } +} + +} // namespace ltr390 +} // namespace esphome diff --git a/esphome/components/ltr390/ltr390.h b/esphome/components/ltr390/ltr390.h new file mode 100644 index 0000000000..d607a3e55f --- /dev/null +++ b/esphome/components/ltr390/ltr390.h @@ -0,0 +1,93 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/optional.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" +#include + +namespace esphome { +namespace ltr390 { + +enum LTR390CTRL { + LTR390_CTRL_EN = 1, + LTR390_CTRL_MODE = 3, + LTR390_CTRL_RST = 4, +}; + +// enums from https://github.com/adafruit/Adafruit_LTR390/ + +static const uint8_t LTR390_MAIN_CTRL = 0x00; +static const uint8_t LTR390_MEAS_RATE = 0x04; +static const uint8_t LTR390_GAIN = 0x05; +static const uint8_t LTR390_PART_ID = 0x06; +static const uint8_t LTR390_MAIN_STATUS = 0x07; +static const float LTR390_SENSITIVITY = 2300.0; + +// Sensing modes +enum LTR390MODE { + LTR390_MODE_ALS, + LTR390_MODE_UVS, +}; + +// Sensor gain levels +enum LTR390GAIN { + LTR390_GAIN_1 = 0, + LTR390_GAIN_3, // Default + LTR390_GAIN_6, + LTR390_GAIN_9, + LTR390_GAIN_18, +}; + +// Sensor resolution +enum LTR390RESOLUTION { + LTR390_RESOLUTION_20BIT, + LTR390_RESOLUTION_19BIT, + LTR390_RESOLUTION_18BIT, // Default + LTR390_RESOLUTION_17BIT, + LTR390_RESOLUTION_16BIT, + LTR390_RESOLUTION_13BIT, +}; + +class LTR390Component : public PollingComponent, public i2c::I2CDevice { + public: + float get_setup_priority() const override { return setup_priority::DATA; } + void setup() override; + void dump_config() override; + void update() override; + + void set_gain_value(LTR390GAIN gain) { this->gain_ = gain; } + void set_res_value(LTR390RESOLUTION res) { this->res_ = res; } + void set_wfac_value(float wfac) { this->wfac_ = wfac; } + + void set_light_sensor(sensor::Sensor *light_sensor) { this->light_sensor_ = light_sensor; } + void set_als_sensor(sensor::Sensor *als_sensor) { this->als_sensor_ = als_sensor; } + void set_uvi_sensor(sensor::Sensor *uvi_sensor) { this->uvi_sensor_ = uvi_sensor; } + void set_uv_sensor(sensor::Sensor *uv_sensor) { this->uv_sensor_ = uv_sensor; } + + protected: + optional read_sensor_data_(LTR390MODE mode); + + void read_als_(); + void read_uvs_(); + + void read_mode_(int mode_index); + + bool reading_; + + // a list of modes and corresponding read functions + std::vector>> mode_funcs_; + + LTR390GAIN gain_; + LTR390RESOLUTION res_; + float wfac_; + + sensor::Sensor *light_sensor_{nullptr}; + sensor::Sensor *als_sensor_{nullptr}; + + sensor::Sensor *uvi_sensor_{nullptr}; + sensor::Sensor *uv_sensor_{nullptr}; +}; + +} // namespace ltr390 +} // namespace esphome diff --git a/esphome/components/ltr390/sensor.py b/esphome/components/ltr390/sensor.py new file mode 100644 index 0000000000..0e70f7bb1b --- /dev/null +++ b/esphome/components/ltr390/sensor.py @@ -0,0 +1,98 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_GAIN, + CONF_LIGHT, + CONF_RESOLUTION, + UNIT_LUX, + ICON_BRIGHTNESS_5, + DEVICE_CLASS_ILLUMINANCE, +) + +CODEOWNERS = ["@sjtrny"] +DEPENDENCIES = ["i2c"] + +ltr390_ns = cg.esphome_ns.namespace("ltr390") + +LTR390Component = ltr390_ns.class_( + "LTR390Component", cg.PollingComponent, i2c.I2CDevice +) + +CONF_AMBIENT_LIGHT = "ambient_light" +CONF_UV_INDEX = "uv_index" +CONF_UV = "uv" +CONF_WINDOW_CORRECTION_FACTOR = "window_correction_factor" + +UNIT_COUNTS = "#" +UNIT_UVI = "UVI" + +LTR390GAIN = ltr390_ns.enum("LTR390GAIN") +GAIN_OPTIONS = { + "X1": LTR390GAIN.LTR390_GAIN_1, + "X3": LTR390GAIN.LTR390_GAIN_3, + "X6": LTR390GAIN.LTR390_GAIN_6, + "X9": LTR390GAIN.LTR390_GAIN_9, + "X18": LTR390GAIN.LTR390_GAIN_18, +} + +LTR390RESOLUTION = ltr390_ns.enum("LTR390RESOLUTION") +RES_OPTIONS = { + 20: LTR390RESOLUTION.LTR390_RESOLUTION_20BIT, + 19: LTR390RESOLUTION.LTR390_RESOLUTION_19BIT, + 18: LTR390RESOLUTION.LTR390_RESOLUTION_18BIT, + 17: LTR390RESOLUTION.LTR390_RESOLUTION_17BIT, + 16: LTR390RESOLUTION.LTR390_RESOLUTION_16BIT, + 13: LTR390RESOLUTION.LTR390_RESOLUTION_13BIT, +} + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(LTR390Component), + cv.Optional(CONF_LIGHT): sensor.sensor_schema( + UNIT_LUX, ICON_BRIGHTNESS_5, 1, DEVICE_CLASS_ILLUMINANCE + ), + cv.Optional(CONF_AMBIENT_LIGHT): sensor.sensor_schema( + UNIT_COUNTS, ICON_BRIGHTNESS_5, 1, DEVICE_CLASS_ILLUMINANCE + ), + cv.Optional(CONF_UV_INDEX): sensor.sensor_schema( + UNIT_UVI, ICON_BRIGHTNESS_5, 5, DEVICE_CLASS_ILLUMINANCE + ), + cv.Optional(CONF_UV): sensor.sensor_schema( + UNIT_COUNTS, ICON_BRIGHTNESS_5, 1, DEVICE_CLASS_ILLUMINANCE + ), + cv.Optional(CONF_GAIN, default="X3"): cv.enum(GAIN_OPTIONS), + cv.Optional(CONF_RESOLUTION, default=18): cv.enum(RES_OPTIONS), + cv.Optional(CONF_WINDOW_CORRECTION_FACTOR, default=1.0): cv.float_range( + min=1.0 + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x53)), + cv.has_at_least_one_key(CONF_LIGHT, CONF_AMBIENT_LIGHT, CONF_UV_INDEX, CONF_UV), +) + +TYPES = { + CONF_LIGHT: "set_light_sensor", + CONF_AMBIENT_LIGHT: "set_als_sensor", + CONF_UV_INDEX: "set_uvi_sensor", + CONF_UV: "set_uv_sensor", +} + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + cg.add(var.set_gain_value(config[CONF_GAIN])) + cg.add(var.set_res_value(config[CONF_RESOLUTION])) + cg.add(var.set_wfac_value(config[CONF_WINDOW_CORRECTION_FACTOR])) + + for key, funcName in TYPES.items(): + if key in config: + sens = await sensor.new_sensor(config[key]) + cg.add(getattr(var, funcName)(sens)) diff --git a/tests/test2.yaml b/tests/test2.yaml index e6df4d513e..d0634e0f7b 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -238,6 +238,20 @@ sensor: name: 'Inkbird IBS-TH1 Humidity' battery_level: name: 'Inkbird IBS-TH1 Battery Level' + - platform: ltr390 + uv: + name: "LTR390 UV" + uv_index: + name: "LTR390 UVI" + light: + name: "LTR390 Light" + ambient_light: + name: "LTR390 ALS" + gain: "X3" + resolution: 18 + window_correction_factor: 1.0 + address: 0x53 + update_interval: 60s - platform: sgp40 name: 'Workshop VOC' update_interval: 5s From e32722db70fb6dbc2d44aaa80fa7607d59ae3bc9 Mon Sep 17 00:00:00 2001 From: Trevor North Date: Wed, 22 Sep 2021 12:29:05 +0100 Subject: [PATCH 046/549] Allow sloppy datapoint message length (#1982) This allows datapoint update messages to be handled even if the overall message is longer than required (likely that it contains trailing empty bytes). The specific type handling will read only the expected data lengths so we only need to hard bail if we have too little data not too much. --- esphome/components/tuya/tuya.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index bbbc9274c3..d73ba50462 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -241,8 +241,10 @@ void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) { size_t data_size = (buffer[2] << 8) + buffer[3]; const uint8_t *data = buffer + 4; size_t data_len = len - 4; - if (data_size != data_len) { - ESP_LOGW(TAG, "Datapoint %u is not expected size (%zu != %zu)", datapoint.id, data_size, data_len); + if (data_size > data_len) { + ESP_LOGW(TAG, "Datapoint %u has extra bytes that will be ignored (%zu > %zu)", datapoint.id, data_size, data_len); + } else if (data_size < data_len) { + ESP_LOGW(TAG, "Datapoint %u is truncated and cannot be parsed (%zu < %zu)", datapoint.id, data_size, data_len); return; } datapoint.len = data_len; From fd836e982e31d8ff082a9f5c6847f217d4163b41 Mon Sep 17 00:00:00 2001 From: wifwucite <74489218+wifwucite@users.noreply.github.com> Date: Wed, 22 Sep 2021 13:42:58 +0200 Subject: [PATCH 047/549] Mqtt topics to support numeric fan speed (#1859) * numeric speed added * when dumping config for MQTT components log a note when skipped due to is_internal * added new topics to paython code validation/generation * reformatted with black * formatting corrected * use dump_config_ mechanism to skip internal components * use dump_config_ mechanism to skip internal components * style issues resolved * do_dump_config removed * formatting fixed * formatting fixed * Drop parent dump_config() calls Co-authored-by: Oxan van Leeuwen --- esphome/components/fan/__init__.py | 20 +++++++++++++ esphome/components/mqtt/mqtt_fan.cpp | 44 ++++++++++++++++++++++++++++ esphome/components/mqtt/mqtt_fan.h | 5 ++++ esphome/const.py | 2 ++ tests/test1.yaml | 2 ++ 5 files changed, 73 insertions(+) diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 46ff0c2d53..f8772948fc 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -12,6 +12,8 @@ from esphome.const import ( CONF_OSCILLATION_COMMAND_TOPIC, CONF_OSCILLATION_STATE_TOPIC, CONF_SPEED, + CONF_SPEED_LEVEL_COMMAND_TOPIC, + CONF_SPEED_LEVEL_STATE_TOPIC, CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, CONF_NAME, @@ -57,6 +59,12 @@ FAN_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All( cv.requires_component("mqtt"), cv.subscribe_topic ), + cv.Optional(CONF_SPEED_LEVEL_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_SPEED_LEVEL_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), cv.Optional(CONF_SPEED_STATE_TOPIC): cv.All( cv.requires_component("mqtt"), cv.publish_topic ), @@ -104,6 +112,18 @@ async def setup_fan_core_(var, config): config[CONF_OSCILLATION_COMMAND_TOPIC] ) ) + if CONF_SPEED_LEVEL_STATE_TOPIC in config: + cg.add( + mqtt_.set_custom_speed_level_state_topic( + config[CONF_SPEED_LEVEL_STATE_TOPIC] + ) + ) + if CONF_SPEED_LEVEL_COMMAND_TOPIC in config: + cg.add( + mqtt_.set_custom_speed_level_command_topic( + config[CONF_SPEED_LEVEL_COMMAND_TOPIC] + ) + ) if CONF_SPEED_STATE_TOPIC in config: cg.add(mqtt_.set_custom_speed_state_topic(config[CONF_SPEED_STATE_TOPIC])) if CONF_SPEED_COMMAND_TOPIC in config: diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index c8db5ecece..ed1ab605aa 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -16,6 +16,7 @@ MQTTFanComponent::MQTTFanComponent(FanState *state) : MQTTComponent(), state_(st FanState *MQTTFanComponent::get_state() const { return this->state_; } std::string MQTTFanComponent::component_type() const { return "fan"; } + void MQTTFanComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { auto val = parse_on_off(payload.c_str()); @@ -64,6 +65,26 @@ void MQTTFanComponent::setup() { }); } + if (this->state_->get_traits().supports_speed()) { + this->subscribe(this->get_speed_level_command_topic(), + [this](const std::string &topic, const std::string &payload) { + optional speed_level_opt = parse_int(payload); + if (speed_level_opt.has_value()) { + const int speed_level = speed_level_opt.value(); + if (speed_level >= 0 && speed_level <= this->state_->get_traits().supported_speed_count()) { + ESP_LOGD(TAG, "New speed level %d", speed_level); + this->state_->make_call().set_speed(speed_level).perform(); + } else { + ESP_LOGW(TAG, "Invalid speed level %d", speed_level); + this->status_momentary_warning("speed", 5000); + } + } else { + ESP_LOGW(TAG, "Invalid speed level %s (int expected)", payload.c_str()); + this->status_momentary_warning("speed", 5000); + } + }); + } + 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() @@ -75,6 +96,22 @@ void MQTTFanComponent::setup() { auto f = std::bind(&MQTTFanComponent::publish_state, this); this->state_->add_on_state_callback([this, f]() { this->defer("send", f); }); } + +void MQTTFanComponent::dump_config() { + ESP_LOGCONFIG(TAG, "MQTT Fan '%s': ", this->state_->get_name().c_str()); + LOG_MQTT_COMPONENT(true, true); + if (this->state_->get_traits().supports_oscillation()) { + ESP_LOGCONFIG(TAG, " Oscillation State Topic: '%s'", this->get_oscillation_state_topic().c_str()); + ESP_LOGCONFIG(TAG, " Oscillation Command Topic: '%s'", this->get_oscillation_command_topic().c_str()); + } + if (this->state_->get_traits().supports_speed()) { + ESP_LOGCONFIG(TAG, " Speed Level State Topic: '%s'", this->get_speed_level_state_topic().c_str()); + ESP_LOGCONFIG(TAG, " Speed Level Command Topic: '%s'", this->get_speed_level_command_topic().c_str()); + ESP_LOGCONFIG(TAG, " Speed State Topic: '%s'", this->get_speed_state_topic().c_str()); + ESP_LOGCONFIG(TAG, " Speed Command Topic: '%s'", this->get_speed_command_topic().c_str()); + } +} + bool MQTTFanComponent::send_initial_state() { return this->publish_state(); } std::string MQTTFanComponent::friendly_name() const { return this->state_->get_name(); } void MQTTFanComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { @@ -83,6 +120,8 @@ void MQTTFanComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfi root["oscillation_state_topic"] = this->get_oscillation_state_topic(); } if (this->state_->get_traits().supports_speed()) { + root["speed_level_command_topic"] = this->get_speed_level_command_topic(); + root["speed_level_state_topic"] = this->get_speed_level_state_topic(); root["speed_command_topic"] = this->get_speed_command_topic(); root["speed_state_topic"] = this->get_speed_state_topic(); } @@ -99,6 +138,11 @@ bool MQTTFanComponent::publish_state() { failed = failed || !success; } auto traits = this->state_->get_traits(); + if (traits.supports_speed()) { + std::string payload = to_string(this->state_->speed); + bool success = this->publish(this->get_speed_level_state_topic(), payload); + failed = failed || !success; + } if (traits.supports_speed()) { const char *payload; // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) diff --git a/esphome/components/mqtt/mqtt_fan.h b/esphome/components/mqtt/mqtt_fan.h index 99d9c055cf..00263e13eb 100644 --- a/esphome/components/mqtt/mqtt_fan.h +++ b/esphome/components/mqtt/mqtt_fan.h @@ -17,6 +17,8 @@ class MQTTFanComponent : public mqtt::MQTTComponent { MQTT_COMPONENT_CUSTOM_TOPIC(oscillation, command) MQTT_COMPONENT_CUSTOM_TOPIC(oscillation, state) + MQTT_COMPONENT_CUSTOM_TOPIC(speed_level, command) + MQTT_COMPONENT_CUSTOM_TOPIC(speed_level, state) MQTT_COMPONENT_CUSTOM_TOPIC(speed, command) MQTT_COMPONENT_CUSTOM_TOPIC(speed, state) @@ -26,6 +28,9 @@ class MQTTFanComponent : public mqtt::MQTTComponent { // (In most use cases you won't need these) /// Setup the fan subscriptions and discovery. void setup() override; + + void dump_config() override; + /// Send the full current state to MQTT. bool send_initial_state() override; bool publish_state(); diff --git a/esphome/const.py b/esphome/const.py index f032cf0fc3..2cf261b4b5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -597,6 +597,8 @@ CONF_SOURCE = "source" CONF_SPEED = "speed" CONF_SPEED_COMMAND_TOPIC = "speed_command_topic" CONF_SPEED_COUNT = "speed_count" +CONF_SPEED_LEVEL_COMMAND_TOPIC = "speed_level_command_topic" +CONF_SPEED_LEVEL_STATE_TOPIC = "speed_level_state_topic" CONF_SPEED_STATE_TOPIC = "speed_state_topic" CONF_SPI_ID = "spi_id" CONF_SPIKE_REJECTION = "spike_rejection" diff --git a/tests/test1.yaml b/tests/test1.yaml index c1ddf26488..0fb14fd34b 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1978,6 +1978,8 @@ fan: direction_output: gpio_26 oscillation_state_topic: oscillation/state/topic oscillation_command_topic: oscillation/command/topic + speed_level_state_topic: speed_level/state/topic + speed_level_command_topic: speed_level/command/topic speed_state_topic: speed/state/topic speed_command_topic: speed/command/topic on_speed_set: From 8bebf138ee7010a09c14ac547e1e74299d4f599d Mon Sep 17 00:00:00 2001 From: Gustavo Ambrozio Date: Wed, 22 Sep 2021 01:44:09 -1000 Subject: [PATCH 048/549] Wifi scan results (#1605) * adding a scan results wifi text sensor * Code comment * Adding scan results to test * Removing redundant call * linting * Better method to update wifi info Co-authored-by: Otto Winter * Getting loop back At least for now. * Trying out suggestion again * Applying cr suggestions Co-authored-by: Otto Winter --- esphome/components/wifi_info/text_sensor.py | 10 +++++++ .../wifi_info/wifi_info_text_sensor.cpp | 1 + .../wifi_info/wifi_info_text_sensor.h | 29 +++++++++++++++++++ esphome/const.py | 1 + tests/test1.yaml | 2 ++ 5 files changed, 43 insertions(+) diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index 50ec3eb272..1922502204 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -5,6 +5,7 @@ from esphome.const import ( CONF_BSSID, CONF_ID, CONF_IP_ADDRESS, + CONF_SCAN_RESULTS, CONF_SSID, CONF_MAC_ADDRESS, ) @@ -15,6 +16,9 @@ wifi_info_ns = cg.esphome_ns.namespace("wifi_info") IPAddressWiFiInfo = wifi_info_ns.class_( "IPAddressWiFiInfo", text_sensor.TextSensor, cg.Component ) +ScanResultsWiFiInfo = wifi_info_ns.class_( + "ScanResultsWiFiInfo", text_sensor.TextSensor, cg.PollingComponent +) SSIDWiFiInfo = wifi_info_ns.class_("SSIDWiFiInfo", text_sensor.TextSensor, cg.Component) BSSIDWiFiInfo = wifi_info_ns.class_( "BSSIDWiFiInfo", text_sensor.TextSensor, cg.Component @@ -30,6 +34,11 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(): cv.declare_id(IPAddressWiFiInfo), } ), + cv.Optional(CONF_SCAN_RESULTS): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ScanResultsWiFiInfo), + } + ).extend(cv.polling_component_schema("60s")), cv.Optional(CONF_SSID): text_sensor.TEXT_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(SSIDWiFiInfo), @@ -62,3 +71,4 @@ async def to_code(config): await setup_conf(config, CONF_SSID) await setup_conf(config, CONF_BSSID) await setup_conf(config, CONF_MAC_ADDRESS) + await setup_conf(config, CONF_SCAN_RESULTS) diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.cpp b/esphome/components/wifi_info/wifi_info_text_sensor.cpp index 92e5d93a5a..0b73de68de 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.cpp +++ b/esphome/components/wifi_info/wifi_info_text_sensor.cpp @@ -7,6 +7,7 @@ namespace wifi_info { static const char *const TAG = "wifi_info"; void IPAddressWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "WifiInfo IPAddress", this); } +void ScanResultsWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "WifiInfo Scan Results", this); } void SSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "WifiInfo SSID", this); } void BSSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "WifiInfo BSSID", this); } void MacAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "WifiInfo Mac Address", this); } diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index de1c7f71fc..b2f37de363 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -24,6 +24,35 @@ class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor { network::IPAddress last_ip_; }; +class ScanResultsWiFiInfo : public PollingComponent, public text_sensor::TextSensor { + public: + void update() override { + std::string scan_results; + for (auto &scan : wifi::global_wifi_component->get_scan_result()) { + if (scan.get_is_hidden()) + continue; + + scan_results += scan.get_ssid(); + scan_results += ": "; + scan_results += esphome::to_string(scan.get_rssi()); + scan_results += "dB\n"; + } + + if (this->last_scan_results_ != scan_results) { + this->last_scan_results_ = scan_results; + // There's a limit of 255 characters per state. + // Longer states just don't get sent so we truncate it. + this->publish_state(scan_results.substr(0, 255)); + } + } + float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + std::string unique_id() override { return get_mac_address() + "-wifiinfo-scanresults"; } + void dump_config() override; + + protected: + std::string last_scan_results_; +}; + class SSIDWiFiInfo : public Component, public text_sensor::TextSensor { public: void loop() override { diff --git a/esphome/const.py b/esphome/const.py index 2cf261b4b5..a52085fbb7 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -558,6 +558,7 @@ CONF_SAFE_MODE = "safe_mode" CONF_SAMSUNG = "samsung" CONF_SATELLITES = "satellites" CONF_SCAN = "scan" +CONF_SCAN_RESULTS = "scan_results" CONF_SCL = "scl" CONF_SCL_PIN = "scl_pin" CONF_SDA = "sda" diff --git a/tests/test1.yaml b/tests/test1.yaml index 0fb14fd34b..5bec56c80f 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2359,6 +2359,8 @@ text_sensor: name: Template Text Sensor id: ${textname}_text - platform: wifi_info + scan_results: + name: 'Scan Results' ip_address: name: 'IP Address' ssid: From 66761ff3400681828940e6b28c4466679bf045e3 Mon Sep 17 00:00:00 2001 From: ZJY <934526987@qq.com> Date: Wed, 22 Sep 2021 19:47:41 +0800 Subject: [PATCH 049/549] Add SSD1305 support to SSD1306 integration along with few new options (#1902) * Add serveral options for SSD1306 integration * Add SSD1305 support (SSD1305 is similar to SSD1306, it seems SSD1305 has brightness and color register but does not have charge pump) * Add some description when manipulating registers * Add flip, offset and invert option to get more compatibility with various display modules * Fix typo `setup_ssd1036' -> `setup_ssd1306' * Add SSD1306 brightness validation tip * Add more description, limit offset range * Changes according to linter * Fix test * Raise error instead of using warning * Fix wrong logic * Remove logger Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> * Remove logging import Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/ssd1306_base/__init__.py | 44 +++++++- .../components/ssd1306_base/ssd1306_base.cpp | 106 ++++++++++++++---- .../components/ssd1306_base/ssd1306_base.h | 16 +++ esphome/components/ssd1306_i2c/display.py | 4 +- .../components/ssd1306_i2c/ssd1306_i2c.cpp | 5 + esphome/components/ssd1306_spi/display.py | 4 +- .../components/ssd1306_spi/ssd1306_spi.cpp | 5 + tests/test1.yaml | 2 +- 8 files changed, 159 insertions(+), 27 deletions(-) diff --git a/esphome/components/ssd1306_base/__init__.py b/esphome/components/ssd1306_base/__init__.py index 9652d01efa..bc2e558f1b 100644 --- a/esphome/components/ssd1306_base/__init__.py +++ b/esphome/components/ssd1306_base/__init__.py @@ -8,12 +8,19 @@ from esphome.const import ( CONF_MODEL, CONF_RESET_PIN, CONF_BRIGHTNESS, + CONF_CONTRAST, + CONF_INVERT, ) ssd1306_base_ns = cg.esphome_ns.namespace("ssd1306_base") SSD1306 = ssd1306_base_ns.class_("SSD1306", cg.PollingComponent, display.DisplayBuffer) SSD1306Model = ssd1306_base_ns.enum("SSD1306Model") +CONF_FLIP_X = "flip_x" +CONF_FLIP_Y = "flip_y" +CONF_OFFSET_X = "offset_x" +CONF_OFFSET_Y = "offset_y" + MODELS = { "SSD1306_128X32": SSD1306Model.SSD1306_MODEL_128_32, "SSD1306_128X64": SSD1306Model.SSD1306_MODEL_128_64, @@ -23,21 +30,44 @@ MODELS = { "SH1106_128X64": SSD1306Model.SH1106_MODEL_128_64, "SH1106_96X16": SSD1306Model.SH1106_MODEL_96_16, "SH1106_64X48": SSD1306Model.SH1106_MODEL_64_48, + "SSD1305_128X32": SSD1306Model.SSD1305_MODEL_128_32, + "SSD1305_128X64": SSD1306Model.SSD1305_MODEL_128_64, } SSD1306_MODEL = cv.enum(MODELS, upper=True, space="_") + +def _validate(value): + model = value[CONF_MODEL] + if model not in ("SSD1305_128X32", "SSD1305_128X64"): + # Contrast is default value (1.0) while brightness is not + # Indicates user is using old `brightness` option + if value[CONF_BRIGHTNESS] != 1.0 and value[CONF_CONTRAST] == 1.0: + raise cv.Invalid( + "SSD1306/SH1106 no longer accepts brightness option, " + 'please use "contrast" instead.' + ) + + return value + + SSD1306_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend( { cv.Required(CONF_MODEL): SSD1306_MODEL, cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + cv.Optional(CONF_CONTRAST, default=1.0): cv.percentage, cv.Optional(CONF_EXTERNAL_VCC): cv.boolean, + cv.Optional(CONF_FLIP_X, default=True): cv.boolean, + cv.Optional(CONF_FLIP_Y, default=True): cv.boolean, + cv.Optional(CONF_OFFSET_X, default=0): cv.int_range(min=0, max=15), + cv.Optional(CONF_OFFSET_Y, default=0): cv.int_range(min=0, max=15), + cv.Optional(CONF_INVERT, default=False): cv.boolean, } ).extend(cv.polling_component_schema("1s")) -async def setup_ssd1036(var, config): +async def setup_ssd1306(var, config): await cg.register_component(var, config) await display.register_display(var, config) @@ -47,8 +77,20 @@ async def setup_ssd1036(var, config): cg.add(var.set_reset_pin(reset)) if CONF_BRIGHTNESS in config: cg.add(var.init_brightness(config[CONF_BRIGHTNESS])) + if CONF_CONTRAST in config: + cg.add(var.init_contrast(config[CONF_CONTRAST])) if CONF_EXTERNAL_VCC in config: cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC])) + if CONF_FLIP_X in config: + cg.add(var.init_flip_x(config[CONF_FLIP_X])) + if CONF_FLIP_Y in config: + cg.add(var.init_flip_y(config[CONF_FLIP_X])) + if CONF_OFFSET_X in config: + cg.add(var.init_offset_x(config[CONF_OFFSET_X])) + if CONF_OFFSET_Y in config: + cg.add(var.init_offset_y(config[CONF_OFFSET_Y])) + if CONF_INVERT in config: + cg.add(var.init_invert(config[CONF_INVERT])) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index d321933e8f..b1a2538ebd 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -8,12 +8,13 @@ namespace ssd1306_base { static const char *const TAG = "ssd1306"; static const uint8_t SSD1306_MAX_CONTRAST = 255; +static const uint8_t SSD1305_MAX_BRIGHTNESS = 255; static const uint8_t SSD1306_COMMAND_DISPLAY_OFF = 0xAE; static const uint8_t SSD1306_COMMAND_DISPLAY_ON = 0xAF; static const uint8_t SSD1306_COMMAND_SET_DISPLAY_CLOCK_DIV = 0xD5; static const uint8_t SSD1306_COMMAND_SET_MULTIPLEX = 0xA8; -static const uint8_t SSD1306_COMMAND_SET_DISPLAY_OFFSET = 0xD3; +static const uint8_t SSD1306_COMMAND_SET_DISPLAY_OFFSET_Y = 0xD3; static const uint8_t SSD1306_COMMAND_SET_START_LINE = 0x40; static const uint8_t SSD1306_COMMAND_CHARGE_PUMP = 0x8D; static const uint8_t SSD1306_COMMAND_MEMORY_MODE = 0x20; @@ -28,33 +29,60 @@ static const uint8_t SSD1306_COMMAND_DISPLAY_ALL_ON_RESUME = 0xA4; static const uint8_t SSD1306_COMMAND_DEACTIVATE_SCROLL = 0x2E; static const uint8_t SSD1306_COMMAND_COLUMN_ADDRESS = 0x21; static const uint8_t SSD1306_COMMAND_PAGE_ADDRESS = 0x22; +static const uint8_t SSD1306_COMMAND_NORMAL_DISPLAY = 0xA6; +static const uint8_t SSD1306_COMMAND_INVERSE_DISPLAY = 0xA7; -static const uint8_t SSD1306_NORMAL_DISPLAY = 0xA6; +static const uint8_t SSD1305_COMMAND_SET_BRIGHTNESS = 0x82; +static const uint8_t SSD1305_COMMAND_SET_AREA_COLOR = 0xD8; void SSD1306::setup() { this->init_internal_(this->get_buffer_length_()); + // Turn off display during initialization (0xAE) this->command(SSD1306_COMMAND_DISPLAY_OFF); - this->command(SSD1306_COMMAND_SET_DISPLAY_CLOCK_DIV); - this->command(0x80); // suggested ratio + // Set oscillator frequency to 4'b1000 with no clock division (0xD5) + this->command(SSD1306_COMMAND_SET_DISPLAY_CLOCK_DIV); + // Oscillator frequency <= 4'b1000, no clock division + this->command(0x80); + + // Enable low power display mode for SSD1305 (0xD8) + if (this->is_ssd1305_()) { + this->command(SSD1305_COMMAND_SET_AREA_COLOR); + this->command(0x05); + } + + // Set mux ratio to [Y pixels - 1] (0xA8) this->command(SSD1306_COMMAND_SET_MULTIPLEX); this->command(this->get_height_internal() - 1); - this->command(SSD1306_COMMAND_SET_DISPLAY_OFFSET); - this->command(0x00); // no offset - this->command(SSD1306_COMMAND_SET_START_LINE | 0x00); // start at line 0 - this->command(SSD1306_COMMAND_CHARGE_PUMP); - if (this->external_vcc_) - this->command(0x10); - else - this->command(0x14); + // Set Y offset (0xD3) + this->command(SSD1306_COMMAND_SET_DISPLAY_OFFSET_Y); + this->command(0x00 + this->offset_y_); + // Set start line at line 0 (0x40) + this->command(SSD1306_COMMAND_SET_START_LINE | 0x00); + // SSD1305 does not have charge pump + if (!this->is_ssd1305_()) { + // Enable charge pump (0x8D) + this->command(SSD1306_COMMAND_CHARGE_PUMP); + if (this->external_vcc_) + this->command(0x10); + else + this->command(0x14); + } + + // Set addressing mode to horizontal (0x20) this->command(SSD1306_COMMAND_MEMORY_MODE); this->command(0x00); - this->command(SSD1306_COMMAND_SEGRE_MAP | 0x01); - this->command(SSD1306_COMMAND_COM_SCAN_DEC); + // X flip mode (0xA0, 0xA1) + this->command(SSD1306_COMMAND_SEGRE_MAP | this->flip_x_); + + // Y flip mode (0xC0, 0xC8) + this->command(SSD1306_COMMAND_COM_SCAN_INC | (this->flip_y_ << 3)); + + // Set pin configuration (0xDA) this->command(SSD1306_COMMAND_SET_COM_PINS); switch (this->model_) { case SSD1306_MODEL_128_32: @@ -67,25 +95,37 @@ void SSD1306::setup() { case SH1106_MODEL_128_64: case SSD1306_MODEL_64_48: case SH1106_MODEL_64_48: + case SSD1305_MODEL_128_32: + case SSD1305_MODEL_128_64: this->command(0x12); break; } + // Pre-charge period (0xD9) this->command(SSD1306_COMMAND_SET_PRE_CHARGE); if (this->external_vcc_) this->command(0x22); else this->command(0xF1); + // Set V_COM (0xDB) this->command(SSD1306_COMMAND_SET_VCOM_DETECT); this->command(0x00); + // Display output follow RAM (0xA4) this->command(SSD1306_COMMAND_DISPLAY_ALL_ON_RESUME); - this->command(SSD1306_NORMAL_DISPLAY); + // Inverse display mode (0xA6, 0xA7) + this->command(SSD1306_COMMAND_NORMAL_DISPLAY | this->invert_); + + // Disable scrolling mode (0x2E) this->command(SSD1306_COMMAND_DEACTIVATE_SCROLL); - set_brightness(this->brightness_); + // Contrast and brighrness + // SSD1306 does not have brightness setting + set_contrast(this->contrast_); + if (this->is_ssd1305_()) + set_brightness(this->brightness_); this->fill(Color::BLACK); // clear display - ensures we do not see garbage at power-on this->display(); // ...write buffer, which actually clears the display's memory @@ -101,12 +141,12 @@ void SSD1306::display() { this->command(SSD1306_COMMAND_COLUMN_ADDRESS); switch (this->model_) { case SSD1306_MODEL_64_48: - this->command(0x20); - this->command(0x20 + this->get_width_internal() - 1); + this->command(0x20 + this->offset_x_); + this->command(0x20 + this->offset_x_ + this->get_width_internal() - 1); break; default: - this->command(0); // Page start address, 0 - this->command(this->get_width_internal() - 1); + this->command(0 + this->offset_x_); // Page start address, 0 + this->command(this->get_width_internal() + this->offset_x_ - 1); break; } @@ -122,16 +162,28 @@ bool SSD1306::is_sh1106_() const { return this->model_ == SH1106_MODEL_96_16 || this->model_ == SH1106_MODEL_128_32 || this->model_ == SH1106_MODEL_128_64; } +bool SSD1306::is_ssd1305_() const { + return this->model_ == SSD1305_MODEL_128_64 || this->model_ == SSD1305_MODEL_128_64; +} void SSD1306::update() { this->do_update_(); this->display(); } +void SSD1306::set_contrast(float contrast) { + // validation + this->contrast_ = clamp(contrast, 0.0F, 1.0F); + // now write the new contrast level to the display (0x81) + this->command(SSD1306_COMMAND_SET_CONTRAST); + this->command(int(SSD1306_MAX_CONTRAST * (this->contrast_))); +} void SSD1306::set_brightness(float brightness) { // validation + if (!this->is_ssd1305_()) + return; this->brightness_ = clamp(brightness, 0.0F, 1.0F); - // now write the new brightness level to the display - this->command(SSD1306_COMMAND_SET_CONTRAST); - this->command(int(SSD1306_MAX_CONTRAST * (this->brightness_))); + // now write the new brightness level to the display (0x82) + this->command(SSD1305_COMMAND_SET_BRIGHTNESS); + this->command(int(SSD1305_MAX_BRIGHTNESS * (this->brightness_))); } bool SSD1306::is_on() { return this->is_on_; } void SSD1306::turn_on() { @@ -146,9 +198,11 @@ int SSD1306::get_height_internal() { switch (this->model_) { case SSD1306_MODEL_128_32: case SH1106_MODEL_128_32: + case SSD1305_MODEL_128_32: return 32; case SSD1306_MODEL_128_64: case SH1106_MODEL_128_64: + case SSD1305_MODEL_128_64: return 64; case SSD1306_MODEL_96_16: case SH1106_MODEL_96_16: @@ -166,6 +220,8 @@ int SSD1306::get_width_internal() { case SH1106_MODEL_128_32: case SSD1306_MODEL_128_64: case SH1106_MODEL_128_64: + case SSD1305_MODEL_128_32: + case SSD1305_MODEL_128_64: return 128; case SSD1306_MODEL_96_16: case SH1106_MODEL_96_16: @@ -227,6 +283,10 @@ const char *SSD1306::model_str_() { return "SH1106 96x16"; case SH1106_MODEL_64_48: return "SH1106 64x48"; + case SSD1305_MODEL_128_32: + return "SSD1305 128x32"; + case SSD1305_MODEL_128_64: + return "SSD1305 128x32"; default: return "Unknown"; } diff --git a/esphome/components/ssd1306_base/ssd1306_base.h b/esphome/components/ssd1306_base/ssd1306_base.h index 2c54af7a67..54cb10d153 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.h +++ b/esphome/components/ssd1306_base/ssd1306_base.h @@ -16,6 +16,8 @@ enum SSD1306Model { SH1106_MODEL_128_64, SH1106_MODEL_96_16, SH1106_MODEL_64_48, + SSD1305_MODEL_128_32, + SSD1305_MODEL_128_64, }; class SSD1306 : public PollingComponent, public display::DisplayBuffer { @@ -29,8 +31,15 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer { void set_model(SSD1306Model model) { this->model_ = model; } void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } void set_external_vcc(bool external_vcc) { this->external_vcc_ = external_vcc; } + void init_contrast(float contrast) { this->contrast_ = contrast; } + void set_contrast(float contrast); void init_brightness(float brightness) { this->brightness_ = brightness; } void set_brightness(float brightness); + void init_flip_x(boolean flip_x) { this->flip_x_ = flip_x; } + void init_flip_y(boolean flip_y) { this->flip_y_ = flip_y; } + void init_offset_x(uint8_t offset_x) { this->offset_x_ = offset_x; } + void init_offset_y(uint8_t offset_y) { this->offset_y_ = offset_y; } + void init_invert(boolean invert) { this->invert_ = invert; } bool is_on(); void turn_on(); void turn_off(); @@ -43,6 +52,7 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer { void init_reset_(); bool is_sh1106_() const; + bool is_ssd1305_() const; void draw_absolute_pixel_internal(int x, int y, Color color) override; @@ -55,7 +65,13 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer { GPIOPin *reset_pin_{nullptr}; bool external_vcc_{false}; bool is_on_{false}; + float contrast_{1.0}; float brightness_{1.0}; + bool flip_x_{true}; + bool flip_y_{true}; + uint8_t offset_x_{0}; + uint8_t offset_y_{0}; + bool invert_{false}; }; } // namespace ssd1306_base diff --git a/esphome/components/ssd1306_i2c/display.py b/esphome/components/ssd1306_i2c/display.py index 4b51a90431..c51ab5f93e 100644 --- a/esphome/components/ssd1306_i2c/display.py +++ b/esphome/components/ssd1306_i2c/display.py @@ -1,6 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import ssd1306_base, i2c +from esphome.components.ssd1306_base import _validate from esphome.const import CONF_ID, CONF_LAMBDA, CONF_PAGES AUTO_LOAD = ["ssd1306_base"] @@ -18,10 +19,11 @@ CONFIG_SCHEMA = cv.All( .extend(cv.COMPONENT_SCHEMA) .extend(i2c.i2c_device_schema(0x3C)), cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), + _validate, ) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await ssd1306_base.setup_ssd1036(var, config) + await ssd1306_base.setup_ssd1306(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp index 45fda4870e..fddea25fc8 100644 --- a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp +++ b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp @@ -25,6 +25,11 @@ void I2CSSD1306::dump_config() { ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); LOG_PIN(" Reset Pin: ", this->reset_pin_); ESP_LOGCONFIG(TAG, " External VCC: %s", YESNO(this->external_vcc_)); + ESP_LOGCONFIG(TAG, " Flip X: %s", YESNO(this->flip_x_)); + ESP_LOGCONFIG(TAG, " Flip Y: %s", YESNO(this->flip_y_)); + ESP_LOGCONFIG(TAG, " Offset X: %d", this->offset_x_); + ESP_LOGCONFIG(TAG, " Offset Y: %d", this->offset_y_); + ESP_LOGCONFIG(TAG, " Inverted Color: %s", YESNO(this->invert_)); LOG_UPDATE_INTERVAL(this); if (this->error_code_ == COMMUNICATION_FAILED) { diff --git a/esphome/components/ssd1306_spi/display.py b/esphome/components/ssd1306_spi/display.py index f7dd1553ba..0af1168bde 100644 --- a/esphome/components/ssd1306_spi/display.py +++ b/esphome/components/ssd1306_spi/display.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.components import spi, ssd1306_base +from esphome.components.ssd1306_base import _validate from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES AUTO_LOAD = ["ssd1306_base"] @@ -20,12 +21,13 @@ CONFIG_SCHEMA = cv.All( .extend(cv.COMPONENT_SCHEMA) .extend(spi.spi_device_schema()), cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), + _validate, ) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await ssd1306_base.setup_ssd1036(var, config) + await ssd1306_base.setup_ssd1306(var, config) await spi.register_spi_device(var, config) dc = await cg.gpio_pin_expression(config[CONF_DC_PIN]) diff --git a/esphome/components/ssd1306_spi/ssd1306_spi.cpp b/esphome/components/ssd1306_spi/ssd1306_spi.cpp index 5ef25b8139..33d474a8ee 100644 --- a/esphome/components/ssd1306_spi/ssd1306_spi.cpp +++ b/esphome/components/ssd1306_spi/ssd1306_spi.cpp @@ -22,6 +22,11 @@ void SPISSD1306::dump_config() { LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); ESP_LOGCONFIG(TAG, " External VCC: %s", YESNO(this->external_vcc_)); + ESP_LOGCONFIG(TAG, " Flip X: %s", YESNO(this->flip_x_)); + ESP_LOGCONFIG(TAG, " Flip Y: %s", YESNO(this->flip_y_)); + ESP_LOGCONFIG(TAG, " Offset X: %d", this->offset_x_); + ESP_LOGCONFIG(TAG, " Offset Y: %d", this->offset_y_); + ESP_LOGCONFIG(TAG, " Inverted Color: %s", YESNO(this->invert_)); LOG_UPDATE_INTERVAL(this); } void SPISSD1306::command(uint8_t value) { diff --git a/tests/test1.yaml b/tests/test1.yaml index 5bec56c80f..6109e9f5c2 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2072,7 +2072,7 @@ display: reset_pin: GPIO23 address: 0x3C id: display1 - brightness: 60% + contrast: 60% pages: - id: page1 lambda: |- From ed3ad615d8500fd4844db457926e6d6fbb000569 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 22 Sep 2021 14:07:39 +0200 Subject: [PATCH 050/549] Fix compilation due to incompatibility between #1237 and IDF changes (#2372) --- esphome/components/ssd1306_base/ssd1306_base.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/ssd1306_base/ssd1306_base.h b/esphome/components/ssd1306_base/ssd1306_base.h index 54cb10d153..09417a2c10 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.h +++ b/esphome/components/ssd1306_base/ssd1306_base.h @@ -35,11 +35,11 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer { void set_contrast(float contrast); void init_brightness(float brightness) { this->brightness_ = brightness; } void set_brightness(float brightness); - void init_flip_x(boolean flip_x) { this->flip_x_ = flip_x; } - void init_flip_y(boolean flip_y) { this->flip_y_ = flip_y; } + void init_flip_x(bool flip_x) { this->flip_x_ = flip_x; } + void init_flip_y(bool flip_y) { this->flip_y_ = flip_y; } void init_offset_x(uint8_t offset_x) { this->offset_x_ = offset_x; } void init_offset_y(uint8_t offset_y) { this->offset_y_ = offset_y; } - void init_invert(boolean invert) { this->invert_ = invert; } + void init_invert(bool invert) { this->invert_ = invert; } bool is_on(); void turn_on(); void turn_off(); From 0406e271007c539de4d7decd7692dea8a1f046ed Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 22 Sep 2021 19:07:57 +0200 Subject: [PATCH 051/549] Don't generate IDs with the name of loaded integrations (#2373) --- esphome/core/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 9ec7fe358d..8021fc2f53 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -312,7 +312,7 @@ class ID: if self.id is None: base = str(self.type).replace("::", "_").lower() name = "".join(c for c in base if c.isalnum() or c == "_") - used = set(registered_ids) | set(RESERVED_IDS) + used = set(registered_ids) | set(RESERVED_IDS) | CORE.loaded_integrations self.id = ensure_unique_string(name, used) return self.id From 262d69308dc45f9ec133ad9aae532dad81efaa8f Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Wed, 22 Sep 2021 19:08:42 +0200 Subject: [PATCH 052/549] fix i2c scanning eror for Arduino (#2364) --- esphome/components/i2c/i2c_bus_arduino.cpp | 5 ++--- esphome/components/i2c/i2c_bus_esp_idf.cpp | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index aba412c3f7..40d8049617 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -33,9 +33,8 @@ void ArduinoI2CBus::dump_config() { 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); - + for (uint8_t address = 8; address < 120; address++) { + auto err = writev(address, nullptr, 0); if (err == ERROR_OK) { ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address); found++; diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 4b93b41877..8bf97b63ec 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -43,8 +43,8 @@ void IDFI2CBus::dump_config() { 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); + for (uint8_t address = 8; address < 120; address++) { + auto err = writev(address, nullptr, 0); if (err == ERROR_OK) { ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address); From f463cd98f8543a36ec9925083561796b088d030d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Sep 2021 19:50:11 +0200 Subject: [PATCH 053/549] Bump tzlocal from 2.1 to 3.0 (#2294) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Otto winter --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e97c79985f..95ca95430d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ PyYAML==5.4.1 paho-mqtt==1.5.1 colorama==0.4.4 tornado==6.1 -tzlocal==2.1 +tzlocal==3.0 pytz==2021.1 pyserial==3.5 platformio==5.2.0 From edb557f79e1cf3fc33a93d8da59748ea2c097fb0 Mon Sep 17 00:00:00 2001 From: Philipp Riederer Date: Wed, 22 Sep 2021 19:50:19 +0200 Subject: [PATCH 054/549] ledc: do not try to write_state to an uninitialized output (#1732) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Philipp Tölke Co-authored-by: Otto winter --- esphome/components/ledc/ledc_output.cpp | 11 +++++++++++ esphome/components/ledc/ledc_output.h | 1 + 2 files changed, 12 insertions(+) diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index 77610a476f..21a747e34d 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -31,6 +31,11 @@ optional ledc_bit_depth_for_frequency(float frequency) { } void LEDCOutput::write_state(float state) { + if (!initialized_) { + ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!"); + return; + } + if (this->pin_->is_inverted()) state = 1.0f - state; @@ -81,6 +86,7 @@ void LEDCOutput::setup() { chan_conf.duty = inverted_ == pin_->is_inverted() ? 0 : (1U << bit_depth_); chan_conf.hpoint = 0; ledc_channel_config(&chan_conf); + initialized_ = true; #endif } @@ -101,8 +107,13 @@ void LEDCOutput::update_frequency(float frequency) { this->frequency_ = frequency; #ifdef USE_ARDUINO ledcSetup(this->channel_, frequency, this->bit_depth_); + initialized_ = true; #endif // USE_ARDUINO #ifdef USE_ESP_IDF + if (!initialized_) { + ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!"); + return; + } auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; auto timer_num = static_cast((channel_ % 8) / 2); diff --git a/esphome/components/ledc/ledc_output.h b/esphome/components/ledc/ledc_output.h index f810ce1e35..e02cefd170 100644 --- a/esphome/components/ledc/ledc_output.h +++ b/esphome/components/ledc/ledc_output.h @@ -36,6 +36,7 @@ class LEDCOutput : public output::FloatOutput, public Component { uint8_t bit_depth_{}; float frequency_{}; float duty_{0.0f}; + bool initialized_ = false; }; template class SetFrequencyAction : public Action { From b398d826c17adcb8ef9d6c330fca7d496cab8ba8 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 22 Sep 2021 20:07:43 +0200 Subject: [PATCH 055/549] Fix two i2c error code return errors (#2375) --- esphome/components/pn532_i2c/pn532_i2c.cpp | 4 +++- esphome/components/scd30/scd30.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/pn532_i2c/pn532_i2c.cpp b/esphome/components/pn532_i2c/pn532_i2c.cpp index ef7480ec25..e7c99e94b0 100644 --- a/esphome/components/pn532_i2c/pn532_i2c.cpp +++ b/esphome/components/pn532_i2c/pn532_i2c.cpp @@ -12,7 +12,9 @@ namespace pn532_i2c { static const char *const TAG = "pn532_i2c"; -bool PN532I2C::write_data(const std::vector &data) { return this->write(data.data(), data.size()); } +bool PN532I2C::write_data(const std::vector &data) { + return this->write(data.data(), data.size()) == i2c::ERROR_OK; +} bool PN532I2C::read_data(std::vector &data, uint8_t len) { delay(1); diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index 30775fdea4..d1246d9766 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -198,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(raw, 5); + return this->write(raw, 5) == i2c::ERROR_OK; } uint8_t SCD30Component::sht_crc_(uint8_t data1, uint8_t data2) { From 5ddba719c56bc59a88f41c4d2ba896402774d5c1 Mon Sep 17 00:00:00 2001 From: Stijn Tintel Date: Wed, 22 Sep 2021 21:13:24 +0300 Subject: [PATCH 056/549] Fix ir_climate on ESP32-C3 (#2314) Co-authored-by: Otto winter --- esphome/components/remote_base/remote_base.cpp | 4 ++-- .../remote_transmitter/remote_transmitter_esp32.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/remote_base/remote_base.cpp b/esphome/components/remote_base/remote_base.cpp index a43f743ab6..a853c9849e 100644 --- a/esphome/components/remote_base/remote_base.cpp +++ b/esphome/components/remote_base/remote_base.cpp @@ -14,8 +14,8 @@ RemoteRMTChannel::RemoteRMTChannel(uint8_t mem_block_num) : mem_block_num_(mem_b } void RemoteRMTChannel::config_rmt(rmt_config_t &rmt) { - if (rmt_channel_t(int(this->channel_) + this->mem_block_num_) > RMT_CHANNEL_7) { - this->mem_block_num_ = int(RMT_CHANNEL_7) - int(this->channel_) + 1; + if (rmt_channel_t(int(this->channel_) + this->mem_block_num_) >= RMT_CHANNEL_MAX) { + this->mem_block_num_ = int(RMT_CHANNEL_MAX) - int(this->channel_); ESP_LOGW(TAG, "Not enough RMT memory blocks available, reduced to %i blocks.", this->mem_block_num_); } rmt.channel = this->channel_; diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index ff53d0be84..a1f7663a24 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -92,7 +92,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen val = this->from_microseconds(static_cast(val)); do { - int32_t item = std::min(val, 32767); + int32_t item = std::min(val, int32_t(32767)); val -= item; if (rmt_i % 2 == 0) { From ea6a7a22ff451d164285b92574d4dd846f37d111 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 23 Sep 2021 20:45:41 +1200 Subject: [PATCH 057/549] Fix ESP8266 ADC (#2376) --- esphome/components/adc/adc_sensor.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 9a05c1d66b..c8f8b0e0f6 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -1,9 +1,13 @@ #include "adc_sensor.h" #include "esphome/core/log.h" +#ifdef USE_ESP8266 #ifdef USE_ADC_SENSOR_VCC #include ADC_MODE(ADC_VCC) +#else +#include +#endif #endif namespace esphome { From 17dcba8f8ac932460ca87c68cfcd052958ed0305 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 23 Sep 2021 21:19:17 +1200 Subject: [PATCH 058/549] Fix: Pin flags code generation returning FLAG_NONE (#2377) Co-authored-by: Otto winter --- esphome/cpp_generator.py | 17 +++++++++++++++++ esphome/pins.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 691f45f91f..8460c1d462 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -310,6 +310,19 @@ class FloatLiteral(Literal): return f"{self.f}f" +class BinOpExpression(Expression): + __slots__ = ("op", "lhs", "rhs") + + def __init__(self, op: str, lhs: SafeExpType, rhs: SafeExpType): + # Remove every None on end + self.op = op + self.lhs = safe_exp(lhs) + self.rhs = safe_exp(rhs) + + def __str__(self): + return f"{self.lhs} {self.op} {self.rhs}" + + def safe_exp(obj: SafeExpType) -> Expression: """Try to convert obj to an expression by automatically converting native python types to expressions/literals. @@ -756,6 +769,10 @@ class MockObj(Expression): next_op = "->" return MockObj(f"{self.base}[{item}]", next_op) + def __or__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression("|", self, other) + return MockObj(op) + class MockObjEnum(MockObj): def __init__(self, *args, **kwargs): diff --git a/esphome/pins.py b/esphome/pins.py index ae762c1a1a..2b3adce86d 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -90,7 +90,7 @@ def gpio_flags_expr(mode): CONF_PULLDOWN: cg.gpio_Flags.FLAG_PULLDOWN, } active_flags = [v for k, v in FLAGS_MAPPING.items() if mode.get(k)] - if active_flags: + if not active_flags: return cg.gpio_Flags.FLAG_NONE return reduce(operator.or_, active_flags) From a27a8841913e1447d08b9c64c86c686780463842 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 23 Sep 2021 11:53:10 +0200 Subject: [PATCH 059/549] Add missing MockObj operators (#2378) --- esphome/cpp_generator.py | 183 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 176 insertions(+), 7 deletions(-) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 8460c1d462..cf357f2814 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -313,14 +313,26 @@ class FloatLiteral(Literal): class BinOpExpression(Expression): __slots__ = ("op", "lhs", "rhs") - def __init__(self, op: str, lhs: SafeExpType, rhs: SafeExpType): - # Remove every None on end - self.op = op + def __init__(self, lhs: SafeExpType, op: str, rhs: SafeExpType): self.lhs = safe_exp(lhs) + self.op = op self.rhs = safe_exp(rhs) def __str__(self): - return f"{self.lhs} {self.op} {self.rhs}" + # Surround with parentheses to ensure generated code has same + # order as python one + return f"({self.lhs} {self.op} {self.rhs})" + + +class UnaryOpExpression(Expression): + __slots__ = ("op", "exp") + + def __init__(self, op: str, exp: SafeExpType): + self.op = op + self.exp = safe_exp(exp) + + def __str__(self): + return f"({self.op}{self.exp})" def safe_exp(obj: SafeExpType) -> Expression: @@ -729,6 +741,7 @@ class MockObj(Expression): return MockObj(f"new {self.base}", "->") def template(self, *args: SafeExpType) -> "MockObj": + """Apply template parameters to this object.""" if len(args) != 1 or not isinstance(args[0], TemplateArguments): args = TemplateArguments(*args) else: @@ -749,6 +762,10 @@ class MockObj(Expression): return MockObjEnum(enum=name, is_class=is_class, base=self.base, op=self.op) def operator(self, name: str) -> "MockObj": + """Various other operations. + + Named operator because it's a C++ keyword and can't occur in valid code. + """ if name == "ref": return MockObj(f"{self.base} &", "") if name == "ptr": @@ -769,8 +786,160 @@ class MockObj(Expression): next_op = "->" return MockObj(f"{self.base}[{item}]", next_op) + def __lt__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "<", other) + return MockObj(op) + + def __le__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "<=", other) + return MockObj(op) + + def __eq__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "==", other) + return MockObj(op) + + def __ne__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "!=", other) + return MockObj(op) + + def __gt__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, ">", other) + return MockObj(op) + + def __ge__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, ">=", other) + return MockObj(op) + + def __add__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "+", other) + return MockObj(op) + + def __sub__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "-", other) + return MockObj(op) + + def __mul__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "*", other) + return MockObj(op) + + def __truediv__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "/", other) + return MockObj(op) + + def __mod__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "%", other) + return MockObj(op) + + def __lshift__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "<<", other) + return MockObj(op) + + def __rshift__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, ">>", other) + return MockObj(op) + + def __and__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "&", other) + return MockObj(op) + + def __xor__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "^", other) + return MockObj(op) + def __or__(self, other: SafeExpType) -> "MockObj": - op = BinOpExpression("|", self, other) + op = BinOpExpression(self, "|", other) + return MockObj(op) + + def __radd__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "+", self) + return MockObj(op) + + def __rsub__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "-", self) + return MockObj(op) + + def __rmul__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "*", self) + return MockObj(op) + + def __rtruediv__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "/", self) + return MockObj(op) + + def __rmod__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "%", self) + return MockObj(op) + + def __rlshift__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "<<", self) + return MockObj(op) + + def __rrshift__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, ">>", self) + return MockObj(op) + + def __rand__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "&", self) + return MockObj(op) + + def __rxor__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "^", self) + return MockObj(op) + + def __ror__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "|", self) + return MockObj(op) + + def __iadd__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "+=", other) + return MockObj(op) + + def __isub__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "-=", other) + return MockObj(op) + + def __imul__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "*=", other) + return MockObj(op) + + def __itruediv__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "/=", other) + return MockObj(op) + + def __imod__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "%=", other) + return MockObj(op) + + def __ilshift__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "<<=", other) + return MockObj(op) + + def __irshift__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, ">>=", other) + return MockObj(op) + + def __iand__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "&=", other) + return MockObj(op) + + def __ixor__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "^=", other) + return MockObj(op) + + def __ior__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "|=", other) + return MockObj(op) + + def __neg__(self) -> "MockObj": + op = UnaryOpExpression("-", self) + return MockObj(op) + + def __pos__(self) -> "MockObj": + op = UnaryOpExpression("+", self) + return MockObj(op) + + def __invert__(self) -> "MockObj": + op = UnaryOpExpression("~", self) return MockObj(op) @@ -807,10 +976,10 @@ class MockObjClass(MockObj): self._parents += paren._parents def inherits_from(self, other: "MockObjClass") -> bool: - if self == other: + if str(self) == str(other): return True for parent in self._parents: - if parent == other: + if str(parent) == str(other): return True return False From 210a9a41621c3e391531d31d8c8822a4e9d79ec7 Mon Sep 17 00:00:00 2001 From: Christian Taedcke Date: Thu, 23 Sep 2021 18:24:29 +0200 Subject: [PATCH 060/549] Fix esp-idf pinmask bit-shift overflow (#2380) --- esphome/components/esp32/gpio_idf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32/gpio_idf.h b/esphome/components/esp32/gpio_idf.h index 6a383afcae..a83c6bbc97 100644 --- a/esphome/components/esp32/gpio_idf.h +++ b/esphome/components/esp32/gpio_idf.h @@ -20,7 +20,7 @@ class IDFInternalGPIOPin : public InternalGPIOPin { } void pin_mode(gpio::Flags flags) override { gpio_config_t conf{}; - conf.pin_bit_mask = 1 << static_cast(pin_); + conf.pin_bit_mask = 1ULL << 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; From 963b28181fdbcc2c3e77dec5adc7b4c653a6bc7d Mon Sep 17 00:00:00 2001 From: Christian Taedcke Date: Thu, 23 Sep 2021 20:11:40 +0200 Subject: [PATCH 061/549] Always execute i2c bus recovery on setup (#2379) --- esphome/components/i2c/i2c_bus_arduino.cpp | 28 ++++++++++++++++ esphome/components/i2c/i2c_bus_arduino.h | 3 ++ esphome/components/i2c/i2c_bus_esp_idf.cpp | 38 ++++++++++++++++++++++ esphome/components/i2c/i2c_bus_esp_idf.h | 3 ++ 4 files changed, 72 insertions(+) diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 40d8049617..b983fb7636 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -2,6 +2,7 @@ #include "i2c_bus_arduino.h" #include "esphome/core/log.h" +#include #include namespace esphome { @@ -10,6 +11,7 @@ namespace i2c { static const char *const TAG = "i2c.arduino"; void ArduinoI2CBus::setup() { + recover(); #ifdef USE_ESP32 static uint8_t next_bus_num = 0; if (next_bus_num == 0) @@ -90,6 +92,32 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn return ERROR_UNKNOWN; } +void ArduinoI2CBus::recover() { + // Perform I2C bus recovery, see + // https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf + // or see the linux kernel implementation, e.g. + // https://elixir.bootlin.com/linux/v5.14.6/source/drivers/i2c/i2c-core-base.c#L200 + + // try to get about 100kHz toggle frequency + const auto half_period_usec = 1000000 / 100000 / 2; + const auto recover_scl_periods = 9; + + // configure scl as output + pinMode(scl_pin_, OUTPUT); // NOLINT + + // set scl high + digitalWrite(scl_pin_, 1); // NOLINT + + // in total generate 9 falling-rising edges + for (auto i = 0; i < recover_scl_periods; i++) { + delayMicroseconds(half_period_usec); + digitalWrite(scl_pin_, 0); // NOLINT + delayMicroseconds(half_period_usec); + digitalWrite(scl_pin_, 1); // NOLINT + } + + delayMicroseconds(half_period_usec); +} } // namespace i2c } // namespace esphome diff --git a/esphome/components/i2c/i2c_bus_arduino.h b/esphome/components/i2c/i2c_bus_arduino.h index 220027b3d4..49be0c358c 100644 --- a/esphome/components/i2c/i2c_bus_arduino.h +++ b/esphome/components/i2c/i2c_bus_arduino.h @@ -22,6 +22,9 @@ class ArduinoI2CBus : public I2CBus, public Component { void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; } void set_frequency(uint32_t frequency) { frequency_ = frequency; } + private: + void recover(); + protected: TwoWire *wire_; bool scan_; diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 8bf97b63ec..5ce5d40c00 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -1,6 +1,7 @@ #ifdef USE_ESP_IDF #include "i2c_bus_esp_idf.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" #include @@ -13,6 +14,8 @@ void IDFI2CBus::setup() { static i2c_port_t next_port = 0; port_ = next_port++; + recover(); + i2c_config_t conf{}; memset(&conf, 0, sizeof(conf)); conf.mode = I2C_MODE_MASTER; @@ -141,6 +144,41 @@ ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { return ERROR_OK; } +void IDFI2CBus::recover() { + // Perform I2C bus recovery, see + // https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf + // or see the linux kernel implementation, e.g. + // https://elixir.bootlin.com/linux/v5.14.6/source/drivers/i2c/i2c-core-base.c#L200 + + // try to get about 100kHz toggle frequency + const auto half_period_usec = 1000000 / 100000 / 2; + const auto recover_scl_periods = 9; + const gpio_num_t scl_pin = static_cast(scl_pin_); + + // configure scl as output + gpio_config_t conf{}; + conf.pin_bit_mask = 1ULL << static_cast(scl_pin_); + conf.mode = GPIO_MODE_OUTPUT; + conf.pull_up_en = GPIO_PULLUP_DISABLE; + conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + conf.intr_type = GPIO_INTR_DISABLE; + + gpio_config(&conf); + + // set scl high + gpio_set_level(scl_pin, 1); + + // in total generate 9 falling-rising edges + for (auto i = 0; i < recover_scl_periods; i++) { + delayMicroseconds(half_period_usec); + gpio_set_level(scl_pin, 0); + delayMicroseconds(half_period_usec); + gpio_set_level(scl_pin, 1); + } + + delayMicroseconds(half_period_usec); +} + } // namespace i2c } // namespace esphome diff --git a/esphome/components/i2c/i2c_bus_esp_idf.h b/esphome/components/i2c/i2c_bus_esp_idf.h index c7e67145a3..9985e618f8 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.h +++ b/esphome/components/i2c/i2c_bus_esp_idf.h @@ -24,6 +24,9 @@ class IDFI2CBus : public I2CBus, public Component { void set_scl_pullup_enabled(bool scl_pullup_enabled) { scl_pullup_enabled_ = scl_pullup_enabled; } void set_frequency(uint32_t frequency) { frequency_ = frequency; } + private: + void recover(); + protected: i2c_port_t port_; bool scan_; From aea2491fa4f7e649241265ae0f2d21b697b48930 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Sep 2021 23:36:19 +0200 Subject: [PATCH 062/549] Bump voluptuous from 0.12.1 to 0.12.2 (#2381) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 95ca95430d..537a802e06 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -voluptuous==0.12.1 +voluptuous==0.12.2 PyYAML==5.4.1 paho-mqtt==1.5.1 colorama==0.4.4 From 52dd79691b82e32fd9e61ade165c551109de7af8 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Fri, 24 Sep 2021 14:15:22 +0200 Subject: [PATCH 063/549] Read unencrypted DSMR telegrams in chunks (#2382) Co-authored-by: Maurice Makaay --- esphome/components/dsmr/dsmr.cpp | 31 +++++++++++++++++++------------ esphome/components/dsmr/dsmr.h | 1 + 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 54c4343cfe..b798fe5d44 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -20,19 +20,22 @@ void Dsmr::loop() { } void Dsmr::receive_telegram_() { - while (available()) { + int count = MAX_BYTES_PER_LOOP; + while (available() && count-- > 0) { const char c = read(); - if (c == '/') { // header: forward slash + // Find a new telegram header, i.e. forward slash. + if (c == '/') { ESP_LOGV(TAG, "Header found"); header_found_ = true; footer_found_ = false; telegram_len_ = 0; } - if (!header_found_) continue; - if (telegram_len_ >= MAX_TELEGRAM_LENGTH) { // Buffer overflow + + // Check for buffer overflow. + if (telegram_len_ >= MAX_TELEGRAM_LENGTH) { header_found_ = false; footer_found_ = false; ESP_LOGE(TAG, "Error: Message larger than buffer"); @@ -45,18 +48,22 @@ void Dsmr::receive_telegram_() { while (c == '(' && (telegram_[telegram_len_ - 1] == '\n' || telegram_[telegram_len_ - 1] == '\r')) telegram_len_--; + // Store the byte in the buffer. telegram_[telegram_len_] = c; telegram_len_++; - if (c == '!') { // footer: exclamation mark + + // Check for a footer, i.e. exlamation mark, followed by a hex checksum. + if (c == '!') { ESP_LOGV(TAG, "Footer found"); footer_found_ = true; - } else { - if (footer_found_ && c == 10) { // last \n after footer - header_found_ = false; - // Parse message - if (parse_telegram()) - return; - } + continue; + } + // Check for the end of the hex checksum, i.e. a newline. + if (footer_found_ && c == '\n') { + header_found_ = false; + // Parse the telegram and publish sensor values. + if (parse_telegram()) + return; } } } diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index dfee3b338a..4f9a66b3d0 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -17,6 +17,7 @@ namespace esphome { namespace dsmr { static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500; +static constexpr uint32_t MAX_BYTES_PER_LOOP = 50; static constexpr uint32_t POLL_TIMEOUT = 1000; using namespace ::dsmr::fields; From aec02afcdc36d0c626db727d27dd46888c2578de Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 24 Sep 2021 18:02:28 +0200 Subject: [PATCH 064/549] Fix clang-tidy header filter (#2385) * Fix clang-tidy header filter * Allow private members * Fix clang-tidy detections * Run clang-format * Fix remaining detections * Fix graph * Run clang-format --- .clang-tidy | 12 +- .../airthings_wave_plus.cpp | 4 +- esphome/components/am43/am43.cpp | 6 +- esphome/components/am43/cover/am43_cover.cpp | 6 +- esphome/components/anova/anova.cpp | 4 +- esphome/components/anova/anova.h | 2 +- esphome/components/api/api_frame_helper.h | 9 +- esphome/components/api/api_noise_context.h | 2 +- esphome/components/api/api_server.h | 2 +- .../atc_mithermometer/atc_mithermometer.cpp | 12 +- .../atc_mithermometer/atc_mithermometer.h | 6 +- esphome/components/ble_client/automation.h | 10 +- esphome/components/ble_client/ble_client.cpp | 28 ++--- esphome/components/ble_client/ble_client.h | 13 ++- .../components/ble_client/sensor/automation.h | 5 +- .../ble_client/sensor/ble_sensor.cpp | 12 +- .../components/ble_client/sensor/ble_sensor.h | 4 +- .../ble_client/switch/ble_switch.cpp | 4 +- .../ble_presence/ble_presence_device.h | 4 +- esphome/components/ble_scanner/ble_scanner.h | 2 +- esphome/components/daly_bms/daly_bms.cpp | 18 +-- esphome/components/daly_bms/daly_bms.h | 4 +- esphome/components/esp32/gpio_arduino.cpp | 2 +- esphome/components/esp32/gpio_arduino.h | 2 +- esphome/components/esp32/gpio_idf.cpp | 2 +- esphome/components/esp32/gpio_idf.h | 13 ++- esphome/components/esp32_ble/ble.h | 2 + esphome/components/esp32_ble/queue.h | 26 +++-- .../esp32_ble_beacon/esp32_ble_beacon.h | 4 + .../esp32_ble_server/ble_characteristic.h | 4 +- .../components/esp32_ble_server/ble_server.h | 1 + .../esp32_ble_tracker/esp32_ble_tracker.cpp | 38 +++---- .../esp32_ble_tracker/esp32_ble_tracker.h | 25 +++-- esphome/components/esp32_ble_tracker/queue.h | 26 ++--- .../components/esp32_camera/esp32_camera.h | 1 + .../esp32_improv/esp32_improv_component.cpp | 4 +- .../esp32_improv/esp32_improv_component.h | 3 +- esphome/components/esp8266/gpio.cpp | 2 +- esphome/components/esp8266/gpio.h | 2 +- .../ethernet/ethernet_component.cpp | 28 ++--- .../components/ethernet/ethernet_component.h | 7 +- .../components/fastled_base/fastled_light.h | 104 +++++++++--------- esphome/components/graph/graph.cpp | 73 ++++++------ esphome/components/graph/graph.h | 29 ++--- esphome/components/i2c/i2c_bus_arduino.cpp | 4 +- esphome/components/i2c/i2c_bus_arduino.h | 2 +- esphome/components/i2c/i2c_bus_esp_idf.cpp | 4 +- esphome/components/i2c/i2c_bus_esp_idf.h | 2 +- .../inkbird_ibsth1_mini.cpp | 4 +- .../inkbird_ibsth1_mini/inkbird_ibsth1_mini.h | 2 +- .../components/inkbird_ibsth1_mini/sensor.py | 6 +- esphome/components/ledc/ledc_output.h | 1 + esphome/components/light/addressable_light.h | 2 +- .../light/addressable_light_wrapper.h | 2 +- esphome/components/light/esp_range_view.h | 2 + esphome/components/midea/appliance_base.h | 17 +-- .../pvvx_mithermometer/pvvx_mithermometer.cpp | 12 +- .../pvvx_mithermometer/pvvx_mithermometer.h | 6 +- .../components/remote_base/midea_protocol.h | 2 +- esphome/components/remote_base/remote_base.h | 4 +- .../remote_receiver/remote_receiver_esp32.cpp | 28 ++--- .../remote_transmitter/remote_transmitter.h | 2 +- .../remote_transmitter_esp32.cpp | 8 +- esphome/components/socket/headers.h | 27 +++-- esphome/components/t6615/t6615.h | 2 +- .../uart/uart_component_esp8266.cpp | 10 +- .../components/uart/uart_component_esp8266.h | 2 +- .../uart/uart_component_esp_idf.cpp | 4 +- .../components/uart/uart_component_esp_idf.h | 2 +- .../wifi_info/wifi_info_text_sensor.h | 2 +- .../xiaomi_miscale/xiaomi_miscale.cpp | 20 ++-- .../xiaomi_miscale/xiaomi_miscale.h | 10 +- esphome/core/gpio.h | 6 +- esphome/core/hal.h | 2 +- esphome/core/helpers.h | 4 +- script/clang-tidy | 3 +- 76 files changed, 404 insertions(+), 367 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 98a1568e5a..79276f81c3 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -135,10 +135,14 @@ CheckOptions: value: 'UPPER_CASE' - key: readability-identifier-naming.ParameterCase value: 'lower_case' - - key: readability-identifier-naming.PrivateMemberPrefix - value: 'NO_PRIVATE_MEMBERS_ALWAYS_USE_PROTECTED' - - key: readability-identifier-naming.PrivateMethodPrefix - value: 'NO_PRIVATE_METHODS_ALWAYS_USE_PROTECTED' + - key: readability-identifier-naming.PrivateMemberCase + value: 'lower_case' + - key: readability-identifier-naming.PrivateMemberSuffix + value: '_' + - key: readability-identifier-naming.PrivateMethodCase + value: 'lower_case' + - key: readability-identifier-naming.PrivateMethodSuffix + value: '_' - key: readability-identifier-naming.ClassMemberCase value: 'lower_case' - key: readability-identifier-naming.ClassMemberCase diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index a8153ae87a..0eaffbd889 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -31,7 +31,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt break; } this->handle_ = chr->handle; - this->node_state = esp32_ble_tracker::ClientState::Established; + this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; request_read_values_(); break; @@ -99,7 +99,7 @@ bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && c void AirthingsWavePlus::loop() {} void AirthingsWavePlus::update() { - if (this->node_state != esp32_ble_tracker::ClientState::Established) { + if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { if (!parent()->enabled) { ESP_LOGW(TAG, "Reconnecting to device"); parent()->set_enabled(true); diff --git a/esphome/components/am43/am43.cpp b/esphome/components/am43/am43.cpp index 2130b334be..a62e3bb6df 100644 --- a/esphome/components/am43/am43.cpp +++ b/esphome/components/am43/am43.cpp @@ -31,7 +31,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i } case ESP_GATTC_DISCONNECT_EVT: { this->logged_in_ = false; - this->node_state = espbt::ClientState::Idle; + this->node_state = espbt::ClientState::IDLE; if (this->battery_ != nullptr) this->battery_->publish_state(NAN); if (this->illuminance_ != nullptr) @@ -54,7 +54,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - this->node_state = espbt::ClientState::Established; + this->node_state = espbt::ClientState::ESTABLISHED; this->update(); break; } @@ -93,7 +93,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i } void Am43::update() { - if (this->node_state != espbt::ClientState::Established) { + if (this->node_state != espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str()); return; } diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp index fd337ba17b..274c527760 100644 --- a/esphome/components/am43/cover/am43_cover.cpp +++ b/esphome/components/am43/cover/am43_cover.cpp @@ -24,7 +24,7 @@ void Am43Component::setup() { } void Am43Component::loop() { - if (this->node_state == espbt::ClientState::Established && !this->logged_in_) { + 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, @@ -46,7 +46,7 @@ CoverTraits Am43Component::get_traits() { } void Am43Component::control(const CoverCall &call) { - if (this->node_state != espbt::ClientState::Established) { + if (this->node_state != espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "[%s] Cannot send cover control, not connected", this->get_name().c_str()); return; } @@ -98,7 +98,7 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - this->node_state = espbt::ClientState::Established; + this->node_state = espbt::ClientState::ESTABLISHED; break; } case ESP_GATTC_NOTIFY_EVT: { diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index 8e0724ad13..5d9afddc74 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -72,7 +72,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - this->node_state = espbt::ClientState::Established; + this->node_state = espbt::ClientState::ESTABLISHED; this->current_request_ = 0; this->update(); break; @@ -129,7 +129,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ void Anova::set_unit_of_measurement(const char *unit) { this->fahrenheit_ = !strncmp(unit, "f", 1); } void Anova::update() { - if (this->node_state != espbt::ClientState::Established) + if (this->node_state != espbt::ClientState::ESTABLISHED) return; if (this->current_request_ < 2) { diff --git a/esphome/components/anova/anova.h b/esphome/components/anova/anova.h index 554024e389..2e6910f326 100644 --- a/esphome/components/anova/anova.h +++ b/esphome/components/anova/anova.h @@ -27,7 +27,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode esp_ble_gattc_cb_param_t *param) override; void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } - climate::ClimateTraits traits() { + climate::ClimateTraits traits() override { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); traits.set_supports_heat_mode(true); diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 44df629b2f..7fdb26fd40 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -1,7 +1,8 @@ #pragma once #include -#include #include +#include +#include #include "esphome/core/defines.h" @@ -75,8 +76,8 @@ class APIFrameHelper { class APINoiseFrameHelper : public APIFrameHelper { public: APINoiseFrameHelper(std::unique_ptr socket, std::shared_ptr ctx) - : socket_(std::move(socket)), ctx_(ctx) {} - ~APINoiseFrameHelper(); + : socket_(std::move(socket)), ctx_(std::move(std::move(ctx))) {} + ~APINoiseFrameHelper() override; APIError init() override; APIError loop() override; APIError read_packet(ReadPacketBuffer *buffer) override; @@ -136,7 +137,7 @@ class APINoiseFrameHelper : public APIFrameHelper { class APIPlaintextFrameHelper : public APIFrameHelper { public: APIPlaintextFrameHelper(std::unique_ptr socket) : socket_(std::move(socket)) {} - ~APIPlaintextFrameHelper() = default; + ~APIPlaintextFrameHelper() override = default; APIError init() override; APIError loop() override; APIError read_packet(ReadPacketBuffer *buffer) override; diff --git a/esphome/components/api/api_noise_context.h b/esphome/components/api/api_noise_context.h index fba6b65a26..324e69d945 100644 --- a/esphome/components/api/api_noise_context.h +++ b/esphome/components/api/api_noise_context.h @@ -11,7 +11,7 @@ using psk_t = std::array; class APINoiseContext { public: - void set_psk(psk_t psk) { psk_ = std::move(psk); } + void set_psk(psk_t psk) { psk_ = psk; } const psk_t &get_psk() const { return psk_; } protected: diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index d659f24358..056d9f54f2 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -32,7 +32,7 @@ class APIServer : public Component, public Controller { 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)); } + void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); } std::shared_ptr get_noise_ctx() { return noise_ctx_; } #endif // USE_API_NOISE diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.cpp b/esphome/components/atc_mithermometer/atc_mithermometer.cpp index b04d634103..42c30598ad 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.cpp +++ b/esphome/components/atc_mithermometer/atc_mithermometer.cpp @@ -25,14 +25,14 @@ 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); + auto res = parse_header_(service_data); if (!res.has_value()) { continue; } - if (!(parse_message(service_data.data, *res))) { + if (!(parse_message_(service_data.data, *res))) { continue; } - if (!(report_results(res, device.address_str()))) { + if (!(report_results_(res, device.address_str()))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) @@ -49,7 +49,7 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device return success; } -optional ATCMiThermometer::parse_header(const esp32_ble_tracker::ServiceData &service_data) { +optional ATCMiThermometer::parse_header_(const esp32_ble_tracker::ServiceData &service_data) { ParseResult result; if (!service_data.uuid.contains(0x1A, 0x18)) { ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes."); @@ -68,7 +68,7 @@ optional ATCMiThermometer::parse_header(const esp32_ble_tracker::Se return result; } -bool ATCMiThermometer::parse_message(const std::vector &message, ParseResult &result) { +bool ATCMiThermometer::parse_message_(const std::vector &message, ParseResult &result) { // Byte 0-5 mac in correct order // Byte 6-7 Temperature in uint16 // Byte 8 Humidity in percent @@ -101,7 +101,7 @@ bool ATCMiThermometer::parse_message(const std::vector &message, ParseR return true; } -bool ATCMiThermometer::report_results(const optional &result, const std::string &address) { +bool ATCMiThermometer::report_results_(const optional &result, const std::string &address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_results(): no results available."); return false; diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.h b/esphome/components/atc_mithermometer/atc_mithermometer.h index 291c1d96cd..ca079bf8c1 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.h +++ b/esphome/components/atc_mithermometer/atc_mithermometer.h @@ -36,9 +36,9 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice sensor::Sensor *battery_level_{nullptr}; sensor::Sensor *battery_voltage_{nullptr}; - optional parse_header(const esp32_ble_tracker::ServiceData &service_data); - bool parse_message(const std::vector &message, ParseResult &result); - bool report_results(const optional &result, const std::string &address); + optional parse_header_(const esp32_ble_tracker::ServiceData &service_data); + bool parse_message_(const std::vector &message, ParseResult &result); + bool report_results_(const optional &result, const std::string &address); }; } // namespace atc_mithermometer diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index d015d4019c..6c374046ba 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -11,11 +11,12 @@ class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode { public: explicit BLEClientConnectTrigger(BLEClient *parent) { parent->register_ble_node(this); } 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) { + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override { if (event == ESP_GATTC_OPEN_EVT && param->open.status == ESP_GATT_OK) this->trigger(); if (event == ESP_GATTC_SEARCH_CMPL_EVT) - this->node_state = espbt::ClientState::Established; + this->node_state = espbt::ClientState::ESTABLISHED; } }; @@ -23,11 +24,12 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode { public: explicit BLEClientDisconnectTrigger(BLEClient *parent) { parent->register_ble_node(this); } 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) { + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override { if (event == ESP_GATTC_DISCONNECT_EVT && memcmp(param->disconnect.remote_bda, this->parent_->remote_bda, 6) == 0) this->trigger(); if (event == ESP_GATTC_SEARCH_CMPL_EVT) - this->node_state = espbt::ClientState::Established; + this->node_state = espbt::ClientState::ESTABLISHED; } }; diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index f584cdf79e..8ff516d735 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -17,12 +17,12 @@ void BLEClient::setup() { ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret); this->mark_failed(); } - this->set_states(espbt::ClientState::Idle); + this->set_states_(espbt::ClientState::IDLE); this->enabled = true; } void BLEClient::loop() { - if (this->state() == espbt::ClientState::Discovered) { + if (this->state() == espbt::ClientState::DISCOVERED) { this->connect(); } for (auto *node : this->nodes_) @@ -39,11 +39,11 @@ bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { return false; if (device.address_uint64() != this->address) return false; - if (this->state() != espbt::ClientState::Idle) + if (this->state() != espbt::ClientState::IDLE) return false; ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str()); - this->set_states(espbt::ClientState::Discovered); + this->set_states_(espbt::ClientState::DISCOVERED); auto addr = device.address_uint64(); this->remote_bda[0] = (addr >> 40) & 0xFF; @@ -69,7 +69,7 @@ std::string BLEClient::address_str() const { void BLEClient::set_enabled(bool enabled) { if (enabled == this->enabled) return; - if (!enabled && this->state() != espbt::ClientState::Idle) { + if (!enabled && this->state() != espbt::ClientState::IDLE) { ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str()); auto ret = esp_ble_gattc_close(this->gattc_if, this->conn_id); if (ret) { @@ -84,9 +84,9 @@ void BLEClient::connect() { auto ret = esp_ble_gattc_open(this->gattc_if, this->remote_bda, BLE_ADDR_TYPE_PUBLIC, true); if (ret) { ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret); - this->set_states(espbt::ClientState::Idle); + this->set_states_(espbt::ClientState::IDLE); } else { - this->set_states(espbt::ClientState::Connecting); + this->set_states_(espbt::ClientState::CONNECTING); } } @@ -97,7 +97,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if) return; - bool all_established = this->all_nodes_established(); + bool all_established = this->all_nodes_established_(); switch (event) { case ESP_GATTC_REG_EVT: { @@ -113,7 +113,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str()); if (param->open.status != ESP_GATT_OK) { ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status); - this->set_states(espbt::ClientState::Idle); + this->set_states_(espbt::ClientState::IDLE); break; } this->conn_id = param->open.conn_id; @@ -126,7 +126,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es case ESP_GATTC_CFG_MTU_EVT: { if (param->cfg_mtu.status != ESP_GATT_OK) { ESP_LOGW(TAG, "cfg_mtu to %s failed, status %d", this->address_str().c_str(), param->cfg_mtu.status); - this->set_states(espbt::ClientState::Idle); + this->set_states_(espbt::ClientState::IDLE); break; } ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu); @@ -141,7 +141,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es for (auto &svc : this->services_) delete svc; // NOLINT(cppcoreguidelines-owning-memory) this->services_.clear(); - this->set_states(espbt::ClientState::Idle); + this->set_states_(espbt::ClientState::IDLE); break; } case ESP_GATTC_SEARCH_RES_EVT: { @@ -160,8 +160,8 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle); svc->parse_characteristics(); } - this->set_states(espbt::ClientState::Connected); - this->set_state(espbt::ClientState::Established); + this->set_states_(espbt::ClientState::CONNECTED); + this->set_state(espbt::ClientState::ESTABLISHED); break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { @@ -192,7 +192,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es node->gattc_event_handler(event, esp_gattc_if, param); // Delete characteristics after clients have used them to save RAM. - if (!all_established && this->all_nodes_established()) { + if (!all_established && this->all_nodes_established_()) { for (auto &svc : this->services_) delete svc; // NOLINT(cppcoreguidelines-owning-memory) this->services_.clear(); diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index a69460e8b6..4a17ccb79b 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -82,10 +82,11 @@ class BLEClient : public espbt::ESPBTClient, public Component { void dump_config() 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); + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; bool parse_device(const espbt::ESPBTDevice &device) override; void on_scan_end() override {} - void connect(); + void connect() override; void set_address(uint64_t address) { this->address = address; } @@ -116,16 +117,16 @@ class BLEClient : public espbt::ESPBTClient, public Component { std::string address_str() const; protected: - void set_states(espbt::ClientState st) { + void set_states_(espbt::ClientState st) { this->set_state(st); for (auto &node : nodes_) node->node_state = st; } - bool all_nodes_established() { - if (this->state() != espbt::ClientState::Established) + bool all_nodes_established_() { + if (this->state() != espbt::ClientState::ESTABLISHED) return false; for (auto &node : nodes_) - if (node->node_state != espbt::ClientState::Established) + if (node->node_state != espbt::ClientState::ESTABLISHED) return false; return true; } diff --git a/esphome/components/ble_client/sensor/automation.h b/esphome/components/ble_client/sensor/automation.h index 2255a5ac55..2baaafe2ec 100644 --- a/esphome/components/ble_client/sensor/automation.h +++ b/esphome/components/ble_client/sensor/automation.h @@ -11,10 +11,11 @@ namespace ble_client { class BLESensorNotifyTrigger : public Trigger, public BLESensor { public: explicit BLESensorNotifyTrigger(BLESensor *sensor) { sensor_ = sensor; } - void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override { switch (event) { case ESP_GATTC_SEARCH_CMPL_EVT: { - this->sensor_->node_state = espbt::ClientState::Established; + this->sensor_->node_state = espbt::ClientState::ESTABLISHED; break; } case ESP_GATTC_NOTIFY_EVT: { diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index 4459163389..7a2e3ddc8b 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -71,7 +71,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status); } } else { - this->node_state = espbt::ClientState::Established; + this->node_state = espbt::ClientState::ESTABLISHED; } break; } @@ -84,7 +84,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga } if (param->read.handle == this->handle) { this->status_clear_warning(); - this->publish_state(this->parse_data(param->read.value, param->read.value_len)); + this->publish_state(this->parse_data_(param->read.value, param->read.value_len)); } break; } @@ -93,11 +93,11 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga break; ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), param->notify.handle, param->notify.value[0]); - this->publish_state(this->parse_data(param->notify.value, param->notify.value_len)); + this->publish_state(this->parse_data_(param->notify.value, param->notify.value_len)); break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - this->node_state = espbt::ClientState::Established; + this->node_state = espbt::ClientState::ESTABLISHED; break; } default: @@ -105,7 +105,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga } } -float BLESensor::parse_data(uint8_t *value, uint16_t value_len) { +float BLESensor::parse_data_(uint8_t *value, uint16_t value_len) { if (this->data_to_value_func_.has_value()) { std::vector data(value, value + value_len); return (*this->data_to_value_func_)(data); @@ -115,7 +115,7 @@ float BLESensor::parse_data(uint8_t *value, uint16_t value_len) { } void BLESensor::update() { - if (this->node_state != espbt::ClientState::Established) { + if (this->node_state != espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str()); return; } diff --git a/esphome/components/ble_client/sensor/ble_sensor.h b/esphome/components/ble_client/sensor/ble_sensor.h index 52c9e9d5ca..d9f310b575 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.h +++ b/esphome/components/ble_client/sensor/ble_sensor.h @@ -32,13 +32,13 @@ class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClie void set_descr_uuid16(uint16_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } - void set_data_to_value(data_to_value_t &&lambda_) { this->data_to_value_func_ = lambda_; } + void set_data_to_value(data_to_value_t &&lambda) { this->data_to_value_func_ = lambda; } void set_enable_notify(bool notify) { this->notify_ = notify; } uint16_t handle; protected: uint32_t hash_base() override; - float parse_data(uint8_t *value, uint16_t value_len); + float parse_data_(uint8_t *value, uint16_t value_len); optional data_to_value_func_{}; bool notify_; espbt::ESPBTUUID service_uuid_; diff --git a/esphome/components/ble_client/switch/ble_switch.cpp b/esphome/components/ble_client/switch/ble_switch.cpp index 00593da9d6..6de5252404 100644 --- a/esphome/components/ble_client/switch/ble_switch.cpp +++ b/esphome/components/ble_client/switch/ble_switch.cpp @@ -21,10 +21,10 @@ void BLEClientSwitch::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i this->publish_state(this->parent_->enabled); break; case ESP_GATTC_OPEN_EVT: - this->node_state = espbt::ClientState::Established; + this->node_state = espbt::ClientState::ESTABLISHED; break; case ESP_GATTC_DISCONNECT_EVT: - this->node_state = espbt::ClientState::Idle; + this->node_state = espbt::ClientState::IDLE; this->publish_state(this->parent_->enabled); break; default: diff --git a/esphome/components/ble_presence/ble_presence_device.h b/esphome/components/ble_presence/ble_presence_device.h index 40cda89e62..dcccf844d2 100644 --- a/esphome/components/ble_presence/ble_presence_device.h +++ b/esphome/components/ble_presence/ble_presence_device.h @@ -93,8 +93,8 @@ 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_; + enum MatchType { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID }; + MatchType match_by_; bool found_{false}; diff --git a/esphome/components/ble_scanner/ble_scanner.h b/esphome/components/ble_scanner/ble_scanner.h index 542d5047ec..b330eff696 100644 --- a/esphome/components/ble_scanner/ble_scanner.h +++ b/esphome/components/ble_scanner/ble_scanner.h @@ -15,7 +15,7 @@ namespace ble_scanner { class BLEScanner : public text_sensor::TextSensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component { public: bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { - this->publish_state("{\"timestamp\":" + to_string(::time(NULL)) + + this->publish_state("{\"timestamp\":" + to_string(::time(nullptr)) + "," "\"address\":\"" + device.address_str() + diff --git a/esphome/components/daly_bms/daly_bms.cpp b/esphome/components/daly_bms/daly_bms.cpp index 19e8f12e1c..44c05f0686 100644 --- a/esphome/components/daly_bms/daly_bms.cpp +++ b/esphome/components/daly_bms/daly_bms.cpp @@ -26,25 +26,25 @@ void DalyBmsComponent::dump_config() { } void DalyBmsComponent::update() { - this->request_data(DALY_REQUEST_BATTERY_LEVEL); - this->request_data(DALY_REQUEST_MIN_MAX_VOLTAGE); - this->request_data(DALY_REQUEST_MIN_MAX_TEMPERATURE); - this->request_data(DALY_REQUEST_MOS); - this->request_data(DALY_REQUEST_STATUS); - this->request_data(DALY_REQUEST_TEMPERATURE); + this->request_data_(DALY_REQUEST_BATTERY_LEVEL); + this->request_data_(DALY_REQUEST_MIN_MAX_VOLTAGE); + this->request_data_(DALY_REQUEST_MIN_MAX_TEMPERATURE); + this->request_data_(DALY_REQUEST_MOS); + this->request_data_(DALY_REQUEST_STATUS); + this->request_data_(DALY_REQUEST_TEMPERATURE); std::vector get_battery_level_data; int available_data = this->available(); if (available_data >= DALY_FRAME_SIZE) { get_battery_level_data.resize(available_data); this->read_array(get_battery_level_data.data(), available_data); - this->decode_data(get_battery_level_data); + this->decode_data_(get_battery_level_data); } } float DalyBmsComponent::get_setup_priority() const { return setup_priority::DATA; } -void DalyBmsComponent::request_data(uint8_t data_id) { +void DalyBmsComponent::request_data_(uint8_t data_id) { uint8_t request_message[DALY_FRAME_SIZE]; request_message[0] = 0xA5; // Start Flag @@ -66,7 +66,7 @@ void DalyBmsComponent::request_data(uint8_t data_id) { this->flush(); } -void DalyBmsComponent::decode_data(std::vector data) { +void DalyBmsComponent::decode_data_(std::vector data) { auto it = data.begin(); while ((it = std::find(it, data.end(), 0xA5)) != data.end()) { diff --git a/esphome/components/daly_bms/daly_bms.h b/esphome/components/daly_bms/daly_bms.h index e4f48776dd..b5d4c8ae39 100644 --- a/esphome/components/daly_bms/daly_bms.h +++ b/esphome/components/daly_bms/daly_bms.h @@ -54,8 +54,8 @@ class DalyBmsComponent : public PollingComponent, public uart::UARTDevice { float get_setup_priority() const override; protected: - void request_data(uint8_t data_id); - void decode_data(std::vector data); + void request_data_(uint8_t data_id); + void decode_data_(std::vector data); sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr}; diff --git a/esphome/components/esp32/gpio_arduino.cpp b/esphome/components/esp32/gpio_arduino.cpp index 11de1e13e6..c4bb21a0aa 100644 --- a/esphome/components/esp32/gpio_arduino.cpp +++ b/esphome/components/esp32/gpio_arduino.cpp @@ -21,7 +21,7 @@ ISRInternalGPIOPin ArduinoInternalGPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } -void ArduinoInternalGPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { +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: diff --git a/esphome/components/esp32/gpio_arduino.h b/esphome/components/esp32/gpio_arduino.h index a077723075..e88d39b1a8 100644 --- a/esphome/components/esp32/gpio_arduino.h +++ b/esphome/components/esp32/gpio_arduino.h @@ -23,7 +23,7 @@ class ArduinoInternalGPIOPin : public InternalGPIOPin { bool is_inverted() const override { return inverted_; } protected: - void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; uint8_t pin_; bool inverted_; diff --git a/esphome/components/esp32/gpio_idf.cpp b/esphome/components/esp32/gpio_idf.cpp index d662d5519a..478b28a89a 100644 --- a/esphome/components/esp32/gpio_idf.cpp +++ b/esphome/components/esp32/gpio_idf.cpp @@ -8,7 +8,7 @@ namespace esp32 { static const char *const TAG = "esp32"; -bool IDFInternalGPIOPin::isr_service_installed_ = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +bool IDFInternalGPIOPin::isr_service_installed = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) struct ISRPinArg { gpio_num_t pin; diff --git a/esphome/components/esp32/gpio_idf.h b/esphome/components/esp32/gpio_idf.h index a83c6bbc97..448151cd0f 100644 --- a/esphome/components/esp32/gpio_idf.h +++ b/esphome/components/esp32/gpio_idf.h @@ -21,7 +21,7 @@ class IDFInternalGPIOPin : public InternalGPIOPin { void pin_mode(gpio::Flags flags) override { gpio_config_t conf{}; conf.pin_bit_mask = 1ULL << static_cast(pin_); - conf.mode = flags_to_mode_(flags); + 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; @@ -36,7 +36,7 @@ class IDFInternalGPIOPin : public InternalGPIOPin { bool is_inverted() const override { return inverted_; } protected: - static gpio_mode_t flags_to_mode_(gpio::Flags flags) { + 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; @@ -55,7 +55,7 @@ class IDFInternalGPIOPin : public InternalGPIOPin { return GPIO_MODE_DISABLE; } } - void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const override { + 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: @@ -76,9 +76,9 @@ class IDFInternalGPIOPin : public InternalGPIOPin { } gpio_set_intr_type(pin_, idf_type); gpio_intr_enable(pin_); - if (!isr_service_installed_) { + if (!isr_service_installed) { gpio_install_isr_service(ESP_INTR_FLAG_LEVEL5); - isr_service_installed_ = true; + isr_service_installed = true; } gpio_isr_handler_add(pin_, func, arg); } @@ -87,7 +87,8 @@ class IDFInternalGPIOPin : public InternalGPIOPin { bool inverted_; gpio_drive_cap_t drive_strength_; gpio::Flags flags_; - static bool isr_service_installed_; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) + static bool isr_service_installed; }; } // namespace esp32 diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 008eba3235..0477dee070 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -19,6 +19,7 @@ namespace esphome { namespace esp32_ble { +// NOLINTNEXTLINE(modernize-use-using) typedef struct { void *peer_device; bool connected; @@ -65,6 +66,7 @@ class ESP32BLE : public Component { BLEAdvertising *advertising_; }; +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern ESP32BLE *global_ble; } // namespace esp32_ble diff --git a/esphome/components/esp32_ble/queue.h b/esphome/components/esp32_ble/queue.h index 8fb2803237..8d05eca058 100644 --- a/esphome/components/esp32_ble/queue.h +++ b/esphome/components/esp32_ble/queue.h @@ -28,33 +28,33 @@ namespace esp32_ble { template class Queue { public: - Queue() { m = xSemaphoreCreateMutex(); } + Queue() { m_ = xSemaphoreCreateMutex(); } void push(T *element) { if (element == nullptr) return; - if (xSemaphoreTake(m, 5L / portTICK_PERIOD_MS)) { - q.push(element); - xSemaphoreGive(m); + if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) { + q_.push(element); + xSemaphoreGive(m_); } } T *pop() { T *element = nullptr; - if (xSemaphoreTake(m, 5L / portTICK_PERIOD_MS)) { - if (!q.empty()) { - element = q.front(); - q.pop(); + if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) { + if (!q_.empty()) { + element = q_.front(); + q_.pop(); } - xSemaphoreGive(m); + xSemaphoreGive(m_); } return element; } protected: - std::queue q; - SemaphoreHandle_t m; + std::queue q_; + SemaphoreHandle_t m_; }; // Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop(). @@ -105,11 +105,13 @@ class BLEEvent { }; union { + // NOLINTNEXTLINE(readability-identifier-naming) struct gap_event { esp_gap_ble_cb_event_t gap_event; esp_ble_gap_cb_param_t gap_param; } gap; + // NOLINTNEXTLINE(readability-identifier-naming) struct gattc_event { esp_gattc_cb_event_t gattc_event; esp_gatt_if_t gattc_if; @@ -117,6 +119,7 @@ class BLEEvent { uint8_t data[64]; } gattc; + // NOLINTNEXTLINE(readability-identifier-naming) struct gatts_event { esp_gatts_cb_event_t gatts_event; esp_gatt_if_t gatts_if; @@ -124,6 +127,7 @@ class BLEEvent { uint8_t data[64]; } gatts; } event_; + // NOLINTNEXTLINE(readability-identifier-naming) enum ble_event_t : uint8_t { GAP, GATTC, diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h index d0ef73899c..80ad2041f2 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h @@ -9,6 +9,7 @@ namespace esphome { namespace esp32_ble_beacon { +// NOLINTNEXTLINE(modernize-use-using) typedef struct { uint8_t flags[3]; uint8_t length; @@ -17,6 +18,7 @@ typedef struct { uint16_t beacon_type; } __attribute__((packed)) esp_ble_ibeacon_head_t; +// NOLINTNEXTLINE(modernize-use-using) typedef struct { uint8_t proximity_uuid[16]; uint16_t major; @@ -24,6 +26,7 @@ typedef struct { uint8_t measured_power; } __attribute__((packed)) esp_ble_ibeacon_vendor_t; +// NOLINTNEXTLINE(modernize-use-using) typedef struct { esp_ble_ibeacon_head_t ibeacon_head; esp_ble_ibeacon_vendor_t ibeacon_vendor; @@ -50,6 +53,7 @@ class ESP32BLEBeacon : public Component { uint16_t minor_{}; }; +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern ESP32BLEBeacon *global_esp32_ble_beacon; } // namespace esp32_ble_beacon diff --git a/esphome/components/esp32_ble_server/ble_characteristic.h b/esphome/components/esp32_ble_server/ble_characteristic.h index d2467dd176..d7af3a934a 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.h +++ b/esphome/components/esp32_ble_server/ble_characteristic.h @@ -24,7 +24,7 @@ class BLEService; class BLECharacteristic { public: - BLECharacteristic(const ESPBTUUID uuid, uint32_t properties); + BLECharacteristic(ESPBTUUID uuid, uint32_t properties); void set_value(const uint8_t *data, size_t length); void set_value(std::vector value); @@ -49,7 +49,7 @@ class BLECharacteristic { 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_ = std::move(func); } + void on_write(const std::function &)> &&func) { this->on_write_ = func; } void add_descriptor(BLEDescriptor *descriptor); diff --git a/esphome/components/esp32_ble_server/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h index 7df44b4c61..9f7e8b8fc0 100644 --- a/esphome/components/esp32_ble_server/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -87,6 +87,7 @@ class BLEServer : public Component { } state_{INIT}; }; +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern BLEServer *global_ble_server; } // namespace esp32_ble_server diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 3ca250d52d..9e987a994a 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -50,29 +50,29 @@ void ESP32BLETracker::setup() { return; } - global_esp32_ble_tracker->start_scan(true); + global_esp32_ble_tracker->start_scan_(true); } void ESP32BLETracker::loop() { BLEEvent *ble_event = this->ble_events_.pop(); while (ble_event != nullptr) { if (ble_event->type_) - this->real_gattc_event_handler(ble_event->event_.gattc.gattc_event, ble_event->event_.gattc.gattc_if, - &ble_event->event_.gattc.gattc_param); + this->real_gattc_event_handler_(ble_event->event_.gattc.gattc_event, ble_event->event_.gattc.gattc_if, + &ble_event->event_.gattc.gattc_param); else - this->real_gap_event_handler(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param); + this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param); delete ble_event; // NOLINT(cppcoreguidelines-owning-memory) ble_event = this->ble_events_.pop(); } bool connecting = false; for (auto *client : this->clients_) { - if (client->state() == ClientState::Connecting || client->state() == ClientState::Discovered) + if (client->state() == ClientState::CONNECTING || client->state() == ClientState::DISCOVERED) connecting = true; } if (!connecting && xSemaphoreTake(this->scan_end_lock_, 0L)) { xSemaphoreGive(this->scan_end_lock_); - global_esp32_ble_tracker->start_scan(false); + global_esp32_ble_tracker->start_scan_(false); } if (xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) { @@ -94,7 +94,7 @@ void ESP32BLETracker::loop() { for (auto *client : this->clients_) if (client->parse_device(device)) { found = true; - if (client->state() == ClientState::Discovered) { + if (client->state() == ClientState::DISCOVERED) { esp_ble_gap_stop_scanning(); if (xSemaphoreTake(this->scan_end_lock_, 10L / portTICK_PERIOD_MS)) { xSemaphoreGive(this->scan_end_lock_); @@ -196,7 +196,7 @@ bool ESP32BLETracker::ble_setup() { return true; } -void ESP32BLETracker::start_scan(bool first) { +void ESP32BLETracker::start_scan_(bool first) { if (!xSemaphoreTake(this->scan_end_lock_, 0L)) { ESP_LOGW(TAG, "Cannot start scan!"); return; @@ -233,38 +233,38 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga 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) { +void ESP32BLETracker::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch (event) { case ESP_GAP_BLE_SCAN_RESULT_EVT: - global_esp32_ble_tracker->gap_scan_result(param->scan_rst); + global_esp32_ble_tracker->gap_scan_result_(param->scan_rst); break; case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: - global_esp32_ble_tracker->gap_scan_set_param_complete(param->scan_param_cmpl); + global_esp32_ble_tracker->gap_scan_set_param_complete_(param->scan_param_cmpl); break; case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: - global_esp32_ble_tracker->gap_scan_start_complete(param->scan_start_cmpl); + global_esp32_ble_tracker->gap_scan_start_complete_(param->scan_start_cmpl); break; case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: - global_esp32_ble_tracker->gap_scan_stop_complete(param->scan_stop_cmpl); + global_esp32_ble_tracker->gap_scan_stop_complete_(param->scan_stop_cmpl); break; default: break; } } -void ESP32BLETracker::gap_scan_set_param_complete(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) { +void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) { this->scan_set_param_failed_ = param.status; } -void ESP32BLETracker::gap_scan_start_complete(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m) { +void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m) { this->scan_start_failed_ = param.status; } -void ESP32BLETracker::gap_scan_stop_complete(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m) { +void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m) { xSemaphoreGive(this->scan_end_lock_); } -void ESP32BLETracker::gap_scan_result(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { +void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { if (xSemaphoreTake(this->scan_result_lock_, 0L)) { if (this->scan_result_index_ < 16) { @@ -283,8 +283,8 @@ void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i 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) { +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) { for (auto *client : global_esp32_ble_tracker->clients_) { client->gattc_event_handler(event, gattc_if, param); } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index fc5498f91e..71885a564f 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -135,15 +135,15 @@ class ESPBTDeviceListener { enum class ClientState { // Connection is idle, no device detected. - Idle, + IDLE, // Device advertisement found. - Discovered, + DISCOVERED, // Connection in progress. - Connecting, + CONNECTING, // Initial connection established. - Connected, + CONNECTED, // The client and sub-clients have completed setup. - Established, + ESTABLISHED, }; class ESPBTClient : public ESPBTDeviceListener { @@ -185,23 +185,23 @@ class ESP32BLETracker : public Component { /// The FreeRTOS task managing the bluetooth interface. static bool ble_setup(); /// Start a single scan by setting up the parameters and doing some esp-idf calls. - void start_scan(bool first); + void start_scan_(bool first); /// Callback that will handle all GAP events and redistribute them to other callbacks. static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); - void real_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); + void real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); /// Called when a `ESP_GAP_BLE_SCAN_RESULT_EVT` event is received. - void gap_scan_result(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m); + void gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m); /// Called when a `ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT` event is received. - void gap_scan_set_param_complete(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m); + void gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m); /// Called when a `ESP_GAP_BLE_SCAN_START_COMPLETE_EVT` event is received. - void gap_scan_start_complete(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m); + void gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m); /// Called when a `ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT` event is received. - void gap_scan_stop_complete(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m); + void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m); int app_id_; /// Callback that will handle all GATTC events and redistribute them to other callbacks. static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); - 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_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); /// Vector of addresses that have already been printed in print_bt_device_info std::vector already_discovered_; @@ -225,6 +225,7 @@ class ESP32BLETracker : public Component { Queue ble_events_; }; +// NOLINTNEXTLINE extern ESP32BLETracker *global_esp32_ble_tracker; } // namespace esp32_ble_tracker diff --git a/esphome/components/esp32_ble_tracker/queue.h b/esphome/components/esp32_ble_tracker/queue.h index 3d38c17584..f09b2ca8d7 100644 --- a/esphome/components/esp32_ble_tracker/queue.h +++ b/esphome/components/esp32_ble_tracker/queue.h @@ -26,33 +26,33 @@ namespace esp32_ble_tracker { template class Queue { public: - Queue() { m = xSemaphoreCreateMutex(); } + Queue() { m_ = xSemaphoreCreateMutex(); } void push(T *element) { if (element == nullptr) return; - if (xSemaphoreTake(m, 5L / portTICK_PERIOD_MS)) { - q.push(element); - xSemaphoreGive(m); + if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) { + q_.push(element); + xSemaphoreGive(m_); } } T *pop() { T *element = nullptr; - if (xSemaphoreTake(m, 5L / portTICK_PERIOD_MS)) { - if (!q.empty()) { - element = q.front(); - q.pop(); + if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) { + if (!q_.empty()) { + element = q_.front(); + q_.pop(); } - xSemaphoreGive(m); + xSemaphoreGive(m_); } return element; } protected: - std::queue q; - SemaphoreHandle_t m; + std::queue q_; + SemaphoreHandle_t m_; }; // Received GAP and GATTC events are only queued, and get processed in the main loop(). @@ -87,12 +87,12 @@ class BLEEvent { }; union { - struct gap_event { + struct gap_event { // NOLINT(readability-identifier-naming) esp_gap_ble_cb_event_t gap_event; esp_ble_gap_cb_param_t gap_param; } gap; - struct gattc_event { + struct gattc_event { // NOLINT(readability-identifier-naming) esp_gattc_cb_event_t gattc_event; esp_gatt_if_t gattc_if; esp_ble_gattc_cb_param_t gattc_param; diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 430391aa76..6246dc2f12 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -106,6 +106,7 @@ class ESP32Camera : public Component, public Nameable { uint32_t last_update_{0}; }; +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern ESP32Camera *global_esp32_camera; } // namespace esp32_camera diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index fc58fbd264..faa9ab7df6 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -126,7 +126,7 @@ void ESP32ImprovComponent::loop() { std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome"; std::vector data = improv::build_rpc_response(improv::WIFI_SETTINGS, {url}); - this->send_response(data); + this->send_response_(data); this->set_timeout("end-service", 1000, [this] { this->service_->stop(); this->set_state_(improv::STATE_STOPPED); @@ -181,7 +181,7 @@ void ESP32ImprovComponent::set_error_(improv::Error error) { } } -void ESP32ImprovComponent::send_response(std::vector &response) { +void ESP32ImprovComponent::send_response_(std::vector &response) { this->rpc_response_->set_value(response); if (this->state_ != improv::STATE_STOPPED) this->rpc_response_->notify(); diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index af39ae4748..53cda5f399 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -63,12 +63,13 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent { void set_state_(improv::State state); void set_error_(improv::Error error); - void send_response(std::vector &response); + void send_response_(std::vector &response); void process_incoming_data_(); void on_wifi_connect_timeout_(); bool check_identify_(); }; +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern ESP32ImprovComponent *global_improv_component; } // namespace esp32_improv diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index 4dbffa7f6c..cb703c18e1 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -20,7 +20,7 @@ ISRInternalGPIOPin ESP8266GPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } -void ESP8266GPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { +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: diff --git a/esphome/components/esp8266/gpio.h b/esphome/components/esp8266/gpio.h index 465d1099ae..0474d0baa6 100644 --- a/esphome/components/esp8266/gpio.h +++ b/esphome/components/esp8266/gpio.h @@ -25,7 +25,7 @@ class ESP8266GPIOPin : public InternalGPIOPin { bool is_inverted() const override { return inverted_; } protected: - void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; uint8_t pin_; bool inverted_; diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index d03211deaf..d55db0a7d8 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -45,11 +45,11 @@ void EthernetComponent::setup() { switch (this->type_) { case ETHERNET_TYPE_LAN8720: { - memcpy(&this->eth_config, &phy_lan8720_default_ethernet_config, sizeof(eth_config_t)); + memcpy(&this->eth_config_, &phy_lan8720_default_ethernet_config, sizeof(eth_config_t)); break; } case ETHERNET_TYPE_TLK110: { - memcpy(&this->eth_config, &phy_tlk110_default_ethernet_config, sizeof(eth_config_t)); + memcpy(&this->eth_config_, &phy_tlk110_default_ethernet_config, sizeof(eth_config_t)); break; } default: { @@ -58,20 +58,20 @@ void EthernetComponent::setup() { } } - this->eth_config.phy_addr = static_cast(this->phy_addr_); - this->eth_config.clock_mode = this->clk_mode_; - this->eth_config.gpio_config = EthernetComponent::eth_phy_config_gpio_; - this->eth_config.tcpip_input = tcpip_adapter_eth_input; + this->eth_config_.phy_addr = static_cast(this->phy_addr_); + this->eth_config_.clock_mode = this->clk_mode_; + this->eth_config_.gpio_config = EthernetComponent::eth_phy_config_gpio; + this->eth_config_.tcpip_input = tcpip_adapter_eth_input; if (this->power_pin_ != nullptr) { - this->orig_power_enable_fun_ = this->eth_config.phy_power_enable; - this->eth_config.phy_power_enable = EthernetComponent::eth_phy_power_enable_; + this->orig_power_enable_fun_ = this->eth_config_.phy_power_enable; + this->eth_config_.phy_power_enable = EthernetComponent::eth_phy_power_enable; } tcpipInit(); esp_err_t err; - err = esp_eth_init(&this->eth_config); + err = esp_eth_init(&this->eth_config_); ESPHL_ERROR_CHECK(err, "ETH init error"); err = esp_eth_enable(); ESPHL_ERROR_CHECK(err, "ETH enable error"); @@ -209,11 +209,11 @@ void EthernetComponent::start_connect_() { this->connect_begin_ = millis(); this->status_set_warning(); } -void EthernetComponent::eth_phy_config_gpio_() { +void EthernetComponent::eth_phy_config_gpio() { phy_rmii_configure_data_interface_pins(); phy_rmii_smi_configure_pins(global_eth_component->mdc_pin_, global_eth_component->mdio_pin_); } -void EthernetComponent::eth_phy_power_enable_(bool enable) { +void EthernetComponent::eth_phy_power_enable(bool enable) { global_eth_component->power_pin_->digital_write(enable); // power up takes some time, datasheet says max 300µs delay(1); @@ -242,9 +242,9 @@ void EthernetComponent::dump_connect_params_() { 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]); - ESP_LOGCONFIG(TAG, " Is Full Duplex: %s", YESNO(this->eth_config.phy_get_duplex_mode())); - ESP_LOGCONFIG(TAG, " Link Up: %s", YESNO(this->eth_config.phy_check_link())); - ESP_LOGCONFIG(TAG, " Link Speed: %u", this->eth_config.phy_get_speed_mode() ? 100 : 10); + ESP_LOGCONFIG(TAG, " Is Full Duplex: %s", YESNO(this->eth_config_.phy_get_duplex_mode())); + ESP_LOGCONFIG(TAG, " Link Up: %s", YESNO(this->eth_config_.phy_check_link())); + ESP_LOGCONFIG(TAG, " Link Speed: %u", this->eth_config_.phy_get_speed_mode() ? 100 : 10); } void EthernetComponent::set_phy_addr(uint8_t phy_addr) { this->phy_addr_ = phy_addr; } void EthernetComponent::set_power_pin(GPIOPin *power_pin) { this->power_pin_ = power_pin; } diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 3e0c798a0c..abe1c62030 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -60,8 +60,8 @@ class EthernetComponent : public Component { void start_connect_(); void dump_connect_params_(); - static void eth_phy_config_gpio_(); - static void eth_phy_power_enable_(bool enable); + static void eth_phy_config_gpio(); + static void eth_phy_power_enable(bool enable); std::string use_address_; uint8_t phy_addr_{0}; @@ -76,10 +76,11 @@ class EthernetComponent : public Component { bool connected_{false}; EthernetComponentState state_{EthernetComponentState::STOPPED}; uint32_t connect_begin_; - eth_config_t eth_config; + eth_config_t eth_config_; eth_phy_power_enable_func orig_power_enable_fun_; }; +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern EthernetComponent *global_eth_component; } // namespace ethernet diff --git a/esphome/components/fastled_base/fastled_light.h b/esphome/components/fastled_base/fastled_light.h index 80840c3003..26f0f33d2a 100644 --- a/esphome/components/fastled_base/fastled_light.h +++ b/esphome/components/fastled_base/fastled_light.h @@ -44,33 +44,33 @@ class FastLEDLightOutput : public light::AddressableLight { CLEDController &add_leds(int num_leds) { switch (CHIPSET) { case LPD8806: { - static LPD8806Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static LPD8806Controller controller; + return add_leds(&controller, num_leds); } case WS2801: { - static WS2801Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static WS2801Controller controller; + return add_leds(&controller, num_leds); } case WS2803: { - static WS2803Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static WS2803Controller controller; + return add_leds(&controller, num_leds); } case SM16716: { - static SM16716Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static SM16716Controller controller; + return add_leds(&controller, num_leds); } case P9813: { - static P9813Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static P9813Controller controller; + return add_leds(&controller, num_leds); } case DOTSTAR: case APA102: { - static APA102Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static APA102Controller controller; + return add_leds(&controller, num_leds); } case SK9822: { - static SK9822Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static SK9822Controller controller; + return add_leds(&controller, num_leds); } } } @@ -78,33 +78,33 @@ class FastLEDLightOutput : public light::AddressableLight { template CLEDController &add_leds(int num_leds) { switch (CHIPSET) { case LPD8806: { - static LPD8806Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static LPD8806Controller controller; + return add_leds(&controller, num_leds); } case WS2801: { - static WS2801Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static WS2801Controller controller; + return add_leds(&controller, num_leds); } case WS2803: { - static WS2803Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static WS2803Controller controller; + return add_leds(&controller, num_leds); } case SM16716: { - static SM16716Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static SM16716Controller controller; + return add_leds(&controller, num_leds); } case P9813: { - static P9813Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static P9813Controller controller; + return add_leds(&controller, num_leds); } case DOTSTAR: case APA102: { - static APA102Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static APA102Controller controller; + return add_leds(&controller, num_leds); } case SK9822: { - static SK9822Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static SK9822Controller controller; + return add_leds(&controller, num_leds); } } } @@ -113,33 +113,33 @@ class FastLEDLightOutput : public light::AddressableLight { CLEDController &add_leds(int num_leds) { switch (CHIPSET) { case LPD8806: { - static LPD8806Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static LPD8806Controller controller; + return add_leds(&controller, num_leds); } case WS2801: { - static WS2801Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static WS2801Controller controller; + return add_leds(&controller, num_leds); } case WS2803: { - static WS2803Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static WS2803Controller controller; + return add_leds(&controller, num_leds); } case SM16716: { - static SM16716Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static SM16716Controller controller; + return add_leds(&controller, num_leds); } case P9813: { - static P9813Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static P9813Controller controller; + return add_leds(&controller, num_leds); } case DOTSTAR: case APA102: { - static APA102Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static APA102Controller controller; + return add_leds(&controller, num_leds); } case SK9822: { - static SK9822Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static SK9822Controller controller; + return add_leds(&controller, num_leds); } } } @@ -147,30 +147,30 @@ class FastLEDLightOutput : public light::AddressableLight { #ifdef FASTLED_HAS_CLOCKLESS template class CHIPSET, uint8_t DATA_PIN, EOrder RGB_ORDER> CLEDController &add_leds(int num_leds) { - static CHIPSET CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static CHIPSET controller; + return add_leds(&controller, num_leds); } template class CHIPSET, uint8_t DATA_PIN> CLEDController &add_leds(int num_leds) { - static CHIPSET CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static CHIPSET controller; + return add_leds(&controller, num_leds); } template class CHIPSET, uint8_t DATA_PIN> CLEDController &add_leds(int num_leds) { - static CHIPSET CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static CHIPSET controller; + return add_leds(&controller, num_leds); } #endif template class CHIPSET, EOrder RGB_ORDER> CLEDController &add_leds(int num_leds) { - static CHIPSET CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static CHIPSET controller; + return add_leds(&controller, num_leds); } template class CHIPSET> CLEDController &add_leds(int num_leds) { - static CHIPSET CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static CHIPSET controller; + return add_leds(&controller, num_leds); } #ifdef FASTLED_HAS_BLOCKLESS diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index ff736b7cb7..a9daad4ab9 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -185,7 +185,7 @@ void GraphLegend::init(Graph *g) { 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); + this->font_label_->measure(txtstr.c_str(), &fw, &fos, &fbl, &fh); if (fw > txtw) txtw = fw; if (fh > txth) @@ -201,7 +201,7 @@ void GraphLegend::init(Graph *g) { if (this->units_) { valstr += trace->sensor_->get_unit_of_measurement(); } - this->font_value->measure(valstr.c_str(), &fw, &fos, &fbl, &fh); + this->font_value_->measure(valstr.c_str(), &fw, &fos, &fbl, &fh); if (fw > valw) valw = fw; if (fh > valh) @@ -218,11 +218,11 @@ void GraphLegend::init(Graph *g) { uint16_t h = this->height_; DirectionType dir = this->direction_; ValuePositionType valpos = this->values_; - if (!this->font_value) { + if (!this->font_value_) { valpos = VALUE_POSITION_TYPE_NONE; } // Line sample always goes below text for compactness - this->yl = txth + (txth / 4) + lt / 2; + this->yl_ = txth + (txth / 4) + lt / 2; if (dir == DIRECTION_TYPE_AUTO) { dir = DIRECTION_TYPE_HORIZONTAL; // as default @@ -237,62 +237,62 @@ void GraphLegend::init(Graph *g) { } if (valpos == VALUE_POSITION_TYPE_BELOW) { - this->yv = txth + (txth / 4); + this->yv_ = txth + (txth / 4); if (this->lines_) - this->yv += txth / 4 + lt; + this->yv_ += txth / 4 + lt; } else if (valpos == VALUE_POSITION_TYPE_BESIDE) { - this->xv = (txtw + valw) / 2; + 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; + this->x0_ = txtw / 2; + this->xs_ = txtw; if (valpos == VALUE_POSITION_TYPE_BELOW) { - this->xs = std::max(txtw, valw); + this->xs_ = std::max(txtw, valw); ; - this->x0 = this->xs / 2; + this->x0_ = this->xs_ / 2; } else if (valpos == VALUE_POSITION_TYPE_BESIDE) { - this->xs = txtw + valw; + this->xs_ = txtw + valw; } if (dir == DIRECTION_TYPE_VERTICAL) { - this->width_ = this->xs; + this->width_ = this->xs_; } else { - this->width_ = this->xs * n; + this->width_ = this->xs_ * n; } } else { - this->xs = w / n; - this->x0 = this->xs / 2; + this->xs_ = w / n; + this->x0_ = this->xs_ / 2; } if (h == 0) { - this->ys = txth; + this->ys_ = txth; if (valpos == VALUE_POSITION_TYPE_BELOW) { - this->ys = txth + txth / 2 + valh; + this->ys_ = txth + txth / 2 + valh; if (this->lines_) { - this->ys += lt; + 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); + 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->ys_ = std::max(txth + txth / 4, valh + valh / 4); } - this->height_ = this->ys * n; + this->height_ = this->ys_ * n; } if (dir == DIRECTION_TYPE_HORIZONTAL) { - this->height_ = this->ys; + this->height_ = this->ys_; } else { - this->height_ = this->ys * n; + this->height_ = this->ys_ * n; } } else { - this->ys = h / n; + this->ys_ = h / n; } if (dir == DIRECTION_TYPE_HORIZONTAL) { - this->ys = 0; + this->ys_ = 0; } else { - this->xs = 0; + this->xs_ = 0; } } @@ -310,38 +310,39 @@ void Graph::draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_ buff->vertical_line(x_offset + w - 1, y_offset, h, color); } - int x = x_offset + legend_->x0; + 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()); + 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++) { + 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()); + 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; + 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()); + 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; + x += legend_->xs_; + y += legend_->ys_; } } diff --git a/esphome/components/graph/graph.h b/esphome/components/graph/graph.h index 8f6e74f67a..f935917c57 100644 --- a/esphome/components/graph/graph.h +++ b/esphome/components/graph/graph.h @@ -1,8 +1,9 @@ #pragma once -#include +#include "esphome/components/sensor/sensor.h" #include "esphome/core/color.h" #include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" +#include +#include namespace esphome { @@ -43,8 +44,8 @@ enum ValuePositionType { 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_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; } @@ -61,8 +62,8 @@ class GraphLegend { ValuePositionType values_{VALUE_POSITION_TYPE_AUTO}; bool units_{true}; DirectionType direction_{DIRECTION_TYPE_AUTO}; - display::Font *font_label{nullptr}; - display::Font *font_value{nullptr}; + display::Font *font_label_{nullptr}; + display::Font *font_value_{nullptr}; // Calculated values Graph *parent_{nullptr}; // (x0) (xs,ys) (xs,ys) @@ -72,12 +73,12 @@ class GraphLegend { // (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 + 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; }; @@ -106,7 +107,7 @@ class HistoryData { class GraphTrace { public: void init(Graph *g); - void set_name(std::string name) { name_ = name; } + void set_name(std::string name) { name_ = std::move(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; } @@ -114,7 +115,7 @@ class GraphTrace { 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 std::string get_name() { return name_; } const HistoryData *get_tracedata() { return &data_; } protected: diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index b983fb7636..87dbcb66d8 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -11,7 +11,7 @@ namespace i2c { static const char *const TAG = "i2c.arduino"; void ArduinoI2CBus::setup() { - recover(); + recover_(); #ifdef USE_ESP32 static uint8_t next_bus_num = 0; if (next_bus_num == 0) @@ -92,7 +92,7 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn return ERROR_UNKNOWN; } -void ArduinoI2CBus::recover() { +void ArduinoI2CBus::recover_() { // Perform I2C bus recovery, see // https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf // or see the linux kernel implementation, e.g. diff --git a/esphome/components/i2c/i2c_bus_arduino.h b/esphome/components/i2c/i2c_bus_arduino.h index 49be0c358c..42589dcfb7 100644 --- a/esphome/components/i2c/i2c_bus_arduino.h +++ b/esphome/components/i2c/i2c_bus_arduino.h @@ -23,7 +23,7 @@ class ArduinoI2CBus : public I2CBus, public Component { void set_frequency(uint32_t frequency) { frequency_ = frequency; } private: - void recover(); + void recover_(); protected: TwoWire *wire_; diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 5ce5d40c00..28e71ab2a0 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -14,7 +14,7 @@ void IDFI2CBus::setup() { static i2c_port_t next_port = 0; port_ = next_port++; - recover(); + recover_(); i2c_config_t conf{}; memset(&conf, 0, sizeof(conf)); @@ -144,7 +144,7 @@ ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { return ERROR_OK; } -void IDFI2CBus::recover() { +void IDFI2CBus::recover_() { // Perform I2C bus recovery, see // https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf // or see the linux kernel implementation, e.g. diff --git a/esphome/components/i2c/i2c_bus_esp_idf.h b/esphome/components/i2c/i2c_bus_esp_idf.h index 9985e618f8..ba5fbf25c5 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.h +++ b/esphome/components/i2c/i2c_bus_esp_idf.h @@ -25,7 +25,7 @@ class IDFI2CBus : public I2CBus, public Component { void set_frequency(uint32_t frequency) { frequency_ = frequency; } private: - void recover(); + void recover_(); protected: i2c_port_t port_; diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp index 177bc76072..c01fc274f4 100644 --- a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp @@ -8,7 +8,7 @@ namespace inkbird_ibsth1_mini { static const char *const TAG = "inkbird_ibsth1_mini"; -void InkbirdIBSTH1_MINI::dump_config() { +void InkbirdIbstH1Mini::dump_config() { ESP_LOGCONFIG(TAG, "Inkbird IBS TH1 MINI"); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "External Temperature", this->external_temperature_); @@ -16,7 +16,7 @@ void InkbirdIBSTH1_MINI::dump_config() { LOG_SENSOR(" ", "Battery Level", this->battery_level_); } -bool InkbirdIBSTH1_MINI::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { +bool InkbirdIbstH1Mini::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { // The below is based on my research and reverse engineering of a single device // It is entirely possible that some of that may be inaccurate or incomplete diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h index 1b6be7afe0..bdca2d0cac 100644 --- a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h @@ -9,7 +9,7 @@ namespace esphome { namespace inkbird_ibsth1_mini { -class InkbirdIBSTH1_MINI : public Component, public esp32_ble_tracker::ESPBTDeviceListener { +class InkbirdIbstH1Mini : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; } diff --git a/esphome/components/inkbird_ibsth1_mini/sensor.py b/esphome/components/inkbird_ibsth1_mini/sensor.py index a71921f8ed..0ab9f8b3e0 100644 --- a/esphome/components/inkbird_ibsth1_mini/sensor.py +++ b/esphome/components/inkbird_ibsth1_mini/sensor.py @@ -21,14 +21,14 @@ DEPENDENCIES = ["esp32_ble_tracker"] CONF_EXTERNAL_TEMPERATURE = "external_temperature" inkbird_ibsth1_mini_ns = cg.esphome_ns.namespace("inkbird_ibsth1_mini") -InkbirdUBSTH1_MINI = inkbird_ibsth1_mini_ns.class_( - "InkbirdIBSTH1_MINI", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +InkbirdIbstH1Mini = inkbird_ibsth1_mini_ns.class_( + "InkbirdIbstH1Mini", esp32_ble_tracker.ESPBTDeviceListener, cg.Component ) CONFIG_SCHEMA = ( cv.Schema( { - cv.GenerateID(): cv.declare_id(InkbirdUBSTH1_MINI), + cv.GenerateID(): cv.declare_id(InkbirdIbstH1Mini), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, diff --git a/esphome/components/ledc/ledc_output.h b/esphome/components/ledc/ledc_output.h index e02cefd170..a78bf440a9 100644 --- a/esphome/components/ledc/ledc_output.h +++ b/esphome/components/ledc/ledc_output.h @@ -10,6 +10,7 @@ namespace esphome { namespace ledc { +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern uint8_t next_ledc_channel; class LEDCOutput : public output::FloatOutput, public Component { diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index 97f4a4687d..fea7508515 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -80,7 +80,7 @@ class AddressableLight : public LightOutput, public Component { void mark_shown_() { #ifdef USE_POWER_SUPPLY - for (auto c : *this) { + for (const auto &c : *this) { if (c.get().is_on()) { this->power_.request(); return; diff --git a/esphome/components/light/addressable_light_wrapper.h b/esphome/components/light/addressable_light_wrapper.h index 813dd43313..cd5bcabd47 100644 --- a/esphome/components/light/addressable_light_wrapper.h +++ b/esphome/components/light/addressable_light_wrapper.h @@ -9,7 +9,7 @@ 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]; + this->wrapper_state_ = new uint8_t[5]; // NOLINT(cppcoreguidelines-owning-memory) } int32_t size() const override { return 1; } diff --git a/esphome/components/light/esp_range_view.h b/esphome/components/light/esp_range_view.h index f4a7980543..07d18af79f 100644 --- a/esphome/components/light/esp_range_view.h +++ b/esphome/components/light/esp_range_view.h @@ -18,6 +18,7 @@ class ESPRangeView : public ESPColorSettable { public: ESPRangeView(AddressableLight *parent, int32_t begin, int32_t end) : parent_(parent), begin_(begin), end_(end < begin ? begin : end) {} + ESPRangeView(const ESPRangeView &) = default; int32_t size() const { return this->end_ - this->begin_; } ESPColorView operator[](int32_t index) const; @@ -62,6 +63,7 @@ class ESPRangeView : public ESPColorSettable { class ESPRangeIterator { public: ESPRangeIterator(const ESPRangeView &range, int32_t i) : range_(range), i_(i) {} + ESPRangeIterator(const ESPRangeIterator &) = default; ESPRangeIterator operator++() { this->i_++; return *this; diff --git a/esphome/components/midea/appliance_base.h b/esphome/components/midea/appliance_base.h index 43dd2ffb32..88a722e389 100644 --- a/esphome/components/midea/appliance_base.h +++ b/esphome/components/midea/appliance_base.h @@ -31,9 +31,10 @@ class ApplianceBase : public Component, public uart::UARTDevice, public climate: 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); - }); + dudanov::midea::ApplianceBase::setLogger( + [](int level, const char *tag, int line, const 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; @@ -46,11 +47,11 @@ class ApplianceBase : public Component, public uart::UARTDevice, public climate: 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); } + void set_supported_modes(const std::set &modes) { this->supported_modes_ = modes; } + void set_supported_swing_modes(const std::set &modes) { this->supported_swing_modes_ = modes; } + void set_supported_presets(const std::set &presets) { this->supported_presets_ = presets; } + void set_custom_presets(const std::set &presets) { this->supported_custom_presets_ = presets; } + void set_custom_fan_modes(const std::set &modes) { this->supported_custom_fan_modes_ = modes; } virtual void on_status_change() = 0; #ifdef USE_REMOTE_TRANSMITTER void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) { diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp index ff9723ab2f..a41ad1bfcb 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp @@ -25,14 +25,14 @@ 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); + auto res = parse_header_(service_data); if (!res.has_value()) { continue; } - if (!(parse_message(service_data.data, *res))) { + if (!(parse_message_(service_data.data, *res))) { continue; } - if (!(report_results(res, device.address_str()))) { + if (!(report_results_(res, device.address_str()))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) @@ -49,7 +49,7 @@ bool PVVXMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &devic return success; } -optional PVVXMiThermometer::parse_header(const esp32_ble_tracker::ServiceData &service_data) { +optional PVVXMiThermometer::parse_header_(const esp32_ble_tracker::ServiceData &service_data) { ParseResult result; if (!service_data.uuid.contains(0x1A, 0x18)) { ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes."); @@ -68,7 +68,7 @@ optional PVVXMiThermometer::parse_header(const esp32_ble_tracker::S return result; } -bool PVVXMiThermometer::parse_message(const std::vector &message, ParseResult &result) { +bool PVVXMiThermometer::parse_message_(const std::vector &message, ParseResult &result) { /* All data little endian uint8_t size; // = 19 @@ -109,7 +109,7 @@ bool PVVXMiThermometer::parse_message(const std::vector &message, Parse return true; } -bool PVVXMiThermometer::report_results(const optional &result, const std::string &address) { +bool PVVXMiThermometer::report_results_(const optional &result, const std::string &address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_results(): no results available."); return false; diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h index bb67769d4f..ad8baed35f 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h @@ -36,9 +36,9 @@ class PVVXMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevic sensor::Sensor *battery_level_{nullptr}; sensor::Sensor *battery_voltage_{nullptr}; - optional parse_header(const esp32_ble_tracker::ServiceData &service_data); - bool parse_message(const std::vector &message, ParseResult &result); - bool report_results(const optional &result, const std::string &address); + optional parse_header_(const esp32_ble_tracker::ServiceData &service_data); + bool parse_message_(const std::vector &message, ParseResult &result); + bool report_results_(const optional &result, const std::string &address); }; } // namespace pvvx_mithermometer diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h index 12916bd44d..35ea23acfb 100644 --- a/esphome/components/remote_base/midea_protocol.h +++ b/esphome/components/remote_base/midea_protocol.h @@ -92,7 +92,7 @@ using MideaDumper = RemoteReceiverDumper; template class MideaAction : public RemoteTransmitterActionBase { TEMPLATABLE_VALUE(std::vector, code) - void set_code(std::vector code) { code_ = code; } + void set_code(const 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.h b/esphome/components/remote_base/remote_base.h index 68cf67d175..dd6f7c3482 100644 --- a/esphome/components/remote_base/remote_base.h +++ b/esphome/components/remote_base/remote_base.h @@ -161,11 +161,11 @@ class RemoteRMTChannel { void set_clock_divider(uint8_t clock_divider) { this->clock_divider_ = clock_divider; } protected: - uint32_t from_microseconds(uint32_t us) { + uint32_t from_microseconds_(uint32_t us) { const uint32_t ticks_per_ten_us = 80000000u / this->clock_divider_ / 100000u; return us * ticks_per_ten_us / 10; } - uint32_t to_microseconds(uint32_t ticks) { + uint32_t to_microseconds_(uint32_t ticks) { const uint32_t ticks_per_ten_us = 80000000u / this->clock_divider_ / 100000u; return (ticks * 10) / ticks_per_ten_us; } diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_esp32.cpp index bec2af6718..dde9b843c9 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp32.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp32.cpp @@ -20,9 +20,9 @@ void RemoteReceiverComponent::setup() { rmt.rx_config.filter_en = false; } else { rmt.rx_config.filter_en = true; - rmt.rx_config.filter_ticks_thresh = this->from_microseconds(this->filter_us_); + rmt.rx_config.filter_ticks_thresh = this->from_microseconds_(this->filter_us_); } - rmt.rx_config.idle_threshold = this->from_microseconds(this->idle_us_); + rmt.rx_config.idle_threshold = this->from_microseconds_(this->idle_us_); esp_err_t error = rmt_config(&rmt); if (error != ESP_OK) { @@ -90,14 +90,14 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { ESP_LOGVV(TAG, "START:"); for (size_t i = 0; i < len; i++) { if (item[i].level0) { - ESP_LOGVV(TAG, "%u A: ON %uus (%u ticks)", i, this->to_microseconds(item[i].duration0), item[i].duration0); + ESP_LOGVV(TAG, "%u A: ON %uus (%u ticks)", i, this->to_microseconds_(item[i].duration0), item[i].duration0); } else { - ESP_LOGVV(TAG, "%u A: OFF %uus (%u ticks)", i, this->to_microseconds(item[i].duration0), item[i].duration0); + ESP_LOGVV(TAG, "%u A: OFF %uus (%u ticks)", i, this->to_microseconds_(item[i].duration0), item[i].duration0); } if (item[i].level1) { - ESP_LOGVV(TAG, "%u B: ON %uus (%u ticks)", i, this->to_microseconds(item[i].duration1), item[i].duration1); + ESP_LOGVV(TAG, "%u B: ON %uus (%u ticks)", i, this->to_microseconds_(item[i].duration1), item[i].duration1); } else { - ESP_LOGVV(TAG, "%u B: OFF %uus (%u ticks)", i, this->to_microseconds(item[i].duration1), item[i].duration1); + ESP_LOGVV(TAG, "%u B: OFF %uus (%u ticks)", i, this->to_microseconds_(item[i].duration1), item[i].duration1); } } ESP_LOGVV(TAG, "\n"); @@ -111,16 +111,16 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { } else { if (prev_length > 0) { if (prev_level) { - this->temp_.push_back(this->to_microseconds(prev_length) * multiplier); + this->temp_.push_back(this->to_microseconds_(prev_length) * multiplier); } else { - this->temp_.push_back(-int32_t(this->to_microseconds(prev_length)) * multiplier); + this->temp_.push_back(-int32_t(this->to_microseconds_(prev_length)) * multiplier); } } prev_level = bool(item[i].level0); prev_length = item[i].duration0; } - if (this->to_microseconds(prev_length) > this->idle_us_) { + if (this->to_microseconds_(prev_length) > this->idle_us_) { break; } @@ -131,24 +131,24 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { } else { if (prev_length > 0) { if (prev_level) { - this->temp_.push_back(this->to_microseconds(prev_length) * multiplier); + this->temp_.push_back(this->to_microseconds_(prev_length) * multiplier); } else { - this->temp_.push_back(-int32_t(this->to_microseconds(prev_length)) * multiplier); + this->temp_.push_back(-int32_t(this->to_microseconds_(prev_length)) * multiplier); } } prev_level = bool(item[i].level1); prev_length = item[i].duration1; } - if (this->to_microseconds(prev_length) > this->idle_us_) { + if (this->to_microseconds_(prev_length) > this->idle_us_) { break; } } if (prev_length > 0) { if (prev_level) { - this->temp_.push_back(this->to_microseconds(prev_length) * multiplier); + this->temp_.push_back(this->to_microseconds_(prev_length) * multiplier); } else { - this->temp_.push_back(-int32_t(this->to_microseconds(prev_length)) * multiplier); + this->temp_.push_back(-int32_t(this->to_microseconds_(prev_length)) * multiplier); } } } diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index d05942de3b..733ac5e50d 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -35,7 +35,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, #endif #ifdef USE_ESP32 - void configure_rmt(); + void configure_rmt_(); uint32_t current_carrier_frequency_{UINT32_MAX}; bool initialized_{false}; diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index a1f7663a24..500d7193f3 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() { this->configure_rmt(); } +void RemoteTransmitterComponent::setup() { this->configure_rmt_(); } void RemoteTransmitterComponent::dump_config() { ESP_LOGCONFIG(TAG, "Remote Transmitter..."); @@ -27,7 +27,7 @@ void RemoteTransmitterComponent::dump_config() { } } -void RemoteTransmitterComponent::configure_rmt() { +void RemoteTransmitterComponent::configure_rmt_() { rmt_config_t c{}; this->config_rmt(c); @@ -77,7 +77,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen if (this->current_carrier_frequency_ != this->temp_.get_carrier_frequency()) { this->current_carrier_frequency_ = this->temp_.get_carrier_frequency(); - this->configure_rmt(); + this->configure_rmt_(); } this->rmt_temp_.clear(); @@ -89,7 +89,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen bool level = val >= 0; if (!level) val = -val; - val = this->from_microseconds(static_cast(val)); + val = this->from_microseconds_(static_cast(val)); do { int32_t item = std::min(val, int32_t(32767)); diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h index f9697fd421..a383c0071d 100644 --- a/esphome/components/socket/headers.h +++ b/esphome/components/socket/headers.h @@ -7,10 +7,10 @@ #ifdef USE_SOCKET_IMPL_LWIP_TCP #define LWIP_INTERNAL -#include #include "lwip/inet.h" -#include -#include +#include +#include +#include /* Address families. */ #define AF_UNSPEC 0 @@ -45,9 +45,10 @@ #define SOL_SOCKET 0xfff /* options for socket level */ -typedef uint8_t sa_family_t; -typedef uint16_t in_port_t; +using sa_family_t = uint8_t; +using in_port_t = uint16_t; +// NOLINTNEXTLINE(readability-identifier-naming) struct sockaddr_in { uint8_t sin_len; sa_family_t sin_family; @@ -57,6 +58,7 @@ struct sockaddr_in { char sin_zero[SIN_ZERO_LEN]; }; +// NOLINTNEXTLINE(readability-identifier-naming) struct sockaddr_in6 { uint8_t sin6_len; /* length of this structure */ sa_family_t sin6_family; /* AF_INET6 */ @@ -66,12 +68,14 @@ struct sockaddr_in6 { uint32_t sin6_scope_id; /* Set of interfaces for scope */ }; +// NOLINTNEXTLINE(readability-identifier-naming) struct sockaddr { uint8_t sa_len; sa_family_t sa_family; char sa_data[14]; }; +// NOLINTNEXTLINE(readability-identifier-naming) struct sockaddr_storage { uint8_t s2_len; sa_family_t ss_family; @@ -79,8 +83,9 @@ struct sockaddr_storage { uint32_t s2_data2[3]; uint32_t s2_data3[3]; }; -typedef uint32_t socklen_t; +using socklen_t = uint32_t; +// NOLINTNEXTLINE(readability-identifier-naming) struct iovec { void *iov_base; size_t iov_len; @@ -106,13 +111,13 @@ struct iovec { #ifdef USE_SOCKET_IMPL_BSD_SOCKETS -#include -#include +#include +#include #include +#include +#include #include #include -#include -#include #ifdef USE_ARDUINO // arduino-esp32 declares a global var called INADDR_NONE which is replaced @@ -121,7 +126,7 @@ struct iovec { #undef INADDR_NONE #endif // not defined for ESP32 -typedef uint32_t socklen_t; +using socklen_t = uint32_t; #define ESPHOME_INADDR_ANY ((uint32_t) 0x00000000UL) #define ESPHOME_INADDR_NONE ((uint32_t) 0xFFFFFFFFUL) diff --git a/esphome/components/t6615/t6615.h b/esphome/components/t6615/t6615.h index a075685023..fb53032e8d 100644 --- a/esphome/components/t6615/t6615.h +++ b/esphome/components/t6615/t6615.h @@ -35,7 +35,7 @@ class T6615Component : public PollingComponent, public uart::UARTDevice { void send_ppm_command_(); T6615Command command_ = T6615Command::NONE; - unsigned long command_time_ = 0; + uint32_t command_time_ = 0; sensor::Sensor *co2_sensor_{nullptr}; }; diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index 2188a4a4bc..973306cde2 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -13,7 +13,7 @@ namespace esphome { namespace uart { static const char *const TAG = "uart.arduino_esp8266"; -bool ESP8266UartComponent::serial0InUse = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +bool ESP8266UartComponent::serial0_in_use = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) uint32_t ESP8266UartComponent::get_config() { uint32_t config = 0; @@ -55,7 +55,7 @@ void ESP8266UartComponent::setup() { // is 1 we still want to use Serial. SerialConfig config = static_cast(get_config()); - if (!ESP8266UartComponent::serial0InUse && (tx_pin_ == nullptr || tx_pin_->get_pin() == 1) && + if (!ESP8266UartComponent::serial0_in_use && (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 @@ -66,8 +66,8 @@ void ESP8266UartComponent::setup() { this->hw_serial_ = &Serial; this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); - ESP8266UartComponent::serial0InUse = true; - } else if (!ESP8266UartComponent::serial0InUse && (tx_pin_ == nullptr || tx_pin_->get_pin() == 15) && + ESP8266UartComponent::serial0_in_use = true; + } else if (!ESP8266UartComponent::serial0_in_use && (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 @@ -79,7 +79,7 @@ void ESP8266UartComponent::setup() { this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); this->hw_serial_->swap(); - ESP8266UartComponent::serial0InUse = true; + ESP8266UartComponent::serial0_in_use = 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); diff --git a/esphome/components/uart/uart_component_esp8266.h b/esphome/components/uart/uart_component_esp8266.h index 921d77e4f3..eed14f3265 100644 --- a/esphome/components/uart/uart_component_esp8266.h +++ b/esphome/components/uart/uart_component_esp8266.h @@ -70,7 +70,7 @@ class ESP8266UartComponent : public UARTComponent, public Component { ESP8266SoftwareSerial *sw_serial_{nullptr}; private: - static bool serial0InUse; + static bool serial0_in_use; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) }; } // namespace uart diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 3d4a634a72..1cccd5821e 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -14,7 +14,7 @@ namespace esphome { namespace uart { static const char *const TAG = "uart.idf"; -uart_config_t IDFUARTComponent::get_config() { +uart_config_t IDFUARTComponent::get_config_() { uart_parity_t parity = UART_PARITY_DISABLE; if (this->parity_ == UART_CONFIG_PARITY_EVEN) parity = UART_PARITY_EVEN; @@ -70,7 +70,7 @@ void IDFUARTComponent::setup() { xSemaphoreTake(this->lock_, portMAX_DELAY); - uart_config_t uart_config = this->get_config(); + 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)); diff --git a/esphome/components/uart/uart_component_esp_idf.h b/esphome/components/uart/uart_component_esp_idf.h index 68cceafda2..27fb80d2cc 100644 --- a/esphome/components/uart/uart_component_esp_idf.h +++ b/esphome/components/uart/uart_component_esp_idf.h @@ -26,7 +26,7 @@ class IDFUARTComponent : public UARTComponent, public Component { protected: void check_logger_conflict() override; uart_port_t uart_num_; - uart_config_t get_config(); + uart_config_t get_config_(); SemaphoreHandle_t lock_; bool has_peek_{false}; diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index b2f37de363..5b54451ed0 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -13,7 +13,7 @@ class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor { auto ip = wifi::global_wifi_component->wifi_sta_ip(); if (ip != this->last_ip_) { this->last_ip_ = ip; - this->publish_state(ip.str().c_str()); + this->publish_state(ip.str()); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp index 4587045136..de77e6146b 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp @@ -23,16 +23,16 @@ bool XiaomiMiscale::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); + auto res = parse_header_(service_data); if (!res.has_value()) { continue; } - if (!(parse_message(service_data.data, *res))) { + if (!(parse_message_(service_data.data, *res))) { continue; } - if (!(report_results(res, device.address_str()))) { + if (!(report_results_(res, device.address_str()))) { continue; } @@ -49,7 +49,7 @@ bool XiaomiMiscale::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return success; } -optional XiaomiMiscale::parse_header(const esp32_ble_tracker::ServiceData &service_data) { +optional XiaomiMiscale::parse_header_(const esp32_ble_tracker::ServiceData &service_data) { ParseResult result; if (service_data.uuid == esp32_ble_tracker::ESPBTUUID::from_uint16(0x181D) && service_data.data.size() == 10) { result.version = 1; @@ -65,15 +65,15 @@ optional XiaomiMiscale::parse_header(const esp32_ble_tracker::Servi return result; } -bool XiaomiMiscale::parse_message(const std::vector &message, ParseResult &result) { +bool XiaomiMiscale::parse_message_(const std::vector &message, ParseResult &result) { if (result.version == 1) { - return parse_message_V1(message, result); + return parse_message_v1_(message, result); } else { - return parse_message_V2(message, result); + return parse_message_v2_(message, result); } } -bool XiaomiMiscale::parse_message_V1(const std::vector &message, ParseResult &result) { +bool XiaomiMiscale::parse_message_v1_(const std::vector &message, ParseResult &result) { // message size is checked in parse_header // 1-2 Weight (MISCALE 181D) // 3-4 Years (MISCALE 181D) @@ -97,7 +97,7 @@ bool XiaomiMiscale::parse_message_V1(const std::vector &message, ParseR return true; } -bool XiaomiMiscale::parse_message_V2(const std::vector &message, ParseResult &result) { +bool XiaomiMiscale::parse_message_v2_(const std::vector &message, ParseResult &result) { // message size is checked in parse_header // 2-3 Years (MISCALE 2 181B) // 4 month (MISCALE 2 181B) @@ -138,7 +138,7 @@ bool XiaomiMiscale::parse_message_V2(const std::vector &message, ParseR return true; } -bool XiaomiMiscale::report_results(const optional &result, const std::string &address) { +bool XiaomiMiscale::report_results_(const optional &result, const std::string &address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_results(): no results available."); return false; diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.h b/esphome/components/xiaomi_miscale/xiaomi_miscale.h index 3c958afc03..3e51405ddc 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.h +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.h @@ -30,11 +30,11 @@ class XiaomiMiscale : public Component, public esp32_ble_tracker::ESPBTDeviceLis sensor::Sensor *weight_{nullptr}; sensor::Sensor *impedance_{nullptr}; - optional parse_header(const esp32_ble_tracker::ServiceData &service_data); - bool parse_message(const std::vector &message, ParseResult &result); - bool parse_message_V1(const std::vector &message, ParseResult &result); - bool parse_message_V2(const std::vector &message, ParseResult &result); - bool report_results(const optional &result, const std::string &address); + optional parse_header_(const esp32_ble_tracker::ServiceData &service_data); + bool parse_message_(const std::vector &message, ParseResult &result); + bool parse_message_v1_(const std::vector &message, ParseResult &result); + bool parse_message_v2_(const std::vector &message, ParseResult &result); + bool report_results_(const optional &result, const std::string &address); }; } // namespace xiaomi_miscale diff --git a/esphome/core/gpio.h b/esphome/core/gpio.h index 25d56b9020..1d3fb89805 100644 --- a/esphome/core/gpio.h +++ b/esphome/core/gpio.h @@ -6,7 +6,7 @@ namespace esphome { #define LOG_PIN(prefix, pin) \ if ((pin) != nullptr) { \ - ESP_LOGCONFIG(TAG, prefix "%s", pin->dump_summary().c_str()); \ + ESP_LOGCONFIG(TAG, prefix "%s", (pin)->dump_summary().c_str()); \ } // put GPIO flags in a namepsace to not pollute esphome namespace @@ -78,7 +78,7 @@ class ISRInternalGPIOPin { 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); + this->attach_interrupt(reinterpret_cast(func), arg, type); } virtual void detach_interrupt() const = 0; @@ -92,7 +92,7 @@ class InternalGPIOPin : public GPIOPin { virtual bool is_inverted() const = 0; protected: - virtual void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const = 0; + 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 index 2843bb9b15..a86dbf2534 100644 --- a/esphome/core/hal.h +++ b/esphome/core/hal.h @@ -37,7 +37,7 @@ void yield(); uint32_t millis(); uint32_t micros(); void delay(uint32_t ms); -void delayMicroseconds(uint32_t us); +void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming) void __attribute__((noreturn)) arch_restart(); void arch_feed_wdt(); uint32_t arch_get_cpu_cycle_count(); diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 40d94005e7..86cd3b086e 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -333,10 +333,10 @@ template T *new_buffer(size_t length) { if (psramFound()) { buffer = (T *) ps_malloc(length); } else { - buffer = new T[length]; + buffer = new T[length]; // NOLINT(cppcoreguidelines-owning-memory) } #else - buffer = new T[length]; // NOLINT + buffer = new T[length]; // NOLINT(cppcoreguidelines-owning-memory) #endif return buffer; diff --git a/script/clang-tidy b/script/clang-tidy index 2612a18c1c..6e79059372 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -17,7 +17,7 @@ import pexpect sys.path.append(os.path.dirname(__file__)) from helpers import shlex_quote, get_output, filter_grep, \ - build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata + build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata, basepath def clang_options(idedata): @@ -89,6 +89,7 @@ def run_tidy(args, options, tmpdir, queue, lock, failed_files): invocation.append('-quiet') invocation.append(os.path.abspath(path)) + invocation.append(f"--header-filter={os.path.abspath(basepath)}/.*") invocation.append('--') invocation.extend(options) invocation_s = ' '.join(shlex_quote(x) for x in invocation) From 8503e08ee66f57b4e727550615f26af3e15ce9fe Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 25 Sep 2021 09:14:07 +0200 Subject: [PATCH 065/549] Fix InterruptLock on ESP-IDF (#2388) --- esphome/components/dallas/__init__.py | 18 ++++++++----- esphome/components/dht/dht.cpp | 39 +++++++++++++-------------- esphome/core/helpers.cpp | 2 +- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/esphome/components/dallas/__init__.py b/esphome/components/dallas/__init__.py index 762bfdc3c3..2dbc69b8e2 100644 --- a/esphome/components/dallas/__init__.py +++ b/esphome/components/dallas/__init__.py @@ -11,13 +11,17 @@ dallas_ns = cg.esphome_ns.namespace("dallas") DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent) ESPOneWire = dallas_ns.class_("ESPOneWire") -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(DallasComponent), - cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire), - cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, - } -).extend(cv.polling_component_schema("60s")) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(DallasComponent), + cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire), + cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, + } + ).extend(cv.polling_component_schema("60s")), + # pin_mode call logs in esp-idf, but InterruptLock is active -> crash + cv.only_with_arduino, +) async def to_code(config): diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index 2539bfe5ee..2a4ccf1529 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -79,28 +79,27 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r int8_t i = 0; uint8_t data[5] = {0, 0, 0, 0, 0}; + this->pin_->digital_write(false); + this->pin_->pin_mode(gpio::FLAG_OUTPUT); + this->pin_->digital_write(false); + + if (this->model_ == DHT_MODEL_DHT11) { + delayMicroseconds(18000); + } else if (this->model_ == DHT_MODEL_SI7021) { + delayMicroseconds(500); + this->pin_->digital_write(true); + delayMicroseconds(40); + } else if (this->model_ == DHT_MODEL_DHT22_TYPE2) { + delayMicroseconds(2000); + } else if (this->model_ == DHT_MODEL_AM2302) { + delayMicroseconds(1000); + } else { + delayMicroseconds(800); + } + this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + { InterruptLock lock; - - this->pin_->digital_write(false); - this->pin_->pin_mode(gpio::FLAG_OUTPUT); - this->pin_->digital_write(false); - - if (this->model_ == DHT_MODEL_DHT11) { - delayMicroseconds(18000); - } else if (this->model_ == DHT_MODEL_SI7021) { - delayMicroseconds(500); - this->pin_->digital_write(true); - delayMicroseconds(40); - } else if (this->model_ == DHT_MODEL_DHT22_TYPE2) { - delayMicroseconds(2000); - } else if (this->model_ == DHT_MODEL_AM2302) { - delayMicroseconds(1000); - } else { - delayMicroseconds(800); - } - 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 // expect the DHT response (30us+40us) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index a90eb74be2..2b77c5827a 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -350,7 +350,7 @@ std::string hexencode(const uint8_t *data, uint32_t len) { IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } #endif -#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#ifdef USE_ESP32 IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } #endif From 278863d0272c3005b6e5b2f6c0ff3d1c297cb4e4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 25 Sep 2021 09:16:32 +0200 Subject: [PATCH 066/549] Fix some issues with wifi driver after IDF refactor (#2387) --- .../wifi/wifi_component_esp32_arduino.cpp | 156 ++++++++++-------- .../wifi/wifi_component_esp8266.cpp | 14 +- .../wifi/wifi_component_esp_idf.cpp | 13 +- 3 files changed, 102 insertions(+), 81 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index df2692ea81..a0f8a3d114 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -24,11 +24,7 @@ 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) +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(); @@ -122,8 +118,8 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { info.netmask.addr = static_cast(manual_ip->subnet); esp_err_t dhcp_stop_ret = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); - if (dhcp_stop_ret != ESP_OK) { - ESP_LOGV(TAG, "Stopping DHCP client failed! %d", dhcp_stop_ret); + if (dhcp_stop_ret != ESP_OK && dhcp_stop_ret != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) { + ESP_LOGV(TAG, "Stopping DHCP client failed! %s", esp_err_to_name(dhcp_stop_ret)); } esp_err_t wifi_set_info_ret = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &info); @@ -280,18 +276,14 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { this->wifi_apply_hostname_(); + s_sta_connecting = true; + err = esp_wifi_connect(); if (err != ESP_OK) { ESP_LOGW(TAG, "esp_wifi_connect failed! %d", 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; } const char *get_auth_mode_str(uint8_t mode) { @@ -402,35 +394,78 @@ const char *get_disconnect_reason_str(uint8_t reason) { return "Unspecified"; } } + #if ESP_IDF_VERSION_MAJOR >= 4 -void WiFiComponent::wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info) { -#else -void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_info_t info) { -#endif + +#define ESPHOME_EVENT_ID_WIFI_READY ARDUINO_EVENT_WIFI_READY +#define ESPHOME_EVENT_ID_WIFI_SCAN_DONE ARDUINO_EVENT_WIFI_SCAN_DONE +#define ESPHOME_EVENT_ID_WIFI_STA_START ARDUINO_EVENT_WIFI_STA_START +#define ESPHOME_EVENT_ID_WIFI_STA_STOP ARDUINO_EVENT_WIFI_STA_STOP +#define ESPHOME_EVENT_ID_WIFI_STA_CONNECTED ARDUINO_EVENT_WIFI_STA_CONNECTED +#define ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED ARDUINO_EVENT_WIFI_STA_DISCONNECTED +#define ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE +#define ESPHOME_EVENT_ID_WIFI_STA_GOT_IP ARDUINO_EVENT_WIFI_STA_GOT_IP +#define ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6 ARDUINO_EVENT_WIFI_STA_GOT_IP6 +#define ESPHOME_EVENT_ID_WIFI_STA_LOST_IP ARDUINO_EVENT_WIFI_STA_LOST_IP +#define ESPHOME_EVENT_ID_WIFI_AP_START ARDUINO_EVENT_WIFI_AP_START +#define ESPHOME_EVENT_ID_WIFI_AP_STOP ARDUINO_EVENT_WIFI_AP_STOP +#define ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED ARDUINO_EVENT_WIFI_AP_STACONNECTED +#define ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED ARDUINO_EVENT_WIFI_AP_STADISCONNECTED +#define ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED +#define ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED +#define ESPHOME_EVENT_ID_WIFI_AP_GOT_IP6 ARDUINO_EVENT_WIFI_AP_GOT_IP6 +using esphome_wifi_event_id_t = arduino_event_id_t; +using esphome_wifi_event_info_t = arduino_event_info_t; + +#else // ESP_IDF_VERSION_MAJOR >= 4 + +#define ESPHOME_EVENT_ID_WIFI_READY SYSTEM_EVENT_WIFI_READY +#define ESPHOME_EVENT_ID_WIFI_SCAN_DONE SYSTEM_EVENT_SCAN_DONE +#define ESPHOME_EVENT_ID_WIFI_STA_START SYSTEM_EVENT_STA_START +#define ESPHOME_EVENT_ID_WIFI_STA_STOP SYSTEM_EVENT_STA_STOP +#define ESPHOME_EVENT_ID_WIFI_STA_CONNECTED SYSTEM_EVENT_STA_CONNECTED +#define ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED SYSTEM_EVENT_STA_DISCONNECTED +#define ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE SYSTEM_EVENT_STA_AUTHMODE_CHANGE +#define ESPHOME_EVENT_ID_WIFI_STA_GOT_IP SYSTEM_EVENT_STA_GOT_IP +#define ESPHOME_EVENT_ID_WIFI_STA_LOST_IP SYSTEM_EVENT_STA_LOST_IP +#define ESPHOME_EVENT_ID_WIFI_AP_START SYSTEM_EVENT_AP_START +#define ESPHOME_EVENT_ID_WIFI_AP_STOP SYSTEM_EVENT_AP_STOP +#define ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED SYSTEM_EVENT_AP_STACONNECTED +#define ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED SYSTEM_EVENT_AP_STADISCONNECTED +#define ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED SYSTEM_EVENT_AP_STAIPASSIGNED +#define ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED SYSTEM_EVENT_AP_PROBEREQRECVED +using esphome_wifi_event_id_t = system_event_id_t; +using esphome_wifi_event_info_t = system_event_info_t; + +#endif // !(ESP_IDF_VERSION_MAJOR >= 4) + +void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_wifi_event_info_t info) { switch (event) { - case SYSTEM_EVENT_WIFI_READY: { + case ESPHOME_EVENT_ID_WIFI_READY: { ESP_LOGV(TAG, "Event: WiFi ready"); break; } - case SYSTEM_EVENT_SCAN_DONE: { + case ESPHOME_EVENT_ID_WIFI_SCAN_DONE: { #if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_scan_done; #else auto it = info.scan_done; #endif ESP_LOGV(TAG, "Event: WiFi Scan Done status=%u number=%u scan_id=%u", it.status, it.number, it.scan_id); + + this->wifi_scan_done_callback_(); break; } - case SYSTEM_EVENT_STA_START: { + case ESPHOME_EVENT_ID_WIFI_STA_START: { ESP_LOGV(TAG, "Event: WiFi STA start"); tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, App.get_name().c_str()); break; } - case SYSTEM_EVENT_STA_STOP: { + case ESPHOME_EVENT_ID_WIFI_STA_STOP: { ESP_LOGV(TAG, "Event: WiFi STA stop"); break; } - case SYSTEM_EVENT_STA_CONNECTED: { + case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: { #if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_sta_connected; #else @@ -441,10 +476,10 @@ 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: { + case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: { #if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_sta_disconnected; #else @@ -455,17 +490,26 @@ 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; + + uint8_t reason = it.reason; + if (reason == WIFI_REASON_AUTH_EXPIRE || reason == WIFI_REASON_BEACON_TIMEOUT || + reason == WIFI_REASON_NO_AP_FOUND || reason == WIFI_REASON_ASSOC_FAIL || + reason == WIFI_REASON_HANDSHAKE_TIMEOUT) { + err_t err = esp_wifi_disconnect(); + if (err != ESP_OK) { + ESP_LOGV(TAG, "Disconnect failed: %s", esp_err_to_name(err)); + } + this->error_from_callback_ = true; + } + s_sta_connecting = false; break; } - case SYSTEM_EVENT_STA_AUTHMODE_CHANGE: { + case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: { #if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_sta_authmode_change; #else @@ -487,27 +531,26 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i } break; } - case SYSTEM_EVENT_STA_GOT_IP: { + case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP: { 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; + s_sta_connecting = false; break; } - case SYSTEM_EVENT_STA_LOST_IP: { + case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: { ESP_LOGV(TAG, "Event: Lost IP"); - s_sta_got_ip = false; break; } - case SYSTEM_EVENT_AP_START: { + case ESPHOME_EVENT_ID_WIFI_AP_START: { ESP_LOGV(TAG, "Event: WiFi AP start"); break; } - case SYSTEM_EVENT_AP_STOP: { + case ESPHOME_EVENT_ID_WIFI_AP_STOP: { ESP_LOGV(TAG, "Event: WiFi AP stop"); break; } - case SYSTEM_EVENT_AP_STACONNECTED: { + case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: { #if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_sta_connected; auto &mac = it.bssid; @@ -518,7 +561,7 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i ESP_LOGV(TAG, "Event: AP client connected MAC=%s", format_mac_addr(mac).c_str()); break; } - case SYSTEM_EVENT_AP_STADISCONNECTED: { + case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: { #if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_sta_disconnected; auto &mac = it.bssid; @@ -529,11 +572,11 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i ESP_LOGV(TAG, "Event: AP client disconnected MAC=%s", format_mac_addr(mac).c_str()); break; } - case SYSTEM_EVENT_AP_STAIPASSIGNED: { + case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: { ESP_LOGV(TAG, "Event: AP client assigned IP"); break; } - case SYSTEM_EVENT_AP_PROBEREQRECVED: { + case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: { #if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_ap_probereqrecved; #else @@ -545,31 +588,6 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i default: break; } - -#if ESP_IDF_VERSION_MAJOR >= 4 - if (event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) { - uint8_t reason = info.wifi_sta_disconnected.reason; -#else - if (event == SYSTEM_EVENT_STA_DISCONNECTED) { - uint8_t reason = info.disconnected.reason; -#endif - if (reason == WIFI_REASON_AUTH_EXPIRE || reason == WIFI_REASON_BEACON_TIMEOUT || - reason == WIFI_REASON_NO_AP_FOUND || reason == WIFI_REASON_ASSOC_FAIL || - reason == WIFI_REASON_HANDSHAKE_TIMEOUT) { - err_t err = esp_wifi_disconnect(); - if (err != ESP_OK) { - ESP_LOGV(TAG, "Disconnect failed: %s", esp_err_to_name(err)); - } - this->error_from_callback_ = true; - } - } -#if ESP_IDF_VERSION_MAJOR >= 4 - if (event == ARDUINO_EVENT_WIFI_SCAN_DONE) { -#else - if (event == SYSTEM_EVENT_SCAN_DONE) { -#endif - this->wifi_scan_done_callback_(); - } } void WiFiComponent::wifi_pre_setup_() { auto f = std::bind(&WiFiComponent::wifi_event_callback_, this, std::placeholders::_1, std::placeholders::_2); @@ -579,16 +597,14 @@ void WiFiComponent::wifi_pre_setup_() { this->wifi_mode_(false, false); } WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { - if (s_sta_connected && s_sta_got_ip) { + auto status = WiFiClass::status(); + if (status == WL_CONNECTED) { return WiFiSTAConnectStatus::CONNECTED; - } - if (s_sta_connect_error) { + } else if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST || status == WL_DISCONNECTED) { return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; - } - if (s_sta_connect_not_found) { + } else if (status == WL_NO_SSID_AVAIL) { return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; - } - if (s_sta_connecting) { + } else if (s_sta_connecting) { return WiFiSTAConnectStatus::CONNECTING; } return WiFiSTAConnectStatus::IDLE; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 5dcfe7a108..2021773209 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -309,6 +309,14 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { this->wifi_apply_hostname_(); + // Reset flags, do this _before_ wifi_station_connect as the callback method + // may be called from wifi_station_connect + s_sta_connecting = true; + s_sta_connected = false; + s_sta_got_ip = false; + s_sta_connect_error = false; + s_sta_connect_not_found = false; + ETS_UART_INTR_DISABLE(); ret = wifi_station_connect(); ETS_UART_INTR_ENABLE(); @@ -325,12 +333,6 @@ 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; } diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 676b7f8fba..9ec3f80014 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -377,17 +377,20 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { } #endif // USE_WIFI_WPA2_EAP + // Reset flags, do this _before_ wifi_station_connect as the callback method + // may be called from wifi_station_connect + s_sta_connecting = true; + s_sta_connected = false; + s_sta_got_ip = false; + s_sta_connect_error = false; + s_sta_connect_not_found = false; + 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; } From d344b1ca0efcb93a74bb64adf5c5cb4e907c1f54 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 25 Sep 2021 10:04:57 +0200 Subject: [PATCH 067/549] Fix arduino esp32 wifi v2 (#2389) --- esphome/components/wifi/wifi_component_esp32_arduino.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index a0f8a3d114..e1332e3181 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -600,7 +600,7 @@ WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { auto status = WiFiClass::status(); if (status == WL_CONNECTED) { return WiFiSTAConnectStatus::CONNECTED; - } else if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST || status == WL_DISCONNECTED) { + } else if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST) { return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; } else if (status == WL_NO_SSID_AVAIL) { return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; From 5342edf04af2476ba88eceadbe1a2e08682ab2f3 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 25 Sep 2021 10:05:32 +0200 Subject: [PATCH 068/549] Misc fixes for esp-idf (#2386) --- esphome/components/logger/logger.cpp | 13 ++++++------- esphome/core/config.py | 5 +++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 045f7059a9..4352b7e208 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -176,13 +176,12 @@ void Logger::pre_setup() { 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_config_t uart_config{}; + uart_config.baud_rate = (int) baud_rate_; + uart_config.data_bits = UART_DATA_8_BITS; + uart_config.parity = UART_PARITY_DISABLE; + uart_config.stop_bits = UART_STOP_BITS_1; + uart_config.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 diff --git a/esphome/core/config.py b/esphome/core/config.py index 71add56b13..bbdfcf124c 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -247,6 +247,11 @@ async def _add_automations(config): @coroutine_with_priority(100.0) async def to_code(config): cg.add_global(cg.global_ns.namespace("esphome").using) + # These can be used by user lambdas, put them to default scope + cg.add_global(cg.RawExpression("using std::isnan")) + cg.add_global(cg.RawExpression("using std::min")) + cg.add_global(cg.RawExpression("using std::max")) + cg.add( cg.App.pre_setup( config[CONF_NAME], From 95a6715b2b3ebc30d31430b24beb888583a1f913 Mon Sep 17 00:00:00 2001 From: rbaron Date: Sat, 25 Sep 2021 13:16:27 +0200 Subject: [PATCH 069/549] Adds light sensor support for b-parasites (#2391) --- esphome/components/b_parasite/b_parasite.cpp | 20 ++++++++++++++++++++ esphome/components/b_parasite/b_parasite.h | 2 ++ esphome/components/b_parasite/sensor.py | 10 ++++++++++ tests/test2.yaml | 2 ++ 4 files changed, 34 insertions(+) diff --git a/esphome/components/b_parasite/b_parasite.cpp b/esphome/components/b_parasite/b_parasite.cpp index bc1463fd1c..ee12226977 100644 --- a/esphome/components/b_parasite/b_parasite.cpp +++ b/esphome/components/b_parasite/b_parasite.cpp @@ -14,6 +14,7 @@ void BParasite::dump_config() { LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Soil Moisture", this->soil_moisture_); + LOG_SENSOR(" ", "Illuminance", this->illuminance_); } bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { @@ -36,6 +37,15 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { const auto &data = service_data.data; + const uint8_t protocol_version = data[0] >> 4; + if (protocol_version != 1) { + ESP_LOGE(TAG, "Unsupported protocol version: %u", protocol_version); + return false; + } + + // Some b-parasite versions have an (optional) illuminance sensor. + bool has_illuminance = data[0] & 0x1; + // Counter for deduplicating messages. uint8_t counter = data[1] & 0x0f; if (last_processed_counter_ == counter) { @@ -59,6 +69,9 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { uint16_t soil_moisture = data[8] << 8 | data[9]; float moisture_percent = (100.0f * soil_moisture) / (1 << 16); + // Ambient light in lux. + float illuminance = has_illuminance ? data[16] << 8 | data[17] : 0.0f; + if (battery_voltage_ != nullptr) { battery_voltage_->publish_state(battery_voltage); } @@ -71,6 +84,13 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (soil_moisture_ != nullptr) { soil_moisture_->publish_state(moisture_percent); } + if (illuminance_ != nullptr) { + if (has_illuminance) { + illuminance_->publish_state(illuminance); + } else { + ESP_LOGE(TAG, "No lux information is present in the BLE packet"); + } + } last_processed_counter_ = counter; return true; diff --git a/esphome/components/b_parasite/b_parasite.h b/esphome/components/b_parasite/b_parasite.h index bdd9a01b83..70ee4ab23c 100644 --- a/esphome/components/b_parasite/b_parasite.h +++ b/esphome/components/b_parasite/b_parasite.h @@ -22,6 +22,7 @@ class BParasite : public Component, public esp32_ble_tracker::ESPBTDeviceListene void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } void set_soil_moisture(sensor::Sensor *soil_moisture) { soil_moisture_ = soil_moisture; } + void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; } protected: // The received advertisement packet contains an unsigned 4 bits wrap-around counter @@ -32,6 +33,7 @@ class BParasite : public Component, public esp32_ble_tracker::ESPBTDeviceListene sensor::Sensor *temperature_{nullptr}; sensor::Sensor *humidity_{nullptr}; sensor::Sensor *soil_moisture_{nullptr}; + sensor::Sensor *illuminance_{nullptr}; }; } // namespace b_parasite diff --git a/esphome/components/b_parasite/sensor.py b/esphome/components/b_parasite/sensor.py index 46ed64337f..d51c48c602 100644 --- a/esphome/components/b_parasite/sensor.py +++ b/esphome/components/b_parasite/sensor.py @@ -5,14 +5,17 @@ from esphome.const import ( CONF_BATTERY_VOLTAGE, CONF_HUMIDITY, CONF_ID, + CONF_ILLUMINANCE, CONF_MOISTURE, CONF_MAC_ADDRESS, CONF_TEMPERATURE, DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, + UNIT_LUX, UNIT_PERCENT, UNIT_VOLT, ) @@ -55,6 +58,12 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_HUMIDITY, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( + unit_of_measurement=UNIT_LUX, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), } ) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) @@ -74,6 +83,7 @@ async def to_code(config): (CONF_HUMIDITY, var.set_humidity), (CONF_BATTERY_VOLTAGE, var.set_battery_voltage), (CONF_MOISTURE, var.set_soil_moisture), + (CONF_ILLUMINANCE, var.set_illuminance), ]: if config_key in config: sens = await sensor.new_sensor(config[config_key]) diff --git a/tests/test2.yaml b/tests/test2.yaml index d0634e0f7b..4541fba616 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -91,6 +91,8 @@ sensor: name: 'b-parasite Soil Moisture' battery_voltage: name: 'b-parasite Battery Voltage' + illuminance: + name: 'b-parasite Illuminance' - platform: senseair id: senseair0 co2: From bdcffc7ba91c32e33b77df1e22f8868a9421eb29 Mon Sep 17 00:00:00 2001 From: irtimaled Date: Sun, 26 Sep 2021 01:27:43 -0700 Subject: [PATCH 070/549] fix: Setting Tuya string DP value (#2394) --- 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 d73ba50462..4f65fa7118 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -479,7 +479,7 @@ void Tuya::set_string_datapoint_value(uint8_t datapoint_id, const std::string &v for (char const &c : value) { data.push_back(c); } - this->send_datapoint_command_(datapoint->id, datapoint->type, data); + this->send_datapoint_command_(datapoint_id, TuyaDatapointType::STRING, data); } void Tuya::set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) { From 7246f42a8ec4ba2c15ff930c69dacc4f174f6269 Mon Sep 17 00:00:00 2001 From: irtimaled Date: Sun, 26 Sep 2021 01:34:06 -0700 Subject: [PATCH 071/549] Tuya rgb support (#2278) Co-authored-by: Oxan van Leeuwen Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/rgbct/light.py | 2 +- esphome/components/rgbw/light.py | 10 ++- esphome/components/rgbww/light.py | 2 +- esphome/components/tuya/light/__init__.py | 12 ++- esphome/components/tuya/light/tuya_light.cpp | 91 ++++++++++++++------ esphome/components/tuya/light/tuya_light.h | 5 ++ esphome/const.py | 1 + esphome/core/helpers.cpp | 33 +++++++ esphome/core/helpers.h | 3 +- 9 files changed, 129 insertions(+), 30 deletions(-) diff --git a/esphome/components/rgbct/light.py b/esphome/components/rgbct/light.py index e525c207c7..0565057316 100644 --- a/esphome/components/rgbct/light.py +++ b/esphome/components/rgbct/light.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome.components import light, output from esphome.const import ( CONF_BLUE, + CONF_COLOR_INTERLOCK, CONF_COLOR_TEMPERATURE, CONF_GREEN, CONF_RED, @@ -16,7 +17,6 @@ CODEOWNERS = ["@jesserockz"] rgbct_ns = cg.esphome_ns.namespace("rgbct") RGBCTLightOutput = rgbct_ns.class_("RGBCTLightOutput", light.LightOutput) -CONF_COLOR_INTERLOCK = "color_interlock" CONF_WHITE_BRIGHTNESS = "white_brightness" CONFIG_SCHEMA = cv.All( diff --git a/esphome/components/rgbw/light.py b/esphome/components/rgbw/light.py index de26edf7d5..f747580f61 100644 --- a/esphome/components/rgbw/light.py +++ b/esphome/components/rgbw/light.py @@ -1,11 +1,17 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import light, output -from esphome.const import CONF_BLUE, CONF_GREEN, CONF_RED, CONF_OUTPUT_ID, CONF_WHITE +from esphome.const import ( + CONF_BLUE, + CONF_COLOR_INTERLOCK, + CONF_GREEN, + CONF_RED, + CONF_OUTPUT_ID, + CONF_WHITE, +) rgbw_ns = cg.esphome_ns.namespace("rgbw") RGBWLightOutput = rgbw_ns.class_("RGBWLightOutput", light.LightOutput) -CONF_COLOR_INTERLOCK = "color_interlock" CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend( { diff --git a/esphome/components/rgbww/light.py b/esphome/components/rgbww/light.py index c0ce85e267..35f77b154b 100644 --- a/esphome/components/rgbww/light.py +++ b/esphome/components/rgbww/light.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome.components import light, output from esphome.const import ( CONF_BLUE, + CONF_COLOR_INTERLOCK, CONF_CONSTANT_BRIGHTNESS, CONF_GREEN, CONF_RED, @@ -16,7 +17,6 @@ from esphome.const import ( rgbww_ns = cg.esphome_ns.namespace("rgbww") RGBWWLightOutput = rgbww_ns.class_("RGBWWLightOutput", light.LightOutput) -CONF_COLOR_INTERLOCK = "color_interlock" CONFIG_SCHEMA = cv.All( light.RGB_LIGHT_SCHEMA.extend( diff --git a/esphome/components/tuya/light/__init__.py b/esphome/components/tuya/light/__init__.py index f43cc570ca..6678fc47d8 100644 --- a/esphome/components/tuya/light/__init__.py +++ b/esphome/components/tuya/light/__init__.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_SWITCH_DATAPOINT, CONF_COLD_WHITE_COLOR_TEMPERATURE, CONF_WARM_WHITE_COLOR_TEMPERATURE, + CONF_COLOR_INTERLOCK, ) from .. import tuya_ns, CONF_TUYA_ID, Tuya @@ -20,6 +21,7 @@ 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" +CONF_RGB_DATAPOINT = "rgb_datapoint" TuyaLight = tuya_ns.class_("TuyaLight", light.LightOutput, cg.Component) @@ -31,6 +33,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DIMMER_DATAPOINT): cv.uint8_t, cv.Optional(CONF_MIN_VALUE_DATAPOINT): cv.uint8_t, cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_RGB_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean, cv.Inclusive( CONF_COLOR_TEMPERATURE_DATAPOINT, "color_temperature" ): cv.uint8_t, @@ -52,7 +56,9 @@ CONFIG_SCHEMA = cv.All( ): cv.positive_time_period_milliseconds, } ).extend(cv.COMPONENT_SCHEMA), - cv.has_at_least_one_key(CONF_DIMMER_DATAPOINT, CONF_SWITCH_DATAPOINT), + cv.has_at_least_one_key( + CONF_DIMMER_DATAPOINT, CONF_SWITCH_DATAPOINT, CONF_RGB_DATAPOINT + ), ) @@ -67,6 +73,8 @@ async def to_code(config): cg.add(var.set_min_value_datapoint_id(config[CONF_MIN_VALUE_DATAPOINT])) if CONF_SWITCH_DATAPOINT in config: cg.add(var.set_switch_id(config[CONF_SWITCH_DATAPOINT])) + if CONF_RGB_DATAPOINT in config: + cg.add(var.set_rgb_id(config[CONF_RGB_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])) @@ -87,5 +95,7 @@ async def to_code(config): config[CONF_COLOR_TEMPERATURE_MAX_VALUE] ) ) + + cg.add(var.set_color_interlock(config[CONF_COLOR_INTERLOCK])) paren = await cg.get_variable(config[CONF_TUYA_ID]) cg.add(var.set_tuya_parent(paren)) diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index 6f3adfcdfd..97f6de9bae 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -1,5 +1,6 @@ #include "esphome/core/log.h" #include "tuya_light.h" +#include "esphome/core/helpers.h" namespace esphome { namespace tuya { @@ -34,6 +35,18 @@ void TuyaLight::setup() { call.perform(); }); } + if (rgb_id_.has_value()) { + this->parent_->register_listener(*this->rgb_id_, [this](const TuyaDatapoint &datapoint) { + auto red = parse_hex(datapoint.value_string, 0, 2); + auto green = parse_hex(datapoint.value_string, 2, 2); + auto blue = parse_hex(datapoint.value_string, 4, 2); + if (red.has_value() && green.has_value() && blue.has_value()) { + auto call = this->state_->make_call(); + call.set_rgb(float(*red) / 255, float(*green) / 255, float(*blue) / 255); + call.perform(); + } + }); + } if (min_value_datapoint_id_.has_value()) { parent_->set_integer_datapoint_value(*this->min_value_datapoint_id_, this->min_value_); } @@ -45,14 +58,31 @@ void TuyaLight::dump_config() { ESP_LOGCONFIG(TAG, " Dimmer has datapoint ID %u", *this->dimmer_id_); if (this->switch_id_.has_value()) ESP_LOGCONFIG(TAG, " Switch has datapoint ID %u", *this->switch_id_); + if (this->rgb_id_.has_value()) + ESP_LOGCONFIG(TAG, " RGB has datapoint ID %u", *this->rgb_id_); } light::LightTraits TuyaLight::get_traits() { auto traits = light::LightTraits(); if (this->color_temperature_id_.has_value() && this->dimmer_id_.has_value()) { - traits.set_supported_color_modes({light::ColorMode::COLOR_TEMPERATURE}); + if (this->rgb_id_.has_value()) { + if (this->color_interlock_) + traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::COLOR_TEMPERATURE}); + else + traits.set_supported_color_modes( + {light::ColorMode::RGB_COLOR_TEMPERATURE, light::ColorMode::COLOR_TEMPERATURE}); + } else + traits.set_supported_color_modes({light::ColorMode::COLOR_TEMPERATURE}); traits.set_min_mireds(this->cold_white_temperature_); traits.set_max_mireds(this->warm_white_temperature_); + } else if (this->rgb_id_.has_value()) { + if (this->dimmer_id_.has_value()) { + if (this->color_interlock_) + traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::WHITE}); + else + traits.set_supported_color_modes({light::ColorMode::RGB_WHITE}); + } else + traits.set_supported_color_modes({light::ColorMode::RGB}); } else if (this->dimmer_id_.has_value()) { traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS}); } else { @@ -64,38 +94,51 @@ light::LightTraits TuyaLight::get_traits() { void TuyaLight::setup_state(light::LightState *state) { state_ = state; } void TuyaLight::write_state(light::LightState *state) { - float brightness; - state->current_values_as_brightness(&brightness); + float red = 0.0f, green = 0.0f, blue = 0.0f; + float color_temperature = 0.0f, brightness = 0.0f; - if (brightness == 0.0f) { - // turning off, first try via switch (if exists), then dimmer - if (switch_id_.has_value()) { - parent_->set_boolean_datapoint_value(*this->switch_id_, false); - } else if (dimmer_id_.has_value()) { - parent_->set_integer_datapoint_value(*this->dimmer_id_, 0); + if (this->rgb_id_.has_value()) { + if (this->color_temperature_id_.has_value()) { + state->current_values_as_rgbct(&red, &green, &blue, &color_temperature, &brightness); + } else if (this->dimmer_id_.has_value()) { + state->current_values_as_rgbw(&red, &green, &blue, &brightness); + } else { + state->current_values_as_rgb(&red, &green, &blue); } - return; + } else if (this->color_temperature_id_.has_value()) { + state->current_values_as_ct(&color_temperature, &brightness); + } else { + state->current_values_as_brightness(&brightness); } - if (this->color_temperature_id_.has_value()) { - uint32_t color_temp_int = - 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; + if (brightness > 0.0f || !color_interlock_) { + if (this->color_temperature_id_.has_value()) { + uint32_t color_temp_int = static_cast(color_temperature * this->color_temperature_max_value_); + 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); + } + + if (this->dimmer_id_.has_value()) { + auto brightness_int = static_cast(brightness * this->max_value_); + brightness_int = std::max(brightness_int, this->min_value_); + + parent_->set_integer_datapoint_value(*this->dimmer_id_, brightness_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_integer_datapoint_value(*this->dimmer_id_, brightness_int); + if (brightness == 0.0f || !color_interlock_) { + if (this->rgb_id_.has_value()) { + char buffer[7]; + sprintf(buffer, "%02X%02X%02X", int(red * 255), int(green * 255), int(blue * 255)); + std::string value = buffer; + this->parent_->set_string_datapoint_value(*this->rgb_id_, value); + } } + if (this->switch_id_.has_value()) { - parent_->set_boolean_datapoint_value(*this->switch_id_, true); + parent_->set_boolean_datapoint_value(*this->switch_id_, state->current_values.is_on()); } } diff --git a/esphome/components/tuya/light/tuya_light.h b/esphome/components/tuya/light/tuya_light.h index 20753fa90b..de9ec5e45f 100644 --- a/esphome/components/tuya/light/tuya_light.h +++ b/esphome/components/tuya/light/tuya_light.h @@ -16,6 +16,7 @@ class TuyaLight : public Component, public light::LightOutput { this->min_value_datapoint_id_ = min_value_datapoint_id; } void set_switch_id(uint8_t switch_id) { this->switch_id_ = switch_id; } + void set_rgb_id(uint8_t rgb_id) { this->rgb_id_ = rgb_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; @@ -32,6 +33,8 @@ class TuyaLight : public Component, public light::LightOutput { void set_warm_white_temperature(float warm_white_temperature) { this->warm_white_temperature_ = warm_white_temperature; } + void set_color_interlock(bool color_interlock) { color_interlock_ = color_interlock; } + light::LightTraits get_traits() override; void setup_state(light::LightState *state) override; void write_state(light::LightState *state) override; @@ -44,6 +47,7 @@ class TuyaLight : public Component, public light::LightOutput { optional dimmer_id_{}; optional min_value_datapoint_id_{}; optional switch_id_{}; + optional rgb_id_{}; optional color_temperature_id_{}; uint32_t min_value_ = 0; uint32_t max_value_ = 255; @@ -51,6 +55,7 @@ class TuyaLight : public Component, public light::LightOutput { float cold_white_temperature_; float warm_white_temperature_; bool color_temperature_invert_{false}; + bool color_interlock_{false}; light::LightState *state_{nullptr}; }; diff --git a/esphome/const.py b/esphome/const.py index a52085fbb7..054f032da4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -111,6 +111,7 @@ CONF_COLD_WHITE_COLOR_TEMPERATURE = "cold_white_color_temperature" CONF_COLOR = "color" CONF_COLOR_BRIGHTNESS = "color_brightness" CONF_COLOR_CORRECT = "color_correct" +CONF_COLOR_INTERLOCK = "color_interlock" CONF_COLOR_MODE = "color_mode" CONF_COLOR_TEMPERATURE = "color_temperature" CONF_COLORS = "colors" diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 2b77c5827a..a190566bea 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -270,6 +270,39 @@ optional parse_int(const std::string &str) { return {}; return value; } + +optional parse_hex(const char chr) { + int out = chr; + if (out >= '0' && out <= '9') + return (out - '0'); + if (out >= 'A' && out <= 'F') + return (10 + (out - 'A')); + if (out >= 'a' && out <= 'f') + return (10 + (out - 'a')); + return {}; +} + +optional parse_hex(const std::string &str, size_t start, size_t length) { + if (str.length() < start) { + return {}; + } + size_t end = start + length; + if (str.length() < end) { + return {}; + } + int out = 0; + for (size_t i = start; i < end; i++) { + char chr = str[i]; + auto digit = parse_hex(chr); + if (!digit.has_value()) { + ESP_LOGW(TAG, "Can't convert '%s' to number, invalid character %c!", str.substr(start, length).c_str(), chr); + return {}; + } + out = (out << 4) | *digit; + } + return out; +} + uint32_t fnv1_hash(const std::string &str) { uint32_t hash = 2166136261UL; for (char c : str) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 86cd3b086e..8118585db5 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -43,7 +43,8 @@ std::string to_string(double val); std::string to_string(long double val); optional parse_float(const std::string &str); optional parse_int(const std::string &str); - +optional parse_hex(const std::string &str, size_t start, size_t length); +optional parse_hex(char chr); /// Sanitize the hostname by removing characters that are not in the allowlist and truncating it to 63 chars. std::string sanitize_hostname(const std::string &hostname); From 4d28afc153ba51b133e48e81d98694d3c47152ee Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Mon, 27 Sep 2021 05:32:46 +1000 Subject: [PATCH 072/549] add fan.cycle_speed action (#2329) --- esphome/components/fan/__init__.py | 7 ++++++ esphome/components/fan/automation.h | 36 +++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index f8772948fc..15895976d1 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -41,6 +41,7 @@ FAN_DIRECTION_ENUM = { TurnOnAction = fan_ns.class_("TurnOnAction", automation.Action) TurnOffAction = fan_ns.class_("TurnOffAction", automation.Action) ToggleAction = fan_ns.class_("ToggleAction", automation.Action) +CycleSpeedAction = fan_ns.class_("CycleSpeedAction", automation.Action) FanTurnOnTrigger = fan_ns.class_("FanTurnOnTrigger", automation.Trigger.template()) FanTurnOffTrigger = fan_ns.class_("FanTurnOffTrigger", automation.Trigger.template()) @@ -204,6 +205,12 @@ async def fan_turn_on_to_code(config, action_id, template_arg, args): return var +@automation.register_action("fan.cycle_speed", CycleSpeedAction, FAN_ACTION_SCHEMA) +async def fan_cycle_speed_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) + + @automation.register_condition( "fan.is_on", FanIsOnCondition, diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index 7ff7c720df..608f772b75 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -50,6 +50,42 @@ template class ToggleAction : public Action { FanState *state_; }; +template class CycleSpeedAction : public Action { + public: + explicit CycleSpeedAction(FanState *state) : state_(state) {} + + void play(Ts... x) override { + // check to see if fan supports speeds and is on + if (this->state_->get_traits().supported_speed_count()) { + if (this->state_->state) { + int speed = this->state_->speed + 1; + int supported_speed_count = this->state_->get_traits().supported_speed_count(); + if (speed > supported_speed_count) { + // was running at max speed, so turn off + speed = 1; + auto call = this->state_->turn_off(); + call.set_speed(speed); + call.perform(); + } else { + auto call = this->state_->turn_on(); + call.set_speed(speed); + call.perform(); + } + } else { + // fan was off, so set speed to 1 + auto call = this->state_->turn_on(); + call.set_speed(1); + call.perform(); + } + } else { + // fan doesn't support speed counts, so toggle + this->state_->toggle().perform(); + } + } + + FanState *state_; +}; + template class FanIsOnCondition : public Condition { public: explicit FanIsOnCondition(FanState *state) : state_(state) {} From 7672ba2c8d6d918a4b44220d2d02198332e6762a Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Sun, 26 Sep 2021 22:27:24 +0200 Subject: [PATCH 073/549] Modbus controller (#1779) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 7 + esphome/components/modbus/__init__.py | 14 +- esphome/components/modbus/modbus.cpp | 126 +++- esphome/components/modbus/modbus.h | 20 +- .../components/modbus_controller/__init__.py | 114 ++++ .../binary_sensor/__init__.py | 81 +++ .../binary_sensor/modbus_binarysensor.cpp | 40 ++ .../binary_sensor/modbus_binarysensor.h | 43 ++ esphome/components/modbus_controller/const.py | 13 + .../modbus_controller/modbus_controller.cpp | 559 ++++++++++++++++++ .../modbus_controller/modbus_controller.h | 454 ++++++++++++++ .../modbus_controller/number/__init__.py | 157 +++++ .../number/modbus_number.cpp | 83 +++ .../modbus_controller/number/modbus_number.h | 48 ++ .../modbus_controller/output/__init__.py | 74 +++ .../output/modbus_output.cpp | 61 ++ .../modbus_controller/output/modbus_output.h | 45 ++ .../modbus_controller/sensor/__init__.py | 109 ++++ .../sensor/modbus_sensor.cpp | 36 ++ .../modbus_controller/sensor/modbus_sensor.h | 35 ++ .../modbus_controller/switch/__init__.py | 81 +++ .../switch/modbus_switch.cpp | 70 +++ .../modbus_controller/switch/modbus_switch.h | 44 ++ .../modbus_controller/text_sensor/__init__.py | 101 ++++ .../text_sensor/modbus_textsensor.cpp | 56 ++ .../text_sensor/modbus_textsensor.h | 52 ++ tests/test5.yaml | 16 + 27 files changed, 2505 insertions(+), 34 deletions(-) create mode 100644 esphome/components/modbus_controller/__init__.py create mode 100644 esphome/components/modbus_controller/binary_sensor/__init__.py create mode 100644 esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp create mode 100644 esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h create mode 100644 esphome/components/modbus_controller/const.py create mode 100644 esphome/components/modbus_controller/modbus_controller.cpp create mode 100644 esphome/components/modbus_controller/modbus_controller.h create mode 100644 esphome/components/modbus_controller/number/__init__.py create mode 100644 esphome/components/modbus_controller/number/modbus_number.cpp create mode 100644 esphome/components/modbus_controller/number/modbus_number.h create mode 100644 esphome/components/modbus_controller/output/__init__.py create mode 100644 esphome/components/modbus_controller/output/modbus_output.cpp create mode 100644 esphome/components/modbus_controller/output/modbus_output.h create mode 100644 esphome/components/modbus_controller/sensor/__init__.py create mode 100644 esphome/components/modbus_controller/sensor/modbus_sensor.cpp create mode 100644 esphome/components/modbus_controller/sensor/modbus_sensor.h create mode 100644 esphome/components/modbus_controller/switch/__init__.py create mode 100644 esphome/components/modbus_controller/switch/modbus_switch.cpp create mode 100644 esphome/components/modbus_controller/switch/modbus_switch.h create mode 100644 esphome/components/modbus_controller/text_sensor/__init__.py create mode 100644 esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp create mode 100644 esphome/components/modbus_controller/text_sensor/modbus_textsensor.h diff --git a/CODEOWNERS b/CODEOWNERS index 92ee989309..17825a9205 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -88,6 +88,13 @@ esphome/components/mcp9808/* @k7hpn esphome/components/mdns/* @esphome/core esphome/components/midea/* @dudanov esphome/components/mitsubishi/* @RubyBailey +esphome/components/modbus_controller/* @martgras +esphome/components/modbus_controller/binary_sensor/* @martgras +esphome/components/modbus_controller/number/* @martgras +esphome/components/modbus_controller/output/* @martgras +esphome/components/modbus_controller/sensor/* @martgras +esphome/components/modbus_controller/switch/* @martgras +esphome/components/modbus_controller/text_sensor/* @martgras esphome/components/network/* @esphome/core esphome/components/nextion/* @senexcrenshaw esphome/components/nextion/binary_sensor/* @senexcrenshaw diff --git a/esphome/components/modbus/__init__.py b/esphome/components/modbus/__init__.py index 6b454cbaf0..254322d097 100644 --- a/esphome/components/modbus/__init__.py +++ b/esphome/components/modbus/__init__.py @@ -2,7 +2,11 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.cpp_helpers import gpio_pin_expression from esphome.components import uart -from esphome.const import CONF_FLOW_CONTROL_PIN, CONF_ID, CONF_ADDRESS +from esphome.const import ( + CONF_FLOW_CONTROL_PIN, + CONF_ID, + CONF_ADDRESS, +) from esphome import pins DEPENDENCIES = ["uart"] @@ -13,11 +17,16 @@ ModbusDevice = modbus_ns.class_("ModbusDevice") MULTI_CONF = True CONF_MODBUS_ID = "modbus_id" +CONF_SEND_WAIT_TIME = "send_wait_time" + CONFIG_SCHEMA = ( cv.Schema( { cv.GenerateID(): cv.declare_id(Modbus), cv.Optional(CONF_FLOW_CONTROL_PIN): pins.gpio_output_pin_schema, + cv.Optional( + CONF_SEND_WAIT_TIME, default="250ms" + ): cv.positive_time_period_milliseconds, } ) .extend(cv.COMPONENT_SCHEMA) @@ -36,6 +45,9 @@ async def to_code(config): pin = await gpio_pin_expression(config[CONF_FLOW_CONTROL_PIN]) cg.add(var.set_flow_control_pin(pin)) + if CONF_SEND_WAIT_TIME in config: + cg.add(var.set_send_wait_time(config[CONF_SEND_WAIT_TIME])) + def modbus_device_schema(default_address): schema = { diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 2d714e72a2..1f6d868baf 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -1,5 +1,6 @@ #include "modbus.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" namespace esphome { namespace modbus { @@ -13,10 +14,15 @@ void Modbus::setup() { } void Modbus::loop() { const uint32_t now = millis(); + if (now - this->last_modbus_byte_ > 50) { this->rx_buffer_.clear(); this->last_modbus_byte_ = now; } + // stop blocking new send commands after send_wait_time_ ms regardless if a response has been received since then + if (now - this->last_send_ > send_wait_time_) { + waiting_for_response = 0; + } while (this->available()) { uint8_t byte; @@ -49,48 +55,66 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { size_t at = this->rx_buffer_.size(); this->rx_buffer_.push_back(byte); const uint8_t *raw = &this->rx_buffer_[0]; - + ESP_LOGV(TAG, "Modbus received Byte %d (0X%x)", byte, byte); // Byte 0: modbus address (match all) if (at == 0) return true; uint8_t address = raw[0]; - - // Byte 1: Function (msb indicates error) - if (at == 1) - return (byte & 0x80) != 0x80; - + uint8_t function_code = raw[1]; // Byte 2: Size (with modbus rtu function code 4/3) // See also https://en.wikipedia.org/wiki/Modbus if (at == 2) return true; uint8_t data_len = raw[2]; - // Byte 3..3+data_len-1: Data - if (at < 3 + data_len) + uint8_t data_offset = 3; + // the response for write command mirrors the requests and data startes at offset 2 instead of 3 for read commands + if (function_code == 0x5 || function_code == 0x06 || function_code == 0x10) { + data_offset = 2; + data_len = 4; + } + + // Error ( msb indicates error ) + // response format: Byte[0] = device address, Byte[1] function code | 0x80 , Byte[2] excpetion code, Byte[3-4] crc + if ((function_code & 0x80) == 0x80) { + data_offset = 2; + data_len = 1; + } + + // Byte data_offset..data_offset+data_len-1: Data + if (at < data_offset + data_len) return true; // Byte 3+data_len: CRC_LO (over all bytes) - if (at == 3 + data_len) + if (at == data_offset + data_len) return true; - // Byte 3+len+1: CRC_HI (over all bytes) - uint16_t computed_crc = crc16(raw, 3 + data_len); - uint16_t remote_crc = uint16_t(raw[3 + data_len]) | (uint16_t(raw[3 + data_len + 1]) << 8); + + // Byte data_offset+len+1: CRC_HI (over all bytes) + uint16_t computed_crc = crc16(raw, data_offset + data_len); + uint16_t remote_crc = uint16_t(raw[data_offset + data_len]) | (uint16_t(raw[data_offset + data_len + 1]) << 8); if (computed_crc != remote_crc) { ESP_LOGW(TAG, "Modbus CRC Check failed! %02X!=%02X", computed_crc, remote_crc); return false; } - std::vector data(this->rx_buffer_.begin() + 3, this->rx_buffer_.begin() + 3 + data_len); + waiting_for_response = 0; + std::vector data(this->rx_buffer_.begin() + data_offset, this->rx_buffer_.begin() + data_offset + data_len); bool found = false; for (auto *device : this->devices_) { if (device->address_ == address) { - device->on_modbus_data(data); + // Is it an error response? + if ((function_code & 0x80) == 0x80) { + ESP_LOGW(TAG, "Modbus error function code: 0x%X exception: %d", function_code, raw[2]); + device->on_modbus_error(function_code & 0x7F, raw[2]); + } else { + device->on_modbus_data(data); + } found = true; } } if (!found) { - ESP_LOGW(TAG, "Got Modbus frame from unknown address 0x%02X!", address); + ESP_LOGW(TAG, "Got Modbus frame from unknown address 0x%02X! ", address); } // return false to reset buffer @@ -100,31 +124,79 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { void Modbus::dump_config() { ESP_LOGCONFIG(TAG, "Modbus:"); LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_); + ESP_LOGCONFIG(TAG, " Send Wait Time: %d ms", this->send_wait_time_); } float Modbus::get_setup_priority() const { // After UART bus return setup_priority::BUS - 1.0f; } -void Modbus::send(uint8_t address, uint8_t function, uint16_t start_address, uint16_t register_count) { - uint8_t frame[8]; - frame[0] = address; - frame[1] = function; - frame[2] = start_address >> 8; - frame[3] = start_address >> 0; - frame[4] = register_count >> 8; - frame[5] = register_count >> 0; - auto crc = crc16(frame, 6); - frame[6] = crc >> 0; - frame[7] = crc >> 8; + +void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address, uint16_t number_of_entities, + uint8_t payload_len, const uint8_t *payload) { + static const size_t MAX_VALUES = 128; + + if (number_of_entities > MAX_VALUES) { + ESP_LOGE(TAG, "send too many values %d max=%zu", number_of_entities, MAX_VALUES); + return; + } + + std::vector data; + data.push_back(address); + data.push_back(function_code); + data.push_back(start_address >> 8); + data.push_back(start_address >> 0); + if (function_code != 0x5 && function_code != 0x6) { + data.push_back(number_of_entities >> 8); + data.push_back(number_of_entities >> 0); + } + + if (payload != nullptr) { + if (function_code == 0xF || function_code == 0x10) { // Write multiple + data.push_back(payload_len); // Byte count is required for write + } else { + payload_len = 2; // Write single register or coil + } + for (int i = 0; i < payload_len; i++) { + data.push_back(payload[i]); + } + } + + auto crc = crc16(data.data(), data.size()); + data.push_back(crc >> 0); + data.push_back(crc >> 8); if (this->flow_control_pin_ != nullptr) this->flow_control_pin_->digital_write(true); - this->write_array(frame, 8); + this->write_array(data); this->flush(); if (this->flow_control_pin_ != nullptr) this->flow_control_pin_->digital_write(false); + waiting_for_response = address; + last_send_ = millis(); + ESP_LOGV(TAG, "Modbus write: %s", hexencode(data).c_str()); +} + +// Helper function for lambdas +// Send raw command. Except CRC everything must be contained in payload +void Modbus::send_raw(const std::vector &payload) { + if (payload.empty()) { + return; + } + + if (this->flow_control_pin_ != nullptr) + this->flow_control_pin_->digital_write(true); + + auto crc = crc16(payload.data(), payload.size()); + this->write_array(payload); + this->write_byte(crc & 0xFF); + this->write_byte((crc >> 8) & 0xFF); + this->flush(); + if (this->flow_control_pin_ != nullptr) + this->flow_control_pin_->digital_write(false); + waiting_for_response = payload[0]; + last_send_ = millis(); } } // namespace modbus diff --git a/esphome/components/modbus/modbus.h b/esphome/components/modbus/modbus.h index 876c46b688..400e29e08b 100644 --- a/esphome/components/modbus/modbus.h +++ b/esphome/components/modbus/modbus.h @@ -22,17 +22,21 @@ class Modbus : public uart::UARTDevice, public Component { float get_setup_priority() const override; - void send(uint8_t address, uint8_t function, uint16_t start_address, uint16_t register_count); - + void send(uint8_t address, uint8_t function_code, uint16_t start_address, uint16_t number_of_entities, + uint8_t payload_len = 0, const uint8_t *payload = nullptr); + void send_raw(const std::vector &payload); void set_flow_control_pin(GPIOPin *flow_control_pin) { this->flow_control_pin_ = flow_control_pin; } + uint8_t waiting_for_response{0}; + void set_send_wait_time(uint16_t time_in_ms) { send_wait_time_ = time_in_ms; } protected: GPIOPin *flow_control_pin_{nullptr}; bool parse_modbus_byte_(uint8_t byte); - + uint16_t send_wait_time_{250}; std::vector rx_buffer_; uint32_t last_modbus_byte_{0}; + uint32_t last_send_{0}; std::vector devices_; }; @@ -43,10 +47,14 @@ class ModbusDevice { void set_parent(Modbus *parent) { parent_ = parent; } void set_address(uint8_t address) { address_ = address; } virtual void on_modbus_data(const std::vector &data) = 0; - - void send(uint8_t function, uint16_t start_address, uint16_t register_count) { - this->parent_->send(this->address_, function, start_address, register_count); + virtual void on_modbus_error(uint8_t function_code, uint8_t exception_code) {} + void send(uint8_t function, uint16_t start_address, uint16_t number_of_entities, uint8_t payload_len = 0, + const uint8_t *payload = nullptr) { + this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload); } + void send_raw(const std::vector &payload) { this->parent_->send_raw(payload); } + // If more than one device is connected block sending a new command before a response is received + bool waiting_for_response() { return parent_->waiting_for_response != 0; } protected: friend Modbus; diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py new file mode 100644 index 0000000000..7a69029dab --- /dev/null +++ b/esphome/components/modbus_controller/__init__.py @@ -0,0 +1,114 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import modbus +from esphome.const import CONF_ID, CONF_ADDRESS +from esphome.cpp_helpers import logging +from .const import ( + CONF_COMMAND_THROTTLE, +) + +CODEOWNERS = ["@martgras"] + +AUTO_LOAD = ["modbus"] + +MULTI_CONF = True + +# pylint: disable=invalid-name +modbus_controller_ns = cg.esphome_ns.namespace("modbus_controller") +ModbusController = modbus_controller_ns.class_( + "ModbusController", cg.PollingComponent, modbus.ModbusDevice +) + +SensorItem = modbus_controller_ns.struct("SensorItem") + +ModbusFunctionCode_ns = modbus_controller_ns.namespace("ModbusFunctionCode") +ModbusFunctionCode = ModbusFunctionCode_ns.enum("ModbusFunctionCode") +MODBUS_FUNCTION_CODE = { + "read_coils": ModbusFunctionCode.READ_COILS, + "read_discrete_inputs": ModbusFunctionCode.READ_DISCRETE_INPUTS, + "read_holding_registers": ModbusFunctionCode.READ_HOLDING_REGISTERS, + "read_input_registers": ModbusFunctionCode.READ_INPUT_REGISTERS, + "write_single_coil": ModbusFunctionCode.WRITE_SINGLE_COIL, + "write_single_register": ModbusFunctionCode.WRITE_SINGLE_REGISTER, + "write_multiple_coils": ModbusFunctionCode.WRITE_MULTIPLE_COILS, + "write_multiple_registers": ModbusFunctionCode.WRITE_MULTIPLE_REGISTERS, +} + +ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType") +ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType") +MODBUS_REGISTER_TYPE = { + "coil": ModbusRegisterType.COIL, + "discrete_input": ModbusRegisterType.DISCRETE, + "holding": ModbusRegisterType.HOLDING, + "read": ModbusRegisterType.READ, +} + +SensorValueType_ns = modbus_controller_ns.namespace("SensorValueType") +SensorValueType = SensorValueType_ns.enum("SensorValueType") +SENSOR_VALUE_TYPE = { + "RAW": SensorValueType.RAW, + "U_WORD": SensorValueType.U_WORD, + "S_WORD": SensorValueType.S_WORD, + "U_DWORD": SensorValueType.U_DWORD, + "U_DWORD_R": SensorValueType.U_DWORD_R, + "S_DWORD": SensorValueType.S_DWORD, + "S_DWORD_R": SensorValueType.S_DWORD_R, + "U_QWORD": SensorValueType.U_QWORD, + "U_QWORDU_R": SensorValueType.U_QWORD_R, + "S_QWORD": SensorValueType.S_QWORD, + "U_QWORD_R": SensorValueType.S_QWORD_R, + "FP32": SensorValueType.FP32, + "FP32_R": SensorValueType.FP32_R, +} + + +MULTI_CONF = True + +_LOGGER = logging.getLogger(__name__) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ModbusController), + cv.Optional( + CONF_COMMAND_THROTTLE, default="0ms" + ): cv.positive_time_period_milliseconds, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(modbus.modbus_device_schema(0x01)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID], config[CONF_COMMAND_THROTTLE]) + cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE])) + await register_modbus_device(var, config) + + +async def register_modbus_device(var, config): + cg.add(var.set_address(config[CONF_ADDRESS])) + await cg.register_component(var, config) + return await modbus.register_modbus_device(var, config) + + +def function_code_to_register(function_code): + FUNCTION_CODE_TYPE_MAP = { + "read_coils": ModbusRegisterType.COIL, + "read_discrete_inputs": ModbusRegisterType.DISCRETE, + "read_holding_registers": ModbusRegisterType.HOLDING, + "read_input_registers": ModbusRegisterType.READ, + "write_single_coil": ModbusRegisterType.COIL, + "write_single_register": ModbusRegisterType.HOLDING, + "write_multiple_coils": ModbusRegisterType.COIL, + "write_multiple_registers": ModbusRegisterType.HOLDING, + } + return FUNCTION_CODE_TYPE_MAP[function_code] + + +def find_by_value(dict, find_value): + for (key, value) in MODBUS_REGISTER_TYPE.items(): + print(find_value, value) + if find_value == value: + return key + return "not found" diff --git a/esphome/components/modbus_controller/binary_sensor/__init__.py b/esphome/components/modbus_controller/binary_sensor/__init__.py new file mode 100644 index 0000000000..d46ff71f2d --- /dev/null +++ b/esphome/components/modbus_controller/binary_sensor/__init__.py @@ -0,0 +1,81 @@ +from esphome.components import binary_sensor +import esphome.config_validation as cv +import esphome.codegen as cg + +from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OFFSET +from .. import ( + SensorItem, + modbus_controller_ns, + ModbusController, + MODBUS_REGISTER_TYPE, +) +from ..const import ( + CONF_BITMASK, + CONF_BYTE_OFFSET, + CONF_FORCE_NEW_RANGE, + CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_TYPE, + CONF_SKIP_UPDATES, +) + +DEPENDENCIES = ["modbus_controller"] +CODEOWNERS = ["@martgras"] + + +ModbusBinarySensor = modbus_controller_ns.class_( + "ModbusBinarySensor", cg.Component, binary_sensor.BinarySensor, SensorItem +) + +CONFIG_SCHEMA = cv.All( + binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ModbusBinarySensor), + cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), + cv.Required(CONF_ADDRESS): cv.positive_int, + cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), + cv.Optional(CONF_OFFSET, default=0): cv.positive_int, + cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, + cv.Optional(CONF_BITMASK, default=0x1): cv.hex_uint32_t, + cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, + cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + } + ).extend(cv.COMPONENT_SCHEMA), +) + + +async def to_code(config): + byte_offset = 0 + if CONF_OFFSET in config: + byte_offset = config[CONF_OFFSET] + # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET + if CONF_BYTE_OFFSET in config: + byte_offset = config[CONF_BYTE_OFFSET] + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_REGISTER_TYPE], + config[CONF_ADDRESS], + byte_offset, + config[CONF_BITMASK], + config[CONF_SKIP_UPDATES], + config[CONF_FORCE_NEW_RANGE], + ) + await cg.register_component(var, config) + await binary_sensor.register_binary_sensor(var, config) + + paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) + cg.add(paren.add_sensor_item(var)) + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], + [ + (ModbusBinarySensor.operator("ptr"), "item"), + (cg.float_, "x"), + ( + cg.std_vector.template(cg.uint8).operator("const").operator("ref"), + "data", + ), + ], + return_type=cg.optional.template(bool), + ) + cg.add(var.set_template(template_)) diff --git a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp new file mode 100644 index 0000000000..81066b3f5c --- /dev/null +++ b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp @@ -0,0 +1,40 @@ +#include "modbus_binarysensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace modbus_controller { + +static const char *const TAG = "modbus_controller.binary_sensor"; + +void ModbusBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Modbus Controller Binary Sensor", this); } + +void ModbusBinarySensor::parse_and_publish(const std::vector &data) { + bool value; + + switch (this->register_type) { + case ModbusRegisterType::DISCRETE_INPUT: + value = coil_from_vector(this->offset, data); + break; + case ModbusRegisterType::COIL: + // offset for coil is the actual number of the coil not the byte offset + value = coil_from_vector(this->offset, data); + break; + default: + value = get_data(data, this->offset) & this->bitmask; + break; + } + // Is there a lambda registered + // call it with the pre converted value and the raw data array + if (this->transform_func_.has_value()) { + // the lambda can parse the response itself + auto val = (*this->transform_func_)(this, value, data); + if (val.has_value()) { + ESP_LOGV(TAG, "Value overwritten by lambda"); + value = val.value(); + } + } + this->publish_state(value); +} + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h new file mode 100644 index 0000000000..c516d6b916 --- /dev/null +++ b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h @@ -0,0 +1,43 @@ +#pragma once + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +class ModbusBinarySensor : public Component, public binary_sensor::BinarySensor, public SensorItem { + public: + ModbusBinarySensor(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask, + uint8_t skip_updates, bool force_new_range) + : Component(), binary_sensor::BinarySensor() { + this->register_type = register_type; + this->start_address = start_address; + this->offset = offset; + this->bitmask = bitmask; + this->sensor_value_type = SensorValueType::BIT; + this->skip_updates = skip_updates; + this->force_new_range = force_new_range; + + if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) + this->register_count = offset + 1; + else + this->register_count = 1; + } + + void parse_and_publish(const std::vector &data) override; + void set_state(bool state) { this->state = state; } + + void dump_config() override; + + using transform_func_t = + optional(ModbusBinarySensor *, bool, const std::vector &)>>; + void set_template(transform_func_t &&f) { this->transform_func_ = f; } + + protected: + transform_func_t transform_func_{nullopt}; +}; + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/const.py b/esphome/components/modbus_controller/const.py new file mode 100644 index 0000000000..3cd114e673 --- /dev/null +++ b/esphome/components/modbus_controller/const.py @@ -0,0 +1,13 @@ +CONF_BITMASK = "bitmask" +CONF_BYTE_OFFSET = "byte_offset" +CONF_COMMAND_THROTTLE = "command_throttle" +CONF_FORCE_NEW_RANGE = "force_new_range" +CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id" +CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode" +CONF_RAW_ENCODE = "raw_encode" +CONF_REGISTER_COUNT = "register_count" +CONF_REGISTER_TYPE = "register_type" +CONF_RESPONSE_SIZE = "response_size" +CONF_SKIP_UPDATES = "skip_updates" +CONF_VALUE_TYPE = "value_type" +CONF_WRITE_LAMBDA = "write_lambda" diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp new file mode 100644 index 0000000000..70b5bf8eae --- /dev/null +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -0,0 +1,559 @@ +#include "modbus_controller.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace modbus_controller { + +static const char *const TAG = "modbus_controller"; + +void ModbusController::setup() { + // Modbus::setup(); + this->create_register_ranges_(); +} + +/* + To work with the existing modbus class and avoid polling for responses a command queue is used. + send_next_command will submit the command at the top of the queue and set the corresponding callback + to handle the response from the device. + Once the response has been processed it is removed from the queue and the next command is sent +*/ +bool ModbusController::send_next_command_() { + uint32_t last_send = millis() - this->last_command_timestamp_; + + if ((last_send > this->command_throttle_) && !waiting_for_response() && !command_queue_.empty()) { + auto &command = command_queue_.front(); + + ESP_LOGV(TAG, "Sending next modbus command to device %d register 0x%02X count %d", this->address_, + command->register_address, command->register_count); + command->send(); + this->last_command_timestamp_ = millis(); + if (!command->on_data_func) { // No handler remove from queue directly after sending + command_queue_.pop_front(); + } + } + return (!command_queue_.empty()); +} + +// Queue incoming response +void ModbusController::on_modbus_data(const std::vector &data) { + auto ¤t_command = this->command_queue_.front(); + if (current_command != nullptr) { + // Move the commandItem to the response queue + current_command->payload = data; + this->incoming_queue_.push(std::move(current_command)); + ESP_LOGV(TAG, "Modbus response queued"); + command_queue_.pop_front(); + } +} + +// Dispatch the response to the registered handler +void ModbusController::process_modbus_data_(const ModbusCommandItem *response) { + ESP_LOGV(TAG, "Process modbus response for address 0x%X size: %zu", response->register_address, + response->payload.size()); + response->on_data_func(response->register_type, response->register_address, response->payload); +} + +void ModbusController::on_modbus_error(uint8_t function_code, uint8_t exception_code) { + ESP_LOGE(TAG, "Modbus error function code: 0x%X exception: %d ", function_code, exception_code); + // Remove pending command waiting for a response + auto ¤t_command = this->command_queue_.front(); + if (current_command != nullptr) { + ESP_LOGE(TAG, + "Modbus error - last command: function code=0x%X register adddress = 0x%X " + "registers count=%d " + "payload size=%zu", + function_code, current_command->register_address, current_command->register_count, + current_command->payload.size()); + command_queue_.pop_front(); + } +} + +void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data) { + ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address); + + auto vec_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) { + return (r.start_address == start_address && r.register_type == register_type); + }); + + if (vec_it == register_ranges_.end()) { + ESP_LOGE(TAG, "Handle incoming data : No matching range for sensor found - start_address : 0x%X", start_address); + return; + } + auto map_it = sensormap_.find(vec_it->first_sensorkey); + if (map_it == sensormap_.end()) { + ESP_LOGE(TAG, "Handle incoming data : No sensor found in at start_address : 0x%X (0x%llX)", start_address, + vec_it->first_sensorkey); + return; + } + // loop through all sensors with the same start address + while (map_it != sensormap_.end() && map_it->second->start_address == start_address) { + if (map_it->second->register_type == register_type) { + map_it->second->parse_and_publish(data); + } + map_it++; + } +} + +void ModbusController::queue_command(const ModbusCommandItem &command) { + // check if this commmand is already qeued. + // not very effective but the queue is never really large + for (auto &item : command_queue_) { + if (item->register_address == command.register_address && item->register_count == command.register_count && + item->register_type == command.register_type) { + ESP_LOGW(TAG, "Duplicate modbus command found"); + // update the payload of the queued command + // replaces a previous command + item->payload = command.payload; + return; + } + } + command_queue_.push_back(make_unique(command)); +} + +void ModbusController::update_range_(RegisterRange &r) { + ESP_LOGV(TAG, "Range : %X Size: %x (%d) skip: %d", r.start_address, r.register_count, (int) r.register_type, + r.skip_updates_counter); + if (r.skip_updates_counter == 0) { + ModbusCommandItem command_item = + ModbusCommandItem::create_read_command(this, r.register_type, r.start_address, r.register_count); + queue_command(command_item); + r.skip_updates_counter = r.skip_updates; // reset counter to config value + } else { + r.skip_updates_counter--; + } +} +// +// Queue the modbus requests to be send. +// Once we get a response to the command it is removed from the queue and the next command is send +// +void ModbusController::update() { + if (!command_queue_.empty()) { + ESP_LOGV(TAG, "%zu modbus commands already in queue", command_queue_.size()); + } else { + ESP_LOGV(TAG, "Updating modbus component"); + } + + for (auto &r : this->register_ranges_) { + ESP_LOGVV(TAG, "Updating range 0x%X", r.start_address); + update_range_(r); + } +} + +// walk through the sensors and determine the registerranges to read +size_t ModbusController::create_register_ranges_() { + register_ranges_.clear(); + uint8_t n = 0; + if (sensormap_.empty()) { + return 0; + } + + auto ix = sensormap_.begin(); + auto prev = ix; + int total_register_count = 0; + uint16_t current_start_address = ix->second->start_address; + uint8_t buffer_offset = ix->second->offset; + uint8_t skip_updates = ix->second->skip_updates; + auto first_sensorkey = ix->second->getkey(); + total_register_count = 0; + while (ix != sensormap_.end()) { + ESP_LOGV(TAG, "Register: 0x%X %d %d 0x%llx (%d) buffer_offset = %d (0x%X) skip=%u", ix->second->start_address, + ix->second->register_count, ix->second->offset, ix->second->getkey(), total_register_count, buffer_offset, + buffer_offset, ix->second->skip_updates); + // if this is a sequential address based on number of registers and address of previous sensor + // convert to an offset to the previous sensor (address 0x101 becomes address 0x100 offset 2 bytes) + if (!ix->second->force_new_range && total_register_count >= 0 && + prev->second->register_type == ix->second->register_type && + prev->second->start_address + total_register_count == ix->second->start_address && + prev->second->start_address < ix->second->start_address) { + ix->second->start_address = prev->second->start_address; + ix->second->offset += prev->second->offset + prev->second->get_register_size(); + + // replace entry in sensormap_ + auto const value = ix->second; + sensormap_.erase(ix); + sensormap_.insert({value->getkey(), value}); + // move iterator back to new element + ix = sensormap_.find(value->getkey()); // next(prev, 1); + } + if (current_start_address != ix->second->start_address || + // ( prev->second->start_address + prev->second->offset != ix->second->start_address) || + ix->second->register_type != prev->second->register_type) { + // Difference doesn't match so we have a gap + if (n > 0) { + RegisterRange r; + r.start_address = current_start_address; + r.register_count = total_register_count; + if (prev->second->register_type == ModbusRegisterType::COIL || + prev->second->register_type == ModbusRegisterType::DISCRETE_INPUT) { + r.register_count = prev->second->offset + 1; + } + r.register_type = prev->second->register_type; + r.first_sensorkey = first_sensorkey; + r.skip_updates = skip_updates; + r.skip_updates_counter = 0; + ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates); + register_ranges_.push_back(r); + } + skip_updates = ix->second->skip_updates; + current_start_address = ix->second->start_address; + first_sensorkey = ix->second->getkey(); + total_register_count = ix->second->register_count; + buffer_offset = ix->second->offset; + n = 1; + } else { + n++; + if (ix->second->offset != prev->second->offset || n == 1) { + total_register_count += ix->second->register_count; + buffer_offset += ix->second->get_register_size(); + } + // use the lowest non zero value for the whole range + // Because zero is the default value for skip_updates it is excluded from getting the min value. + if (ix->second->skip_updates != 0) { + if (skip_updates != 0) { + skip_updates = std::min(skip_updates, ix->second->skip_updates); + } else { + skip_updates = ix->second->skip_updates; + } + } + } + prev = ix++; + } + // Add the last range + if (n > 0) { + RegisterRange r; + r.start_address = current_start_address; + // r.register_count = prev->second->offset>>1 + prev->second->get_register_size(); + r.register_count = total_register_count; + if (prev->second->register_type == ModbusRegisterType::COIL || + prev->second->register_type == ModbusRegisterType::DISCRETE_INPUT) { + r.register_count = prev->second->offset + 1; + } + r.register_type = prev->second->register_type; + r.first_sensorkey = first_sensorkey; + r.skip_updates = skip_updates; + r.skip_updates_counter = 0; + ESP_LOGV(TAG, "Add last range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates); + register_ranges_.push_back(r); + } + return register_ranges_.size(); +} + +void ModbusController::dump_config() { + ESP_LOGCONFIG(TAG, "ModbusController:"); + ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + ESP_LOGCONFIG(TAG, "sensormap"); + for (auto &it : sensormap_) { + ESP_LOGCONFIG("TAG", " Sensor 0x%llX start=0x%X count=%d size=%d", it.second->getkey(), it.second->start_address, + it.second->register_count, it.second->get_register_size()); + } +#endif +} + +void ModbusController::loop() { + // Incoming data to process? + if (!incoming_queue_.empty()) { + auto &message = incoming_queue_.front(); + if (message != nullptr) + process_modbus_data_(message.get()); + incoming_queue_.pop(); + + } else { + // all messages processed send pending commmands + send_next_command_(); + } +} + +void ModbusController::on_write_register_response(ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data) { + ESP_LOGV(TAG, "Command ACK 0x%X %d ", get_data(data, 0), get_data(data, 1)); +} + +void ModbusController::dump_sensormap_() { + ESP_LOGV("modbuscontroller.h", "sensormap"); + for (auto &it : sensormap_) { + ESP_LOGV("modbuscontroller.h", " Sensor 0x%llX start=0x%X count=%d size=%d", it.second->getkey(), + it.second->start_address, it.second->register_count, it.second->get_register_size()); + } +} + +ModbusCommandItem ModbusCommandItem::create_read_command( + ModbusController *modbusdevice, ModbusRegisterType register_type, uint16_t start_address, uint16_t register_count, + std::function &data)> + &&handler) { + ModbusCommandItem cmd; + cmd.modbusdevice = modbusdevice; + cmd.register_type = register_type; + cmd.function_code = modbus_register_read_function(register_type); + cmd.register_address = start_address; + cmd.register_count = register_count; + cmd.on_data_func = std::move(handler); + return cmd; +} + +ModbusCommandItem ModbusCommandItem::create_read_command(ModbusController *modbusdevice, + ModbusRegisterType register_type, uint16_t start_address, + uint16_t register_count) { + ModbusCommandItem cmd; + cmd.modbusdevice = modbusdevice; + cmd.register_type = register_type; + cmd.function_code = modbus_register_read_function(register_type); + cmd.register_address = start_address; + cmd.register_count = register_count; + cmd.on_data_func = [modbusdevice](ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data) { + modbusdevice->on_register_data(register_type, start_address, data); + }; + return cmd; +} + +ModbusCommandItem ModbusCommandItem::create_write_multiple_command(ModbusController *modbusdevice, + uint16_t start_address, uint16_t register_count, + const std::vector &values) { + ModbusCommandItem cmd; + cmd.modbusdevice = modbusdevice; + cmd.register_type = ModbusRegisterType::HOLDING; + cmd.function_code = ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS; + cmd.register_address = start_address; + cmd.register_count = register_count; + cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data) { + modbusdevice->on_write_register_response(cmd.register_type, start_address, data); + }; + for (auto v : values) { + cmd.payload.push_back((v / 256) & 0xFF); + cmd.payload.push_back(v & 0xFF); + } + return cmd; +} + +ModbusCommandItem ModbusCommandItem::create_write_single_coil(ModbusController *modbusdevice, uint16_t address, + bool value) { + ModbusCommandItem cmd; + cmd.modbusdevice = modbusdevice; + cmd.register_type = ModbusRegisterType::COIL; + cmd.function_code = ModbusFunctionCode::WRITE_SINGLE_COIL; + cmd.register_address = address; + cmd.register_count = 1; + cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data) { + modbusdevice->on_write_register_response(cmd.register_type, start_address, data); + }; + cmd.payload.push_back(value ? 0xFF : 0); + cmd.payload.push_back(0); + return cmd; +} + +ModbusCommandItem ModbusCommandItem::create_write_multiple_coils(ModbusController *modbusdevice, uint16_t start_address, + const std::vector &values) { + ModbusCommandItem cmd; + cmd.modbusdevice = modbusdevice; + cmd.register_type = ModbusRegisterType::COIL; + cmd.function_code = ModbusFunctionCode::WRITE_MULTIPLE_COILS; + cmd.register_address = start_address; + cmd.register_count = values.size(); + cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data) { + modbusdevice->on_write_register_response(cmd.register_type, start_address, data); + }; + + uint8_t bitmask = 0; + int bitcounter = 0; + for (auto coil : values) { + if (coil) { + bitmask |= (1 << bitcounter); + } + bitcounter++; + if (bitcounter % 8 == 0) { + cmd.payload.push_back(bitmask); + bitmask = 0; + } + } + // add remaining bits + if (bitcounter % 8) { + cmd.payload.push_back(bitmask); + } + return cmd; +} + +ModbusCommandItem ModbusCommandItem::create_write_single_command(ModbusController *modbusdevice, uint16_t start_address, + int16_t value) { + ModbusCommandItem cmd; + cmd.modbusdevice = modbusdevice; + cmd.register_type = ModbusRegisterType::HOLDING; + cmd.function_code = ModbusFunctionCode::WRITE_SINGLE_REGISTER; + cmd.register_address = start_address; + cmd.register_count = 1; // not used here anyways + cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data) { + modbusdevice->on_write_register_response(cmd.register_type, start_address, data); + }; + cmd.payload.push_back((value / 256) & 0xFF); + cmd.payload.push_back((value % 256) & 0xFF); + return cmd; +} + +ModbusCommandItem ModbusCommandItem::create_custom_command( + ModbusController *modbusdevice, const std::vector &values, + std::function &data)> + &&handler) { + ModbusCommandItem cmd; + cmd.modbusdevice = modbusdevice; + cmd.function_code = ModbusFunctionCode::CUSTOM; + if (handler == nullptr) { + cmd.on_data_func = [](ModbusRegisterType, uint16_t, const std::vector &data) { + ESP_LOGI(TAG, "Custom Command sent"); + }; + } else { + cmd.on_data_func = handler; + } + cmd.payload = values; + + return cmd; +} + +bool ModbusCommandItem::send() { + if (this->function_code != ModbusFunctionCode::CUSTOM) { + modbusdevice->send(uint8_t(this->function_code), this->register_address, this->register_count, this->payload.size(), + this->payload.empty() ? nullptr : &this->payload[0]); + } else { + modbusdevice->send_raw(this->payload); + } + ESP_LOGV(TAG, "Command sent %d 0x%X %d", uint8_t(this->function_code), this->register_address, this->register_count); + return true; +} + +std::vector float_to_payload(float value, SensorValueType value_type) { + union { + float float_value; + uint32_t raw; + } raw_to_float; + + std::vector data; + int32_t val; + + switch (value_type) { + case SensorValueType::U_WORD: + case SensorValueType::S_WORD: + // cast truncates the float do some rounding here + data.push_back(lroundf(value) & 0xFFFF); + break; + case SensorValueType::U_DWORD: + case SensorValueType::S_DWORD: + val = lroundf(value); + data.push_back((val & 0xFFFF0000) >> 16); + data.push_back(val & 0xFFFF); + break; + case SensorValueType::U_DWORD_R: + case SensorValueType::S_DWORD_R: + val = lroundf(value); + data.push_back(val & 0xFFFF); + data.push_back((val & 0xFFFF0000) >> 16); + break; + case SensorValueType::FP32: + raw_to_float.float_value = value; + data.push_back((raw_to_float.raw & 0xFFFF0000) >> 16); + data.push_back(raw_to_float.raw & 0xFFFF); + break; + case SensorValueType::FP32_R: + raw_to_float.float_value = value; + data.push_back(raw_to_float.raw & 0xFFFF); + data.push_back((raw_to_float.raw & 0xFFFF0000) >> 16); + break; + default: + ESP_LOGE(TAG, "Invalid data type for modbus float to payload conversation"); + break; + } + return data; +} + +float payload_to_float(const std::vector &data, SensorValueType sensor_value_type, uint8_t offset, + uint32_t bitmask) { + union { + float float_value; + uint32_t raw; + } raw_to_float; + + int64_t value = 0; // int64_t because it can hold signed and unsigned 32 bits + float result = NAN; + + switch (sensor_value_type) { + case SensorValueType::U_WORD: + value = mask_and_shift_by_rightbit(get_data(data, offset), bitmask); // default is 0xFFFF ; + result = static_cast(value); + break; + case SensorValueType::U_DWORD: + value = get_data(data, offset); + value = mask_and_shift_by_rightbit((uint32_t) value, bitmask); + result = static_cast(value); + break; + case SensorValueType::U_DWORD_R: + value = get_data(data, offset); + value = static_cast(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16; + value = mask_and_shift_by_rightbit((uint32_t) value, bitmask); + result = static_cast(value); + break; + case SensorValueType::S_WORD: + value = mask_and_shift_by_rightbit(get_data(data, offset), + bitmask); // default is 0xFFFF ; + result = static_cast(value); + break; + case SensorValueType::S_DWORD: + value = mask_and_shift_by_rightbit(get_data(data, offset), bitmask); + result = static_cast(value); + break; + case SensorValueType::S_DWORD_R: { + value = get_data(data, offset); + // Currently the high word is at the low position + // the sign bit is therefore at low before the switch + uint32_t sign_bit = (value & 0x8000) << 16; + value = mask_and_shift_by_rightbit( + static_cast(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask); + result = static_cast(value); + } break; + case SensorValueType::U_QWORD: + // Ignore bitmask for U_QWORD + value = get_data(data, offset); + result = static_cast(value); + break; + + case SensorValueType::S_QWORD: + // Ignore bitmask for S_QWORD + value = get_data(data, offset); + result = static_cast(value); + break; + case SensorValueType::U_QWORD_R: + // Ignore bitmask for U_QWORD + value = get_data(data, offset); + value = static_cast(value & 0xFFFF) << 48 | (value & 0xFFFF000000000000) >> 48 | + static_cast(value & 0xFFFF0000) << 32 | (value & 0x0000FFFF00000000) >> 32 | + static_cast(value & 0xFFFF00000000) << 16 | (value & 0x00000000FFFF0000) >> 16; + result = static_cast(value); + break; + + case SensorValueType::S_QWORD_R: + // Ignore bitmask for S_QWORD + value = get_data(data, offset); + result = static_cast(value); + break; + case SensorValueType::FP32: + raw_to_float.raw = get_data(data, offset); + ESP_LOGD(TAG, "FP32 = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value); + result = raw_to_float.float_value; + break; + case SensorValueType::FP32_R: { + auto tmp = get_data(data, offset); + raw_to_float.raw = static_cast(tmp & 0xFFFF) << 16 | (tmp & 0xFFFF0000) >> 16; + ESP_LOGD(TAG, "FP32_R = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value); + result = raw_to_float.float_value; + } break; + default: + break; + } + return result; +} + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h new file mode 100644 index 0000000000..4b5f4337db --- /dev/null +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -0,0 +1,454 @@ +#pragma once + +#include "esphome/core/component.h" + +#include "esphome/core/automation.h" +#include "esphome/components/modbus/modbus.h" + +#include +#include +#include +#include + +namespace esphome { +namespace modbus_controller { + +class ModbusController; + +enum class ModbusFunctionCode { + CUSTOM = 0x00, + READ_COILS = 0x01, + READ_DISCRETE_INPUTS = 0x02, + READ_HOLDING_REGISTERS = 0x03, + READ_INPUT_REGISTERS = 0x04, + WRITE_SINGLE_COIL = 0x05, + WRITE_SINGLE_REGISTER = 0x06, + READ_EXCEPTION_STATUS = 0x07, // not implemented + DIAGNOSTICS = 0x08, // not implemented + GET_COMM_EVENT_COUNTER = 0x0B, // not implemented + GET_COMM_EVENT_LOG = 0x0C, // not implemented + WRITE_MULTIPLE_COILS = 0x0F, + WRITE_MULTIPLE_REGISTERS = 0x10, + REPORT_SERVER_ID = 0x11, // not implemented + READ_FILE_RECORD = 0x14, // not implemented + WRITE_FILE_RECORD = 0x15, // not implemented + MASK_WRITE_REGISTER = 0x16, // not implemented + READ_WRITE_MULTIPLE_REGISTERS = 0x17, // not implemented + READ_FIFO_QUEUE = 0x18, // not implemented +}; + +enum class ModbusRegisterType : int { + CUSTOM = 0x0, + COIL = 0x01, + DISCRETE_INPUT = 0x02, + HOLDING = 0x03, + READ = 0x04, +}; + +enum class SensorValueType : uint8_t { + RAW = 0x00, // variable length + U_WORD = 0x1, // 1 Register unsigned + U_DWORD = 0x2, // 2 Registers unsigned + S_WORD = 0x3, // 1 Register signed + S_DWORD = 0x4, // 2 Registers signed + BIT = 0x5, + U_DWORD_R = 0x6, // 2 Registers unsigned + S_DWORD_R = 0x7, // 2 Registers unsigned + U_QWORD = 0x8, + S_QWORD = 0x9, + U_QWORD_R = 0xA, + S_QWORD_R = 0xB, + FP32 = 0xC, + FP32_R = 0xD +}; + +struct RegisterRange { + uint16_t start_address; + ModbusRegisterType register_type; + uint8_t register_count; + uint8_t skip_updates; // the config value + uint64_t first_sensorkey; + uint8_t skip_updates_counter; // the running value +} __attribute__((packed)); + +inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) { + switch (reg_type) { + case ModbusRegisterType::COIL: + return ModbusFunctionCode::READ_COILS; + break; + case ModbusRegisterType::DISCRETE_INPUT: + return ModbusFunctionCode::READ_DISCRETE_INPUTS; + break; + case ModbusRegisterType::HOLDING: + return ModbusFunctionCode::READ_HOLDING_REGISTERS; + break; + case ModbusRegisterType::READ: + return ModbusFunctionCode::READ_INPUT_REGISTERS; + break; + default: + return ModbusFunctionCode::CUSTOM; + break; + } +} +inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_type) { + switch (reg_type) { + case ModbusRegisterType::COIL: + return ModbusFunctionCode::WRITE_SINGLE_COIL; + break; + case ModbusRegisterType::DISCRETE_INPUT: + return ModbusFunctionCode::CUSTOM; + break; + case ModbusRegisterType::HOLDING: + return ModbusFunctionCode::READ_WRITE_MULTIPLE_REGISTERS; + break; + case ModbusRegisterType::READ: + return ModbusFunctionCode::CUSTOM; + break; + default: + return ModbusFunctionCode::CUSTOM; + break; + } +} + +/** All sensors are stored in a map + * to enable binary sensors for values encoded as bits in the same register the key of each sensor + * the key is a 64 bit integer that combines the register properties + * sensormap_ is sorted by this key. The key ensures the correct order when creating consequtive ranges + * Format: function_code (8 bit) | start address (16 bit)| offset (8bit)| bitmask (32 bit) + */ +inline uint64_t calc_key(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset = 0, + uint32_t bitmask = 0) { + return uint64_t((uint16_t(register_type) << 24) + (uint32_t(start_address) << 8) + (offset & 0xFF)) << 32 | bitmask; +} +inline uint16_t register_from_key(uint64_t key) { return (key >> 40) & 0xFFFF; } + +inline uint8_t c_to_hex(char c) { return (c >= 'A') ? (c >= 'a') ? (c - 'a' + 10) : (c - 'A' + 10) : (c - '0'); } + +/** Get a byte from a hex string + * hex_byte_from_str("1122",1) returns uint_8 value 0x22 == 34 + * hex_byte_from_str("1122",0) returns 0x11 + * @param value string containing hex encoding + * @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in + * the hex string is byte_pos * 2 + * @return byte value + */ +inline uint8_t byte_from_hex_str(const std::string &value, uint8_t pos) { + if (value.length() < pos * 2 + 1) + return 0; + return (c_to_hex(value[pos * 2]) << 4) | c_to_hex(value[pos * 2 + 1]); +} + +/** Get a word from a hex string + * @param value string containing hex encoding + * @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in + * the hex string is byte_pos * 2 + * @return word value + */ +inline uint16_t word_from_hex_str(const std::string &value, uint8_t pos) { + return byte_from_hex_str(value, pos) << 8 | byte_from_hex_str(value, pos + 1); +} + +/** Get a dword from a hex string + * @param value string containing hex encoding + * @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in + * the hex string is byte_pos * 2 + * @return dword value + */ +inline uint32_t dword_from_hex_str(const std::string &value, uint8_t pos) { + return word_from_hex_str(value, pos) << 16 | word_from_hex_str(value, pos + 2); +} + +/** Get a qword from a hex string + * @param value string containing hex encoding + * @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in + * the hex string is byte_pos * 2 + * @return qword value + */ +inline uint64_t qword_from_hex_str(const std::string &value, uint8_t pos) { + return static_cast(dword_from_hex_str(value, pos)) << 32 | dword_from_hex_str(value, pos + 4); +} + +// Extract data from modbus response buffer +/** Extract data from modbus response buffer + * @param T one of supported integer data types int_8,int_16,int_32,int_64 + * @param data modbus response buffer (uint8_t) + * @param buffer_offset offset in bytes. + * @return value of type T extracted from buffer + */ +template T get_data(const std::vector &data, size_t buffer_offset) { + if (sizeof(T) == sizeof(uint8_t)) { + return T(data[buffer_offset]); + } + if (sizeof(T) == sizeof(uint16_t)) { + return T((uint16_t(data[buffer_offset + 0]) << 8) | (uint16_t(data[buffer_offset + 1]) << 0)); + } + + if (sizeof(T) == sizeof(uint32_t)) { + return get_data(data, buffer_offset) << 16 | get_data(data, (buffer_offset + 2)); + } + + if (sizeof(T) == sizeof(uint64_t)) { + return static_cast(get_data(data, buffer_offset)) << 32 | + (static_cast(get_data(data, buffer_offset + 4))); + } +} + +/** Extract coil data from modbus response buffer + * Responses for coil are packed into bytes . + * coil 3 is bit 3 of the first response byte + * coil 9 is bit 2 of the second response byte + * @param coil number of the cil + * @param data modbus response buffer (uint8_t) + * @return content of coil register + */ +inline bool coil_from_vector(int coil, const std::vector &data) { + auto data_byte = coil / 8; + return (data[data_byte] & (1 << (coil % 8))) > 0; +} + +/** Extract bits from value and shift right according to the bitmask + * if the bitmask is 0x00F0 we want the values frrom bit 5 - 8. + * the result is then shifted right by the postion if the first right set bit in the mask + * Usefull for modbus data where more than one value is packed in a 16 bit register + * Example: on Epever the "Length of night" register 0x9065 encodes values of the whole night length of time as + * D15 - D8 = hour, D7 - D0 = minute + * To get the hours use mask 0xFF00 and 0x00FF for the minute + * @param data an integral value between 16 aand 32 bits, + * @param bitmask the bitmask to apply + */ +template N mask_and_shift_by_rightbit(N data, uint32_t mask) { + auto result = (mask & data); + if (result == 0) { + return result; + } + for (int pos = 0; pos < sizeof(N) << 3; pos++) { + if ((mask & (1 << pos)) != 0) + return result >> pos; + } + return 0; +} + +/** convert float value to vector suitable for sending + * @param value float value to cconvert + * @param value_type defines if 16/32 or FP32 is used + * @return vector containing the modbus register words in correct order + */ +std::vector float_to_payload(float value, SensorValueType value_type); + +/** convert vector response payload to float + * @param value float value to cconvert + * @param sensor_value_type defines if 16/32/64 bits or FP32 is used + * @param offset offset to the data in data + * @param bitmask bitmask used for masking and shifting + * @return float version of the input + */ +float payload_to_float(const std::vector &data, SensorValueType sensor_value_type, uint8_t offset, + uint32_t bitmask); + +class ModbusController; + +struct SensorItem { + ModbusRegisterType register_type; + SensorValueType sensor_value_type; + uint16_t start_address; + uint32_t bitmask; + uint8_t offset; + uint8_t register_count; + uint8_t skip_updates; + bool force_new_range{false}; + + virtual void parse_and_publish(const std::vector &data) = 0; + + uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); } + + size_t virtual get_register_size() const { + size_t size = 0; + switch (sensor_value_type) { + case SensorValueType::BIT: + size = 1; + break; + case SensorValueType::U_WORD: + case SensorValueType::S_WORD: + size = 2; + break; + case SensorValueType::U_DWORD: + case SensorValueType::S_DWORD: + case SensorValueType::U_DWORD_R: + case SensorValueType::S_DWORD_R: + case SensorValueType::FP32: + case SensorValueType::FP32_R: + size = 4; + break; + case SensorValueType::U_QWORD: + case SensorValueType::U_QWORD_R: + case SensorValueType::S_QWORD: + case SensorValueType::S_QWORD_R: + size = 8; + break; + case SensorValueType::RAW: + size = this->register_count * 2; + } + return size; + } +}; + +struct ModbusCommandItem { + static const size_t MAX_PAYLOAD_BYTES = 240; + ModbusController *modbusdevice; + uint16_t register_address; + uint16_t register_count; + ModbusFunctionCode function_code; + ModbusRegisterType register_type; + std::function &data)> + on_data_func; + std::vector payload = {}; + bool send(); + + /// factory methods + /** Create modbus read command + * Function code 02-04 + * @param modbusdevice pointer to the device to execute the command + * @param function_code modbus function code for the read command + * @param start_address modbus address of the first register to read + * @param register_count number of registers to read + * @param handler function called when the response is received + * @return ModbusCommandItem with the prepared command + */ + static ModbusCommandItem create_read_command( + ModbusController *modbusdevice, ModbusRegisterType register_type, uint16_t start_address, uint16_t register_count, + std::function &data)> + &&handler); + /** Create modbus read command + * Function code 02-04 + * @param modbusdevice pointer to the device to execute the command + * @param function_code modbus function code for the read command + * @param start_address modbus address of the first register to read + * @param register_count number of registers to read + * @return ModbusCommandItem with the prepared command + */ + static ModbusCommandItem create_read_command(ModbusController *modbusdevice, ModbusRegisterType register_type, + uint16_t start_address, uint16_t register_count); + /** Create modbus read command + * Function code 02-04 + * @param modbusdevice pointer to the device to execute the command + * @param function_code modbus function code for the read command + * @param start_address modbus address of the first register to read + * @param register_count number of registers to read + * @param handler function called when the response is received + * @return ModbusCommandItem with the prepared command + */ + static ModbusCommandItem create_write_multiple_command(ModbusController *modbusdevice, uint16_t start_address, + uint16_t register_count, const std::vector &values); + /** Create modbus write multiple registers command + * Function 16 (10hex) Write Multiple Registers + * @param modbusdevice pointer to the device to execute the command + * @param start_address modbus address of the first register to read + * @param register_count number of registers to read + * @param values uint16_t array to be written to the registers + * @return ModbusCommandItem with the prepared command + */ + static ModbusCommandItem create_write_single_command(ModbusController *modbusdevice, uint16_t start_address, + int16_t value); + /** Create modbus write single registers command + * Function 05 (05hex) Write Single Coil + * @param modbusdevice pointer to the device to execute the command + * @param start_address modbus address of the first register to read + * @param value uint16_t data to be written to the registers + * @return ModbusCommandItem with the prepared command + */ + static ModbusCommandItem create_write_single_coil(ModbusController *modbusdevice, uint16_t address, bool value); + + /** Create modbus write multiple registers command + * Function 15 (0Fhex) Write Multiple Coils + * @param modbusdevice pointer to the device to execute the command + * @param start_address modbus address of the first register to read + * @param value bool vector of values to be written to the registers + * @return ModbusCommandItem with the prepared command + */ + static ModbusCommandItem create_write_multiple_coils(ModbusController *modbusdevice, uint16_t start_address, + const std::vector &values); + /** Create custom modbus command + * @param modbusdevice pointer to the device to execute the command + * @param values byte vector of data to be sent to the device. The compplete payload must be provided with the + * exception of the crc codess + * @param handler function called when the response is received. Default is just logging a response + * @return ModbusCommandItem with the prepared command + */ + static ModbusCommandItem create_custom_command( + ModbusController *modbusdevice, const std::vector &values, + std::function &data)> + &&handler = nullptr); +}; + +/** Modbus controller class. + * Each instance handles the modbus commuinication for all sensors with the same modbus address + * + * all sensor items (sensors, switches, binarysensor ...) are parsed in modbus address ranges. + * when esphome calls ModbusController::Update the commands for each range are created and sent + * Responses for the commands are dispatched to the modbus sensor items. + */ + +class ModbusController : public PollingComponent, public modbus::ModbusDevice { + public: + ModbusController(uint16_t throttle = 0) : modbus::ModbusDevice(), command_throttle_(throttle){}; + void dump_config() override; + void loop() override; + void setup() override; + void update() override; + + /// queues a modbus command in the send queue + void queue_command(const ModbusCommandItem &command); + /// Registers a sensor with the controller. Called by esphomes code generator + void add_sensor_item(SensorItem *item) { sensormap_[item->getkey()] = item; } + /// called when a modbus response was prased without errors + void on_modbus_data(const std::vector &data) override; + /// called when a modbus error response was received + void on_modbus_error(uint8_t function_code, uint8_t exception_code) override; + /// default delegate called by process_modbus_data when a response has retrieved from the incoming queue + void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector &data); + /// default delegate called by process_modbus_data when a response for a write response has retrieved from the + /// incoming queue + void on_write_register_response(ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data); + /// called by esphome generated code to set the command_throttle period + void set_command_throttle(uint16_t command_throttle) { this->command_throttle_ = command_throttle; } + + protected: + /// parse sensormap_ and create range of sequential addresses + size_t create_register_ranges_(); + /// submit the read command for the address range to the send queue + void update_range_(RegisterRange &r); + /// parse incoming modbus data + void process_modbus_data_(const ModbusCommandItem *response); + /// send the next modbus command from the send queue + bool send_next_command_(); + /// get the number of queued modbus commands (should be mostly empty) + size_t get_command_queue_length_() { return command_queue_.size(); } + /// dump the parsed sensormap for diagnostics + void dump_sensormap_(); + /// Collection of all sensors for this component + /// see calc_key how the key is contructed + std::map sensormap_; + /// Continous range of modbus registers + std::vector register_ranges_; + /// Hold the pending requests to be sent + std::list> command_queue_; + /// modbus response data waiting to get processed + std::queue> incoming_queue_; + /// when was the last send operation + uint32_t last_command_timestamp_; + /// min time in ms between sending modbus commands + uint16_t command_throttle_; +}; + +/** convert vector response payload to float + * @param value float value to cconvert + * @param item SensorItem object + * @return float version of the input + */ +inline float payload_to_float(const std::vector &data, const SensorItem &item) { + return payload_to_float(data, item.sensor_value_type, item.offset, item.bitmask); +} + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py new file mode 100644 index 0000000000..c7919bb972 --- /dev/null +++ b/esphome/components/modbus_controller/number/__init__.py @@ -0,0 +1,157 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import number +from esphome.const import ( + CONF_ADDRESS, + CONF_ID, + CONF_LAMBDA, + CONF_MAX_VALUE, + CONF_MIN_VALUE, + CONF_MULTIPLY, + CONF_OFFSET, + CONF_STEP, +) + +from .. import ( + modbus_controller_ns, + ModbusController, + SENSOR_VALUE_TYPE, + SensorItem, +) + + +from ..const import ( + CONF_BITMASK, + CONF_BYTE_OFFSET, + CONF_FORCE_NEW_RANGE, + CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_COUNT, + CONF_SKIP_UPDATES, + CONF_VALUE_TYPE, + CONF_WRITE_LAMBDA, +) + +DEPENDENCIES = ["modbus_controller"] +CODEOWNERS = ["@martgras"] + + +ModbusNumber = modbus_controller_ns.class_( + "ModbusNumber", cg.Component, number.Number, SensorItem +) + +TYPE_REGISTER_MAP = { + "RAW": 1, + "U_WORD": 1, + "S_WORD": 1, + "U_DWORD": 2, + "U_DWORD_R": 2, + "S_DWORD": 2, + "S_DWORD_R": 2, + "U_QWORD": 4, + "U_QWORDU_R": 4, + "S_QWORD": 4, + "U_QWORD_R": 4, + "FP32": 2, + "FP32_R": 2, +} + + +def validate_min_max(config): + if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]: + raise cv.Invalid("max_value must be greater than min_value") + if config[CONF_MIN_VALUE] < -16777215: + raise cv.Invalid("max_value must be greater than -16777215") + if config[CONF_MAX_VALUE] > 16777215: + raise cv.Invalid("max_value must not be greater than 16777215") + return config + + +CONFIG_SCHEMA = cv.All( + number.NUMBER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ModbusNumber), + cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), + cv.Required(CONF_ADDRESS): cv.positive_int, + cv.Optional(CONF_OFFSET, default=0): cv.positive_int, + cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, + cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t, + cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), + cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, + cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, + cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, + cv.GenerateID(): cv.declare_id(ModbusNumber), + # 24 bits are the maximum value for fp32 before precison is lost + # 0x00FFFFFF = 16777215 + cv.Optional(CONF_MAX_VALUE, default=16777215.0): cv.float_, + cv.Optional(CONF_MIN_VALUE, default=-16777215.0): cv.float_, + cv.Optional(CONF_STEP, default=1): cv.positive_float, + cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, + } + ).extend(cv.polling_component_schema("60s")), + validate_min_max, +) + + +async def to_code(config): + byte_offset = 0 + if CONF_OFFSET in config: + byte_offset = config[CONF_OFFSET] + # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET + if CONF_BYTE_OFFSET in config: + byte_offset = config[CONF_BYTE_OFFSET] + value_type = config[CONF_VALUE_TYPE] + reg_count = config[CONF_REGISTER_COUNT] + if reg_count == 0: + reg_count = TYPE_REGISTER_MAP[value_type] + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_ADDRESS], + byte_offset, + config[CONF_BITMASK], + config[CONF_VALUE_TYPE], + reg_count, + config[CONF_SKIP_UPDATES], + config[CONF_FORCE_NEW_RANGE], + ) + + await cg.register_component(var, config) + await number.register_number( + var, + config, + min_value=config[CONF_MIN_VALUE], + max_value=config[CONF_MAX_VALUE], + step=config[CONF_STEP], + ) + + cg.add(var.set_write_multiply(config[CONF_MULTIPLY])) + parent = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) + + cg.add(var.set_parent(parent)) + cg.add(parent.add_sensor_item(var)) + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], + [ + (ModbusNumber.operator("ptr"), "item"), + (cg.float_, "x"), + ( + cg.std_vector.template(cg.uint8).operator("const").operator("ref"), + "data", + ), + ], + return_type=cg.optional.template(float), + ) + cg.add(var.set_template(template_)) + if CONF_WRITE_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_WRITE_LAMBDA], + [ + (ModbusNumber.operator("ptr"), "item"), + (cg.float_, "x"), + (cg.std_vector.template(cg.uint16).operator("ref"), "payload"), + ], + return_type=cg.optional.template(float), + ) + cg.add(var.set_write_template(template_)) diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp new file mode 100644 index 0000000000..95c6ac6f6a --- /dev/null +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -0,0 +1,83 @@ +#include +#include "modbus_number.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace modbus_controller { + +static const char *const TAG = "modbus.number"; + +void ModbusNumber::parse_and_publish(const std::vector &data) { + union { + float float_value; + uint32_t raw; + } raw_to_float; + + float result = payload_to_float(data, *this); + + // Is there a lambda registered + // call it with the pre converted value and the raw data array + if (this->transform_func_.has_value()) { + // the lambda can parse the response itself + auto val = (*this->transform_func_)(this, result, data); + if (val.has_value()) { + ESP_LOGV(TAG, "Value overwritten by lambda"); + result = val.value(); + } + } + ESP_LOGD(TAG, "Number new state : %.02f", result); + // this->sensor_->raw_state = result; + this->publish_state(result); +} + +void ModbusNumber::control(float value) { + union { + float float_value; + uint32_t raw; + } raw_to_float; + + std::vector data; + auto original_value = value; + // Is there are lambda configured? + if (this->write_transform_func_.has_value()) { + // data is passed by reference + // the lambda can fill the empty vector directly + // in that case the return value is ignored + auto val = (*this->write_transform_func_)(this, value, data); + if (val.has_value()) { + ESP_LOGV(TAG, "Value overwritten by lambda"); + value = val.value(); + } else { + ESP_LOGV(TAG, "Communication handled by lambda - exiting control"); + return; + } + } else { + value = multiply_by_ * value; + } + + // lambda didn't set payload + if (data.empty()) { + data = float_to_payload(value, this->sensor_value_type); + } + + ESP_LOGD(TAG, + "Updating register: connected Sensor=%s start address=0x%X register count=%d new value=%.02f (val=%.02f)", + this->get_name().c_str(), this->start_address, this->register_count, value, value); + + // Create and send the write command + auto write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, + this->register_count, data); + + // publish new value + write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data) { + // gets called when the write command is ack'd from the device + parent_->on_write_register_response(write_cmd.register_type, start_address, data); + this->publish_state(value); + }; + parent_->queue_command(write_cmd); +} +void ModbusNumber::dump_config() { LOG_NUMBER(TAG, "Modbus Number", this); } + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/number/modbus_number.h b/esphome/components/modbus_controller/number/modbus_number.h new file mode 100644 index 0000000000..0fd4e314bc --- /dev/null +++ b/esphome/components/modbus_controller/number/modbus_number.h @@ -0,0 +1,48 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +using value_to_data_t = std::function(float); + +class ModbusNumber : public number::Number, public Component, public SensorItem { + public: + ModbusNumber(uint16_t start_address, uint8_t offset, uint32_t bitmask, SensorValueType value_type, int register_count, + uint8_t skip_updates, bool force_new_range) + : number::Number(), Component(), SensorItem() { + this->register_type = ModbusRegisterType::HOLDING; + this->start_address = start_address; + this->offset = offset; + this->bitmask = bitmask; + this->sensor_value_type = value_type; + this->register_count = register_count; + this->skip_updates = skip_updates; + this->force_new_range = force_new_range; + }; + + void dump_config() override; + void parse_and_publish(const std::vector &data) override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + void set_update_interval(int) {} + void set_parent(ModbusController *parent) { this->parent_ = parent; } + void set_write_multiply(float factor) { multiply_by_ = factor; } + + using transform_func_t = std::function(ModbusNumber *, float, const std::vector &)>; + using write_transform_func_t = std::function(ModbusNumber *, float, std::vector &)>; + void set_template(transform_func_t &&f) { this->transform_func_ = f; } + void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + + protected: + void control(float value) override; + optional transform_func_; + optional write_transform_func_; + ModbusController *parent_; + float multiply_by_{1.0}; +}; + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/output/__init__.py b/esphome/components/modbus_controller/output/__init__.py new file mode 100644 index 0000000000..9c41fc011c --- /dev/null +++ b/esphome/components/modbus_controller/output/__init__.py @@ -0,0 +1,74 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output + +from esphome.const import ( + CONF_ADDRESS, + CONF_ID, + CONF_MULTIPLY, + CONF_OFFSET, +) + +from .. import ( + SensorItem, + modbus_controller_ns, + ModbusController, +) + +from ..const import ( + CONF_BYTE_OFFSET, + CONF_MODBUS_CONTROLLER_ID, + CONF_VALUE_TYPE, + CONF_WRITE_LAMBDA, +) +from ..sensor import SENSOR_VALUE_TYPE + +DEPENDENCIES = ["modbus_controller"] +CODEOWNERS = ["@martgras"] + + +ModbusOutput = modbus_controller_ns.class_( + "ModbusOutput", cg.Component, output.FloatOutput, SensorItem +) + +CONFIG_SCHEMA = cv.All( + output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), + cv.GenerateID(): cv.declare_id(ModbusOutput), + cv.Required(CONF_ADDRESS): cv.positive_int, + cv.Optional(CONF_OFFSET, default=0): cv.positive_int, + cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, + cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), + cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, + } + ), +) + + +async def to_code(config): + byte_offset = 0 + if CONF_OFFSET in config: + byte_offset = config[CONF_OFFSET] + # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET + if CONF_BYTE_OFFSET in config: + byte_offset = config[CONF_BYTE_OFFSET] + var = cg.new_Pvariable( + config[CONF_ID], config[CONF_ADDRESS], byte_offset, config[CONF_VALUE_TYPE] + ) + await output.register_output(var, config) + cg.add(var.set_write_multiply(config[CONF_MULTIPLY])) + parent = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) + cg.add(var.set_parent(parent)) + if CONF_WRITE_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_WRITE_LAMBDA], + [ + (ModbusOutput.operator("ptr"), "item"), + (cg.float_, "x"), + (cg.std_vector.template(cg.uint16).operator("ref"), "payload"), + ], + return_type=cg.optional.template(float), + ) + cg.add(var.set_write_template(template_)) diff --git a/esphome/components/modbus_controller/output/modbus_output.cpp b/esphome/components/modbus_controller/output/modbus_output.cpp new file mode 100644 index 0000000000..f7d7c42342 --- /dev/null +++ b/esphome/components/modbus_controller/output/modbus_output.cpp @@ -0,0 +1,61 @@ +#include +#include "modbus_output.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace modbus_controller { + +static const char *const TAG = "modbus_controller.output"; + +void ModbusOutput::setup() {} + +/** Write a value to the device + * + */ +void ModbusOutput::write_state(float value) { + union { + float float_value; + uint32_t raw; + } raw_to_float; + + std::vector data; + auto original_value = value; + // Is there are lambda configured? + if (this->write_transform_func_.has_value()) { + // data is passed by reference + // the lambda can fill the empty vector directly + // in that case the return value is ignored + auto val = (*this->write_transform_func_)(this, value, data); + if (val.has_value()) { + ESP_LOGV(TAG, "Value overwritten by lambda"); + value = val.value(); + } else { + ESP_LOGV(TAG, "Communication handled by lambda - exiting control"); + return; + } + } else { + value = multiply_by_ * value; + } + // lambda didn't set payload + if (data.empty()) { + data = float_to_payload(value, this->sensor_value_type); + } + + ESP_LOGD(TAG, "Updating register: start address=0x%X register count=%d new value=%.02f (val=%.02f)", + this->start_address, this->register_count, value, original_value); + + // Create and send the write command + auto write_cmd = + ModbusCommandItem::create_write_multiple_command(parent_, this->start_address, this->register_count, data); + parent_->queue_command(write_cmd); +} + +void ModbusOutput::dump_config() { + ESP_LOGCONFIG(TAG, "Modbus Float Output:"); + LOG_FLOAT_OUTPUT(this); + ESP_LOGCONFIG(TAG, "Modbus device start address=0x%X register count=%d value type=%hhu", this->start_address, + this->register_count, this->sensor_value_type); +} + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/output/modbus_output.h b/esphome/components/modbus_controller/output/modbus_output.h new file mode 100644 index 0000000000..f46aef4683 --- /dev/null +++ b/esphome/components/modbus_controller/output/modbus_output.h @@ -0,0 +1,45 @@ +#pragma once + +#include "esphome/components/output/float_output.h" +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +using value_to_data_t = std::function(float); + +class ModbusOutput : public output::FloatOutput, public Component, public SensorItem { + public: + ModbusOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type) + : output::FloatOutput(), Component() { + this->register_type = ModbusRegisterType::HOLDING; + this->start_address = start_address; + this->offset = offset; + this->bitmask = bitmask; + this->sensor_value_type = value_type; + this->skip_updates = 0; + this->start_address += offset; + this->offset = 0; + } + void setup() override; + void dump_config() override; + + void set_parent(ModbusController *parent) { this->parent_ = parent; } + void set_write_multiply(float factor) { multiply_by_ = factor; } + // Do nothing + void parse_and_publish(const std::vector &data) override{}; + + using write_transform_func_t = std::function(ModbusOutput *, float, std::vector &)>; + void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + + protected: + void write_state(float value) override; + optional write_transform_func_{nullopt}; + + ModbusController *parent_; + float multiply_by_{1.0}; +}; + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/sensor/__init__.py b/esphome/components/modbus_controller/sensor/__init__.py new file mode 100644 index 0000000000..687f3d82fb --- /dev/null +++ b/esphome/components/modbus_controller/sensor/__init__.py @@ -0,0 +1,109 @@ +from esphome.components import sensor +import esphome.config_validation as cv +import esphome.codegen as cg + +from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET +from .. import ( + SensorItem, + modbus_controller_ns, + ModbusController, + MODBUS_REGISTER_TYPE, + SENSOR_VALUE_TYPE, +) +from ..const import ( + CONF_BITMASK, + CONF_BYTE_OFFSET, + CONF_FORCE_NEW_RANGE, + CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_COUNT, + CONF_REGISTER_TYPE, + CONF_SKIP_UPDATES, + CONF_VALUE_TYPE, +) + +DEPENDENCIES = ["modbus_controller"] +CODEOWNERS = ["@martgras"] + + +ModbusSensor = modbus_controller_ns.class_( + "ModbusSensor", cg.Component, sensor.Sensor, SensorItem +) + +TYPE_REGISTER_MAP = { + "RAW": 1, + "U_WORD": 1, + "S_WORD": 1, + "U_DWORD": 2, + "U_DWORD_R": 2, + "S_DWORD": 2, + "S_DWORD_R": 2, + "U_QWORD": 4, + "U_QWORDU_R": 4, + "S_QWORD": 4, + "U_QWORD_R": 4, + "FP32": 2, + "FP32_R": 2, +} + + +CONFIG_SCHEMA = cv.All( + sensor.SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ModbusSensor), + cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), + cv.Required(CONF_ADDRESS): cv.positive_int, + cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), + cv.Optional(CONF_OFFSET, default=0): cv.positive_int, + cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, + cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t, + cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), + cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, + cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, + cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + } + ).extend(cv.COMPONENT_SCHEMA), +) + + +async def to_code(config): + byte_offset = 0 + if CONF_OFFSET in config: + byte_offset = config[CONF_OFFSET] + # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET + if CONF_BYTE_OFFSET in config: + byte_offset = config[CONF_BYTE_OFFSET] + value_type = config[CONF_VALUE_TYPE] + reg_count = config[CONF_REGISTER_COUNT] + if reg_count == 0: + reg_count = TYPE_REGISTER_MAP[value_type] + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_REGISTER_TYPE], + config[CONF_ADDRESS], + byte_offset, + config[CONF_BITMASK], + config[CONF_VALUE_TYPE], + reg_count, + config[CONF_SKIP_UPDATES], + config[CONF_FORCE_NEW_RANGE], + ) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + + paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) + cg.add(paren.add_sensor_item(var)) + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], + [ + (ModbusSensor.operator("ptr"), "item"), + (cg.float_, "x"), + ( + cg.std_vector.template(cg.uint8).operator("const").operator("ref"), + "data", + ), + ], + return_type=cg.optional.template(float), + ) + cg.add(var.set_template(template_)) diff --git a/esphome/components/modbus_controller/sensor/modbus_sensor.cpp b/esphome/components/modbus_controller/sensor/modbus_sensor.cpp new file mode 100644 index 0000000000..dbd0525347 --- /dev/null +++ b/esphome/components/modbus_controller/sensor/modbus_sensor.cpp @@ -0,0 +1,36 @@ + +#include "modbus_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace modbus_controller { + +static const char *const TAG = "modbus_controller.sensor"; + +void ModbusSensor::dump_config() { LOG_SENSOR(TAG, "Modbus Controller Sensor", this); } + +void ModbusSensor::parse_and_publish(const std::vector &data) { + union { + float float_value; + uint32_t raw; + } raw_to_float; + + float result = payload_to_float(data, *this); + + // Is there a lambda registered + // call it with the pre converted value and the raw data array + if (this->transform_func_.has_value()) { + // the lambda can parse the response itself + auto val = (*this->transform_func_)(this, result, data); + if (val.has_value()) { + ESP_LOGV(TAG, "Value overwritten by lambda"); + result = val.value(); + } + } + ESP_LOGD(TAG, "Sensor new state: %.02f", result); + // this->sensor_->raw_state = result; + this->publish_state(result); +} + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/sensor/modbus_sensor.h b/esphome/components/modbus_controller/sensor/modbus_sensor.h new file mode 100644 index 0000000000..4f48c2a4dd --- /dev/null +++ b/esphome/components/modbus_controller/sensor/modbus_sensor.h @@ -0,0 +1,35 @@ +#pragma once + +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +class ModbusSensor : public Component, public sensor::Sensor, public SensorItem { + public: + ModbusSensor(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask, + SensorValueType value_type, int register_count, uint8_t skip_updates, bool force_new_range) + : Component(), sensor::Sensor() { + this->register_type = register_type; + this->start_address = start_address; + this->offset = offset; + this->bitmask = bitmask; + this->sensor_value_type = value_type; + this->register_count = register_count; + this->skip_updates = skip_updates; + this->force_new_range = force_new_range; + } + + void parse_and_publish(const std::vector &data) override; + void dump_config() override; + using transform_func_t = std::function(ModbusSensor *, float, const std::vector &)>; + void set_template(transform_func_t &&f) { this->transform_func_ = f; } + + protected: + optional transform_func_{nullopt}; +}; + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/switch/__init__.py b/esphome/components/modbus_controller/switch/__init__.py new file mode 100644 index 0000000000..e03b0d37be --- /dev/null +++ b/esphome/components/modbus_controller/switch/__init__.py @@ -0,0 +1,81 @@ +from esphome.components import switch +import esphome.config_validation as cv +import esphome.codegen as cg + + +from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET +from .. import ( + MODBUS_REGISTER_TYPE, + SensorItem, + modbus_controller_ns, + ModbusController, +) +from ..const import ( + CONF_BITMASK, + CONF_BYTE_OFFSET, + CONF_FORCE_NEW_RANGE, + CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_TYPE, +) + +DEPENDENCIES = ["modbus_controller"] +CODEOWNERS = ["@martgras"] + + +ModbusSwitch = modbus_controller_ns.class_( + "ModbusSwitch", cg.Component, switch.Switch, SensorItem +) + + +CONFIG_SCHEMA = cv.All( + switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ModbusSwitch), + cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), + cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), + cv.Required(CONF_ADDRESS): cv.positive_int, + cv.Optional(CONF_OFFSET, default=0): cv.positive_int, + cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, + cv.Optional(CONF_BITMASK, default=0x1): cv.hex_uint32_t, + cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + } + ).extend(cv.COMPONENT_SCHEMA), +) + + +async def to_code(config): + byte_offset = 0 + if CONF_OFFSET in config: + byte_offset = config[CONF_OFFSET] + # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET + if CONF_BYTE_OFFSET in config: + byte_offset = config[CONF_BYTE_OFFSET] + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_REGISTER_TYPE], + config[CONF_ADDRESS], + byte_offset, + config[CONF_BITMASK], + config[CONF_FORCE_NEW_RANGE], + ) + await cg.register_component(var, config) + await switch.register_switch(var, config) + + paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) + cg.add(paren.add_sensor_item(var)) + cg.add(var.set_parent(paren)) + if CONF_LAMBDA in config: + publish_template_ = await cg.process_lambda( + config[CONF_LAMBDA], + [ + (ModbusSwitch.operator("ptr"), "item"), + (bool, "x"), + ( + cg.std_vector.template(cg.uint8).operator("const").operator("ref"), + "data", + ), + ], + return_type=cg.optional.template(bool), + ) + cg.add(var.set_template(publish_template_)) diff --git a/esphome/components/modbus_controller/switch/modbus_switch.cpp b/esphome/components/modbus_controller/switch/modbus_switch.cpp new file mode 100644 index 0000000000..ce9557e6c4 --- /dev/null +++ b/esphome/components/modbus_controller/switch/modbus_switch.cpp @@ -0,0 +1,70 @@ + +#include "modbus_switch.h" +#include "esphome/core/log.h" +namespace esphome { +namespace modbus_controller { + +static const char *const TAG = "modbus_controller.switch"; + +void ModbusSwitch::setup() { + // value isn't required + // without it we crash on save + this->get_initial_state(); +} +void ModbusSwitch::dump_config() { LOG_SWITCH(TAG, "Modbus Controller Switch", this); } + +void ModbusSwitch::parse_and_publish(const std::vector &data) { + bool value = false; + switch (this->register_type) { + case ModbusRegisterType::DISCRETE_INPUT: + case ModbusRegisterType::COIL: + // offset for coil is the actual number of the coil not the byte offset + value = coil_from_vector(this->offset, data); + break; + default: + value = get_data(data, this->offset) & this->bitmask; + break; + } + + // Is there a lambda registered + // call it with the pre converted value and the raw data array + if (this->publish_transform_func_) { + // the lambda can parse the response itself + auto val = (*this->publish_transform_func_)(this, value, data); + if (val.has_value()) { + ESP_LOGV(TAG, "Value overwritten by lambda"); + value = val.value(); + } + } + + ESP_LOGV(TAG, "Publish '%s': new value = %s type = %d address = %X offset = %x", this->get_name().c_str(), + ONOFF(value), (int) this->register_type, this->start_address, this->offset); + this->publish_state(value); +} + +void ModbusSwitch::write_state(bool state) { + // This will be called every time the user requests a state change. + ModbusCommandItem cmd; + ESP_LOGV(TAG, "write_state '%s': new value = %s type = %d address = %X offset = %x", this->get_name().c_str(), + ONOFF(state), (int) this->register_type, this->start_address, this->offset); + switch (this->register_type) { + case ModbusRegisterType::COIL: + // offset for coil and discrete inputs is the coil/register number not bytes + cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state); + break; + case ModbusRegisterType::DISCRETE_INPUT: + cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, state); + break; + + default: + // since offset is in bytes and a register is 16 bits we get the start by adding offset/2 + cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, + state ? 0xFFFF & this->bitmask : 0); + break; + } + this->parent_->queue_command(cmd); + publish_state(state); +} +// ModbusSwitch end +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/switch/modbus_switch.h b/esphome/components/modbus_controller/switch/modbus_switch.h new file mode 100644 index 0000000000..a38668fabb --- /dev/null +++ b/esphome/components/modbus_controller/switch/modbus_switch.h @@ -0,0 +1,44 @@ +#pragma once + +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/components/switch/switch.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +class ModbusSwitch : public Component, public switch_::Switch, public SensorItem { + public: + ModbusSwitch(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask, + bool force_new_range) + : Component(), switch_::Switch() { + this->register_type = register_type; + this->start_address = start_address; + this->offset = offset; + this->bitmask = bitmask; + this->sensor_value_type = SensorValueType::BIT; + this->skip_updates = 0; + this->register_count = 1; + if (register_type == ModbusRegisterType::HOLDING || register_type == ModbusRegisterType::COIL) { + this->start_address += offset; + this->offset = 0; + } + this->force_new_range = force_new_range; + }; + void setup() override; + void write_state(bool state) override; + void dump_config() override; + void set_state(bool state) { this->state = state; } + void parse_and_publish(const std::vector &data) override; + void set_parent(ModbusController *parent) { this->parent_ = parent; } + + using transform_func_t = std::function(ModbusSwitch *, bool, const std::vector &)>; + void set_template(transform_func_t &&f) { this->publish_transform_func_ = f; } + + protected: + ModbusController *parent_; + optional publish_transform_func_{nullopt}; +}; + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/text_sensor/__init__.py b/esphome/components/modbus_controller/text_sensor/__init__.py new file mode 100644 index 0000000000..2c02c86795 --- /dev/null +++ b/esphome/components/modbus_controller/text_sensor/__init__.py @@ -0,0 +1,101 @@ +from esphome.components import text_sensor +import esphome.config_validation as cv +import esphome.codegen as cg + + +from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET +from .. import ( + SensorItem, + modbus_controller_ns, + ModbusController, + MODBUS_REGISTER_TYPE, +) +from ..const import ( + CONF_BYTE_OFFSET, + CONF_FORCE_NEW_RANGE, + CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_COUNT, + CONF_RESPONSE_SIZE, + CONF_SKIP_UPDATES, + CONF_RAW_ENCODE, + CONF_REGISTER_TYPE, +) + +DEPENDENCIES = ["modbus_controller"] +CODEOWNERS = ["@martgras"] + + +ModbusTextSensor = modbus_controller_ns.class_( + "ModbusTextSensor", cg.Component, text_sensor.TextSensor, SensorItem +) + +RawEncoding_ns = modbus_controller_ns.namespace("RawEncoding") +RawEncoding = RawEncoding_ns.enum("RawEncoding") +RAW_ENCODING = { + "NONE": RawEncoding.NONE, + "HEXBYTES": RawEncoding.HEXBYTES, + "COMMA": RawEncoding.COMMA, +} + +CONFIG_SCHEMA = cv.All( + text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ModbusTextSensor), + cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), + cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), + cv.Required(CONF_ADDRESS): cv.positive_int, + cv.Optional(CONF_OFFSET, default=0): cv.positive_int, + cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, + cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, + cv.Optional(CONF_RESPONSE_SIZE, default=2): cv.positive_int, + cv.Optional(CONF_RAW_ENCODE, default="NONE"): cv.enum(RAW_ENCODING), + cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, + cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + } + ).extend(cv.COMPONENT_SCHEMA), +) + + +async def to_code(config): + byte_offset = 0 + if CONF_OFFSET in config: + byte_offset = config[CONF_OFFSET] + # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET + if CONF_BYTE_OFFSET in config: + byte_offset = config[CONF_BYTE_OFFSET] + response_size = config[CONF_RESPONSE_SIZE] + reg_count = config[CONF_REGISTER_COUNT] + if reg_count == 0: + reg_count = response_size / 2 + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_REGISTER_TYPE], + config[CONF_ADDRESS], + byte_offset, + reg_count, + config[CONF_RESPONSE_SIZE], + config[CONF_RAW_ENCODE], + config[CONF_SKIP_UPDATES], + config[CONF_FORCE_NEW_RANGE], + ) + + await cg.register_component(var, config) + await text_sensor.register_text_sensor(var, config) + + paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) + cg.add(paren.add_sensor_item(var)) + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], + [ + (ModbusTextSensor.operator("ptr"), "item"), + (cg.std_string.operator("const").operator("ref"), "x"), + ( + cg.std_vector.template(cg.uint8).operator("const").operator("ref"), + "data", + ), + ], + return_type=cg.optional.template(cg.std_string), + ) + cg.add(var.set_template(template_)) diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp new file mode 100644 index 0000000000..a06d44e90b --- /dev/null +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp @@ -0,0 +1,56 @@ + +#include "modbus_textsensor.h" +#include "esphome/core/log.h" +#include +#include + +namespace esphome { +namespace modbus_controller { + +static const char *const TAG = "modbus_controller.text_sensor"; + +void ModbusTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Modbus Controller Text Sensor", this); } + +void ModbusTextSensor::parse_and_publish(const std::vector &data) { + std::ostringstream output; + uint8_t max_items = this->response_bytes_; + char buffer[4]; + bool add_comma = false; + for (auto b : data) { + switch (this->encode_) { + case RawEncoding::HEXBYTES: + sprintf(buffer, "%02x", b); + output << buffer; + break; + case RawEncoding::COMMA: + sprintf(buffer, add_comma ? ",%d" : "%d", b); + output << buffer; + add_comma = true; + break; + // Anything else no encoding + case RawEncoding::NONE: + default: + output << (char) b; + break; + } + if (--max_items == 0) { + break; + } + } + + auto result = output.str(); + // Is there a lambda registered + // call it with the pre converted value and the raw data array + if (this->transform_func_.has_value()) { + // the lambda can parse the response itself + auto val = (*this->transform_func_)(this, result, data); + if (val.has_value()) { + ESP_LOGV(TAG, "Value overwritten by lambda"); + result = val.value(); + } + } + this->publish_state(result); +} + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h new file mode 100644 index 0000000000..28d0f0b241 --- /dev/null +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h @@ -0,0 +1,52 @@ +#pragma once + +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +enum class RawEncoding { NONE = 0, HEXBYTES = 1, COMMA = 2 }; + +class ModbusTextSensor : public Component, public text_sensor::TextSensor, public SensorItem { + public: + ModbusTextSensor(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint8_t register_count, + uint16_t response_bytes, RawEncoding encode, uint8_t skip_updates, bool force_new_range) + : Component() { + this->register_type = register_type; + this->start_address = start_address; + this->offset = offset; + this->response_bytes_ = response_bytes; + this->register_count = register_count; + this->encode_ = encode; + this->skip_updates = skip_updates; + this->bitmask = 0xFFFFFFFF; + this->sensor_value_type = SensorValueType::RAW; + this->force_new_range = force_new_range; + } + size_t get_register_size() const override { + if (sensor_value_type == SensorValueType::RAW) { + return this->response_bytes_; + } else { + return SensorItem::get_register_size(); + } + } + + void dump_config() override; + + void parse_and_publish(const std::vector &data) override; + using transform_func_t = + std::function(ModbusTextSensor *, std::string, const std::vector &)>; + void set_template(transform_func_t &&f) { this->transform_func_ = f; } + + protected: + optional transform_func_{nullopt}; + + protected: + RawEncoding encode_; + uint16_t response_bytes_; +}; + +} // namespace modbus_controller +} // namespace esphome diff --git a/tests/test5.yaml b/tests/test5.yaml index f165617551..b22b19550e 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -36,6 +36,14 @@ i2c: modbus: uart_id: uart1 + flow_control_pin: 5 + id: mod_bus1 + +modbus_controller: + - id: modbus_controller_test + address: 0x2 + modbus_id: mod_bus1 + binary_sensor: - platform: gpio @@ -150,6 +158,14 @@ sensor: name: "SelecEM2M Maximum Demand Apparent Power" disabled_by_default: true + - id: battery_voltage + name: "Battery voltage2" + platform: modbus_controller + modbus_controller_id: modbus_controller_test + address: 0x331A + register_type: read + value_type: U_WORD + - platform: t6615 uart_id: uart2 co2: From 0d0954d74b3609615a583730479bdaa62113613c Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Mon, 27 Sep 2021 00:32:33 +0400 Subject: [PATCH 074/549] Midea fix (#2395) --- esphome/components/midea/climate.py | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/midea/climate.py b/esphome/components/midea/climate.py index 0d0bdce471..08e82025b6 100644 --- a/esphome/components/midea/climate.py +++ b/esphome/components/midea/climate.py @@ -282,4 +282,4 @@ async def to_code(config): 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") + cg.add_library("dudanov/MideaUART", "1.1.8") diff --git a/platformio.ini b/platformio.ini index 1901f175af..6e605768fc 100644 --- a/platformio.ini +++ b/platformio.ini @@ -48,7 +48,7 @@ lib_deps = 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 + dudanov/MideaUART@1.1.8 ; midea build_flags = ${common.build_flags} -DUSE_ARDUINO From 4c390d9f9f323c7af7093d77392d55d5fd46f354 Mon Sep 17 00:00:00 2001 From: JonasEr <8407728+JonasEr@users.noreply.github.com> Date: Sun, 26 Sep 2021 23:38:08 +0300 Subject: [PATCH 075/549] Extend nfc ndef records with Text (#2191) Co-authored-by: Oxan van Leeuwen --- esphome/components/nfc/ndef_message.cpp | 50 +++++++------- esphome/components/nfc/ndef_message.h | 4 +- esphome/components/nfc/ndef_record.cpp | 52 +++++--------- esphome/components/nfc/ndef_record.h | 76 ++++----------------- esphome/components/nfc/ndef_record_text.cpp | 40 +++++++++++ esphome/components/nfc/ndef_record_text.h | 41 +++++++++++ esphome/components/nfc/ndef_record_uri.cpp | 48 +++++++++++++ esphome/components/nfc/ndef_record_uri.h | 76 +++++++++++++++++++++ 8 files changed, 264 insertions(+), 123 deletions(-) create mode 100644 esphome/components/nfc/ndef_record_text.cpp create mode 100644 esphome/components/nfc/ndef_record_text.h create mode 100644 esphome/components/nfc/ndef_record_uri.cpp create mode 100644 esphome/components/nfc/ndef_record_uri.h diff --git a/esphome/components/nfc/ndef_message.cpp b/esphome/components/nfc/ndef_message.cpp index b1554f41ae..d8c940254e 100644 --- a/esphome/components/nfc/ndef_message.cpp +++ b/esphome/components/nfc/ndef_message.cpp @@ -10,16 +10,13 @@ NdefMessage::NdefMessage(std::vector &data) { uint8_t index = 0; while (index <= data.size()) { uint8_t tnf_byte = data[index++]; - bool me = tnf_byte & 0x40; - bool sr = tnf_byte & 0x10; - bool il = tnf_byte & 0x08; - uint8_t tnf = tnf_byte & 0x07; + bool me = tnf_byte & 0x40; // Message End bit (is set if this is the last record of the message) + bool sr = tnf_byte & 0x10; // Short record bit (is set if payload size is less or equal to 255 bytes) + bool il = tnf_byte & 0x08; // ID length bit (is set if ID Length field exists) + uint8_t tnf = tnf_byte & 0x07; // Type Name Format ESP_LOGVV(TAG, "me=%s, sr=%s, il=%s, tnf=%d", YESNO(me), YESNO(sr), YESNO(il), tnf); - auto record = make_unique(); - record->set_tnf(tnf); - uint8_t type_length = data[index++]; uint32_t payload_length = 0; if (sr) { @@ -38,28 +35,34 @@ NdefMessage::NdefMessage(std::vector &data) { ESP_LOGVV(TAG, "Lengths: type=%d, payload=%d, id=%d", type_length, payload_length, id_length); std::string type_str(data.begin() + index, data.begin() + index + type_length); - record->set_type(type_str); + index += type_length; + std::string id_str = ""; if (il) { - std::string id_str(data.begin() + index, data.begin() + index + id_length); - record->set_id(id_str); + id_str = std::string(data.begin() + index, data.begin() + index + id_length); index += id_length; } - uint8_t payload_identifier = 0x00; - if (type_str == "U") { - payload_identifier = data[index++]; - payload_length -= 1; + std::vector payload_data(data.begin() + index, data.begin() + index + payload_length); + + std::unique_ptr record; + + // Based on tnf and type, create a more specific NdefRecord object + // constructed from the payload data + if (tnf == TNF_WELL_KNOWN && type_str == "U") { + record = make_unique(payload_data); + } else if (tnf == TNF_WELL_KNOWN && type_str == "T") { + record = make_unique(payload_data); + } else { + // Could not recognize the record, so store as generic one. + record = make_unique(payload_data); + record->set_tnf(tnf); + record->set_type(type_str); } - std::string payload_str(data.begin() + index, data.begin() + index + payload_length); + record->set_id(id_str); - if (payload_identifier > 0x00 && payload_identifier <= PAYLOAD_IDENTIFIERS_COUNT) { - payload_str.insert(0, PAYLOAD_IDENTIFIERS[payload_identifier]); - } - - record->set_payload(payload_str); index += payload_length; ESP_LOGV(TAG, "Adding record type %s = %s", record->get_type().c_str(), record->get_payload().c_str()); @@ -82,13 +85,10 @@ bool NdefMessage::add_record(std::unique_ptr record) { bool NdefMessage::add_text_record(const std::string &text) { return this->add_text_record(text, "en"); }; 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(make_unique(encoding, text)); } -bool NdefMessage::add_uri_record(const std::string &uri) { - return this->add_record(make_unique(TNF_WELL_KNOWN, "U", uri)); -} +bool NdefMessage::add_uri_record(const std::string &uri) { return this->add_record(make_unique(uri)); } std::vector NdefMessage::encode() { std::vector data; diff --git a/esphome/components/nfc/ndef_message.h b/esphome/components/nfc/ndef_message.h index 20140e8c1a..5e44a06011 100644 --- a/esphome/components/nfc/ndef_message.h +++ b/esphome/components/nfc/ndef_message.h @@ -5,6 +5,8 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "ndef_record.h" +#include "ndef_record_text.h" +#include "ndef_record_uri.h" namespace esphome { namespace nfc { @@ -18,7 +20,7 @@ class NdefMessage { NdefMessage(const NdefMessage &msg) { records_.reserve(msg.records_.size()); for (const auto &r : msg.records_) { - records_.emplace_back(make_unique(*r)); + records_.emplace_back(r->clone()); } } diff --git a/esphome/components/nfc/ndef_record.cpp b/esphome/components/nfc/ndef_record.cpp index a75f5978ec..8a3a7d375d 100644 --- a/esphome/components/nfc/ndef_record.cpp +++ b/esphome/components/nfc/ndef_record.cpp @@ -5,40 +5,22 @@ namespace nfc { static const char *const TAG = "nfc.ndef_record"; -uint32_t NdefRecord::get_encoded_size() { - uint32_t size = 2; - if (this->payload_.length() > 255) { - size += 4; - } else { - size += 1; - } - if (this->id_.length()) { - size += 1; - } - size += (this->type_.length() + this->payload_.length() + this->id_.length()); - return size; +NdefRecord::NdefRecord(std::vector payload_data) { + this->payload_ = std::string(payload_data.begin(), payload_data.end()); } std::vector NdefRecord::encode(bool first, bool last) { std::vector data; - data.push_back(this->get_tnf_byte(first, last)); + // Get encoded payload, this is overriden by more specific record classes + std::vector payload_data = get_encoded_payload(); + + size_t payload_length = payload_data.size(); + + data.push_back(this->create_flag_byte(first, last, payload_length)); data.push_back(this->type_.length()); - uint8_t payload_prefix = 0x00; - uint8_t payload_prefix_length = 0x00; - for (uint8_t i = 1; i < PAYLOAD_IDENTIFIERS_COUNT; i++) { - std::string prefix = PAYLOAD_IDENTIFIERS[i]; - if (this->payload_.substr(0, prefix.length()).find(prefix) != std::string::npos) { - payload_prefix = i; - payload_prefix_length = prefix.length(); - break; - } - } - - uint32_t payload_length = this->payload_.length() - payload_prefix_length + 1; - if (payload_length <= 255) { data.push_back(payload_length); } else { @@ -58,25 +40,23 @@ std::vector NdefRecord::encode(bool first, bool last) { data.insert(data.end(), this->id_.begin(), this->id_.end()); } - data.push_back(payload_prefix); - - data.insert(data.end(), this->payload_.begin() + payload_prefix_length, this->payload_.end()); + data.insert(data.end(), payload_data.begin(), payload_data.end()); return data; } -uint8_t NdefRecord::get_tnf_byte(bool first, bool last) { - uint8_t value = this->tnf_; +uint8_t NdefRecord::create_flag_byte(bool first, bool last, size_t payload_size) { + uint8_t value = this->tnf_ & 0b00000111; if (first) { - value = value | 0x80; + value = value | 0x80; // Set MB bit } if (last) { - value = value | 0x40; + value = value | 0x40; // Set ME bit } - if (this->payload_.length() <= 255) { - value = value | 0x10; + if (payload_size <= 255) { + value = value | 0x10; // Set SR bit } if (this->id_.length()) { - value = value | 0x08; + value = value | 0x08; // Set IL bit } return value; }; diff --git a/esphome/components/nfc/ndef_record.h b/esphome/components/nfc/ndef_record.h index 680fe20986..4fab1c03e4 100644 --- a/esphome/components/nfc/ndef_record.h +++ b/esphome/components/nfc/ndef_record.h @@ -15,86 +15,40 @@ static const uint8_t TNF_UNKNOWN = 0x05; static const uint8_t TNF_UNCHANGED = 0x06; static const uint8_t TNF_RESERVED = 0x07; -static const uint8_t PAYLOAD_IDENTIFIERS_COUNT = 0x23; -static const char *const PAYLOAD_IDENTIFIERS[] = {"", - "http://www.", - "https://www.", - "http://", - "https://", - "tel:", - "mailto:", - "ftp://anonymous:anonymous@", - "ftp://ftp.", - "ftps://", - "sftp://", - "smb://", - "nfs://", - "ftp://", - "dav://", - "news:", - "telnet://", - "imap:", - "rtsp://", - "urn:", - "pop:", - "sip:", - "sips:", - "tftp:", - "btspp://", - "btl2cap://", - "btgoep://", - "tcpobex://", - "irdaobex://", - "file://", - "urn:epc:id:", - "urn:epc:tag:", - "urn:epc:pat:", - "urn:epc:raw:", - "urn:epc:", - "urn:nfc:"}; - class NdefRecord { public: NdefRecord(){}; - NdefRecord(uint8_t tnf, const std::string &type, const std::string &payload) { - this->tnf_ = tnf; - this->type_ = type; - this->set_payload(payload); - }; - NdefRecord(uint8_t tnf, const std::string &type, const std::string &payload, const std::string &id) { - this->tnf_ = tnf; - this->type_ = type; - this->set_payload(payload); - this->id_ = id; - }; - NdefRecord(const NdefRecord &rhs) { - this->tnf_ = rhs.tnf_; - this->type_ = rhs.type_; - this->payload_ = rhs.payload_; - this->payload_identifier_ = rhs.payload_identifier_; - this->id_ = rhs.id_; - }; + NdefRecord(std::vector payload_data); void set_tnf(uint8_t tnf) { this->tnf_ = tnf; }; void set_type(const std::string &type) { this->type_ = type; }; - void set_payload_identifier(uint8_t payload_identifier) { this->payload_identifier_ = payload_identifier; }; void set_payload(const std::string &payload) { this->payload_ = payload; }; void set_id(const std::string &id) { this->id_ = id; }; + NdefRecord(const NdefRecord &) = default; + virtual ~NdefRecord() {} + virtual std::unique_ptr clone() const { // To allow copying polymorphic classes + return make_unique(*this); + }; uint32_t get_encoded_size(); std::vector encode(bool first, bool last); - uint8_t get_tnf_byte(bool first, bool last); + + uint8_t create_flag_byte(bool first, bool last, size_t payload_size); 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_; }; + virtual const std::string &get_payload() const { return this->payload_; }; + + virtual std::vector get_encoded_payload() { + std::vector empty_payload; + return empty_payload; + }; protected: uint8_t tnf_; std::string type_; - uint8_t payload_identifier_; - std::string payload_; std::string id_; + std::string payload_; }; } // namespace nfc diff --git a/esphome/components/nfc/ndef_record_text.cpp b/esphome/components/nfc/ndef_record_text.cpp new file mode 100644 index 0000000000..80b0108b46 --- /dev/null +++ b/esphome/components/nfc/ndef_record_text.cpp @@ -0,0 +1,40 @@ +#include "ndef_record_text.h" +#include "ndef_record.h" + +namespace esphome { +namespace nfc { + +static const char *const TAG = "nfc.ndef_record_text"; + +NdefRecordText::NdefRecordText(const std::vector &payload) { + if (payload.empty()) { + ESP_LOGE(TAG, "Record payload too short"); + return; + } + + uint8_t language_code_length = payload[0] & 0b00111111; // Todo, make use of encoding bit? + + this->language_code_ = std::string(payload.begin() + 1, payload.begin() + 1 + language_code_length); + + this->text_ = std::string(payload.begin() + 1 + language_code_length, payload.end()); + + this->tnf_ = TNF_WELL_KNOWN; + + this->type_ = "T"; +} + +std::vector NdefRecordText::get_encoded_payload() { + std::vector data; + + uint8_t flag_byte = this->language_code_.length() & 0b00111111; // UTF8 assumed + + data.push_back(flag_byte); + + data.insert(data.end(), this->language_code_.begin(), this->language_code_.end()); + + data.insert(data.end(), this->text_.begin(), this->text_.end()); + return data; +} + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/ndef_record_text.h b/esphome/components/nfc/ndef_record_text.h new file mode 100644 index 0000000000..94375cc860 --- /dev/null +++ b/esphome/components/nfc/ndef_record_text.h @@ -0,0 +1,41 @@ +#pragma once + +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "ndef_record.h" + +namespace esphome { +namespace nfc { + +class NdefRecordText : public NdefRecord { + public: + NdefRecordText(){}; + NdefRecordText(const std::vector &payload); + NdefRecordText(const std::string &language_code, const std::string &text) { + this->tnf_ = TNF_WELL_KNOWN; + this->type_ = "T"; + this->language_code_ = language_code; + this->text_ = text; + }; + NdefRecordText(const std::string &language_code, const std::string &text, const std::string &id) { + this->tnf_ = TNF_WELL_KNOWN; + this->type_ = "T"; + this->language_code_ = language_code; + this->text_ = text; + this->id_ = id; + }; + NdefRecordText(const NdefRecordText &) = default; + + std::unique_ptr clone() const override { return make_unique(*this); }; + + std::vector get_encoded_payload() override; + + const std::string &get_payload() const override { return this->text_; }; + + protected: + std::string text_; + std::string language_code_; +}; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/ndef_record_uri.cpp b/esphome/components/nfc/ndef_record_uri.cpp new file mode 100644 index 0000000000..8fd043a1de --- /dev/null +++ b/esphome/components/nfc/ndef_record_uri.cpp @@ -0,0 +1,48 @@ +#include "ndef_record_uri.h" + +namespace esphome { +namespace nfc { + +static const char *const TAG = "nfc.ndef_record_uri"; + +NdefRecordUri::NdefRecordUri(const std::vector &payload) { + if (payload.empty()) { + ESP_LOGE(TAG, "Record payload too short"); + return; + } + + uint8_t payload_identifier = payload[0]; // First byte of payload is prefix code + + std::string uri(payload.begin() + 1, payload.end()); + + if (payload_identifier > 0x00 && payload_identifier <= PAYLOAD_IDENTIFIERS_COUNT) { + uri.insert(0, PAYLOAD_IDENTIFIERS[payload_identifier]); + } + + this->tnf_ = TNF_WELL_KNOWN; + this->type_ = "U"; + this->set_URI(uri); +} + +std::vector NdefRecordUri::get_encoded_payload() { + std::vector data; + + uint8_t payload_prefix = 0x00; + uint8_t payload_prefix_length = 0x00; + for (uint8_t i = 1; i < PAYLOAD_IDENTIFIERS_COUNT; i++) { + std::string prefix = PAYLOAD_IDENTIFIERS[i]; + if (this->URI_.substr(0, prefix.length()).find(prefix) != std::string::npos) { + payload_prefix = i; + payload_prefix_length = prefix.length(); + break; + } + } + + data.push_back(payload_prefix); + + data.insert(data.end(), this->URI_.begin() + payload_prefix_length, this->URI_.end()); + return data; +} + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/ndef_record_uri.h b/esphome/components/nfc/ndef_record_uri.h new file mode 100644 index 0000000000..75f9e19ee0 --- /dev/null +++ b/esphome/components/nfc/ndef_record_uri.h @@ -0,0 +1,76 @@ +#pragma once + +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "ndef_record.h" + +namespace esphome { +namespace nfc { + +static const uint8_t PAYLOAD_IDENTIFIERS_COUNT = 0x23; +static const char *const PAYLOAD_IDENTIFIERS[] = {"", + "http://www.", + "https://www.", + "http://", + "https://", + "tel:", + "mailto:", + "ftp://anonymous:anonymous@", + "ftp://ftp.", + "ftps://", + "sftp://", + "smb://", + "nfs://", + "ftp://", + "dav://", + "news:", + "telnet://", + "imap:", + "rtsp://", + "urn:", + "pop:", + "sip:", + "sips:", + "tftp:", + "btspp://", + "btl2cap://", + "btgoep://", + "tcpobex://", + "irdaobex://", + "file://", + "urn:epc:id:", + "urn:epc:tag:", + "urn:epc:pat:", + "urn:epc:raw:", + "urn:epc:", + "urn:nfc:"}; + +class NdefRecordUri : public NdefRecord { + public: + NdefRecordUri(){}; + NdefRecordUri(const std::vector &payload); + NdefRecordUri(const std::string &URI) { + this->tnf_ = TNF_WELL_KNOWN; + this->type_ = "U"; + this->URI_ = URI; + }; + NdefRecordUri(const std::string &URI, const std::string &id) { + this->tnf_ = TNF_WELL_KNOWN; + this->type_ = "U"; + this->URI_ = URI; + this->id_ = id; + }; + NdefRecordUri(const NdefRecordUri &) = default; + std::unique_ptr clone() const override { return make_unique(*this); }; + + void set_URI(const std::string &URI) { this->URI_ = URI; }; + + std::vector get_encoded_payload() override; + const std::string &get_payload() const override { return this->URI_; }; + + protected: + std::string URI_; +}; + +} // namespace nfc +} // namespace esphome From 756c6721e9372667df7466570e170d59b69b1b11 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 27 Sep 2021 11:11:27 +1300 Subject: [PATCH 076/549] Fix NDEF URI casing (#2397) --- esphome/components/nfc/ndef_record_uri.cpp | 6 +++--- esphome/components/nfc/ndef_record_uri.h | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/esphome/components/nfc/ndef_record_uri.cpp b/esphome/components/nfc/ndef_record_uri.cpp index 8fd043a1de..9064f04f29 100644 --- a/esphome/components/nfc/ndef_record_uri.cpp +++ b/esphome/components/nfc/ndef_record_uri.cpp @@ -21,7 +21,7 @@ NdefRecordUri::NdefRecordUri(const std::vector &payload) { this->tnf_ = TNF_WELL_KNOWN; this->type_ = "U"; - this->set_URI(uri); + this->set_uri(uri); } std::vector NdefRecordUri::get_encoded_payload() { @@ -31,7 +31,7 @@ std::vector NdefRecordUri::get_encoded_payload() { uint8_t payload_prefix_length = 0x00; for (uint8_t i = 1; i < PAYLOAD_IDENTIFIERS_COUNT; i++) { std::string prefix = PAYLOAD_IDENTIFIERS[i]; - if (this->URI_.substr(0, prefix.length()).find(prefix) != std::string::npos) { + if (this->uri_.substr(0, prefix.length()).find(prefix) != std::string::npos) { payload_prefix = i; payload_prefix_length = prefix.length(); break; @@ -40,7 +40,7 @@ std::vector NdefRecordUri::get_encoded_payload() { data.push_back(payload_prefix); - data.insert(data.end(), this->URI_.begin() + payload_prefix_length, this->URI_.end()); + data.insert(data.end(), this->uri_.begin() + payload_prefix_length, this->uri_.end()); return data; } diff --git a/esphome/components/nfc/ndef_record_uri.h b/esphome/components/nfc/ndef_record_uri.h index 75f9e19ee0..4c21724c5c 100644 --- a/esphome/components/nfc/ndef_record_uri.h +++ b/esphome/components/nfc/ndef_record_uri.h @@ -49,27 +49,27 @@ class NdefRecordUri : public NdefRecord { public: NdefRecordUri(){}; NdefRecordUri(const std::vector &payload); - NdefRecordUri(const std::string &URI) { + NdefRecordUri(const std::string &uri) { this->tnf_ = TNF_WELL_KNOWN; this->type_ = "U"; - this->URI_ = URI; + this->uri_ = uri; }; - NdefRecordUri(const std::string &URI, const std::string &id) { + NdefRecordUri(const std::string &uri, const std::string &id) { this->tnf_ = TNF_WELL_KNOWN; this->type_ = "U"; - this->URI_ = URI; + this->uri_ = uri; this->id_ = id; }; NdefRecordUri(const NdefRecordUri &) = default; std::unique_ptr clone() const override { return make_unique(*this); }; - void set_URI(const std::string &URI) { this->URI_ = URI; }; + void set_uri(const std::string &uri) { this->uri_ = uri; }; std::vector get_encoded_payload() override; - const std::string &get_payload() const override { return this->URI_; }; + const std::string &get_payload() const override { return this->uri_; }; protected: - std::string URI_; + std::string uri_; }; } // namespace nfc From 97e76d64d60d2d46a4bd4a4b50d11c496d596138 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 27 Sep 2021 11:40:28 +0200 Subject: [PATCH 077/549] Re-enable TCP nodelay for ESP32 (#2390) --- esphome/components/api/api_frame_helper.cpp | 15 +++++++++++ .../components/socket/lwip_raw_tcp_impl.cpp | 27 ++++++++++--------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 00f28457ae..4971272f41 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -127,6 +127,14 @@ APIError APINoiseFrameHelper::init() { 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)); @@ -722,6 +730,13 @@ 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; diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 7521d1339f..54dfddac3f 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -257,7 +257,7 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return -1; } - *reinterpret_cast(optval) = tcp_nagle_disabled(pcb_); + *reinterpret_cast(optval) = nodelay_; *optlen = 4; return 0; } @@ -286,11 +286,7 @@ class LWIPRawImpl : public Socket { return -1; } int val = *reinterpret_cast(optval); - if (val != 0) { - tcp_nagle_disable(pcb_); - } else { - tcp_nagle_enable(pcb_); - } + nodelay_ = val; return 0; } @@ -444,9 +440,11 @@ class LWIPRawImpl : public Socket { if (written == 0) // no need to output if nothing written return 0; - int err = internal_output(); - if (err == -1) - return -1; + if (nodelay_) { + int err = internal_output(); + if (err == -1) + return -1; + } return written; } ssize_t writev(const struct iovec *iov, int iovcnt) override { @@ -466,9 +464,11 @@ class LWIPRawImpl : public Socket { if (written == 0) // no need to output if nothing written return 0; - int err = internal_output(); - if (err == -1) - return -1; + if (nodelay_) { + int err = internal_output(); + if (err == -1) + return -1; + } return written; } int setblocking(bool blocking) override { @@ -550,6 +550,9 @@ class LWIPRawImpl : public Socket { bool rx_closed_ = false; pbuf *rx_buf_ = nullptr; size_t rx_buf_offset_ = 0; + // don't use lwip nodelay flag, it sometimes causes reconnect + // instead use it for determining whether to call lwip_output + bool nodelay_ = false; }; std::unique_ptr socket(int domain, int type, int protocol) { From 45940b051439cb0053889597643d3a5a62ab73ad Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 27 Sep 2021 19:10:53 +0200 Subject: [PATCH 078/549] Dashboard node import and render in browser (#2374) --- CODEOWNERS | 1 + .../components/dashboard_import/__init__.py | 45 ++++++++ .../dashboard_import/dashboard_import.cpp | 12 ++ .../dashboard_import/dashboard_import.h | 12 ++ esphome/components/mdns/mdns_component.cpp | 8 ++ esphome/core/defines.h | 2 + esphome/dashboard/dashboard.py | 104 ++++++++++++++---- esphome/zeroconf.py | 81 +++++++++++++- 8 files changed, 237 insertions(+), 28 deletions(-) create mode 100644 esphome/components/dashboard_import/__init__.py create mode 100644 esphome/components/dashboard_import/dashboard_import.cpp create mode 100644 esphome/components/dashboard_import/dashboard_import.h diff --git a/CODEOWNERS b/CODEOWNERS index 17825a9205..0b974b95e6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -40,6 +40,7 @@ esphome/components/cover/* @esphome/core esphome/components/cs5460a/* @balrog-kun esphome/components/ct_clamp/* @jesserockz esphome/components/daly_bms/* @s1lvi0 +esphome/components/dashboard_import/* @esphome/core esphome/components/debug/* @OttoWinter esphome/components/dfplayer/* @glmnet esphome/components/dht/* @OttoWinter diff --git a/esphome/components/dashboard_import/__init__.py b/esphome/components/dashboard_import/__init__.py new file mode 100644 index 0000000000..2b884d3b9a --- /dev/null +++ b/esphome/components/dashboard_import/__init__.py @@ -0,0 +1,45 @@ +from pathlib import Path + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components.packages import validate_source_shorthand +from esphome.yaml_util import dump + + +dashboard_import_ns = cg.esphome_ns.namespace("dashboard_import") + +# payload is in `esphomelib` mdns record, which only exists if api +# is enabled +DEPENDENCIES = ["api"] +CODEOWNERS = ["@esphome/core"] + + +def validate_import_url(value): + value = cv.string_strict(value) + value = cv.Length(max=255)(value) + # ignore result, only check if it's a valid shorthand + validate_source_shorthand(value) + return value + + +CONF_PACKAGE_IMPORT_URL = "package_import_url" +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_PACKAGE_IMPORT_URL): validate_import_url, + } +) + + +async def to_code(config): + cg.add_define("USE_DASHBOARD_IMPORT") + cg.add(dashboard_import_ns.set_package_import_url(config[CONF_PACKAGE_IMPORT_URL])) + + +def import_config(path: str, name: str, project_name: str, import_url: str) -> None: + p = Path(path) + + if p.exists(): + raise FileExistsError + + config = {"substitutions": {"name": name}, "packages": {project_name: import_url}} + p.write_text(dump(config), encoding="utf8") diff --git a/esphome/components/dashboard_import/dashboard_import.cpp b/esphome/components/dashboard_import/dashboard_import.cpp new file mode 100644 index 0000000000..6875fd61a5 --- /dev/null +++ b/esphome/components/dashboard_import/dashboard_import.cpp @@ -0,0 +1,12 @@ +#include "dashboard_import.h" + +namespace esphome { +namespace dashboard_import { + +static std::string g_package_import_url; // NOLINT + +std::string get_package_import_url() { return g_package_import_url; } +void set_package_import_url(std::string url) { g_package_import_url = std::move(url); } + +} // namespace dashboard_import +} // namespace esphome diff --git a/esphome/components/dashboard_import/dashboard_import.h b/esphome/components/dashboard_import/dashboard_import.h new file mode 100644 index 0000000000..0ca2994aab --- /dev/null +++ b/esphome/components/dashboard_import/dashboard_import.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace esphome { +namespace dashboard_import { + +std::string get_package_import_url(); +void set_package_import_url(std::string url); + +} // namespace dashboard_import +} // namespace esphome diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index c742fe3948..372d980eb0 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -6,6 +6,9 @@ #ifdef USE_API #include "esphome/components/api/api_server.h" #endif +#ifdef USE_DASHBOARD_IMPORT +#include "esphome/components/dashboard_import/dashboard_import.h" +#endif namespace esphome { namespace mdns { @@ -42,6 +45,11 @@ std::vector MDNSComponent::compile_services_() { service.txt_records.push_back({"project_name", ESPHOME_PROJECT_NAME}); service.txt_records.push_back({"project_version", ESPHOME_PROJECT_VERSION}); #endif // ESPHOME_PROJECT_NAME + +#ifdef USE_DASHBOARD_IMPORT + service.txt_records.push_back({"package_import_url", dashboard_import::get_package_import_url()}); +#endif + res.push_back(service); } #endif // USE_API diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 62468dfbfe..1c3b17d071 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -67,3 +67,5 @@ // Disabled feature flags //#define USE_BSEC // Requires a library with proprietary license. + +#define USE_DASHBOARD_IMPORT diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index bfe6808729..492b86384d 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, EsphomeZeroconf +from esphome.zeroconf import DashboardImportDiscovery, DashboardStatus, EsphomeZeroconf _LOGGER = logging.getLogger(__name__) @@ -154,9 +154,6 @@ def is_authenticated(request_handler): def bind_config(func): def decorator(self, *args, **kwargs): configuration = self.get_argument("configuration") - if not is_allowed(configuration): - self.set_status(500) - return None kwargs = kwargs.copy() kwargs["configuration"] = configuration return func(self, *args, **kwargs) @@ -363,8 +360,8 @@ 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: v + for k, v in json.loads(self.request.body.decode()).items() if k in ("name", "platform", "board", "ssid", "psk", "password") } kwargs["ota_password"] = secrets.token_hex(16) @@ -374,6 +371,29 @@ class WizardRequestHandler(BaseHandler): self.finish() +class ImportRequestHandler(BaseHandler): + @authenticated + def post(self): + from esphome.components.dashboard_import import import_config + + args = json.loads(self.request.body.decode()) + try: + name = args["name"] + import_config( + settings.rel_path(f"{name}.yaml"), + name, + args["project_name"], + args["package_import_url"], + ) + except FileExistsError: + self.set_status(500) + self.write("File already exists") + return + + self.set_status(200) + self.finish() + + class DownloadBinaryRequestHandler(BaseHandler): @authenticated @bind_config @@ -469,15 +489,51 @@ class DashboardEntry: return self.storage.loaded_integrations +class ListDevicesHandler(BaseHandler): + @authenticated + def get(self): + entries = _list_dashboard_entries() + self.set_header("content-type", "application/json") + configured = {entry.name for entry in entries} + self.write( + json.dumps( + { + "configured": [ + { + "name": entry.name, + "configuration": entry.filename, + "loaded_integrations": entry.loaded_integrations, + "deployed_version": entry.update_old, + "current_version": entry.update_new, + "path": entry.path, + "comment": entry.comment, + "address": entry.address, + "target_platform": entry.target_platform, + } + for entry in entries + ], + "importable": [ + { + "name": res.device_name, + "package_import_url": res.package_import_url, + "project_name": res.project_name, + "project_version": res.project_version, + } + for res in IMPORT_RESULT.values() + if res.device_name not in configured + ], + } + ) + ) + + class MainRequestHandler(BaseHandler): @authenticated def get(self): begin = bool(self.get_argument("begin", False)) - entries = _list_dashboard_entries() self.render( get_template_path("index"), - entries=entries, begin=begin, **template_args(), login_enabled=settings.using_auth, @@ -495,6 +551,8 @@ def _ping_func(filename, address): class MDNSStatusThread(threading.Thread): def run(self): + global IMPORT_RESULT + zc = EsphomeZeroconf() def on_update(dat): @@ -502,17 +560,22 @@ class MDNSStatusThread(threading.Thread): PING_RESULT[key] = b stat = DashboardStatus(zc, on_update) + imports = DashboardImportDiscovery(zc) + stat.start() while not STOP_EVENT.is_set(): entries = _list_dashboard_entries() stat.request_query( {entry.filename: f"{entry.name}.local." for entry in entries} ) + IMPORT_RESULT = imports.import_state PING_REQUEST.wait() PING_REQUEST.clear() + stat.stop() stat.join() + imports.cancel() zc.close() @@ -567,10 +630,6 @@ class PingRequestHandler(BaseHandler): self.write(json.dumps(PING_RESULT)) -def is_allowed(configuration): - return os.path.sep not in configuration - - class InfoRequestHandler(BaseHandler): @authenticated @bind_config @@ -613,20 +672,18 @@ class DeleteRequestHandler(BaseHandler): def post(self, configuration=None): config_file = settings.rel_path(configuration) storage_path = ext_storage_path(settings.config_dir, configuration) - storage_json = StorageJSON.load(storage_path) - if storage_json is None: - self.set_status(500) - return - name = storage_json.name trash_path = trash_storage_path(settings.config_dir) mkdir_p(trash_path) shutil.move(config_file, os.path.join(trash_path, configuration)) - # Delete build folder (if exists) - build_folder = os.path.join(settings.config_dir, name) - if build_folder is not None: - shutil.rmtree(build_folder, os.path.join(trash_path, name)) + storage_json = StorageJSON.load(storage_path) + if storage_json is not None: + # Delete build folder (if exists) + name = storage_json.name + build_folder = os.path.join(settings.config_dir, name) + if build_folder is not None: + shutil.rmtree(build_folder, os.path.join(trash_path, name)) class UndoDeleteRequestHandler(BaseHandler): @@ -639,6 +696,7 @@ class UndoDeleteRequestHandler(BaseHandler): PING_RESULT = {} # type: dict +IMPORT_RESULT = {} STOP_EVENT = threading.Event() PING_REQUEST = threading.Event() @@ -808,8 +866,10 @@ def make_app(debug=get_bool_env(ENV_DEV)): (f"{rel}ping", PingRequestHandler), (f"{rel}delete", DeleteRequestHandler), (f"{rel}undo-delete", UndoDeleteRequestHandler), - (f"{rel}wizard.html", WizardRequestHandler), + (f"{rel}wizard", WizardRequestHandler), (f"{rel}static/(.*)", StaticFileHandler, {"path": get_static_path()}), + (f"{rel}devices", ListDevicesHandler), + (f"{rel}import", ImportRequestHandler), ], **app_settings, ) diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index e6853531f2..a19fc143ec 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -2,6 +2,8 @@ import socket import threading import time from typing import Dict, Optional +import logging +from dataclasses import dataclass from zeroconf import ( DNSAddress, @@ -10,11 +12,14 @@ from zeroconf import ( DNSQuestion, RecordUpdateListener, Zeroconf, + ServiceBrowser, ) +from zeroconf._services import ServiceStateChange _CLASS_IN = 1 _FLAGS_QR_QUERY = 0x0000 # query _TYPE_A = 1 +_LOGGER = logging.getLogger(__name__) class HostResolver(RecordUpdateListener): @@ -57,7 +62,7 @@ class HostResolver(RecordUpdateListener): return True -class DashboardStatus(RecordUpdateListener, threading.Thread): +class DashboardStatus(threading.Thread): PING_AFTER = 15 * 1000 # Send new mDNS request after 15 seconds OFFLINE_AFTER = PING_AFTER * 2 # Offline if no mDNS response after 30 seconds @@ -70,9 +75,6 @@ class DashboardStatus(RecordUpdateListener, threading.Thread): self.query_event = threading.Event() self.on_update = on_update - def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None: - pass - def request_query(self, hosts: Dict[str, str]) -> None: self.query_hosts = set(hosts.values()) self.key_to_host = hosts @@ -93,7 +95,6 @@ class DashboardStatus(RecordUpdateListener, threading.Thread): ) def run(self) -> None: - self.zc.add_listener(self, None) while not self.stop_event.is_set(): self.on_update( {key: self.host_status(host) for key, host in self.key_to_host.items()} @@ -110,7 +111,75 @@ class DashboardStatus(RecordUpdateListener, threading.Thread): self.zc.send(out) self.query_event.wait() self.query_event.clear() - self.zc.remove_listener(self) + + +ESPHOME_SERVICE_TYPE = "_esphomelib._tcp.local." +TXT_RECORD_PACKAGE_IMPORT_URL = b"package_import_url" +TXT_RECORD_PROJECT_NAME = b"project_name" +TXT_RECORD_PROJECT_VERSION = b"project_version" + + +@dataclass +class DiscoveredImport: + device_name: str + package_import_url: str + project_name: str + project_version: str + + +class DashboardImportDiscovery: + def __init__(self, zc: Zeroconf) -> None: + self.zc = zc + self.service_browser = ServiceBrowser( + self.zc, ESPHOME_SERVICE_TYPE, [self._on_update] + ) + self.import_state = {} + + def _on_update( + self, + zeroconf: Zeroconf, + service_type: str, + name: str, + state_change: ServiceStateChange, + ) -> None: + _LOGGER.debug( + "service_update: type=%s name=%s state_change=%s", + service_type, + name, + state_change, + ) + if service_type != ESPHOME_SERVICE_TYPE: + return + if state_change == ServiceStateChange.Removed: + self.import_state.pop(name, None) + + info = zeroconf.get_service_info(service_type, name) + _LOGGER.debug("-> resolved info: %s", info) + if info is None: + return + node_name = name[: -len(ESPHOME_SERVICE_TYPE) - 1] + required_keys = [ + TXT_RECORD_PACKAGE_IMPORT_URL, + TXT_RECORD_PROJECT_NAME, + TXT_RECORD_PROJECT_VERSION, + ] + if any(key not in info.properties for key in required_keys): + # Not a dashboard import device + return + + import_url = info.properties[TXT_RECORD_PACKAGE_IMPORT_URL].decode() + project_name = info.properties[TXT_RECORD_PROJECT_NAME].decode() + project_version = info.properties[TXT_RECORD_PROJECT_VERSION].decode() + + self.import_state[name] = DiscoveredImport( + device_name=node_name, + package_import_url=import_url, + project_name=project_name, + project_version=project_version, + ) + + def cancel(self) -> None: + self.service_browser.cancel() class EsphomeZeroconf(Zeroconf): From b2d516c70a5b2357cf0c7c7cf394269c631e6653 Mon Sep 17 00:00:00 2001 From: Christian Taedcke Date: Mon, 27 Sep 2021 21:53:05 +0200 Subject: [PATCH 079/549] ccs811: Skip reading data if it is not available (#2404) On bootup the ccs811 reports that no data is available. No error flag is set in that case. The current implementation ignores this, reads and publishes the invalid data, which is 0xFDFD for both tvoc and co2 in my case. This commit fixes this and does not read and publish invalid data. --- esphome/components/ccs811/ccs811.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/ccs811/ccs811.cpp b/esphome/components/ccs811/ccs811.cpp index 6bdb4739cf..11a66f5100 100644 --- a/esphome/components/ccs811/ccs811.cpp +++ b/esphome/components/ccs811/ccs811.cpp @@ -86,8 +86,11 @@ void CCS811Component::setup() { } } void CCS811Component::update() { - if (!this->status_has_data_()) + if (!this->status_has_data_()) { + ESP_LOGD(TAG, "Status indicates no data ready!"); this->status_set_warning(); + return; + } // page 12 - alg result data auto alg_data = this->read_bytes<4>(0x02); From 8c86a18dc6b3b8f62eed60f2310f638d2de313a5 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Mon, 27 Sep 2021 21:53:47 +0200 Subject: [PATCH 080/549] Add missing include for defines.h. (#2403) Co-authored-by: Maurice Makaay --- esphome/components/bme680_bsec/bme680_bsec.h | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/bme680_bsec/bme680_bsec.h b/esphome/components/bme680_bsec/bme680_bsec.h index 365aec725e..53bc5c3280 100644 --- a/esphome/components/bme680_bsec/bme680_bsec.h +++ b/esphome/components/bme680_bsec/bme680_bsec.h @@ -5,6 +5,7 @@ #include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/i2c/i2c.h" #include "esphome/core/preferences.h" +#include "esphome/core/defines.h" #include #ifdef USE_BSEC From 1ba560dc9e61ab3bbdc4d1ae35bcd98a0a6d1cdf Mon Sep 17 00:00:00 2001 From: irtimaled Date: Mon, 27 Sep 2021 12:54:51 -0700 Subject: [PATCH 081/549] fix: stop tuya light state getting reset (#2401) * fix: stop tuya light state getting reset * fix typo --- esphome/components/tuya/light/tuya_light.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index 97f6de9bae..f75cc964aa 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -111,6 +111,11 @@ void TuyaLight::write_state(light::LightState *state) { state->current_values_as_brightness(&brightness); } + if (!state->current_values.is_on() && this->switch_id_.has_value()) { + parent_->set_boolean_datapoint_value(*this->switch_id_, false); + return; + } + if (brightness > 0.0f || !color_interlock_) { if (this->color_temperature_id_.has_value()) { uint32_t color_temp_int = static_cast(color_temperature * this->color_temperature_max_value_); @@ -138,7 +143,7 @@ void TuyaLight::write_state(light::LightState *state) { } if (this->switch_id_.has_value()) { - parent_->set_boolean_datapoint_value(*this->switch_id_, state->current_values.is_on()); + parent_->set_boolean_datapoint_value(*this->switch_id_, true); } } From e30f17f64f9d634984ed421f18dde71bae6d3884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20P=C3=A9rez=20Ferro?= Date: Mon, 27 Sep 2021 22:22:45 +0200 Subject: [PATCH 082/549] Add Current based cover (#1439) * Adding first version of current_base cover. No Interlock yet. * simplifying code * Implementing malfunction protection * Adding test * Fixing too long lines * Fixing test sensor names * Adding missing id's in ade7953 tests * Adding code owners as requested * Fixing issue setting position when stop reached * Fixing issue setting position when stop reached * Black formatting * Fixing format issues * Fix for concurrent changes Co-authored-by: Oxan van Leeuwen --- CODEOWNERS | 1 + esphome/components/current_based/__init__.py | 1 + esphome/components/current_based/cover.py | 124 +++++++++ .../current_based/current_based_cover.cpp | 251 ++++++++++++++++++ .../current_based/current_based_cover.h | 95 +++++++ tests/test3.yaml | 28 ++ 6 files changed, 500 insertions(+) create mode 100644 esphome/components/current_based/__init__.py create mode 100644 esphome/components/current_based/cover.py create mode 100644 esphome/components/current_based/current_based_cover.cpp create mode 100644 esphome/components/current_based/current_based_cover.h diff --git a/CODEOWNERS b/CODEOWNERS index 0b974b95e6..3c3a1e04ee 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -39,6 +39,7 @@ esphome/components/coolix/* @glmnet esphome/components/cover/* @esphome/core esphome/components/cs5460a/* @balrog-kun esphome/components/ct_clamp/* @jesserockz +esphome/components/current_based/* @djwmarcx esphome/components/daly_bms/* @s1lvi0 esphome/components/dashboard_import/* @esphome/core esphome/components/debug/* @OttoWinter diff --git a/esphome/components/current_based/__init__.py b/esphome/components/current_based/__init__.py new file mode 100644 index 0000000000..e7f41154b7 --- /dev/null +++ b/esphome/components/current_based/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@djwmarcx"] diff --git a/esphome/components/current_based/cover.py b/esphome/components/current_based/cover.py new file mode 100644 index 0000000000..eb77a90aff --- /dev/null +++ b/esphome/components/current_based/cover.py @@ -0,0 +1,124 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.components import sensor, cover +from esphome.const import ( + CONF_CLOSE_ACTION, + CONF_CLOSE_DURATION, + CONF_ID, + CONF_OPEN_ACTION, + CONF_OPEN_DURATION, + CONF_STOP_ACTION, + CONF_MAX_DURATION, +) + + +CONF_OPEN_SENSOR = "open_sensor" +CONF_OPEN_MOVING_CURRENT_THRESHOLD = "open_moving_current_threshold" +CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD = "open_obstacle_current_threshold" + +CONF_CLOSE_SENSOR = "close_sensor" +CONF_CLOSE_MOVING_CURRENT_THRESHOLD = "close_moving_current_threshold" +CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD = "close_obstacle_current_threshold" + +CONF_OBSTACLE_ROLLBACK = "obstacle_rollback" +CONF_MALFUNCTION_DETECTION = "malfunction_detection" +CONF_MALFUNCTION_ACTION = "malfunction_action" +CONF_START_SENSING_DELAY = "start_sensing_delay" + +current_based_ns = cg.esphome_ns.namespace("current_based") +CurrentBasedCover = current_based_ns.class_( + "CurrentBasedCover", cover.Cover, cg.Component +) + +CONFIG_SCHEMA = cover.COVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CurrentBasedCover), + cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True), + cv.Required(CONF_OPEN_SENSOR): cv.use_id(sensor.Sensor), + cv.Required(CONF_OPEN_MOVING_CURRENT_THRESHOLD): cv.float_range( + min=0, min_included=False + ), + cv.Optional(CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD): cv.float_range( + min=0, min_included=False + ), + cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True), + cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds, + cv.Required(CONF_CLOSE_SENSOR): cv.use_id(sensor.Sensor), + cv.Required(CONF_CLOSE_MOVING_CURRENT_THRESHOLD): cv.float_range( + min=0, min_included=False + ), + cv.Optional(CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD): cv.float_range( + min=0, min_included=False + ), + cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True), + cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds, + cv.Optional(CONF_OBSTACLE_ROLLBACK, default="10%"): cv.percentage, + cv.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds, + cv.Optional(CONF_MALFUNCTION_DETECTION, default=True): cv.boolean, + cv.Optional(CONF_MALFUNCTION_ACTION): automation.validate_automation( + single=True + ), + cv.Optional( + CONF_START_SENSING_DELAY, default="500ms" + ): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield cover.register_cover(var, config) + + yield automation.build_automation( + var.get_stop_trigger(), [], config[CONF_STOP_ACTION] + ) + + # OPEN + bin = yield cg.get_variable(config[CONF_OPEN_SENSOR]) + cg.add(var.set_open_sensor(bin)) + cg.add( + var.set_open_moving_current_threshold( + config[CONF_OPEN_MOVING_CURRENT_THRESHOLD] + ) + ) + if CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD in config: + cg.add( + var.set_open_obstacle_current_threshold( + config[CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD] + ) + ) + cg.add(var.set_open_duration(config[CONF_OPEN_DURATION])) + yield automation.build_automation( + var.get_open_trigger(), [], config[CONF_OPEN_ACTION] + ) + + # CLOSE + bin = yield cg.get_variable(config[CONF_CLOSE_SENSOR]) + cg.add(var.set_close_sensor(bin)) + cg.add( + var.set_close_moving_current_threshold( + config[CONF_CLOSE_MOVING_CURRENT_THRESHOLD] + ) + ) + if CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD in config: + cg.add( + var.set_close_obstacle_current_threshold( + config[CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD] + ) + ) + cg.add(var.set_close_duration(config[CONF_CLOSE_DURATION])) + yield automation.build_automation( + var.get_close_trigger(), [], config[CONF_CLOSE_ACTION] + ) + + cg.add(var.set_obstacle_rollback(config[CONF_OBSTACLE_ROLLBACK])) + if CONF_MAX_DURATION in config: + cg.add(var.set_max_duration(config[CONF_MAX_DURATION])) + cg.add(var.set_malfunction_detection(config[CONF_MALFUNCTION_DETECTION])) + if CONF_MALFUNCTION_ACTION in config: + yield automation.build_automation( + var.get_malfunction_trigger(), [], config[CONF_MALFUNCTION_ACTION] + ) + cg.add(var.set_start_sensing_delay(config[CONF_START_SENSING_DELAY])) diff --git a/esphome/components/current_based/current_based_cover.cpp b/esphome/components/current_based/current_based_cover.cpp new file mode 100644 index 0000000000..9f0a59377d --- /dev/null +++ b/esphome/components/current_based/current_based_cover.cpp @@ -0,0 +1,251 @@ +#include "current_based_cover.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace current_based { + +static const char *const TAG = "current_based.cover"; + +using namespace esphome::cover; + +CoverTraits CurrentBasedCover::get_traits() { + auto traits = CoverTraits(); + traits.set_supports_position(true); + traits.set_is_assumed_state(false); + return traits; +} +void CurrentBasedCover::control(const CoverCall &call) { + if (call.get_stop()) { + this->direction_idle_(); + } + if (call.get_position().has_value()) { + auto pos = *call.get_position(); + if (pos == this->position) { + // already at target + } else { + auto op = pos < this->position ? COVER_OPERATION_CLOSING : COVER_OPERATION_OPENING; + this->target_position_ = pos; + this->start_direction_(op); + } + } +} +void CurrentBasedCover::setup() { + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->apply(this); + } else { + this->position = 0.5f; + } +} + +void CurrentBasedCover::loop() { + if (this->current_operation == COVER_OPERATION_IDLE) + return; + + const uint32_t now = millis(); + + if (this->current_operation == COVER_OPERATION_OPENING) { + if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction + this->direction_idle_(); + this->malfunction_trigger_->trigger(); + ESP_LOGI(TAG, "'%s' - Malfunction detected during opening. Current flow detected in close circuit", + this->name_.c_str()); + } else if (this->is_opening_blocked_()) { // Blocked + ESP_LOGD(TAG, "'%s' - Obstacle detected during opening.", this->name_.c_str()); + this->direction_idle_(); + if (this->obstacle_rollback_ != 0) { + this->set_timeout("rollback", 300, [this]() { + ESP_LOGD(TAG, "'%s' - Rollback.", this->name_.c_str()); + this->target_position_ = clamp(this->position - this->obstacle_rollback_, 0.0F, 1.0F); + this->start_direction_(COVER_OPERATION_CLOSING); + }); + } + } else if (this->is_initial_delay_finished_() && !this->is_opening_()) { // End reached + auto dur = (now - this->start_dir_time_) / 1e3f; + ESP_LOGD(TAG, "'%s' - Open position reached. Took %.1fs.", this->name_.c_str(), dur); + this->direction_idle_(COVER_OPEN); + } + } else if (this->current_operation == COVER_OPERATION_CLOSING) { + if (this->malfunction_detection_ && this->is_opening_()) { // Malfunction + this->direction_idle_(); + this->malfunction_trigger_->trigger(); + ESP_LOGI(TAG, "'%s' - Malfunction detected during closing. Current flow detected in open circuit", + this->name_.c_str()); + } else if (this->is_closing_blocked_()) { // Blocked + ESP_LOGD(TAG, "'%s' - Obstacle detected during closing.", this->name_.c_str()); + this->direction_idle_(); + if (this->obstacle_rollback_ != 0) { + this->set_timeout("rollback", 300, [this]() { + ESP_LOGD(TAG, "'%s' - Rollback.", this->name_.c_str()); + this->target_position_ = clamp(this->position + this->obstacle_rollback_, 0.0F, 1.0F); + this->start_direction_(COVER_OPERATION_OPENING); + }); + } + } else if (this->is_initial_delay_finished_() && !this->is_closing_()) { // End reached + auto dur = (now - this->start_dir_time_) / 1e3f; + ESP_LOGD(TAG, "'%s' - Close position reached. Took %.1fs.", this->name_.c_str(), dur); + this->direction_idle_(COVER_CLOSED); + } + } else if (now - this->start_dir_time_ > this->max_duration_) { + ESP_LOGD(TAG, "'%s' - Max duration reached. Stopping cover.", this->name_.c_str()); + this->direction_idle_(); + } + + // Recompute position every loop cycle + this->recompute_position_(); + + if (this->current_operation != COVER_OPERATION_IDLE && this->is_at_target_()) { + this->direction_idle_(); + } + + // Send current position every second + if (this->current_operation != COVER_OPERATION_IDLE && now - this->last_publish_time_ > 1000) { + this->publish_state(false); + this->last_publish_time_ = now; + } +} + +void CurrentBasedCover::direction_idle_(float new_position) { + this->start_direction_(COVER_OPERATION_IDLE); + if (new_position != FLT_MAX) { + this->position = new_position; + } + this->publish_state(); +} + +void CurrentBasedCover::dump_config() { + LOG_COVER("", "Endstop Cover", this); + LOG_SENSOR(" ", "Open Sensor", this->open_sensor_); + ESP_LOGCONFIG(TAG, " Open moving current threshold: %.11fA", this->open_moving_current_threshold_); + if (this->open_obstacle_current_threshold_ != FLT_MAX) { + ESP_LOGCONFIG(TAG, " Open obstacle current threshold: %.11fA", this->open_obstacle_current_threshold_); + } + ESP_LOGCONFIG(TAG, " Open Duration: %.1fs", this->open_duration_ / 1e3f); + LOG_SENSOR(" ", "Close Sensor", this->close_sensor_); + ESP_LOGCONFIG(TAG, " Close moving current threshold: %.11fA", this->close_moving_current_threshold_); + if (this->close_obstacle_current_threshold_ != FLT_MAX) { + ESP_LOGCONFIG(TAG, " Close obstacle current threshold: %.11fA", this->close_obstacle_current_threshold_); + } + ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f); + ESP_LOGCONFIG(TAG, "Obstacle Rollback: %.1f%%", this->obstacle_rollback_ * 100); + if (this->max_duration_ != UINT32_MAX) { + ESP_LOGCONFIG(TAG, "Maximun duration: %.1fs", this->max_duration_ / 1e3f); + } + ESP_LOGCONFIG(TAG, "Start sensing delay: %.1fs", this->start_sensing_delay_ / 1e3f); + ESP_LOGCONFIG(TAG, "Malfunction detection: %s", YESNO(this->malfunction_detection_)); +} + +float CurrentBasedCover::get_setup_priority() const { return setup_priority::DATA; } +void CurrentBasedCover::stop_prev_trigger_() { + if (this->prev_command_trigger_ != nullptr) { + this->prev_command_trigger_->stop_action(); + this->prev_command_trigger_ = nullptr; + } +} + +bool CurrentBasedCover::is_opening_() const { + return this->open_sensor_->get_state() > this->open_moving_current_threshold_; +} + +bool CurrentBasedCover::is_opening_blocked_() const { + if (this->open_obstacle_current_threshold_ == FLT_MAX) { + return false; + } + return this->open_sensor_->get_state() > this->open_obstacle_current_threshold_; +} + +bool CurrentBasedCover::is_closing_() const { + return this->close_sensor_->get_state() > this->close_moving_current_threshold_; +} + +bool CurrentBasedCover::is_closing_blocked_() const { + if (this->close_obstacle_current_threshold_ == FLT_MAX) { + return false; + } + return this->open_sensor_->get_state() > this->open_obstacle_current_threshold_; +} +bool CurrentBasedCover::is_initial_delay_finished_() const { + return millis() - this->start_dir_time_ > this->start_sensing_delay_; +} + +bool CurrentBasedCover::is_at_target_() const { + switch (this->current_operation) { + case COVER_OPERATION_OPENING: + if (this->target_position_ == COVER_OPEN) { + if (!this->is_initial_delay_finished_()) // During initial delay, state is assumed + return false; + return !this->is_opening_(); + } + return this->position >= this->target_position_; + case COVER_OPERATION_CLOSING: + if (this->target_position_ == COVER_CLOSED) { + if (!this->is_initial_delay_finished_()) // During initial delay, state is assumed + return false; + return !this->is_closing_(); + } + return this->position <= this->target_position_; + case COVER_OPERATION_IDLE: + default: + return true; + } +} +void CurrentBasedCover::start_direction_(CoverOperation dir) { + if (dir == this->current_operation) + return; + + this->recompute_position_(); + Trigger<> *trig; + switch (dir) { + case COVER_OPERATION_IDLE: + trig = this->stop_trigger_; + break; + case COVER_OPERATION_OPENING: + trig = this->open_trigger_; + break; + case COVER_OPERATION_CLOSING: + trig = this->close_trigger_; + break; + default: + return; + } + + this->current_operation = dir; + + this->stop_prev_trigger_(); + trig->trigger(); + this->prev_command_trigger_ = trig; + + const auto now = millis(); + this->start_dir_time_ = now; + this->last_recompute_time_ = now; +} +void CurrentBasedCover::recompute_position_() { + if (this->current_operation == COVER_OPERATION_IDLE) + return; + + float dir; + float action_dur; + switch (this->current_operation) { + case COVER_OPERATION_OPENING: + dir = 1.0F; + action_dur = this->open_duration_; + break; + case COVER_OPERATION_CLOSING: + dir = -1.0F; + action_dur = this->close_duration_; + break; + default: + return; + } + + const auto now = millis(); + this->position += dir * (now - this->last_recompute_time_) / action_dur; + this->position = clamp(this->position, 0.0F, 1.0F); + + this->last_recompute_time_ = now; +} + +} // namespace current_based +} // namespace esphome diff --git a/esphome/components/current_based/current_based_cover.h b/esphome/components/current_based/current_based_cover.h new file mode 100644 index 0000000000..220b770c05 --- /dev/null +++ b/esphome/components/current_based/current_based_cover.h @@ -0,0 +1,95 @@ +#pragma once + +#include "esphome/components/cover/cover.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include + +namespace esphome { +namespace current_based { + +class CurrentBasedCover : public cover::Cover, public Component { + public: + void setup() override; + void loop() override; + void dump_config() override; + float get_setup_priority() const override; + + Trigger<> *get_stop_trigger() const { return this->stop_trigger_; } + + Trigger<> *get_open_trigger() const { return this->open_trigger_; } + void set_open_sensor(sensor::Sensor *open_sensor) { this->open_sensor_ = open_sensor; } + void set_open_moving_current_threshold(float open_moving_current_threshold) { + this->open_moving_current_threshold_ = open_moving_current_threshold; + } + void set_open_obstacle_current_threshold(float open_obstacle_current_threshold) { + this->open_obstacle_current_threshold_ = open_obstacle_current_threshold; + } + void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; } + + Trigger<> *get_close_trigger() const { return this->close_trigger_; } + void set_close_sensor(sensor::Sensor *close_sensor) { this->close_sensor_ = close_sensor; } + void set_close_moving_current_threshold(float close_moving_current_threshold) { + this->close_moving_current_threshold_ = close_moving_current_threshold; + } + void set_close_obstacle_current_threshold(float close_obstacle_current_threshold) { + this->close_obstacle_current_threshold_ = close_obstacle_current_threshold; + } + void set_close_duration(uint32_t close_duration) { this->close_duration_ = close_duration; } + + void set_max_duration(uint32_t max_duration) { this->max_duration_ = max_duration; } + void set_obstacle_rollback(float obstacle_rollback) { this->obstacle_rollback_ = obstacle_rollback; } + + void set_malfunction_detection(bool malfunction_detection) { this->malfunction_detection_ = malfunction_detection; } + void set_start_sensing_delay(uint32_t start_sensing_delay) { this->start_sensing_delay_ = start_sensing_delay; } + + Trigger<> *get_malfunction_trigger() const { return this->malfunction_trigger_; } + + cover::CoverTraits get_traits() override; + + protected: + void control(const cover::CoverCall &call) override; + void stop_prev_trigger_(); + + bool is_at_target_() const; + bool is_opening_() const; + bool is_opening_blocked_() const; + bool is_closing_() const; + bool is_closing_blocked_() const; + bool is_initial_delay_finished_() const; + + void direction_idle_(float new_position = FLT_MAX); + void start_direction_(cover::CoverOperation dir); + + void recompute_position_(); + + Trigger<> *stop_trigger_{new Trigger<>()}; + + sensor::Sensor *open_sensor_{nullptr}; + Trigger<> *open_trigger_{new Trigger<>()}; + float open_moving_current_threshold_; + float open_obstacle_current_threshold_{FLT_MAX}; + uint32_t open_duration_; + + sensor::Sensor *close_sensor_{nullptr}; + Trigger<> *close_trigger_{new Trigger<>()}; + float close_moving_current_threshold_; + float close_obstacle_current_threshold_{FLT_MAX}; + uint32_t close_duration_; + + uint32_t max_duration_{UINT32_MAX}; + bool malfunction_detection_{true}; + Trigger<> *malfunction_trigger_{new Trigger<>()}; + uint32_t start_sensing_delay_; + float obstacle_rollback_; + + Trigger<> *prev_command_trigger_{nullptr}; + uint32_t last_recompute_time_{0}; + uint32_t start_dir_time_{0}; + uint32_t last_publish_time_{0}; + float target_position_{0}; +}; + +} // namespace current_based +} // namespace esphome diff --git a/tests/test3.yaml b/tests/test3.yaml index 386775749d..49f48f4cfa 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -426,14 +426,19 @@ sensor: irq_pin: GPIO16 voltage: name: ADE7953 Voltage + id: ade7953_voltage current_a: name: ADE7953 Current A + id: ade7953_current_a current_b: name: ADE7953 Current B + id: ade7953_current_b active_power_a: name: ADE7953 Active Power A + id: ade7953_active_power_a active_power_b: name: ADE7953 Active Power B + id: ade7953_active_power_b - platform: pzem004t uart_id: uart3 voltage: @@ -1021,6 +1026,29 @@ cover: close_action: - switch.turn_on: gpio_switch2 close_duration: 4.5min + - platform: current_based + name: "Current Based Cover" + open_sensor: ade7953_current_a + open_moving_current_threshold: 0.5 + open_obstacle_current_threshold: 0.8 + open_duration: 12s + open_action: + - switch.turn_on: gpio_switch1 + close_sensor: ade7953_current_b + close_moving_current_threshold: 0.5 + close_obstacle_current_threshold: 0.8 + close_duration: 10s + close_action: + - switch.turn_on: gpio_switch2 + stop_action: + - switch.turn_off: gpio_switch1 + - switch.turn_off: gpio_switch2 + obstacle_rollback: 30% + start_sensing_delay: 0.8s + malfunction_detection: true + malfunction_action: + then: + - logger.log: "Malfunction Detected" - platform: template name: Template Cover with Tilt tilt_lambda: 'return 0.5;' From 2eb5f89d82bb9ac61aebb096751005f9c5769c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Mon, 27 Sep 2021 22:31:15 +0200 Subject: [PATCH 083/549] Add cover toggle support (#1809) * Add cover toggle support Step through open/stop/close/stop sequence with every toggle * Move the cover toggle logic to perform() * Add clang-tidy CI suggestion * Implement cover toggle action as cover trait * Handle toggle correctly if cover fully closed on POR * Fix CI finding * Add deprecated warning * Don't add already deprecated interface Co-authored-by: Oxan van Leeuwen * Don't add already deprecated interface Co-authored-by: Oxan van Leeuwen * Don't add already deprecated interface Co-authored-by: Oxan van Leeuwen Co-authored-by: Mueller, Daniel Co-authored-by: Oxan van Leeuwen --- esphome/components/cover/__init__.py | 7 +++++++ esphome/components/cover/automation.h | 10 ++++++++++ esphome/components/cover/cover.cpp | 20 +++++++++++++++++++ esphome/components/cover/cover.h | 6 +++++- esphome/components/cover/cover_traits.h | 3 +++ .../time_based/time_based_cover.cpp | 17 ++++++++++++++++ .../components/time_based/time_based_cover.h | 1 + tests/test3.yaml | 7 +++++++ 8 files changed, 70 insertions(+), 1 deletion(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 6fd6ac81b0..46b8906adb 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -59,6 +59,7 @@ validate_cover_operation = cv.enum(COVER_OPERATIONS, upper=True) OpenAction = cover_ns.class_("OpenAction", automation.Action) CloseAction = cover_ns.class_("CloseAction", automation.Action) StopAction = cover_ns.class_("StopAction", automation.Action) +ToggleAction = cover_ns.class_("ToggleAction", automation.Action) ControlAction = cover_ns.class_("ControlAction", automation.Action) CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action) CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition) @@ -119,6 +120,12 @@ async def cover_stop_to_code(config, action_id, template_arg, args): return cg.new_Pvariable(action_id, template_arg, paren) +@automation.register_action("cover.toggle", ToggleAction, COVER_ACTION_SCHEMA) +def cover_toggle_to_code(config, action_id, template_arg, args): + paren = yield cg.get_variable(config[CONF_ID]) + yield cg.new_Pvariable(action_id, template_arg, paren) + + COVER_CONTROL_ACTION_SCHEMA = cv.Schema( { cv.Required(CONF_ID): cv.use_id(Cover), diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index 0b364e1e09..79bca6826e 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -37,6 +37,16 @@ template class StopAction : public Action { Cover *cover_; }; +template class ToggleAction : public Action { + public: + explicit ToggleAction(Cover *cover) : cover_(cover) {} + + void play(Ts... x) override { this->cover_->make_call().set_command_toggle().perform(); } + + protected: + Cover *cover_; +}; + template class ControlAction : public Action { public: explicit ControlAction(Cover *cover) : cover_(cover) {} diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index e1ce211b64..863adb1d81 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -43,6 +43,8 @@ CoverCall &CoverCall::set_command(const char *command) { this->set_command_close(); } else if (strcasecmp(command, "STOP") == 0) { this->set_command_stop(); + } else if (strcasecmp(command, "TOGGLE") == 0) { + this->set_command_toggle(); } else { ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command); } @@ -60,6 +62,10 @@ CoverCall &CoverCall::set_command_stop() { this->stop_ = true; return *this; } +CoverCall &CoverCall::set_command_toggle() { + this->toggle_ = true; + return *this; +} CoverCall &CoverCall::set_position(float position) { this->position_ = position; return *this; @@ -85,10 +91,14 @@ void CoverCall::perform() { if (this->tilt_.has_value()) { ESP_LOGD(TAG, " Tilt: %.0f%%", *this->tilt_ * 100.0f); } + if (this->toggle_.has_value()) { + ESP_LOGD(TAG, " Command: TOGGLE"); + } this->parent_->control(*this); } const optional &CoverCall::get_position() const { return this->position_; } const optional &CoverCall::get_tilt() const { return this->tilt_; } +const optional &CoverCall::get_toggle() const { return this->toggle_; } void CoverCall::validate_() { auto traits = this->parent_->get_traits(); if (this->position_.has_value()) { @@ -111,6 +121,12 @@ void CoverCall::validate_() { this->tilt_ = clamp(tilt, 0.0f, 1.0f); } } + if (this->toggle_.has_value()) { + if (!traits.get_supports_toggle()) { + ESP_LOGW(TAG, "'%s' - This cover device does not support toggle!", this->parent_->get_name().c_str()); + this->toggle_.reset(); + } + } if (this->stop_) { if (this->position_.has_value()) { ESP_LOGW(TAG, "Cannot set position when stopping a cover!"); @@ -120,6 +136,10 @@ void CoverCall::validate_() { ESP_LOGW(TAG, "Cannot set tilt when stopping a cover!"); this->tilt_.reset(); } + if (this->toggle_.has_value()) { + ESP_LOGW(TAG, "Cannot set toggle when stopping a cover!"); + this->toggle_.reset(); + } } } CoverCall &CoverCall::set_stop(bool stop) { diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 72ec15a459..8f98a88a42 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -29,7 +29,7 @@ class CoverCall { public: CoverCall(Cover *parent); - /// Set the command as a string, "STOP", "OPEN", "CLOSE". + /// Set the command as a string, "STOP", "OPEN", "CLOSE", "TOGGLE". CoverCall &set_command(const char *command); /// Set the command to open the cover. CoverCall &set_command_open(); @@ -37,6 +37,8 @@ class CoverCall { CoverCall &set_command_close(); /// Set the command to stop the cover. CoverCall &set_command_stop(); + /// Set the command to toggle the cover. + CoverCall &set_command_toggle(); /// Set the call to a certain target position. CoverCall &set_position(float position); /// Set the call to a certain target tilt. @@ -50,6 +52,7 @@ class CoverCall { const optional &get_position() const; bool get_stop() const; const optional &get_tilt() const; + const optional &get_toggle() const; protected: void validate_(); @@ -58,6 +61,7 @@ class CoverCall { bool stop_{false}; optional position_{}; optional tilt_{}; + optional toggle_{}; }; /// Struct used to store the restored state of a cover diff --git a/esphome/components/cover/cover_traits.h b/esphome/components/cover/cover_traits.h index 2df4a0738e..fb30883f77 100644 --- a/esphome/components/cover/cover_traits.h +++ b/esphome/components/cover/cover_traits.h @@ -13,11 +13,14 @@ class CoverTraits { void set_supports_position(bool supports_position) { this->supports_position_ = supports_position; } bool get_supports_tilt() const { return this->supports_tilt_; } void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; } + bool get_supports_toggle() const { return this->supports_toggle_; } + void set_supports_toggle(bool supports_toggle) { this->supports_toggle_ = supports_toggle; } protected: bool is_assumed_state_{false}; bool supports_position_{false}; bool supports_tilt_{false}; + bool supports_toggle_{false}; }; } // namespace cover diff --git a/esphome/components/time_based/time_based_cover.cpp b/esphome/components/time_based/time_based_cover.cpp index 3fa07167ca..522252e907 100644 --- a/esphome/components/time_based/time_based_cover.cpp +++ b/esphome/components/time_based/time_based_cover.cpp @@ -52,6 +52,7 @@ float TimeBasedCover::get_setup_priority() const { return setup_priority::DATA; CoverTraits TimeBasedCover::get_traits() { auto traits = CoverTraits(); traits.set_supports_position(true); + traits.set_supports_toggle(true); traits.set_is_assumed_state(this->assumed_state_); return traits; } @@ -60,6 +61,20 @@ void TimeBasedCover::control(const CoverCall &call) { this->start_direction_(COVER_OPERATION_IDLE); this->publish_state(); } + if (call.get_toggle().has_value()) { + if (this->current_operation != COVER_OPERATION_IDLE) { + this->start_direction_(COVER_OPERATION_IDLE); + this->publish_state(); + } else { + if (this->position == COVER_CLOSED || this->last_operation_ == COVER_OPERATION_CLOSING) { + this->target_position_ = COVER_OPEN; + this->start_direction_(COVER_OPERATION_OPENING); + } else { + this->target_position_ = COVER_CLOSED; + this->start_direction_(COVER_OPERATION_CLOSING); + } + } + } if (call.get_position().has_value()) { auto pos = *call.get_position(); if (pos == this->position) { @@ -105,9 +120,11 @@ void TimeBasedCover::start_direction_(CoverOperation dir) { trig = this->stop_trigger_; break; case COVER_OPERATION_OPENING: + this->last_operation_ = dir; trig = this->open_trigger_; break; case COVER_OPERATION_CLOSING: + this->last_operation_ = dir; trig = this->close_trigger_; break; default: diff --git a/esphome/components/time_based/time_based_cover.h b/esphome/components/time_based/time_based_cover.h index 6c48c26ed1..517ab77cb3 100644 --- a/esphome/components/time_based/time_based_cover.h +++ b/esphome/components/time_based/time_based_cover.h @@ -45,6 +45,7 @@ class TimeBasedCover : public cover::Cover, public Component { float target_position_{0}; bool has_built_in_endstop_{false}; bool assumed_state_{false}; + cover::CoverOperation last_operation_{cover::COVER_OPERATION_OPENING}; }; } // namespace time_based diff --git a/tests/test3.yaml b/tests/test3.yaml index 49f48f4cfa..b261d6cc8e 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -729,6 +729,12 @@ binary_sensor: id: r0_sensor name: 'R0 Sensor' component_name: page0.r0 + - platform: template + id: 'cover_toggle' + on_press: + then: + - cover.toggle: time_based_cover + globals: - id: my_global_string type: std::string @@ -1018,6 +1024,7 @@ cover: max_duration: 10min - platform: time_based name: Time Based Cover + id: time_based_cover stop_action: - switch.turn_on: gpio_switch1 open_action: From 5624fafb3a55662172a629bb09ea8a3ac105e8e5 Mon Sep 17 00:00:00 2001 From: 0hax <43876620+0hax@users.noreply.github.com> Date: Mon, 27 Sep 2021 22:32:08 +0200 Subject: [PATCH 084/549] Fix handling of timestamps in Teleinfo component. (#2392) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * teleinfo: avoid a buffer overflow. When reading tag or values, data is written to the buffer even if the size if bigger than the buffer. Add a new 'max_len' argument to get_field() to avoid this error. Signed-off-by: 0hax <0hax@protonmail.com> * teleinfo: read extra timestamp field for some tags. Some tags has an extra timestamp field that need to be read before the actual data. The code is inspired by Jpsy work: https://github.com/Jpsy/esphome/commit/29339c14f96ed7cf7a68911ca7d9bd5eb94955d6 Signed-off-by: 0hax <0hax@protonmail.com> * teleinfo: increase MAX_BUF_SIZE to suffice for 3-phase Linky in Standard mode. * teleinfo: handle DATE tag correctly. The DATE tag is special due its format and need to be handled separately. Fix from DrCoolzic. Signed-off-by: 0hax <0hax@protonmail.com> Co-authored-by: Jörg Wagner --- esphome/components/teleinfo/teleinfo.cpp | 37 +++++++++++++++++++++--- esphome/components/teleinfo/teleinfo.h | 4 ++- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/esphome/components/teleinfo/teleinfo.cpp b/esphome/components/teleinfo/teleinfo.cpp index 8240615cc5..badd66ae83 100644 --- a/esphome/components/teleinfo/teleinfo.cpp +++ b/esphome/components/teleinfo/teleinfo.cpp @@ -7,7 +7,7 @@ namespace teleinfo { static const char *const TAG = "teleinfo"; /* Helpers */ -static int get_field(char *dest, char *buf_start, char *buf_end, int sep) { +static int get_field(char *dest, char *buf_start, char *buf_end, int sep, int max_len) { char *field_end; int len; @@ -15,6 +15,8 @@ static int get_field(char *dest, char *buf_start, char *buf_end, int sep) { if (!field_end) return 0; len = field_end - buf_start; + if (len >= max_len) + return len; strncpy(dest, buf_start, len); dest[len] = '\0'; @@ -106,9 +108,22 @@ void TeleInfo::loop() { * 0xa | Tag | 0x9 | Data | 0x9 | CRC | 0xd * ^^^^^^^^^^^^^^^^^^^^^^^^^ * Checksum is computed on the above in standard mode. + * + * Note that some Tags may have a timestamp in Standard mode. In this case + * the group would looks like this: + * 0xa | Tag | 0x9 | Timestamp | 0x9 | Data | 0x9 | CRC | 0xd + * + * The DATE tag is a special case. The group looks like this + * 0xa | Tag | 0x9 | Timestamp | 0x9 | 0x9 | CRC | 0xd + * */ while ((buf_finger = static_cast(memchr(buf_finger, (int) 0xa, buf_index_ - 1))) && ((buf_finger - buf_) < buf_index_)) { + /* + * Make sure timesamp is nullified between each tag as some tags don't + * have a timestamp + */ + timestamp_[0] = '\0'; /* Point to the first char of the group after 0xa */ buf_finger += 1; @@ -123,7 +138,7 @@ void TeleInfo::loop() { continue; /* Get tag */ - field_len = get_field(tag_, buf_finger, grp_end, separator_); + field_len = get_field(tag_, buf_finger, grp_end, separator_, MAX_TAG_SIZE); if (!field_len || field_len >= MAX_TAG_SIZE) { ESP_LOGE(TAG, "Invalid tag."); break; @@ -132,8 +147,22 @@ void TeleInfo::loop() { /* Advance buf_finger to after the tag and the separator. */ buf_finger += field_len + 1; - /* Get value (after next separator) */ - field_len = get_field(val_, buf_finger, grp_end, separator_); + /* + * If there is two separators and the tag is not equal to "DATE", + * it means there is a timestamp to read first. + */ + if (std::count(buf_finger, grp_end, separator_) == 2 && strcmp(tag_, "DATE") != 0) { + field_len = get_field(timestamp_, buf_finger, grp_end, separator_, MAX_TIMESTAMP_SIZE); + if (!field_len || field_len >= MAX_TIMESTAMP_SIZE) { + ESP_LOGE(TAG, "Invalid Timestamp"); + break; + } + + /* Advance buf_finger to after the first data and the separator. */ + buf_finger += field_len + 1; + } + + field_len = get_field(val_, buf_finger, grp_end, separator_, MAX_VAL_SIZE); if (!field_len || field_len >= MAX_VAL_SIZE) { ESP_LOGE(TAG, "Invalid Value"); break; diff --git a/esphome/components/teleinfo/teleinfo.h b/esphome/components/teleinfo/teleinfo.h index f10024691e..2be34cfb78 100644 --- a/esphome/components/teleinfo/teleinfo.h +++ b/esphome/components/teleinfo/teleinfo.h @@ -11,7 +11,8 @@ namespace teleinfo { */ static const uint8_t MAX_TAG_SIZE = 64; static const uint16_t MAX_VAL_SIZE = 256; -static const uint16_t MAX_BUF_SIZE = 1024; +static const uint16_t MAX_BUF_SIZE = 2048; +static const uint16_t MAX_TIMESTAMP_SIZE = 14; class TeleInfoListener { public: @@ -36,6 +37,7 @@ class TeleInfo : public PollingComponent, public uart::UARTDevice { uint32_t buf_index_{0}; char tag_[MAX_TAG_SIZE]; char val_[MAX_VAL_SIZE]; + char timestamp_[MAX_TIMESTAMP_SIZE]; enum State { OFF, ON, From 6417d8132d392c9a43ec94aac661c569ad6fe146 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 27 Sep 2021 14:08:21 -0700 Subject: [PATCH 085/549] bump dashboard to 20210927.0 (#2405) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 537a802e06..7ee22f0415 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.0 esptool==3.1 click==8.0.1 -esphome-dashboard==20210908.0 +esphome-dashboard==20210927.0 aioesphomeapi==9.1.1 # esp-idf requires this, but doesn't bundle it by default From 5596751c2c0a99d1c4df1d281d943007653424dd Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 27 Sep 2021 23:24:55 +0200 Subject: [PATCH 086/549] Add str_sprintf function that returns std::string (#2408) --- esphome/core/helpers.cpp | 15 +++++++++++++++ esphome/core/helpers.h | 3 +++ 2 files changed, 18 insertions(+) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index a190566bea..0092d202c4 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -351,6 +351,21 @@ bool str_startswith(const std::string &full, const std::string &start) { return bool str_endswith(const std::string &full, const std::string &ending) { return full.rfind(ending) == (full.size() - ending.size()); } +std::string str_sprintf(const char *fmt, ...) { + std::string str; + va_list args; + + va_start(args, fmt); + size_t length = vsnprintf(nullptr, 0, fmt, args); + va_end(args); + + str.resize(length); + va_start(args, fmt); + vsnprintf(&str[0], length + 1, fmt, args); + va_end(args); + + return str; +} uint16_t encode_uint16(uint8_t msb, uint8_t lsb) { return (uint16_t(msb) << 8) | uint16_t(lsb); } std::array decode_uint16(uint16_t value) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 8118585db5..f5a7a197ca 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -59,6 +59,9 @@ bool str_equals_case_insensitive(const std::string &a, const std::string &b); bool str_startswith(const std::string &full, const std::string &start); bool str_endswith(const std::string &full, const std::string &ending); +/// sprintf-like function returning std::string instead of writing to char array. +std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...); + class HighFrequencyLoopRequester { public: void start(); From be965a60eba6bb769e2a5afdbc8eed132f077a59 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 28 Sep 2021 02:53:38 +0200 Subject: [PATCH 087/549] Merge pull request from GHSA-48mj-p7x2-5jfm * Move web_server auth to web_server_base * Fix * Fix * Add middleware system --- esphome/components/web_server/__init__.py | 8 +-- esphome/components/web_server/web_server.cpp | 7 -- esphome/components/web_server/web_server.h | 8 --- .../web_server_base/web_server_base.cpp | 11 +++ .../web_server_base/web_server_base.h | 72 +++++++++++++++++-- 5 files changed, 81 insertions(+), 25 deletions(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 7f17767657..240ba7c8a0 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -34,8 +34,8 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_JS_INCLUDE): cv.file_, cv.Optional(CONF_AUTH): cv.Schema( { - cv.Required(CONF_USERNAME): cv.string_strict, - cv.Required(CONF_PASSWORD): cv.string_strict, + cv.Required(CONF_USERNAME): cv.All(cv.string_strict, cv.Length(min=1)), + cv.Required(CONF_PASSWORD): cv.All(cv.string_strict, cv.Length(min=1)), } ), cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( @@ -57,8 +57,8 @@ async def to_code(config): cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) if CONF_AUTH in config: - cg.add(var.set_username(config[CONF_AUTH][CONF_USERNAME])) - cg.add(var.set_password(config[CONF_AUTH][CONF_PASSWORD])) + cg.add(paren.set_auth_username(config[CONF_AUTH][CONF_USERNAME])) + cg.add(paren.set_auth_password(config[CONF_AUTH][CONF_PASSWORD])) if CONF_CSS_INCLUDE in config: cg.add_define("WEBSERVER_CSS_INCLUDE") path = CORE.relative_config_path(config[CONF_CSS_INCLUDE]) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 97777a8986..28e741cf24 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -158,9 +158,6 @@ void WebServer::setup() { void WebServer::dump_config() { ESP_LOGCONFIG(TAG, "Web Server:"); 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"); - } } float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; } @@ -764,10 +761,6 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return false; } void WebServer::handleRequest(AsyncWebServerRequest *request) { - if (this->using_auth() && !request->authenticate(this->username_, this->password_)) { - return request->requestAuthentication(); - } - if (request->url() == "/") { this->handle_index_request(request); return; diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 0eaa2e9a75..021d5a0646 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -32,10 +32,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { public: WebServer(web_server_base::WebServerBase *base) : base_(base) {} - void set_username(const char *username) { username_ = username; } - - void set_password(const char *password) { password_ = password; } - /** Set the URL to the CSS that's sent to each client. Defaults to * https://esphome.io/_static/webserver-v1.min.css * @@ -85,8 +81,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_js_request(AsyncWebServerRequest *request); #endif - bool using_auth() { return username_ != nullptr && password_ != nullptr; } - #ifdef USE_SENSOR void on_sensor_update(sensor::Sensor *obj, float state) override; /// Handle a sensor request under '/sensor/'. @@ -184,8 +178,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { protected: web_server_base::WebServerBase *base_; AsyncEventSource events_{"/events"}; - const char *username_{nullptr}; - const char *password_{nullptr}; const char *css_url_{nullptr}; const char *css_include_{nullptr}; const char *js_url_{nullptr}; diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index b0babcab46..3c269b28b8 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -17,6 +17,17 @@ namespace web_server_base { static const char *const TAG = "web_server_base"; +void WebServerBase::add_handler(AsyncWebHandler *handler) { + // remove all handlers + + if (!credentials_.username.empty()) { + handler = new internal::AuthMiddlewareHandler(handler, &credentials_); + } + this->handlers_.push_back(handler); + if (this->server_ != nullptr) + this->server_->addHandler(handler); +} + void report_ota_error() { StreamString ss; Update.printError(ss); diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index 4ef67f959c..7afc72b9d2 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -10,6 +10,68 @@ namespace esphome { namespace web_server_base { +namespace internal { + +class MiddlewareHandler : public AsyncWebHandler { + public: + MiddlewareHandler(AsyncWebHandler *next) : next_(next) {} + + bool canHandle(AsyncWebServerRequest *request) override { return next_->canHandle(request); } + void handleRequest(AsyncWebServerRequest *request) override { next_->handleRequest(request); } + void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, + bool final) override { + next_->handleUpload(request, filename, index, data, len, final); + } + void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override { + next_->handleBody(request, data, len, index, total); + } + bool isRequestHandlerTrivial() override { return next_->isRequestHandlerTrivial(); } + + protected: + AsyncWebHandler *next_; +}; + +struct Credentials { + std::string username; + std::string password; +}; + +class AuthMiddlewareHandler : public MiddlewareHandler { + public: + AuthMiddlewareHandler(AsyncWebHandler *next, Credentials *credentials) + : MiddlewareHandler(next), credentials_(credentials) {} + + bool check_auth(AsyncWebServerRequest *request) { + bool success = request->authenticate(credentials_->username.c_str(), credentials_->password.c_str()); + if (!success) { + request->requestAuthentication(); + } + return success; + } + + void handleRequest(AsyncWebServerRequest *request) override { + if (!check_auth(request)) + return; + MiddlewareHandler::handleRequest(request); + } + void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, + bool final) override { + if (!check_auth(request)) + return; + MiddlewareHandler::handleUpload(request, filename, index, data, len, final); + } + void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override { + if (!check_auth(request)) + return; + MiddlewareHandler::handleBody(request, data, len, index, total); + } + + protected: + Credentials *credentials_; +}; + +} // namespace internal + class WebServerBase : public Component { public: void init() { @@ -34,13 +96,10 @@ class WebServerBase : public Component { std::shared_ptr get_server() const { return server_; } float get_setup_priority() const override; - void add_handler(AsyncWebHandler *handler) { - // remove all handlers + void set_auth_username(std::string auth_username) { credentials_.username = auth_username; } + void set_auth_password(std::string auth_password) { credentials_.password = auth_password; } - this->handlers_.push_back(handler); - if (this->server_ != nullptr) - this->server_->addHandler(handler); - } + void add_handler(AsyncWebHandler *handler); void add_ota_handler(); @@ -54,6 +113,7 @@ class WebServerBase : public Component { uint16_t port_{80}; std::shared_ptr server_{nullptr}; std::vector handlers_; + internal::Credentials credentials_; }; class OTARequestHandler : public AsyncWebHandler { From 2234f6aacf8cc653307fed80f3750317a82c4f83 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 28 Sep 2021 15:33:30 +1300 Subject: [PATCH 088/549] Fix lint issues in web_server_base (#2409) --- esphome/components/web_server_base/web_server_base.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index 7afc72b9d2..bc37337ca5 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -3,6 +3,7 @@ #ifdef USE_ARDUINO #include +#include #include "esphome/core/component.h" #include @@ -96,8 +97,8 @@ class WebServerBase : public Component { std::shared_ptr get_server() const { return server_; } float get_setup_priority() const override; - void set_auth_username(std::string auth_username) { credentials_.username = auth_username; } - void set_auth_password(std::string auth_password) { credentials_.password = auth_password; } + void set_auth_username(std::string auth_username) { credentials_.username = std::move(auth_username); } + void set_auth_password(std::string auth_password) { credentials_.password = std::move(auth_password); } void add_handler(AsyncWebHandler *handler); From cb5efc1c422f0ced500eebacd44b9fe52ebaff97 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 12:02:37 +0200 Subject: [PATCH 089/549] 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 18752e16a3..bd90f31cbb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,4 @@ platformio==5.2.0 esptool==3.1 click==7.1.2 esphome-dashboard==20210908.0 -aioesphomeapi==9.0.0 +aioesphomeapi==9.1.1 From 185340764548d751e6005b46af9e097c119eb95d Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Mon, 27 Sep 2021 00:32:33 +0400 Subject: [PATCH 090/549] Midea fix (#2395) --- esphome/components/midea/climate.py | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/midea/climate.py b/esphome/components/midea/climate.py index 137fcdd607..7717613fc4 100644 --- a/esphome/components/midea/climate.py +++ b/esphome/components/midea/climate.py @@ -281,4 +281,4 @@ async def to_code(config): 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") + cg.add_library("dudanov/MideaUART", "1.1.8") diff --git a/platformio.ini b/platformio.ini index f4dea3fcb9..73d5595dcd 100644 --- a/platformio.ini +++ b/platformio.ini @@ -37,7 +37,7 @@ lib_deps = 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 + dudanov/MideaUART@1.1.8 ; used by midea build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE From 4579f78bf97dc8f09b2eccd7e58b2804aad33ef5 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 28 Sep 2021 02:53:38 +0200 Subject: [PATCH 091/549] Merge pull request from GHSA-48mj-p7x2-5jfm --- esphome/components/web_server/__init__.py | 8 +-- esphome/components/web_server/web_server.cpp | 13 +--- esphome/components/web_server/web_server.h | 8 --- .../web_server_base/web_server_base.cpp | 11 +++ .../web_server_base/web_server_base.h | 72 +++++++++++++++++-- 5 files changed, 84 insertions(+), 28 deletions(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 7f17767657..240ba7c8a0 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -34,8 +34,8 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_JS_INCLUDE): cv.file_, cv.Optional(CONF_AUTH): cv.Schema( { - cv.Required(CONF_USERNAME): cv.string_strict, - cv.Required(CONF_PASSWORD): cv.string_strict, + cv.Required(CONF_USERNAME): cv.All(cv.string_strict, cv.Length(min=1)), + cv.Required(CONF_PASSWORD): cv.All(cv.string_strict, cv.Length(min=1)), } ), cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( @@ -57,8 +57,8 @@ async def to_code(config): cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) if CONF_AUTH in config: - cg.add(var.set_username(config[CONF_AUTH][CONF_USERNAME])) - cg.add(var.set_password(config[CONF_AUTH][CONF_PASSWORD])) + cg.add(paren.set_auth_username(config[CONF_AUTH][CONF_USERNAME])) + cg.add(paren.set_auth_password(config[CONF_AUTH][CONF_PASSWORD])) if CONF_CSS_INCLUDE in config: cg.add_define("WEBSERVER_CSS_INCLUDE") path = CORE.relative_config_path(config[CONF_CSS_INCLUDE]) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index dc97bcd5c2..e19a54931a 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1,8 +1,8 @@ #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/core/application.h" +#include "esphome/core/log.h" +#include "esphome/core/util.h" #include "StreamString.h" @@ -151,9 +151,6 @@ 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()); - if (this->using_auth()) { - ESP_LOGCONFIG(TAG, " Basic authentication enabled"); - } } float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; } @@ -728,10 +725,6 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return false; } void WebServer::handleRequest(AsyncWebServerRequest *request) { - if (this->using_auth() && !request->authenticate(this->username_, this->password_)) { - return request->requestAuthentication(); - } - if (request->url() == "/") { this->handle_index_request(request); return; diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 54d7356ac9..4e9224ee26 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -30,10 +30,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { public: WebServer(web_server_base::WebServerBase *base) : base_(base) {} - void set_username(const char *username) { username_ = username; } - - void set_password(const char *password) { password_ = password; } - /** Set the URL to the CSS that's sent to each client. Defaults to * https://esphome.io/_static/webserver-v1.min.css * @@ -83,8 +79,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_js_request(AsyncWebServerRequest *request); #endif - bool using_auth() { return username_ != nullptr && password_ != nullptr; } - #ifdef USE_SENSOR void on_sensor_update(sensor::Sensor *obj, float state) override; /// Handle a sensor request under '/sensor/'. @@ -182,8 +176,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { protected: web_server_base::WebServerBase *base_; AsyncEventSource events_{"/events"}; - const char *username_{nullptr}; - const char *password_{nullptr}; const char *css_url_{nullptr}; const char *css_include_{nullptr}; const char *js_url_{nullptr}; diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index 85711704b9..832456dc83 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -15,6 +15,17 @@ namespace web_server_base { static const char *const TAG = "web_server_base"; +void WebServerBase::add_handler(AsyncWebHandler *handler) { + // remove all handlers + + if (!credentials_.username.empty()) { + handler = new internal::AuthMiddlewareHandler(handler, &credentials_); + } + this->handlers_.push_back(handler); + if (this->server_ != nullptr) + this->server_->addHandler(handler); +} + void report_ota_error() { StreamString ss; Update.printError(ss); diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index b6024ceafa..1bfec13fc5 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -7,6 +7,68 @@ namespace esphome { namespace web_server_base { +namespace internal { + +class MiddlewareHandler : public AsyncWebHandler { + public: + MiddlewareHandler(AsyncWebHandler *next) : next_(next) {} + + bool canHandle(AsyncWebServerRequest *request) override { return next_->canHandle(request); } + void handleRequest(AsyncWebServerRequest *request) override { next_->handleRequest(request); } + void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, + bool final) override { + next_->handleUpload(request, filename, index, data, len, final); + } + void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override { + next_->handleBody(request, data, len, index, total); + } + bool isRequestHandlerTrivial() override { return next_->isRequestHandlerTrivial(); } + + protected: + AsyncWebHandler *next_; +}; + +struct Credentials { + std::string username; + std::string password; +}; + +class AuthMiddlewareHandler : public MiddlewareHandler { + public: + AuthMiddlewareHandler(AsyncWebHandler *next, Credentials *credentials) + : MiddlewareHandler(next), credentials_(credentials) {} + + bool check_auth(AsyncWebServerRequest *request) { + bool success = request->authenticate(credentials_->username.c_str(), credentials_->password.c_str()); + if (!success) { + request->requestAuthentication(); + } + return success; + } + + void handleRequest(AsyncWebServerRequest *request) override { + if (!check_auth(request)) + return; + MiddlewareHandler::handleRequest(request); + } + void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, + bool final) override { + if (!check_auth(request)) + return; + MiddlewareHandler::handleUpload(request, filename, index, data, len, final); + } + void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override { + if (!check_auth(request)) + return; + MiddlewareHandler::handleBody(request, data, len, index, total); + } + + protected: + Credentials *credentials_; +}; + +} // namespace internal + class WebServerBase : public Component { public: void init() { @@ -32,13 +94,10 @@ class WebServerBase : public Component { AsyncWebServer *get_server() const { return server_; } float get_setup_priority() const override; - void add_handler(AsyncWebHandler *handler) { - // remove all handlers + void set_auth_username(std::string auth_username) { credentials_.username = auth_username; } + void set_auth_password(std::string auth_password) { credentials_.password = auth_password; } - this->handlers_.push_back(handler); - if (this->server_ != nullptr) - this->server_->addHandler(handler); - } + void add_handler(AsyncWebHandler *handler); void add_ota_handler(); @@ -52,6 +111,7 @@ class WebServerBase : public Component { uint16_t port_{80}; AsyncWebServer *server_{nullptr}; std::vector handlers_; + internal::Credentials credentials_; }; class OTARequestHandler : public AsyncWebHandler { From 8ef2ad17b5e298dd96d0b4b8de38f3be1975ddbe Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 28 Sep 2021 15:33:30 +1300 Subject: [PATCH 092/549] Fix lint issues in web_server_base (#2409) --- esphome/components/web_server_base/web_server_base.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index 1bfec13fc5..9c43358e48 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 +#include +#include #include "esphome/core/component.h" #include @@ -94,8 +96,8 @@ class WebServerBase : public Component { AsyncWebServer *get_server() const { return server_; } float get_setup_priority() const override; - void set_auth_username(std::string auth_username) { credentials_.username = auth_username; } - void set_auth_password(std::string auth_password) { credentials_.password = auth_password; } + void set_auth_username(std::string auth_username) { credentials_.username = std::move(auth_username); } + void set_auth_password(std::string auth_password) { credentials_.password = std::move(auth_password); } void add_handler(AsyncWebHandler *handler); From a2485a18cb0f62c2bb2de6ff1298f8e107173f14 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 28 Sep 2021 15:41:58 +1300 Subject: [PATCH 093/549] Bump version to 2021.9.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index fb7250578c..f69f6e91da 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.1" +__version__ = "2021.9.2" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 2b9054d3b2bedd6b3ed8e5cf87e0236b2acbf8eb Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Wed, 29 Sep 2021 03:26:46 +1300 Subject: [PATCH 094/549] Initialised ESPPreferenceObject::backend_ to nullptr (#2411) --- esphome/core/preferences.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/preferences.h b/esphome/core/preferences.h index b3f4b77f78..ad45cd9684 100644 --- a/esphome/core/preferences.h +++ b/esphome/core/preferences.h @@ -30,7 +30,7 @@ class ESPPreferenceObject { } protected: - ESPPreferenceBackend *backend_; + ESPPreferenceBackend *backend_{nullptr}; }; class ESPPreferences { From af04f565cf203719f6340bcd263e50b856ae2a6f Mon Sep 17 00:00:00 2001 From: Stephen Tierney Date: Wed, 29 Sep 2021 06:10:25 +1000 Subject: [PATCH 095/549] Add support for SCD4X (#2217) * Initial commit * clang-format fixes * Update CODEOWNERS * clang-format fixes * Fix merge error * Fix missing return Co-authored-by: Otto winter --- CODEOWNERS | 1 + esphome/components/scd4x/__init__.py | 0 esphome/components/scd4x/scd4x.cpp | 259 +++++++++++++++++++++++++++ esphome/components/scd4x/scd4x.h | 53 ++++++ esphome/components/scd4x/sensor.py | 98 ++++++++++ tests/test1.yaml | 13 ++ 6 files changed, 424 insertions(+) create mode 100644 esphome/components/scd4x/__init__.py create mode 100644 esphome/components/scd4x/scd4x.cpp create mode 100644 esphome/components/scd4x/scd4x.h create mode 100644 esphome/components/scd4x/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 3c3a1e04ee..2972b30b33 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -125,6 +125,7 @@ esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz esphome/components/rgbct/* @jesserockz esphome/components/rtttl/* @glmnet +esphome/components/scd4x/* @sjtrny esphome/components/script/* @esphome/core esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdp3x/* @Azimath diff --git a/esphome/components/scd4x/__init__.py b/esphome/components/scd4x/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/scd4x/scd4x.cpp b/esphome/components/scd4x/scd4x.cpp new file mode 100644 index 0000000000..c91fd5e882 --- /dev/null +++ b/esphome/components/scd4x/scd4x.cpp @@ -0,0 +1,259 @@ +#include "scd4x.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace scd4x { + +static const char *const TAG = "scd4x"; + +static const uint16_t SCD4X_CMD_GET_SERIAL_NUMBER = 0x3682; +static const uint16_t SCD4X_CMD_TEMPERATURE_OFFSET = 0x241d; +static const uint16_t SCD4X_CMD_ALTITUDE_COMPENSATION = 0x2427; +static const uint16_t SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION = 0xe000; +static const uint16_t SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION = 0x2416; +static const uint16_t SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS = 0x21b1; +static const uint16_t SCD4X_CMD_GET_DATA_READY_STATUS = 0xe4b8; +static const uint16_t SCD4X_CMD_READ_MEASUREMENT = 0xec05; +static const uint16_t SCD4X_CMD_PERFORM_FORCED_CALIBRATION = 0x362f; +static const uint16_t SCD4X_CMD_STOP_MEASUREMENTS = 0x3f86; + +static const float SCD4X_TEMPERATURE_OFFSET_MULTIPLIER = (1 << 16) / 175.0f; + +void SCD4XComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up scd4x..."); + + // the sensor needs 1000 ms to enter the idle state + this->set_timeout(1000, [this]() { + // Check if measurement is ready before reading the value + if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) { + ESP_LOGE(TAG, "Failed to write data ready status command"); + this->mark_failed(); + return; + } + + uint16_t raw_read_status[1]; + if (!this->read_data_(raw_read_status, 1)) { + ESP_LOGE(TAG, "Failed to read data ready status"); + this->mark_failed(); + return; + } + + // In order to query the device periodic measurement must be ceased + if (raw_read_status[0]) { + ESP_LOGD(TAG, "Sensor has data available, stopping periodic measurement"); + if (!this->write_command_(SCD4X_CMD_STOP_MEASUREMENTS)) { + ESP_LOGE(TAG, "Failed to stop measurements"); + this->mark_failed(); + return; + } + } + + if (!this->write_command_(SCD4X_CMD_GET_SERIAL_NUMBER)) { + ESP_LOGE(TAG, "Failed to write get serial command"); + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + + uint16_t raw_serial_number[3]; + if (!this->read_data_(raw_serial_number, 3)) { + ESP_LOGE(TAG, "Failed to read serial number"); + this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED; + this->mark_failed(); + return; + } + ESP_LOGD(TAG, "Serial number %02d.%02d.%02d", (uint16_t(raw_serial_number[0]) >> 8), + uint16_t(raw_serial_number[0] & 0xFF), (uint16_t(raw_serial_number[1]) >> 8)); + + if (!this->write_command_(SCD4X_CMD_TEMPERATURE_OFFSET, + (uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) { + ESP_LOGE(TAG, "Error setting temperature offset."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + + // If pressure compensation available use it + // else use altitude + if (ambient_pressure_compensation_) { + if (!this->write_command_(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, ambient_pressure_compensation_)) { + ESP_LOGE(TAG, "Error setting ambient pressure compensation."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + } else { + if (!this->write_command_(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { + ESP_LOGE(TAG, "Error setting altitude compensation."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + } + + if (!this->write_command_(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) { + ESP_LOGE(TAG, "Error setting automatic self calibration."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + + // Finally start sensor measurements + if (!this->write_command_(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) { + ESP_LOGE(TAG, "Error starting continuous measurements."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + + initialized_ = true; + ESP_LOGD(TAG, "Sensor initialized"); + }); +} + +void SCD4XComponent::dump_config() { + ESP_LOGCONFIG(TAG, "scd4x:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + switch (this->error_code_) { + case COMMUNICATION_FAILED: + ESP_LOGW(TAG, "Communication failed! Is the sensor connected?"); + break; + case MEASUREMENT_INIT_FAILED: + ESP_LOGW(TAG, "Measurement Initialization failed!"); + break; + case SERIAL_NUMBER_IDENTIFICATION_FAILED: + ESP_LOGW(TAG, "Unable to read sensor firmware version"); + break; + default: + ESP_LOGW(TAG, "Unknown setup error!"); + break; + } + } + ESP_LOGCONFIG(TAG, " Automatic self calibration: %s", ONOFF(this->enable_asc_)); + if (this->ambient_pressure_compensation_) { + ESP_LOGCONFIG(TAG, " Altitude compensation disabled"); + ESP_LOGCONFIG(TAG, " Ambient pressure compensation: %dmBar", this->ambient_pressure_); + } else { + ESP_LOGCONFIG(TAG, " Ambient pressure compensation disabled"); + ESP_LOGCONFIG(TAG, " Altitude compensation: %dm", this->altitude_compensation_); + } + ESP_LOGCONFIG(TAG, " Temperature offset: %.2f °C", this->temperature_offset_); + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "CO2", this->co2_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); +} + +void SCD4XComponent::update() { + if (!initialized_) { + return; + } + + // Check if data is ready + if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) { + this->status_set_warning(); + return; + } + + uint16_t raw_read_status[1]; + if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) { + this->status_set_warning(); + ESP_LOGW(TAG, "Data not ready yet!"); + return; + } + + if (!this->write_command_(SCD4X_CMD_READ_MEASUREMENT)) { + ESP_LOGW(TAG, "Error reading measurement!"); + this->status_set_warning(); + return; + } + + // Read off sensor data + uint16_t raw_data[3]; + if (!this->read_data_(raw_data, 3)) { + this->status_set_warning(); + return; + } + + if (this->co2_sensor_ != nullptr) + this->co2_sensor_->publish_state(raw_data[0]); + + if (this->temperature_sensor_ != nullptr) { + const float temperature = -45.0f + (175.0f * (raw_data[1])) / (1 << 16); + this->temperature_sensor_->publish_state(temperature); + } + + if (this->humidity_sensor_ != nullptr) { + const float humidity = (100.0f * raw_data[2]) / (1 << 16); + this->humidity_sensor_->publish_state(humidity); + } + + this->status_clear_warning(); +} + +uint8_t SCD4XComponent::sht_crc_(uint8_t data1, uint8_t data2) { + uint8_t bit; + uint8_t crc = 0xFF; + + crc ^= data1; + for (bit = 8; bit > 0; --bit) { + if (crc & 0x80) + crc = (crc << 1) ^ 0x131; + else + crc = (crc << 1); + } + + crc ^= data2; + for (bit = 8; bit > 0; --bit) { + if (crc & 0x80) + crc = (crc << 1) ^ 0x131; + else + crc = (crc << 1); + } + + return crc; +} + +bool SCD4XComponent::read_data_(uint16_t *data, uint8_t len) { + const uint8_t num_bytes = len * 3; + std::vector buf(num_bytes); + + if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { + return false; + } + + for (uint8_t i = 0; i < len; i++) { + const uint8_t j = 3 * i; + 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); + return false; + } + data[i] = (buf[j] << 8) | buf[j + 1]; + } + return true; +} + +bool SCD4XComponent::write_command_(uint16_t command) { + const uint8_t num_bytes = 2; + uint8_t buffer[num_bytes]; + + buffer[0] = (command >> 8); + buffer[1] = command & 0xff; + + return this->write(buffer, num_bytes) == i2c::ERROR_OK; +} + +bool SCD4XComponent::write_command_(uint16_t command, uint16_t data) { + uint8_t raw[5]; + raw[0] = command >> 8; + raw[1] = command & 0xFF; + raw[2] = data >> 8; + raw[3] = data & 0xFF; + raw[4] = sht_crc_(raw[2], raw[3]); + return this->write(raw, 5) == i2c::ERROR_OK; +} + +} // namespace scd4x +} // namespace esphome diff --git a/esphome/components/scd4x/scd4x.h b/esphome/components/scd4x/scd4x.h new file mode 100644 index 0000000000..3c428b8623 --- /dev/null +++ b/esphome/components/scd4x/scd4x.h @@ -0,0 +1,53 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace scd4x { + +enum ERRORCODE { COMMUNICATION_FAILED, SERIAL_NUMBER_IDENTIFICATION_FAILED, MEASUREMENT_INIT_FAILED, UNKNOWN }; + +class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { + public: + float get_setup_priority() const override { return setup_priority::DATA; } + void setup() override; + void dump_config() override; + void update() override; + + void set_automatic_self_calibration(bool asc) { enable_asc_ = asc; } + void set_altitude_compensation(uint16_t altitude) { altitude_compensation_ = altitude; } + void set_ambient_pressure_compensation(float pressure) { + ambient_pressure_compensation_ = true; + ambient_pressure_ = (uint16_t)(pressure * 1000); + } + void set_temperature_offset(float offset) { temperature_offset_ = offset; }; + + void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; } + void set_temperature_sensor(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }; + void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } + + protected: + uint8_t sht_crc_(uint8_t data1, uint8_t data2); + bool read_data_(uint16_t *data, uint8_t len); + bool write_command_(uint16_t command); + bool write_command_(uint16_t command, uint16_t data); + + ERRORCODE error_code_; + + bool initialized_{false}; + + float temperature_offset_; + uint16_t altitude_compensation_; + bool ambient_pressure_compensation_; + uint16_t ambient_pressure_; + bool enable_asc_; + + sensor::Sensor *co2_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; +}; + +} // namespace scd4x +} // namespace esphome diff --git a/esphome/components/scd4x/sensor.py b/esphome/components/scd4x/sensor.py new file mode 100644 index 0000000000..0b1a960f6f --- /dev/null +++ b/esphome/components/scd4x/sensor.py @@ -0,0 +1,98 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor + +from esphome.const import ( + CONF_ID, + CONF_CO2, + CONF_HUMIDITY, + CONF_TEMPERATURE, + DEVICE_CLASS_CARBON_DIOXIDE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_PARTS_PER_MILLION, + ICON_MOLECULE_CO2, + ICON_THERMOMETER, + ICON_WATER_PERCENT, + UNIT_CELSIUS, + UNIT_PERCENT, +) + +CODEOWNERS = ["@sjtrny"] +DEPENDENCIES = ["i2c"] + +scd4x_ns = cg.esphome_ns.namespace("scd4x") +SCD4XComponent = scd4x_ns.class_("SCD4XComponent", cg.PollingComponent, i2c.I2CDevice) + +CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" +CONF_ALTITUDE_COMPENSATION = "altitude_compensation" +CONF_AMBIENT_PRESSURE_COMPENSATION = "ambient_pressure_compensation" +CONF_TEMPERATURE_OFFSET = "temperature_offset" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(SCD4XComponent), + cv.Optional(CONF_CO2): sensor.sensor_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( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_WATER_PERCENT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_AUTOMATIC_SELF_CALIBRATION, default=True): cv.boolean, + cv.Optional(CONF_ALTITUDE_COMPENSATION, default="0m"): cv.All( + cv.float_with_unit("altitude", "(m|m a.s.l.|MAMSL|MASL)"), + cv.int_range(min=0, max=0xFFFF, max_included=False), + ), + cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION): cv.pressure, + cv.Optional(CONF_TEMPERATURE_OFFSET, default="4°C"): cv.temperature, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x62)) +) + +SENSOR_MAP = { + CONF_CO2: "set_co2_sensor", + CONF_TEMPERATURE: "set_temperature_sensor", + CONF_HUMIDITY: "set_humidity_sensor", +} + +SETTING_MAP = { + CONF_AUTOMATIC_SELF_CALIBRATION: "set_automatic_self_calibration", + CONF_ALTITUDE_COMPENSATION: "set_altitude_compensation", + CONF_AMBIENT_PRESSURE_COMPENSATION: "set_ambient_pressure_compensation", + CONF_TEMPERATURE_OFFSET: "set_temperature_offset", +} + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + for key, funcName in SETTING_MAP.items(): + if key in config: + cg.add(getattr(var, funcName)(config[key])) + + for key, funcName in SENSOR_MAP.items(): + + if key in config: + sens = await sensor.new_sensor(config[key]) + cg.add(getattr(var, funcName)(sens)) diff --git a/tests/test1.yaml b/tests/test1.yaml index 6109e9f5c2..77f7da2b08 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -811,6 +811,19 @@ sensor: ambient_pressure_compensation: 961mBar temperature_offset: 4.2C i2c_id: i2c_bus + - platform: scd4x + co2: + name: "SCD4X CO2" + temperature: + name: "SCD4X Temperature" + humidity: + name: "SCD4X Humidity" + update_interval: 15s + automatic_self_calibration: true + altitude_compensation: 10m + ambient_pressure_compensation: 961mBar + temperature_offset: 4.2C + i2c_id: i2c_bus - platform: sgp30 eco2: name: 'Workshop eCO2' From c39ac9edfebead943cc51fb6ac4fe9b039b5a5c4 Mon Sep 17 00:00:00 2001 From: irtimaled Date: Tue, 28 Sep 2021 13:19:17 -0700 Subject: [PATCH 096/549] Support HSV-based color support on tuya light (#2400) * fix: stop tuya light state getting reset * fix typo * Support for HSV color in Tuya * Clamp formatting --- esphome/components/tuya/light/__init__.py | 11 +++- esphome/components/tuya/light/tuya_light.cpp | 33 ++++++++-- esphome/components/tuya/light/tuya_light.h | 2 + esphome/core/helpers.cpp | 63 ++++++++++++++++++++ esphome/core/helpers.h | 5 ++ 5 files changed, 107 insertions(+), 7 deletions(-) diff --git a/esphome/components/tuya/light/__init__.py b/esphome/components/tuya/light/__init__.py index 6678fc47d8..b983e3f84e 100644 --- a/esphome/components/tuya/light/__init__.py +++ b/esphome/components/tuya/light/__init__.py @@ -22,6 +22,7 @@ CONF_COLOR_TEMPERATURE_DATAPOINT = "color_temperature_datapoint" CONF_COLOR_TEMPERATURE_INVERT = "color_temperature_invert" CONF_COLOR_TEMPERATURE_MAX_VALUE = "color_temperature_max_value" CONF_RGB_DATAPOINT = "rgb_datapoint" +CONF_HSV_DATAPOINT = "hsv_datapoint" TuyaLight = tuya_ns.class_("TuyaLight", light.LightOutput, cg.Component) @@ -33,7 +34,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DIMMER_DATAPOINT): cv.uint8_t, cv.Optional(CONF_MIN_VALUE_DATAPOINT): cv.uint8_t, cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_RGB_DATAPOINT): cv.uint8_t, + cv.Exclusive(CONF_RGB_DATAPOINT, "color"): cv.uint8_t, + cv.Exclusive(CONF_HSV_DATAPOINT, "color"): cv.uint8_t, cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean, cv.Inclusive( CONF_COLOR_TEMPERATURE_DATAPOINT, "color_temperature" @@ -57,7 +59,10 @@ CONFIG_SCHEMA = cv.All( } ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key( - CONF_DIMMER_DATAPOINT, CONF_SWITCH_DATAPOINT, CONF_RGB_DATAPOINT + CONF_DIMMER_DATAPOINT, + CONF_SWITCH_DATAPOINT, + CONF_RGB_DATAPOINT, + CONF_HSV_DATAPOINT, ), ) @@ -75,6 +80,8 @@ async def to_code(config): cg.add(var.set_switch_id(config[CONF_SWITCH_DATAPOINT])) if CONF_RGB_DATAPOINT in config: cg.add(var.set_rgb_id(config[CONF_RGB_DATAPOINT])) + elif CONF_HSV_DATAPOINT in config: + cg.add(var.set_hsv_id(config[CONF_HSV_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])) diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index f75cc964aa..133ee1e557 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -46,6 +46,19 @@ void TuyaLight::setup() { call.perform(); } }); + } else if (hsv_id_.has_value()) { + this->parent_->register_listener(*this->hsv_id_, [this](const TuyaDatapoint &datapoint) { + auto hue = parse_hex(datapoint.value_string, 0, 4); + auto saturation = parse_hex(datapoint.value_string, 4, 4); + auto value = parse_hex(datapoint.value_string, 8, 4); + if (hue.has_value() && saturation.has_value() && value.has_value()) { + float red, green, blue; + hsv_to_rgb(*hue, float(*saturation) / 1000, float(*value) / 1000, red, green, blue); + auto call = this->state_->make_call(); + call.set_rgb(red, green, blue); + call.perform(); + } + }); } if (min_value_datapoint_id_.has_value()) { parent_->set_integer_datapoint_value(*this->min_value_datapoint_id_, this->min_value_); @@ -60,12 +73,14 @@ void TuyaLight::dump_config() { ESP_LOGCONFIG(TAG, " Switch has datapoint ID %u", *this->switch_id_); if (this->rgb_id_.has_value()) ESP_LOGCONFIG(TAG, " RGB has datapoint ID %u", *this->rgb_id_); + else if (this->hsv_id_.has_value()) + ESP_LOGCONFIG(TAG, " HSV has datapoint ID %u", *this->hsv_id_); } light::LightTraits TuyaLight::get_traits() { auto traits = light::LightTraits(); if (this->color_temperature_id_.has_value() && this->dimmer_id_.has_value()) { - if (this->rgb_id_.has_value()) { + if (this->rgb_id_.has_value() || this->hsv_id_.has_value()) { if (this->color_interlock_) traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::COLOR_TEMPERATURE}); else @@ -75,7 +90,7 @@ light::LightTraits TuyaLight::get_traits() { traits.set_supported_color_modes({light::ColorMode::COLOR_TEMPERATURE}); traits.set_min_mireds(this->cold_white_temperature_); traits.set_max_mireds(this->warm_white_temperature_); - } else if (this->rgb_id_.has_value()) { + } else if (this->rgb_id_.has_value() || this->hsv_id_.has_value()) { if (this->dimmer_id_.has_value()) { if (this->color_interlock_) traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::WHITE}); @@ -97,7 +112,7 @@ void TuyaLight::write_state(light::LightState *state) { float red = 0.0f, green = 0.0f, blue = 0.0f; float color_temperature = 0.0f, brightness = 0.0f; - if (this->rgb_id_.has_value()) { + if (this->rgb_id_.has_value() || this->hsv_id_.has_value()) { if (this->color_temperature_id_.has_value()) { state->current_values_as_rgbct(&red, &green, &blue, &color_temperature, &brightness); } else if (this->dimmer_id_.has_value()) { @@ -137,8 +152,16 @@ void TuyaLight::write_state(light::LightState *state) { if (this->rgb_id_.has_value()) { char buffer[7]; sprintf(buffer, "%02X%02X%02X", int(red * 255), int(green * 255), int(blue * 255)); - std::string value = buffer; - this->parent_->set_string_datapoint_value(*this->rgb_id_, value); + std::string rgb_value = buffer; + this->parent_->set_string_datapoint_value(*this->rgb_id_, rgb_value); + } else if (this->hsv_id_.has_value()) { + int hue; + float saturation, value; + rgb_to_hsv(red, green, blue, hue, saturation, value); + char buffer[13]; + sprintf(buffer, "%04X%04X%04X", hue, int(saturation * 1000), int(value * 1000)); + std::string hsv_value = buffer; + this->parent_->set_string_datapoint_value(*this->hsv_id_, hsv_value); } } diff --git a/esphome/components/tuya/light/tuya_light.h b/esphome/components/tuya/light/tuya_light.h index de9ec5e45f..3d9f25271c 100644 --- a/esphome/components/tuya/light/tuya_light.h +++ b/esphome/components/tuya/light/tuya_light.h @@ -17,6 +17,7 @@ class TuyaLight : public Component, public light::LightOutput { } void set_switch_id(uint8_t switch_id) { this->switch_id_ = switch_id; } void set_rgb_id(uint8_t rgb_id) { this->rgb_id_ = rgb_id; } + void set_hsv_id(uint8_t hsv_id) { this->hsv_id_ = hsv_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; @@ -48,6 +49,7 @@ class TuyaLight : public Component, public light::LightOutput { optional min_value_datapoint_id_{}; optional switch_id_{}; optional rgb_id_{}; + optional hsv_id_{}; optional color_temperature_id_{}; uint32_t min_value_ = 0; uint32_t max_value_ = 255; diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 0092d202c4..731ee6c8f5 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -394,6 +394,69 @@ std::string hexencode(const uint8_t *data, uint32_t len) { return res; } +void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value) { + float max_color_value = std::max(std::max(red, green), blue); + float min_color_value = std::min(std::min(red, green), blue); + float delta = max_color_value - min_color_value; + + if (delta == 0) + hue = 0; + else if (max_color_value == red) + hue = int(fmod(((60 * ((green - blue) / delta)) + 360), 360)); + else if (max_color_value == green) + hue = int(fmod(((60 * ((blue - red) / delta)) + 120), 360)); + else if (max_color_value == blue) + hue = int(fmod(((60 * ((red - green) / delta)) + 240), 360)); + + if (max_color_value == 0) + saturation = 0; + else + saturation = delta / max_color_value; + + value = max_color_value; +} + +void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue) { + float chroma = value * saturation; + float hue_prime = fmod(hue / 60.0, 6); + float intermediate = chroma * (1 - fabs(fmod(hue_prime, 2) - 1)); + float delta = value - chroma; + + if (0 <= hue_prime && hue_prime < 1) { + red = chroma; + green = intermediate; + blue = 0; + } else if (1 <= hue_prime && hue_prime < 2) { + red = intermediate; + green = chroma; + blue = 0; + } else if (2 <= hue_prime && hue_prime < 3) { + red = 0; + green = chroma; + blue = intermediate; + } else if (3 <= hue_prime && hue_prime < 4) { + red = 0; + green = intermediate; + blue = chroma; + } else if (4 <= hue_prime && hue_prime < 5) { + red = intermediate; + green = 0; + blue = chroma; + } else if (5 <= hue_prime && hue_prime < 6) { + red = chroma; + green = 0; + blue = intermediate; + } else { + red = 0; + green = 0; + blue = 0; + } + + red += delta; + green += delta; + blue += delta; +} + #ifdef USE_ESP8266 IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index f5a7a197ca..24b55eade0 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -149,6 +149,11 @@ std::array decode_uint16(uint16_t value); /// Encode a 32-bit unsigned integer given four bytes in MSB -> LSB order uint32_t encode_uint32(uint8_t msb, uint8_t byte2, uint8_t byte3, uint8_t lsb); +/// Convert RGB floats (0-1) to hue (0-360) & saturation/value percentage (0-1) +void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value); +/// Convert hue (0-360) & saturation/value percentage (0-1) to RGB floats (0-1) +void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue); + /*** * An interrupt helper class. * From c26ea7e4e093cbedc77ca5cd3fc39e062d37a3de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Tue, 28 Sep 2021 23:02:13 +0200 Subject: [PATCH 097/549] Tuya: add cover component (#2279) --- esphome/components/tuya/cover/__init__.py | 54 ++++++++++++++++++ esphome/components/tuya/cover/tuya_cover.cpp | 58 ++++++++++++++++++++ esphome/components/tuya/cover/tuya_cover.h | 33 +++++++++++ tests/test4.yaml | 5 ++ 4 files changed, 150 insertions(+) create mode 100644 esphome/components/tuya/cover/__init__.py create mode 100644 esphome/components/tuya/cover/tuya_cover.cpp create mode 100644 esphome/components/tuya/cover/tuya_cover.h diff --git a/esphome/components/tuya/cover/__init__.py b/esphome/components/tuya/cover/__init__.py new file mode 100644 index 0000000000..7816f39bf8 --- /dev/null +++ b/esphome/components/tuya/cover/__init__.py @@ -0,0 +1,54 @@ +from esphome.components import cover +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import ( + CONF_OUTPUT_ID, + CONF_MIN_VALUE, + CONF_MAX_VALUE, +) +from .. import tuya_ns, CONF_TUYA_ID, Tuya + +DEPENDENCIES = ["tuya"] + +CONF_POSITION_DATAPOINT = "position_datapoint" +CONF_INVERT_POSITION = "invert_position" + +TuyaCover = tuya_ns.class_("TuyaCover", cover.Cover, cg.Component) + + +def validate_range(config): + if config[CONF_MIN_VALUE] > config[CONF_MAX_VALUE]: + raise cv.Invalid( + "min_value({}) cannot be greater than max_value({})".format( + config[CONF_MIN_VALUE], config[CONF_MAX_VALUE] + ) + ) + return config + + +CONFIG_SCHEMA = cv.All( + cover.COVER_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaCover), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Required(CONF_POSITION_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_MIN_VALUE, default=0): cv.int_, + cv.Optional(CONF_MAX_VALUE, default=100): cv.int_, + cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, + }, + ).extend(cv.COMPONENT_SCHEMA), + validate_range, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + await cg.register_component(var, config) + await cover.register_cover(var, config) + + cg.add(var.set_position_id(config[CONF_POSITION_DATAPOINT])) + cg.add(var.set_min_value(config[CONF_MIN_VALUE])) + cg.add(var.set_max_value(config[CONF_MAX_VALUE])) + cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) + paren = await cg.get_variable(config[CONF_TUYA_ID]) + cg.add(var.set_tuya_parent(paren)) diff --git a/esphome/components/tuya/cover/tuya_cover.cpp b/esphome/components/tuya/cover/tuya_cover.cpp new file mode 100644 index 0000000000..7da1312938 --- /dev/null +++ b/esphome/components/tuya/cover/tuya_cover.cpp @@ -0,0 +1,58 @@ +#include "esphome/core/log.h" +#include "tuya_cover.h" + +namespace esphome { +namespace tuya { + +static const char *const TAG = "tuya.cover"; + +void TuyaCover::setup() { + this->value_range_ = this->max_value_ - this->min_value_; + if (this->position_id_.has_value()) { + this->parent_->register_listener(*this->position_id_, [this](const TuyaDatapoint &datapoint) { + auto pos = float(datapoint.value_uint - this->min_value_) / this->value_range_; + if (this->invert_position_) + pos = 1.0f - pos; + this->position = pos; + this->publish_state(); + }); + } +} + +void TuyaCover::control(const cover::CoverCall &call) { + if (call.get_stop()) { + auto pos = this->position; + if (this->invert_position_) + pos = 1.0f - pos; + auto position_int = static_cast(pos * this->value_range_); + position_int = position_int + this->min_value_; + + parent_->set_integer_datapoint_value(*this->position_id_, position_int); + } + if (call.get_position().has_value()) { + auto pos = *call.get_position(); + if (this->invert_position_) + pos = 1.0f - pos; + auto position_int = static_cast(pos * this->value_range_); + position_int = position_int + this->min_value_; + + parent_->set_integer_datapoint_value(*this->position_id_, position_int); + } + + this->publish_state(); +} + +void TuyaCover::dump_config() { + ESP_LOGCONFIG(TAG, "Tuya Cover:"); + if (this->position_id_.has_value()) + ESP_LOGCONFIG(TAG, " Position has datapoint ID %u", *this->position_id_); +} + +cover::CoverTraits TuyaCover::get_traits() { + auto traits = cover::CoverTraits(); + traits.set_supports_position(true); + return traits; +} + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/cover/tuya_cover.h b/esphome/components/tuya/cover/tuya_cover.h new file mode 100644 index 0000000000..b62e58dc1b --- /dev/null +++ b/esphome/components/tuya/cover/tuya_cover.h @@ -0,0 +1,33 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/tuya/tuya.h" +#include "esphome/components/cover/cover.h" + +namespace esphome { +namespace tuya { + +class TuyaCover : public cover::Cover, public Component { + public: + void setup() override; + void dump_config() override; + void set_position_id(uint8_t dimmer_id) { this->position_id_ = dimmer_id; } + 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; } + void set_invert_position(bool invert_position) { invert_position_ = invert_position; } + + protected: + void control(const cover::CoverCall &call) override; + cover::CoverTraits get_traits() override; + + Tuya *parent_; + optional position_id_{}; + uint32_t min_value_ = 0; + uint32_t max_value_ = 100; + uint32_t value_range_; + bool invert_position_ = false; +}; + +} // namespace tuya +} // namespace esphome diff --git a/tests/test4.yaml b/tests/test4.yaml index a205f0802e..4f2025ad74 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -358,6 +358,11 @@ light: warm_white_color_temperature: 500 mireds gamma_correct: 1 +cover: + - platform: tuya + id: tuya_cover + position_datapoint: 2 + display: - platform: addressable_light id: led_matrix_32x8_display From 855c98d81500448a311263d51250f0babf8aaedb Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 28 Sep 2021 23:15:52 +0200 Subject: [PATCH 098/549] Fix tuya cover lint checks (#2414) --- esphome/components/tuya/cover/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/components/tuya/cover/__init__.py b/esphome/components/tuya/cover/__init__.py index 7816f39bf8..5a654841f7 100644 --- a/esphome/components/tuya/cover/__init__.py +++ b/esphome/components/tuya/cover/__init__.py @@ -19,9 +19,7 @@ TuyaCover = tuya_ns.class_("TuyaCover", cover.Cover, cg.Component) def validate_range(config): if config[CONF_MIN_VALUE] > config[CONF_MAX_VALUE]: raise cv.Invalid( - "min_value({}) cannot be greater than max_value({})".format( - config[CONF_MIN_VALUE], config[CONF_MAX_VALUE] - ) + f"min_value ({config[CONF_MIN_VALUE]}) cannot be greater than max_value ({config[CONF_MAX_VALUE]})" ) return config From 7af1c044939ea54f7fc1e74b15bb2fac9d75a11a Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 28 Sep 2021 23:16:00 +0200 Subject: [PATCH 099/549] Bump debian base to 5.1.0 / 20210902 (#2413) --- docker/Dockerfile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 9c3e864e43..e66c3e1d95 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,12 +5,12 @@ # 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 +FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.0 AS base-hassio-amd64 +FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.0 AS base-hassio-arm64 +FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.0 AS base-hassio-armv7 +FROM debian:bullseye-20210902-slim AS base-docker-amd64 +FROM debian:bullseye-20210902-slim AS base-docker-arm64 +FROM debian:bullseye-20210902-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 505d1d78fb29c55b024d4d0b7eea5a02e3af7bce Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 29 Sep 2021 12:19:19 +1300 Subject: [PATCH 100/549] Remove default initializations from tuya cover (#2415) --- esphome/components/tuya/cover/tuya_cover.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/tuya/cover/tuya_cover.h b/esphome/components/tuya/cover/tuya_cover.h index b62e58dc1b..c3b0c3e069 100644 --- a/esphome/components/tuya/cover/tuya_cover.h +++ b/esphome/components/tuya/cover/tuya_cover.h @@ -23,10 +23,10 @@ class TuyaCover : public cover::Cover, public Component { Tuya *parent_; optional position_id_{}; - uint32_t min_value_ = 0; - uint32_t max_value_ = 100; + uint32_t min_value_; + uint32_t max_value_; uint32_t value_range_; - bool invert_position_ = false; + bool invert_position_; }; } // namespace tuya From 4f5e4f3b86d0d2069b51757025b0c585bf33fc27 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 29 Sep 2021 23:21:52 +0200 Subject: [PATCH 101/549] Move #ifdef to after header include (#2417) defines.h needs to be included first. Fixes esphome/issues#2490. --- esphome/components/nextion/nextion_upload.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index 9a748277d8..cebdbec31a 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -1,6 +1,7 @@ +#include "nextion.h" + #ifdef USE_NEXTION_TFT_UPLOAD -#include "nextion.h" #include "esphome/core/application.h" #include "esphome/core/macros.h" #include "esphome/core/util.h" From 3dfc8d42915f5224a9ac13b6f4be5e4d5828cbac Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Thu, 30 Sep 2021 07:25:06 +1000 Subject: [PATCH 102/549] String manipulation filters for text sensors (#2393) * initial text sensor filter POC * fixed verbose logging * add append, prepend, substitute filters * add to lower, get to upper working without dummy * clang lint * more linting... * std::move append and prepend filters * fix verbose filter::input logging * value.c_str() in input print * lambda filter verbose log fix * correct log tag, neaten to upper and to lower * add on_raw_value automation/trigger --- esphome/components/text_sensor/__init__.py | 97 +++++++++++++++ esphome/components/text_sensor/automation.h | 7 ++ esphome/components/text_sensor/filter.cpp | 74 ++++++++++++ esphome/components/text_sensor/filter.h | 112 ++++++++++++++++++ .../components/text_sensor/text_sensor.cpp | 59 ++++++++- esphome/components/text_sensor/text_sensor.h | 29 ++++- 6 files changed, 373 insertions(+), 5 deletions(-) create mode 100644 esphome/components/text_sensor/filter.cpp create mode 100644 esphome/components/text_sensor/filter.h diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index d06f12de0e..cdb4b85e9a 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -4,16 +4,22 @@ from esphome import automation from esphome.components import mqtt from esphome.const import ( CONF_DISABLED_BY_DEFAULT, + CONF_FILTERS, CONF_ICON, CONF_ID, CONF_INTERNAL, CONF_ON_VALUE, + CONF_ON_RAW_VALUE, CONF_TRIGGER_ID, CONF_MQTT_ID, CONF_NAME, CONF_STATE, + CONF_FROM, + CONF_TO, ) from esphome.core import CORE, coroutine_with_priority +from esphome.util import Registry + IS_PLATFORM_COMPONENT = True @@ -25,6 +31,9 @@ TextSensorPtr = TextSensor.operator("ptr") TextSensorStateTrigger = text_sensor_ns.class_( "TextSensorStateTrigger", automation.Trigger.template(cg.std_string) ) +TextSensorStateRawTrigger = text_sensor_ns.class_( + "TextSensorStateRawTrigger", automation.Trigger.template(cg.std_string) +) TextSensorPublishAction = text_sensor_ns.class_( "TextSensorPublishAction", automation.Action ) @@ -32,21 +41,101 @@ TextSensorStateCondition = text_sensor_ns.class_( "TextSensorStateCondition", automation.Condition ) +FILTER_REGISTRY = Registry() +validate_filters = cv.validate_registry("filter", FILTER_REGISTRY) + +# Filters +Filter = text_sensor_ns.class_("Filter") +LambdaFilter = text_sensor_ns.class_("LambdaFilter", Filter) +ToUpperFilter = text_sensor_ns.class_("ToUpperFilter", Filter) +ToLowerFilter = text_sensor_ns.class_("ToLowerFilter", Filter) +AppendFilter = text_sensor_ns.class_("AppendFilter", Filter) +PrependFilter = text_sensor_ns.class_("PrependFilter", Filter) +SubstituteFilter = text_sensor_ns.class_("SubstituteFilter", Filter) + + +@FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda) +async def lambda_filter_to_code(config, filter_id): + lambda_ = await cg.process_lambda( + config, [(cg.std_string, "x")], return_type=cg.optional.template(cg.std_string) + ) + return cg.new_Pvariable(filter_id, lambda_) + + +@FILTER_REGISTRY.register("to_upper", ToUpperFilter, {}) +async def to_upper_filter_to_code(config, filter_id): + return cg.new_Pvariable(filter_id) + + +@FILTER_REGISTRY.register("to_lower", ToLowerFilter, {}) +async def to_lower_filter_to_code(config, filter_id): + return cg.new_Pvariable(filter_id) + + +@FILTER_REGISTRY.register("append", AppendFilter, cv.string) +async def append_filter_to_code(config, filter_id): + return cg.new_Pvariable(filter_id, config) + + +@FILTER_REGISTRY.register("prepend", PrependFilter, cv.string) +async def prepend_filter_to_code(config, filter_id): + return cg.new_Pvariable(filter_id, config) + + +def validate_substitute(value): + if isinstance(value, dict): + return cv.Schema( + { + cv.Required(CONF_FROM): cv.string, + cv.Required(CONF_TO): cv.string, + } + )(value) + value = cv.string(value) + if "->" not in value: + raise cv.Invalid("Substitute mapping must contain '->'") + a, b = value.split("->", 1) + a, b = a.strip(), b.strip() + return validate_substitute({CONF_FROM: cv.string(a), CONF_TO: cv.string(b)}) + + +@FILTER_REGISTRY.register( + "substitute", + SubstituteFilter, + cv.All(cv.ensure_list(validate_substitute), cv.Length(min=2)), +) +async def substitute_filter_to_code(config, filter_id): + from_strings = [conf[CONF_FROM] for conf in config] + to_strings = [conf[CONF_TO] for conf in config] + return cg.new_Pvariable(filter_id, from_strings, to_strings) + + icon = cv.icon TEXT_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor), cv.Optional(CONF_ICON): icon, + cv.Optional(CONF_FILTERS): validate_filters, cv.Optional(CONF_ON_VALUE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TextSensorStateTrigger), } ), + cv.Optional(CONF_ON_RAW_VALUE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + TextSensorStateRawTrigger + ), + } + ), } ) +async def build_filters(config): + return await cg.build_registry_list(FILTER_REGISTRY, config) + + async def setup_text_sensor_core_(var, config): cg.add(var.set_name(config[CONF_NAME])) cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) @@ -55,10 +144,18 @@ async def setup_text_sensor_core_(var, config): if CONF_ICON in config: cg.add(var.set_icon(config[CONF_ICON])) + if config.get(CONF_FILTERS): # must exist and not be empty + filters = await build_filters(config[CONF_FILTERS]) + cg.add(var.set_filters(filters)) + for conf in config.get(CONF_ON_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [(cg.std_string, "x")], conf) + for conf in config.get(CONF_ON_RAW_VALUE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.std_string, "x")], conf) + if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) diff --git a/esphome/components/text_sensor/automation.h b/esphome/components/text_sensor/automation.h index d82fd27c1f..dd02aeaf3b 100644 --- a/esphome/components/text_sensor/automation.h +++ b/esphome/components/text_sensor/automation.h @@ -16,6 +16,13 @@ class TextSensorStateTrigger : public Trigger { } }; +class TextSensorStateRawTrigger : public Trigger { + public: + explicit TextSensorStateRawTrigger(TextSensor *parent) { + parent->add_on_raw_state_callback([this](std::string value) { this->trigger(std::move(value)); }); + } +}; + template class TextSensorStateCondition : public Condition { public: explicit TextSensorStateCondition(TextSensor *parent) : parent_(parent) {} diff --git a/esphome/components/text_sensor/filter.cpp b/esphome/components/text_sensor/filter.cpp new file mode 100644 index 0000000000..14df6238ff --- /dev/null +++ b/esphome/components/text_sensor/filter.cpp @@ -0,0 +1,74 @@ +#include "filter.h" +#include "text_sensor.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace text_sensor { + +static const char *const TAG = "text_sensor.filter"; + +// Filter +void Filter::input(const std::string &value) { + ESP_LOGVV(TAG, "Filter(%p)::input(%s)", this, value.c_str()); + optional out = this->new_value(value); + if (out.has_value()) + this->output(*out); +} +void Filter::output(const std::string &value) { + if (this->next_ == nullptr) { + ESP_LOGVV(TAG, "Filter(%p)::output(%s) -> SENSOR", this, value.c_str()); + this->parent_->internal_send_state_to_frontend(value); + } else { + ESP_LOGVV(TAG, "Filter(%p)::output(%s) -> %p", this, value.c_str(), this->next_); + this->next_->input(value); + } +} +void Filter::initialize(TextSensor *parent, Filter *next) { + ESP_LOGVV(TAG, "Filter(%p)::initialize(parent=%p next=%p)", this, parent, next); + this->parent_ = parent; + this->next_ = next; +} + +// LambdaFilter +LambdaFilter::LambdaFilter(lambda_filter_t lambda_filter) : lambda_filter_(std::move(lambda_filter)) {} +const lambda_filter_t &LambdaFilter::get_lambda_filter() const { return this->lambda_filter_; } +void LambdaFilter::set_lambda_filter(const lambda_filter_t &lambda_filter) { this->lambda_filter_ = lambda_filter; } + +optional LambdaFilter::new_value(std::string value) { + auto it = this->lambda_filter_(value); + ESP_LOGVV(TAG, "LambdaFilter(%p)::new_value(%s) -> %s", this, value.c_str(), it.value_or("").c_str()); + return it; +} + +// ToUpperFilter +optional ToUpperFilter::new_value(std::string value) { + for (char &c : value) + c = ::toupper(c); + return value; +} + +// ToLowerFilter +optional ToLowerFilter::new_value(std::string value) { + for (char &c : value) + c = ::toupper(c); + return value; +} + +// Append +optional AppendFilter::new_value(std::string value) { return value + this->suffix_; } + +// Prepend +optional PrependFilter::new_value(std::string value) { return this->prefix_ + value; } + +// Substitute +optional SubstituteFilter::new_value(std::string value) { + std::size_t pos; + for (int i = 0; i < this->from_strings_.size(); i++) + while ((pos = value.find(this->from_strings_[i])) != std::string::npos) + value.replace(pos, this->from_strings_[i].size(), this->to_strings_[i]); + return value; +} + +} // namespace text_sensor +} // namespace esphome diff --git a/esphome/components/text_sensor/filter.h b/esphome/components/text_sensor/filter.h new file mode 100644 index 0000000000..6a1d9ab04e --- /dev/null +++ b/esphome/components/text_sensor/filter.h @@ -0,0 +1,112 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include +#include + +namespace esphome { +namespace text_sensor { + +class TextSensor; + +/** Apply a filter to text sensor values such as to_upper. + * + * This class is purposefully kept quite simple, since more complicated + * filters should really be done with the filter sensor in Home Assistant. + */ +class Filter { + public: + /** This will be called every time the filter receives a new value. + * + * It can return an empty optional to indicate that the filter chain + * should stop, otherwise the value in the filter will be passed down + * the chain. + * + * @param value The new value. + * @return An optional string, the new value that should be pushed out. + */ + virtual optional new_value(std::string value); + + /// Initialize this filter, please note this can be called more than once. + virtual void initialize(TextSensor *parent, Filter *next); + + void input(const std::string &value); + + void output(const std::string &value); + + protected: + friend TextSensor; + + Filter *next_{nullptr}; + TextSensor *parent_{nullptr}; +}; + +using lambda_filter_t = std::function(std::string)>; + +/** This class allows for creation of simple template filters. + * + * The constructor accepts a lambda of the form std::string -> optional. + * It will be called with each new value in the filter chain and returns the modified + * value that shall be passed down the filter chain. Returning an empty Optional + * means that the value shall be discarded. + */ +class LambdaFilter : public Filter { + public: + explicit LambdaFilter(lambda_filter_t lambda_filter); + + optional new_value(std::string value) override; + + const lambda_filter_t &get_lambda_filter() const; + void set_lambda_filter(const lambda_filter_t &lambda_filter); + + protected: + lambda_filter_t lambda_filter_; +}; + +/// A simple filter that converts all text to uppercase +class ToUpperFilter : public Filter { + public: + optional new_value(std::string value) override; +}; + +/// A simple filter that converts all text to lowercase +class ToLowerFilter : public Filter { + public: + optional new_value(std::string value) override; +}; + +/// A simple filter that adds a string to the end of another string +class AppendFilter : public Filter { + public: + AppendFilter(std::string suffix) : suffix_(std::move(suffix)) {} + optional new_value(std::string value) override; + + protected: + std::string suffix_; +}; + +/// A simple filter that adds a string to the start of another string +class PrependFilter : public Filter { + public: + PrependFilter(std::string prefix) : prefix_(std::move(prefix)) {} + optional new_value(std::string value) override; + + protected: + std::string prefix_; +}; + +/// A simple filter that replaces a substring with another substring +class SubstituteFilter : public Filter { + public: + SubstituteFilter(std::vector from_strings, std::vector to_strings) + : from_strings_(std::move(from_strings)), to_strings_(std::move(to_strings)) {} + optional new_value(std::string value) override; + + protected: + std::vector from_strings_; + std::vector to_strings_; +}; + +} // namespace text_sensor +} // namespace esphome diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 8738860d55..774f3a8cb6 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -10,21 +10,72 @@ TextSensor::TextSensor() : TextSensor("") {} TextSensor::TextSensor(const std::string &name) : Nameable(name) {} void TextSensor::publish_state(const std::string &state) { - this->state = state; + this->raw_state = state; + this->raw_callback_.call(state); + + ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), state.c_str()); + + if (this->filter_list_ == nullptr) { + this->internal_send_state_to_frontend(state); + } else { + this->filter_list_->input(state); + } +} + +void TextSensor::add_filter(Filter *filter) { + // inefficient, but only happens once on every sensor setup and nobody's going to have massive amounts of + // filters + ESP_LOGVV(TAG, "TextSensor(%p)::add_filter(%p)", this, filter); + if (this->filter_list_ == nullptr) { + this->filter_list_ = filter; + } else { + Filter *last_filter = this->filter_list_; + while (last_filter->next_ != nullptr) + last_filter = last_filter->next_; + last_filter->initialize(this, filter); + } + filter->initialize(this, nullptr); +} +void TextSensor::add_filters(const std::vector &filters) { + for (Filter *filter : filters) { + this->add_filter(filter); + } +} +void TextSensor::set_filters(const std::vector &filters) { + this->clear_filters(); + this->add_filters(filters); +} +void TextSensor::clear_filters() { + if (this->filter_list_ != nullptr) { + ESP_LOGVV(TAG, "TextSensor(%p)::clear_filters()", this); + } + this->filter_list_ = nullptr; +} + +void TextSensor::add_on_state_callback(std::function callback) { + this->callback_.add(std::move(callback)); +} +void TextSensor::add_on_raw_state_callback(std::function callback) { + this->raw_callback_.add(std::move(callback)); +} + +std::string TextSensor::get_state() const { return this->state; } +std::string TextSensor::get_raw_state() const { return this->raw_state; } +void TextSensor::internal_send_state_to_frontend(const std::string &state) { + this->state = this->raw_state; this->has_state_ = true; ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str()); this->callback_.call(state); } + void TextSensor::set_icon(const std::string &icon) { this->icon_ = icon; } -void TextSensor::add_on_state_callback(std::function callback) { - this->callback_.add(std::move(callback)); -} std::string TextSensor::get_icon() { if (this->icon_.has_value()) return *this->icon_; return this->icon(); } std::string TextSensor::icon() { return ""; } + std::string TextSensor::unique_id() { return ""; } bool TextSensor::has_state() { return this->has_state_; } uint32_t TextSensor::hash_base() { return 334300109UL; } diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index 5293f0d216..7804deedb6 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/components/text_sensor/filter.h" namespace esphome { namespace text_sensor { @@ -22,13 +23,33 @@ class TextSensor : public Nameable { explicit TextSensor(); explicit TextSensor(const std::string &name); + /// Getter-syntax for .state. + std::string get_state() const; + /// Getter-syntax for .raw_state + std::string get_raw_state() const; + void publish_state(const std::string &state); void set_icon(const std::string &icon); + /// Add a filter to the filter chain. Will be appended to the back. + void add_filter(Filter *filter); + + /// Add a list of vectors to the back of the filter chain. + void add_filters(const std::vector &filters); + + /// Clear the filters and replace them by filters. + void set_filters(const std::vector &filters); + + /// Clear the entire filter chain. + void clear_filters(); + void add_on_state_callback(std::function callback); + /// Add a callback that will be called every time the sensor sends a raw value. + void add_on_raw_state_callback(std::function callback); std::string state; + std::string raw_state; // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) @@ -40,10 +61,16 @@ class TextSensor : public Nameable { bool has_state(); + void internal_send_state_to_frontend(const std::string &state); + protected: uint32_t hash_base() override; - CallbackManager callback_; + CallbackManager raw_callback_; ///< Storage for raw state callbacks. + CallbackManager callback_; ///< Storage for filtered state callbacks. + + Filter *filter_list_{nullptr}; ///< Store all active filters. + optional icon_; bool has_state_{false}; }; From 946db3fd506509368a7d18aa070eef133ffaeb3f Mon Sep 17 00:00:00 2001 From: Andy Allsopp Date: Thu, 30 Sep 2021 12:03:30 +0100 Subject: [PATCH 103/549] Add viewport meta tag to web server layout (#2419) * Update web_server.cpp Added viewport meta tag to web_server.cpp in order to better control layout on mobile browsers. Adds 70 characters. Vastly improves accessibility on mobile devices. * Update web_server.cpp split line to meet clang format requirement. * Update web_server.cpp Reworked line break for clangtidy --- esphome/components/web_server/web_server.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 28e741cf24..d72262b4d3 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -164,7 +164,9 @@ float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f void WebServer::handle_index_request(AsyncWebServerRequest *request) { AsyncResponseStream *stream = request->beginResponseStream("text/html"); std::string title = App.get_name() + " Web Server"; - stream->print(F("")); + stream->print(F("<!DOCTYPE html><html lang=\"en\"><head><meta charset=UTF-8>" + "<meta name=\"viewport\" content=\"width=device-width, " + "initial-scale=1.0\"><title>")); stream->print(title.c_str()); stream->print(F("")); #ifdef WEBSERVER_CSS_INCLUDE From 0e4f1ac40d58a17af878e7c4bf6ae0bb14ceb543 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 30 Sep 2021 16:24:02 +0200 Subject: [PATCH 104/549] Fix default environment for clang-tidy (#2420) * Drop unnecessary platformio call from script/lint-cpp * Default environment for clang-tidy to esp32-tidy --- script/clang-tidy | 2 +- script/lint-cpp | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/script/clang-tidy b/script/clang-tidy index 6e79059372..87ba1c84b5 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -122,7 +122,7 @@ 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', + parser.add_argument('-e', '--environment', default='esp32-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)') diff --git a/script/lint-cpp b/script/lint-cpp index 170d61d539..ac03ca0f23 100755 --- a/script/lint-cpp +++ b/script/lint-cpp @@ -3,9 +3,6 @@ set -e cd "$(dirname "$0")/.." -if [[ ! -e ".gcc-flags.json" ]]; then - pio init --ide atom -fi set -x From 5b0fbbaada91766de1c4ee950378a82bc8116149 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 30 Sep 2021 16:25:08 +0200 Subject: [PATCH 105/549] Replace std::move() with const references where possible (#2421) * Replace std::move() with const references where possible * Fix formatting --- esphome/components/http_request/http_request.h | 2 +- esphome/components/pipsolar/output/pipsolar_output.h | 2 +- esphome/components/pipsolar/switch/pipsolar_switch.h | 4 ++-- esphome/components/rf_bridge/rf_bridge.h | 3 +-- esphome/components/select/automation.h | 2 +- esphome/components/sim800l/sim800l.h | 2 +- esphome/components/template/select/template_select.h | 2 +- esphome/components/text_sensor/automation.h | 4 ++-- 8 files changed, 10 insertions(+), 11 deletions(-) diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index 511096e7fa..9cc027b58d 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -40,7 +40,7 @@ class HttpRequestComponent : public Component { void set_method(const char *method) { this->method_ = method; } void set_useragent(const char *useragent) { this->useragent_ = useragent; } void set_timeout(uint16_t timeout) { this->timeout_ = timeout; } - void set_body(std::string body) { this->body_ = std::move(body); } + void set_body(const std::string &body) { this->body_ = body; } void set_headers(std::list
headers) { this->headers_ = std::move(headers); } void send(const std::vector &response_triggers); void close(); diff --git a/esphome/components/pipsolar/output/pipsolar_output.h b/esphome/components/pipsolar/output/pipsolar_output.h index 932efe01c2..fe783cf034 100644 --- a/esphome/components/pipsolar/output/pipsolar_output.h +++ b/esphome/components/pipsolar/output/pipsolar_output.h @@ -13,7 +13,7 @@ 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_set_command(const std::string &command) { this->set_command_ = 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); }; diff --git a/esphome/components/pipsolar/switch/pipsolar_switch.h b/esphome/components/pipsolar/switch/pipsolar_switch.h index 3fe4c7dfa1..11ff6c853a 100644 --- a/esphome/components/pipsolar/switch/pipsolar_switch.h +++ b/esphome/components/pipsolar/switch/pipsolar_switch.h @@ -10,8 +10,8 @@ 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 set_on_command(const std::string &command) { this->on_command_ = command; }; + void set_off_command(const std::string &command) { this->off_command_ = command; }; void dump_config() override; protected: diff --git a/esphome/components/rf_bridge/rf_bridge.h b/esphome/components/rf_bridge/rf_bridge.h index 2fa4eb05c5..9156d995bc 100644 --- a/esphome/components/rf_bridge/rf_bridge.h +++ b/esphome/components/rf_bridge/rf_bridge.h @@ -85,8 +85,7 @@ class RFBridgeReceivedCodeTrigger : public Trigger { class RFBridgeReceivedAdvancedCodeTrigger : public Trigger { public: explicit RFBridgeReceivedAdvancedCodeTrigger(RFBridgeComponent *parent) { - parent->add_on_advanced_code_received_callback( - [this](RFBridgeAdvancedData data) { this->trigger(std::move(data)); }); + parent->add_on_advanced_code_received_callback([this](const RFBridgeAdvancedData &data) { this->trigger(data); }); } }; diff --git a/esphome/components/select/automation.h b/esphome/components/select/automation.h index 59525f879e..1e0bfed63d 100644 --- a/esphome/components/select/automation.h +++ b/esphome/components/select/automation.h @@ -10,7 +10,7 @@ namespace select { class SelectStateTrigger : public Trigger { public: explicit SelectStateTrigger(Select *parent) { - parent->add_on_state_callback([this](std::string value) { this->trigger(std::move(value)); }); + parent->add_on_state_callback([this](const std::string &value) { this->trigger(value); }); } }; diff --git a/esphome/components/sim800l/sim800l.h b/esphome/components/sim800l/sim800l.h index fa9c392bfc..21e9ac4a50 100644 --- a/esphome/components/sim800l/sim800l.h +++ b/esphome/components/sim800l/sim800l.h @@ -74,7 +74,7 @@ class Sim800LReceivedMessageTrigger : public Trigger { public: explicit Sim800LReceivedMessageTrigger(Sim800LComponent *parent) { parent->add_on_sms_received_callback( - [this](std::string message, std::string sender) { this->trigger(std::move(message), std::move(sender)); }); + [this](const std::string &message, const std::string &sender) { this->trigger(message, sender); }); } }; diff --git a/esphome/components/template/select/template_select.h b/esphome/components/template/select/template_select.h index e24eb6e880..2f00765c3d 100644 --- a/esphome/components/template/select/template_select.h +++ b/esphome/components/template/select/template_select.h @@ -19,7 +19,7 @@ class TemplateSelect : public select::Select, public PollingComponent { Trigger *get_set_trigger() const { return this->set_trigger_; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } - void set_initial_option(std::string initial_option) { this->initial_option_ = std::move(initial_option); } + void set_initial_option(const std::string &initial_option) { this->initial_option_ = initial_option; } void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } protected: diff --git a/esphome/components/text_sensor/automation.h b/esphome/components/text_sensor/automation.h index dd02aeaf3b..d7286845e0 100644 --- a/esphome/components/text_sensor/automation.h +++ b/esphome/components/text_sensor/automation.h @@ -12,14 +12,14 @@ namespace text_sensor { class TextSensorStateTrigger : public Trigger { public: explicit TextSensorStateTrigger(TextSensor *parent) { - parent->add_on_state_callback([this](std::string value) { this->trigger(std::move(value)); }); + parent->add_on_state_callback([this](const std::string &value) { this->trigger(value); }); } }; class TextSensorStateRawTrigger : public Trigger { public: explicit TextSensorStateRawTrigger(TextSensor *parent) { - parent->add_on_raw_state_callback([this](std::string value) { this->trigger(std::move(value)); }); + parent->add_on_raw_state_callback([this](const std::string &value) { this->trigger(value); }); } }; From 1031ea431348893eb30e1866b327c86bcfabcaf2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 30 Sep 2021 18:07:28 +0200 Subject: [PATCH 106/549] Fix line endings normalization (#2407) * Strip CRLF line endings from modbus controller files * Normalize all line endings to LF --- .gitattributes | 3 +- .../modbus_controller/number/modbus_number.h | 96 +++++++++---------- .../modbus_controller/output/modbus_output.h | 90 ++++++++--------- 3 files changed, 95 insertions(+), 94 deletions(-) diff --git a/.gitattributes b/.gitattributes index 94f480de94..dad0966222 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ -* text=auto eol=lf \ No newline at end of file +# Normalize line endings to LF in the repository +* text eol=lf diff --git a/esphome/components/modbus_controller/number/modbus_number.h b/esphome/components/modbus_controller/number/modbus_number.h index 0fd4e314bc..271bbfac50 100644 --- a/esphome/components/modbus_controller/number/modbus_number.h +++ b/esphome/components/modbus_controller/number/modbus_number.h @@ -1,48 +1,48 @@ -#pragma once - -#include "esphome/components/number/number.h" -#include "esphome/components/modbus_controller/modbus_controller.h" -#include "esphome/core/component.h" - -namespace esphome { -namespace modbus_controller { - -using value_to_data_t = std::function(float); - -class ModbusNumber : public number::Number, public Component, public SensorItem { - public: - ModbusNumber(uint16_t start_address, uint8_t offset, uint32_t bitmask, SensorValueType value_type, int register_count, - uint8_t skip_updates, bool force_new_range) - : number::Number(), Component(), SensorItem() { - this->register_type = ModbusRegisterType::HOLDING; - this->start_address = start_address; - this->offset = offset; - this->bitmask = bitmask; - this->sensor_value_type = value_type; - this->register_count = register_count; - this->skip_updates = skip_updates; - this->force_new_range = force_new_range; - }; - - void dump_config() override; - void parse_and_publish(const std::vector &data) override; - float get_setup_priority() const override { return setup_priority::HARDWARE; } - void set_update_interval(int) {} - void set_parent(ModbusController *parent) { this->parent_ = parent; } - void set_write_multiply(float factor) { multiply_by_ = factor; } - - using transform_func_t = std::function(ModbusNumber *, float, const std::vector &)>; - using write_transform_func_t = std::function(ModbusNumber *, float, std::vector &)>; - void set_template(transform_func_t &&f) { this->transform_func_ = f; } - void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } - - protected: - void control(float value) override; - optional transform_func_; - optional write_transform_func_; - ModbusController *parent_; - float multiply_by_{1.0}; -}; - -} // namespace modbus_controller -} // namespace esphome +#pragma once + +#include "esphome/components/number/number.h" +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +using value_to_data_t = std::function(float); + +class ModbusNumber : public number::Number, public Component, public SensorItem { + public: + ModbusNumber(uint16_t start_address, uint8_t offset, uint32_t bitmask, SensorValueType value_type, int register_count, + uint8_t skip_updates, bool force_new_range) + : number::Number(), Component(), SensorItem() { + this->register_type = ModbusRegisterType::HOLDING; + this->start_address = start_address; + this->offset = offset; + this->bitmask = bitmask; + this->sensor_value_type = value_type; + this->register_count = register_count; + this->skip_updates = skip_updates; + this->force_new_range = force_new_range; + }; + + void dump_config() override; + void parse_and_publish(const std::vector &data) override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + void set_update_interval(int) {} + void set_parent(ModbusController *parent) { this->parent_ = parent; } + void set_write_multiply(float factor) { multiply_by_ = factor; } + + using transform_func_t = std::function(ModbusNumber *, float, const std::vector &)>; + using write_transform_func_t = std::function(ModbusNumber *, float, std::vector &)>; + void set_template(transform_func_t &&f) { this->transform_func_ = f; } + void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + + protected: + void control(float value) override; + optional transform_func_; + optional write_transform_func_; + ModbusController *parent_; + float multiply_by_{1.0}; +}; + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/output/modbus_output.h b/esphome/components/modbus_controller/output/modbus_output.h index f46aef4683..053186a321 100644 --- a/esphome/components/modbus_controller/output/modbus_output.h +++ b/esphome/components/modbus_controller/output/modbus_output.h @@ -1,45 +1,45 @@ -#pragma once - -#include "esphome/components/output/float_output.h" -#include "esphome/components/modbus_controller/modbus_controller.h" -#include "esphome/core/component.h" - -namespace esphome { -namespace modbus_controller { - -using value_to_data_t = std::function(float); - -class ModbusOutput : public output::FloatOutput, public Component, public SensorItem { - public: - ModbusOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type) - : output::FloatOutput(), Component() { - this->register_type = ModbusRegisterType::HOLDING; - this->start_address = start_address; - this->offset = offset; - this->bitmask = bitmask; - this->sensor_value_type = value_type; - this->skip_updates = 0; - this->start_address += offset; - this->offset = 0; - } - void setup() override; - void dump_config() override; - - void set_parent(ModbusController *parent) { this->parent_ = parent; } - void set_write_multiply(float factor) { multiply_by_ = factor; } - // Do nothing - void parse_and_publish(const std::vector &data) override{}; - - using write_transform_func_t = std::function(ModbusOutput *, float, std::vector &)>; - void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } - - protected: - void write_state(float value) override; - optional write_transform_func_{nullopt}; - - ModbusController *parent_; - float multiply_by_{1.0}; -}; - -} // namespace modbus_controller -} // namespace esphome +#pragma once + +#include "esphome/components/output/float_output.h" +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +using value_to_data_t = std::function(float); + +class ModbusOutput : public output::FloatOutput, public Component, public SensorItem { + public: + ModbusOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type) + : output::FloatOutput(), Component() { + this->register_type = ModbusRegisterType::HOLDING; + this->start_address = start_address; + this->offset = offset; + this->bitmask = bitmask; + this->sensor_value_type = value_type; + this->skip_updates = 0; + this->start_address += offset; + this->offset = 0; + } + void setup() override; + void dump_config() override; + + void set_parent(ModbusController *parent) { this->parent_ = parent; } + void set_write_multiply(float factor) { multiply_by_ = factor; } + // Do nothing + void parse_and_publish(const std::vector &data) override{}; + + using write_transform_func_t = std::function(ModbusOutput *, float, std::vector &)>; + void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + + protected: + void write_state(float value) override; + optional write_transform_func_{nullopt}; + + ModbusController *parent_; + float multiply_by_{1.0}; +}; + +} // namespace modbus_controller +} // namespace esphome From c89018a4319ada1a5fe96e6f368e194721cb956c Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Thu, 30 Sep 2021 18:08:15 +0200 Subject: [PATCH 107/549] Option to ignore CRC for EFuse MAC address (#2399) * Accept changes as proposed by black. * Added test and implemented optional correctly. * Disable PHY RF full calibration (because it calls the breaking MAC retrieval function). * Disable CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE instead of enable, dummy! * Rename CONF_IGNORE_EFUSE_MAC_CRC to CONF_ESP32_IGNORE_EFUSE_MAC_CRC. * Removed unused import. * Fix ordering of constants. * Moved all MAC address logic to core helpers. * Use pretty MAC address for the log. * Use standard MAC formatter function for debug component. * Fix clang-formatting. * Fix clang-formatting. * Brought wording of comments in line with other function-describing comments. * Processed code review by @OttoWinter * Add USE_ESP32_IGNORE_EFUSE_MAC_CRC to defines.h Co-authored-by: Maurice Makaay --- esphome/components/debug/debug_component.cpp | 5 +-- esphome/components/esp32/__init__.py | 13 +++++++ .../wifi/wifi_component_esp_idf.cpp | 6 ++++ esphome/const.py | 2 ++ esphome/core/defines.h | 1 + esphome/core/helpers.cpp | 34 ++++++++++++++----- esphome/core/helpers.h | 12 ++++++- tests/test5.yaml | 2 ++ 8 files changed, 61 insertions(+), 14 deletions(-) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index 7fd8956148..b856733121 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -104,10 +104,7 @@ void DebugComponent::dump_config() { ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version()); - 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()); + ESP_LOGD(TAG, "EFuse MAC: %s", get_mac_address_pretty().c_str()); const char *reset_reason; switch (rtc_get_reset_reason(0)) { diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 719b7b5f31..1316c7ccbe 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -10,6 +10,8 @@ from esphome.const import ( CONF_TYPE, CONF_VARIANT, CONF_VERSION, + CONF_ADVANCED, + CONF_IGNORE_EFUSE_MAC_CRC, KEY_CORE, KEY_FRAMEWORK_VERSION, KEY_TARGET_FRAMEWORK, @@ -230,6 +232,11 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All( cv.string_strict: cv.string_strict }, cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, + cv.Optional(CONF_ADVANCED, default={}): cv.Schema( + { + cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean, + } + ), } ), _esp_idf_check_versions, @@ -295,6 +302,12 @@ async def to_code(config): for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): add_idf_sdkconfig_option(name, RawSdkconfigValue(value)) + if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_MAC_CRC]: + cg.add_define("USE_ESP32_IGNORE_EFUSE_MAC_CRC") + add_idf_sdkconfig_option( + "CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE", False + ) + elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: cg.add_platformio_option( "platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}" diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 9ec3f80014..7f71b7078c 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -110,6 +110,12 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi } void WiFiComponent::wifi_pre_setup_() { +#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC + uint8_t mac[6]; + get_mac_address_raw(mac); + set_mac_address(mac); + ESP_LOGV(TAG, "Use EFuse MAC without checking CRC: %s", get_mac_address_pretty().c_str()); +#endif esp_err_t err = esp_netif_init(); if (err != ERR_OK) { ESP_LOGE(TAG, "esp_netif_init failed: %s", esp_err_to_name(err)); diff --git a/esphome/const.py b/esphome/const.py index 054f032da4..265576bfbe 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -42,6 +42,7 @@ CONF_ACTION_ID = "action_id" CONF_ACTIVE_POWER = "active_power" CONF_ADDRESS = "address" CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id" +CONF_ADVANCED = "advanced" CONF_ALPHA = "alpha" CONF_ALTITUDE = "altitude" CONF_AND = "and" @@ -281,6 +282,7 @@ CONF_IDLE_ACTION = "idle_action" CONF_IDLE_LEVEL = "idle_level" CONF_IDLE_TIME = "idle_time" CONF_IF = "if" +CONF_IGNORE_EFUSE_MAC_CRC = "ignore_efuse_mac_crc" CONF_IIR_FILTER = "iir_filter" CONF_ILLUMINANCE = "illuminance" CONF_IMPEDANCE = "impedance" diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 1c3b17d071..7c2261920a 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -50,6 +50,7 @@ #ifdef USE_ESP32 #define USE_ESP32_BLE_SERVER #define USE_ESP32_CAMERA +#define USE_ESP32_IGNORE_EFUSE_MAC_CRC #define USE_IMPROV #define USE_SOCKET_IMPL_BSD_SOCKETS diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 731ee6c8f5..780df3ca6d 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -1,4 +1,5 @@ #include "esphome/core/helpers.h" +#include "esphome/core/defines.h" #include #include #include @@ -14,6 +15,10 @@ #include #include #endif +#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC +#include "esp_efuse.h" +#include "esp_efuse_table.h" +#endif #include "esphome/core/log.h" #include "esphome/core/hal.h" @@ -22,15 +27,27 @@ namespace esphome { static const char *const TAG = "helpers"; -std::string get_mac_address() { - char tmp[20]; - uint8_t mac[6]; +void get_mac_address_raw(uint8_t *mac) { #ifdef USE_ESP32 +#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC + // On some devices, the MAC address that is burnt into EFuse does not + // match the CRC that goes along with it. For those devices, this + // work-around reads and uses the MAC address as-is from EFuse, + // without doing the CRC check. + esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, 48); +#else esp_efuse_mac_get_default(mac); #endif +#endif #ifdef USE_ESP8266 WiFi.macAddress(mac); #endif +} + +std::string get_mac_address() { + char tmp[20]; + uint8_t mac[6]; + get_mac_address_raw(mac); sprintf(tmp, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); return std::string(tmp); } @@ -38,16 +55,15 @@ std::string get_mac_address() { std::string get_mac_address_pretty() { char tmp[20]; uint8_t mac[6]; -#ifdef USE_ESP32 - esp_efuse_mac_get_default(mac); -#endif -#ifdef USE_ESP8266 - WiFi.macAddress(mac); -#endif + get_mac_address_raw(mac); sprintf(tmp, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); return std::string(tmp); } +#ifdef USE_ESP32 +void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); } +#endif + std::string generate_hostname(const std::string &base) { return base + std::string("-") + get_mac_address(); } uint32_t random_uint32() { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 24b55eade0..61cc9a9e4a 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -26,11 +26,21 @@ namespace esphome { /// The characters that are allowed in a hostname. extern const char *const HOSTNAME_CHARACTER_ALLOWLIST; -/// Gets the MAC address as a string, this can be used as way to identify this ESP. +/// Read the raw MAC address into the provided byte array (6 bytes). +void get_mac_address_raw(uint8_t *mac); + +/// Get the MAC address as a string, using lower case hex notation. +/// This can be used as way to identify this ESP. std::string get_mac_address(); +/// Get the MAC address as a string, using colon-separated upper case hex notation. std::string get_mac_address_pretty(); +#ifdef USE_ESP32 +/// Set the MAC address to use from the provided byte array (6 bytes). +void set_mac_address(uint8_t *mac); +#endif + std::string to_string(const std::string &val); std::string to_string(int val); std::string to_string(long val); // NOLINT diff --git a/tests/test5.yaml b/tests/test5.yaml index b22b19550e..aca8434fbf 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -9,6 +9,8 @@ esp32: board: nodemcu-32s framework: type: esp-idf + advanced: + ignore_efuse_mac_crc: true wifi: networks: From 5a2984d03a17cb1843d505b88205b8042f0a05ab Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Fri, 1 Oct 2021 10:11:07 +0200 Subject: [PATCH 108/549] Fix attach_interrupt(...) for esp-idf framework (#2416) Co-authored-by: Maurice Makaay --- esphome/components/esp32/gpio_idf.cpp | 71 +++++++++++++++++++++++++++ esphome/components/esp32/gpio_idf.h | 68 +++---------------------- 2 files changed, 77 insertions(+), 62 deletions(-) diff --git a/esphome/components/esp32/gpio_idf.cpp b/esphome/components/esp32/gpio_idf.cpp index 478b28a89a..d1853e1f8b 100644 --- a/esphome/components/esp32/gpio_idf.cpp +++ b/esphome/components/esp32/gpio_idf.cpp @@ -22,6 +22,77 @@ ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } +void IDFInternalGPIOPin::setup() { + pin_mode(flags_); + gpio_set_drive_capability(pin_, drive_strength_); +} + +void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) { + gpio_config_t conf{}; + conf.pin_bit_mask = 1ULL << 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 IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; } + +void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); } + +gpio_mode_t IDFInternalGPIOPin::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 IDFInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { + 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) { + auto res = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3); + if (res != ESP_OK) { + ESP_LOGE(TAG, "attach_interrupt(): call to gpio_install_isr_service() failed, error code: %d", res); + return; + } + isr_service_installed = true; + } + gpio_isr_handler_add(pin_, func, arg); +} + std::string IDFInternalGPIOPin::dump_summary() const { char buffer[32]; snprintf(buffer, sizeof(buffer), "GPIO%u", static_cast(pin_)); diff --git a/esphome/components/esp32/gpio_idf.h b/esphome/components/esp32/gpio_idf.h index 448151cd0f..a99571cc46 100644 --- a/esphome/components/esp32/gpio_idf.h +++ b/esphome/components/esp32/gpio_idf.h @@ -13,22 +13,10 @@ class IDFInternalGPIOPin : public InternalGPIOPin { 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 = 1ULL << 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); } + void setup() 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 detach_interrupt() const override { gpio_intr_disable(pin_); } ISRInternalGPIOPin to_isr() const override; @@ -36,52 +24,8 @@ class IDFInternalGPIOPin : public InternalGPIOPin { 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); - } + static gpio_mode_t flags_to_mode(gpio::Flags flags); + void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; gpio_num_t pin_; bool inverted_; From d0dfc94a61cd6d3758e4284156d5ce707a7803b2 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Fri, 1 Oct 2021 12:53:37 +0200 Subject: [PATCH 109/549] Fix I2C recovery on Arduino (#2412) Co-authored-by: Maurice Makaay --- esphome/components/i2c/i2c_bus_arduino.cpp | 130 +++++++++++++++++---- esphome/components/i2c/i2c_bus_arduino.h | 7 ++ 2 files changed, 117 insertions(+), 20 deletions(-) diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 87dbcb66d8..539091ed9c 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -32,6 +32,17 @@ void ArduinoI2CBus::dump_config() { 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_); + switch (this->recovery_result_) { + case RECOVERY_COMPLETED: + ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered"); + break; + case RECOVERY_FAILED_SCL_LOW: + ESP_LOGCONFIG(TAG, " Recovery: failed, SCL is held low on the bus"); + break; + case RECOVERY_FAILED_SDA_LOW: + ESP_LOGCONFIG(TAG, " Recovery: failed, SDA is held low on the bus"); + break; + } if (this->scan_) { ESP_LOGI(TAG, "Scanning i2c bus for active devices..."); uint8_t found = 0; @@ -92,31 +103,110 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn return ERROR_UNKNOWN; } +/// Perform I2C bus recovery, see: +/// https://www.nxp.com/docs/en/user-guide/UM10204.pdf +/// https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf void ArduinoI2CBus::recover_() { - // Perform I2C bus recovery, see - // https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf - // or see the linux kernel implementation, e.g. - // https://elixir.bootlin.com/linux/v5.14.6/source/drivers/i2c/i2c-core-base.c#L200 + ESP_LOGI(TAG, "Performing I2C bus recovery"); - // try to get about 100kHz toggle frequency - const auto half_period_usec = 1000000 / 100000 / 2; - const auto recover_scl_periods = 9; - - // configure scl as output - pinMode(scl_pin_, OUTPUT); // NOLINT - - // set scl high - digitalWrite(scl_pin_, 1); // NOLINT - - // in total generate 9 falling-rising edges - for (auto i = 0; i < recover_scl_periods; i++) { - delayMicroseconds(half_period_usec); - digitalWrite(scl_pin_, 0); // NOLINT - delayMicroseconds(half_period_usec); - digitalWrite(scl_pin_, 1); // NOLINT + // Activate the pull up resistor on the SCL pin. This should make the + // signal on the line HIGH. If SCL is pulled low on the I2C bus however, + // then some device is interfering with the SCL line. In that case, + // the I2C bus cannot be recovered. + pinMode(scl_pin_, INPUT_PULLUP); // NOLINT + if (digitalRead(scl_pin_) == LOW) { // NOLINT + ESP_LOGE(TAG, "Recovery failed: SCL is held LOW on the I2C bus"); + recovery_result_ = RECOVERY_FAILED_SCL_LOW; + return; } + // From the specification: + // "If the data line (SDA) is stuck LOW, send nine clock pulses. The + // device that held the bus LOW should release it sometime within + // those nine clocks." + // We don't really have to detect if SDA is stuck low. We'll simply send + // nine clock pulses here, just in case SDA is stuck. + + // Use a 100kHz toggle frequency (i.e. the maximum frequency for I2C + // running in standard-mode). The resulting frequency will be lower, + // because of the additional function calls that are done, but that + // is no problem. + const auto half_period_usec = 1000000 / 100000 / 2; + + // Make sure that switching to mode OUTPUT will make SCL low, just in + // case other code has setup the pin to output a HIGH signal. + digitalWrite(scl_pin_, LOW); // NOLINT + + // Activate the pull up resistor for SDA, so after the clock pulse cycle + // we can verify if SDA is pulled high. Also make sure that switching to + // mode OUTPUT will make SDA low. + pinMode(sda_pin_, INPUT_PULLUP); // NOLINT + digitalWrite(sda_pin_, LOW); // NOLINT + + ESP_LOGI(TAG, "Sending 9 clock pulses to drain any stuck device output"); delayMicroseconds(half_period_usec); + for (auto i = 0; i < 9; i++) { + // Release pull up resistor and switch to output to make the signal LOW. + pinMode(scl_pin_, INPUT); // NOLINT + pinMode(scl_pin_, OUTPUT); // NOLINT + delayMicroseconds(half_period_usec); + + // Release output and activate pull up resistor to make the signal HIGH. + pinMode(scl_pin_, INPUT); // NOLINT + pinMode(scl_pin_, INPUT_PULLUP); // NOLINT + delayMicroseconds(half_period_usec); + + // When SCL is kept LOW at this point, we might be looking at a device + // that applies clock stretching. Wait for the release of the SCL line, + // but not forever. There is no specification for the maximum allowed + // time. We'll stick to 500ms here. + auto wait = 20; + while (wait-- && digitalRead(scl_pin_) == LOW) { // NOLINT + delay(25); + } + if (digitalRead(scl_pin_) == LOW) { // NOLINT + ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle"); + recovery_result_ = RECOVERY_FAILED_SCL_LOW; + return; + } + } + + // By now, any stuck device ought to have sent all remaining bits of its + // transation, meaning that it should have freed up the SDA line, resulting + // in SDA being pulled up. + if (digitalRead(sda_pin_) == LOW) { // NOLINT + ESP_LOGE(TAG, "Recovery failed: SDA is held LOW after clock pulse cycle"); + recovery_result_ = RECOVERY_FAILED_SDA_LOW; + return; + } + + // From the specification: + // "I2C-bus compatible devices must reset their bus logic on receipt of + // a START or repeated START condition such that they all anticipate + // the sending of a target address, even if these START conditions are + // not positioned according to the proper format." + // While the 9 clock pulses from above might have drained all bits of a + // single byte within a transaction, a device might have more bytes to + // transmit. So here we'll generate a START condition to snap the device + // out of this state. + // SCL and SDA are already high at this point, so we can generate a START + // condition by making the SDA signal LOW. + ESP_LOGI(TAG, "Generate START condition to reset bus logic of I2C devices"); + pinMode(sda_pin_, INPUT); // NOLINT + pinMode(sda_pin_, OUTPUT); // NOLINT + delayMicroseconds(half_period_usec); + + // From the specification: + // "A START condition immediately followed by a STOP condition (void + // message) is an illegal format. Many devices however are designed to + // operate properly under this condition." + // Finally, we'll bring the I2C bus into a starting state by generating + // a STOP condition. + ESP_LOGI(TAG, "Generate STOP condition to finalize recovery"); + pinMode(sda_pin_, INPUT); // NOLINT + pinMode(sda_pin_, INPUT_PULLUP); // NOLINT + + recovery_result_ = RECOVERY_COMPLETED; } } // namespace i2c } // namespace esphome diff --git a/esphome/components/i2c/i2c_bus_arduino.h b/esphome/components/i2c/i2c_bus_arduino.h index 42589dcfb7..82f043ef7d 100644 --- a/esphome/components/i2c/i2c_bus_arduino.h +++ b/esphome/components/i2c/i2c_bus_arduino.h @@ -9,6 +9,12 @@ namespace esphome { namespace i2c { +enum RecoveryCode { + RECOVERY_FAILED_SCL_LOW, + RECOVERY_FAILED_SDA_LOW, + RECOVERY_COMPLETED, +}; + class ArduinoI2CBus : public I2CBus, public Component { public: void setup() override; @@ -24,6 +30,7 @@ class ArduinoI2CBus : public I2CBus, public Component { private: void recover_(); + RecoveryCode recovery_result_; protected: TwoWire *wire_; From 15ab8918af0fbc0f7f6eb2cf09456f2b8ce9c640 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Oct 2021 15:20:12 +0200 Subject: [PATCH 110/549] Bump aioesphomeapi from 9.1.1 to 9.1.2 (#2426) 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 7ee22f0415..5ff9f7e282 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.0 esptool==3.1 click==8.0.1 esphome-dashboard==20210927.0 -aioesphomeapi==9.1.1 +aioesphomeapi==9.1.2 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 932e0469f7a63c3a20326f1e911620744a9c7c11 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sat, 2 Oct 2021 16:02:01 +0200 Subject: [PATCH 111/549] Fix ESP32 esp-idf OTA updates (#2424) * WIP on separating out the OTA backends. * Split off the individual OTA backends. * Cleanup the three backends, split into .h and .cpp. * After successfull flashing, activate the new boot partition. * Fix linting issues. * Minor cleanup Co-authored-by: Maurice Makaay Co-authored-by: Otto winter --- esphome/components/ota/ota_backend.h | 18 +++ .../ota/ota_backend_arduino_esp32.cpp | 46 ++++++ .../ota/ota_backend_arduino_esp32.h | 22 +++ .../ota/ota_backend_arduino_esp8266.cpp | 58 ++++++++ .../ota/ota_backend_arduino_esp8266.h | 25 ++++ .../components/ota/ota_backend_esp_idf.cpp | 72 +++++++++ esphome/components/ota/ota_backend_esp_idf.h | 27 ++++ esphome/components/ota/ota_component.cpp | 139 ++---------------- 8 files changed, 279 insertions(+), 128 deletions(-) create mode 100644 esphome/components/ota/ota_backend.h create mode 100644 esphome/components/ota/ota_backend_arduino_esp32.cpp create mode 100644 esphome/components/ota/ota_backend_arduino_esp32.h create mode 100644 esphome/components/ota/ota_backend_arduino_esp8266.cpp create mode 100644 esphome/components/ota/ota_backend_arduino_esp8266.h create mode 100644 esphome/components/ota/ota_backend_esp_idf.cpp create mode 100644 esphome/components/ota/ota_backend_esp_idf.h diff --git a/esphome/components/ota/ota_backend.h b/esphome/components/ota/ota_backend.h new file mode 100644 index 0000000000..c253e009c6 --- /dev/null +++ b/esphome/components/ota/ota_backend.h @@ -0,0 +1,18 @@ +#pragma once +#include "ota_component.h" + +namespace esphome { +namespace ota { + +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; +}; + +} // namespace ota +} // namespace esphome diff --git a/esphome/components/ota/ota_backend_arduino_esp32.cpp b/esphome/components/ota/ota_backend_arduino_esp32.cpp new file mode 100644 index 0000000000..4759737dbd --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_esp32.cpp @@ -0,0 +1,46 @@ +#include "esphome/core/defines.h" +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +#include "ota_backend_arduino_esp32.h" +#include "ota_component.h" +#include "ota_backend.h" + +#include + +namespace esphome { +namespace ota { + +OTAResponseTypes ArduinoESP32OTABackend::begin(size_t image_size) { + bool ret = Update.begin(image_size, U_FLASH); + if (ret) { + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); + if (error == UPDATE_ERROR_SIZE) + return OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; + return OTA_RESPONSE_ERROR_UNKNOWN; +} + +void ArduinoESP32OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } + +OTAResponseTypes ArduinoESP32OTABackend::write(uint8_t *data, size_t len) { + size_t written = Update.write(data, len); + if (written != len) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_OK; +} + +OTAResponseTypes ArduinoESP32OTABackend::end() { + if (!Update.end()) + return OTA_RESPONSE_ERROR_UPDATE_END; + return OTA_RESPONSE_OK; +} + +void ArduinoESP32OTABackend::abort() { Update.abort(); } + +} // namespace ota +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/ota/ota_backend_arduino_esp32.h b/esphome/components/ota/ota_backend_arduino_esp32.h new file mode 100644 index 0000000000..8343bdf94f --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_esp32.h @@ -0,0 +1,22 @@ +#pragma once +#include "esphome/core/defines.h" +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +#include "ota_component.h" +#include "ota_backend.h" + +namespace esphome { +namespace ota { + +class ArduinoESP32OTABackend : public OTABackend { + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; +}; + +} // namespace ota +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.cpp b/esphome/components/ota/ota_backend_arduino_esp8266.cpp new file mode 100644 index 0000000000..8e8a4f36ba --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_esp8266.cpp @@ -0,0 +1,58 @@ +#include "esphome/core/defines.h" +#ifdef USE_ARDUINO +#ifdef USE_ESP8266 + +#include "ota_backend_arduino_esp8266.h" +#include "ota_component.h" +#include "ota_backend.h" +#include "esphome/components/esp8266/preferences.h" + +#include + +namespace esphome { +namespace ota { + +OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) { + bool ret = Update.begin(image_size, U_FLASH); + if (ret) { + esp8266::preferences_prevent_write(true); + } + + uint8_t error = Update.getError(); + 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; + return OTA_RESPONSE_ERROR_UNKNOWN; +} + +void ArduinoESP8266OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } + +OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) { + size_t written = Update.write(data, len); + if (written != len) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_OK; +} + +OTAResponseTypes ArduinoESP8266OTABackend::end() { + if (!Update.end()) + return OTA_RESPONSE_ERROR_UPDATE_END; + return OTA_RESPONSE_OK; +} + +void ArduinoESP8266OTABackend::abort() { + Update.end(); + esp8266::preferences_prevent_write(false); +} + +} // namespace ota +} // namespace esphome + +#endif +#endif diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.h b/esphome/components/ota/ota_backend_arduino_esp8266.h new file mode 100644 index 0000000000..d1195af911 --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_esp8266.h @@ -0,0 +1,25 @@ +#pragma once +#include "esphome/core/defines.h" +#ifdef USE_ARDUINO +#ifdef USE_ESP8266 + +#include "ota_component.h" +#include "ota_backend.h" + +namespace esphome { +namespace ota { + +class ArduinoESP8266OTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; +}; + +} // namespace ota +} // namespace esphome + +#endif +#endif diff --git a/esphome/components/ota/ota_backend_esp_idf.cpp b/esphome/components/ota/ota_backend_esp_idf.cpp new file mode 100644 index 0000000000..4eb17d82f1 --- /dev/null +++ b/esphome/components/ota/ota_backend_esp_idf.cpp @@ -0,0 +1,72 @@ +#include "esphome/core/defines.h" +#ifdef USE_ESP_IDF + +#include "ota_backend_esp_idf.h" +#include "ota_component.h" +#include + +namespace esphome { +namespace ota { + +OTAResponseTypes IDFOTABackend::begin(size_t image_size) { + this->partition_ = esp_ota_get_next_update_partition(nullptr); + if (this->partition_ == nullptr) { + return OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION; + } + esp_err_t err = esp_ota_begin(this->partition_, image_size, &this->update_handle_); + if (err != ESP_OK) { + esp_ota_abort(this->update_handle_); + this->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 IDFOTABackend::set_update_md5(const char *md5) { + // pass +} + +OTAResponseTypes IDFOTABackend::write(uint8_t *data, size_t len) { + esp_err_t err = esp_ota_write(this->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 IDFOTABackend::end() { + esp_err_t err = esp_ota_end(this->update_handle_); + this->update_handle_ = 0; + if (err == ESP_OK) { + err = esp_ota_set_boot_partition(this->partition_); + if (err == ESP_OK) { + return OTA_RESPONSE_OK; + } + } + if (err == ESP_ERR_OTA_VALIDATE_FAILED) { + return OTA_RESPONSE_ERROR_UPDATE_END; + } + if (err == ESP_ERR_FLASH_OP_TIMEOUT || err == ESP_ERR_FLASH_OP_FAIL) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_ERROR_UNKNOWN; +} + +void IDFOTABackend::abort() { + esp_ota_abort(this->update_handle_); + this->update_handle_ = 0; +} + +} // namespace ota +} // namespace esphome +#endif diff --git a/esphome/components/ota/ota_backend_esp_idf.h b/esphome/components/ota/ota_backend_esp_idf.h new file mode 100644 index 0000000000..d6e2e2742a --- /dev/null +++ b/esphome/components/ota/ota_backend_esp_idf.h @@ -0,0 +1,27 @@ +#pragma once +#include "esphome/core/defines.h" +#ifdef USE_ESP_IDF + +#include "ota_component.h" +#include "ota_backend.h" +#include + +namespace esphome { +namespace ota { + +class IDFOTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; + + private: + esp_ota_handle_t update_handle_{0}; + const esp_partition_t *partition_; +}; + +} // namespace ota +} // namespace esphome +#endif diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 0c13efa135..e1188a4d31 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -1,4 +1,8 @@ #include "ota_component.h" +#include "ota_backend.h" +#include "ota_backend_arduino_esp32.h" +#include "ota_backend_arduino_esp8266.h" +#include "ota_backend_esp_idf.h" #include "esphome/core/log.h" #include "esphome/core/application.h" @@ -9,23 +13,8 @@ #include #include -#ifdef USE_ARDUINO #ifdef USE_OTA_PASSWORD #include -#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 namespace esphome { @@ -35,125 +24,19 @@ 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; -}; - +std::unique_ptr make_ota_backend() { #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 + return make_unique(); +#endif // USE_ESP8266 #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(); } + return make_unique(); +#endif // USE_ESP32 #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(); } + return make_unique(); #endif // USE_ESP_IDF +} void OTAComponent::setup() { server_ = socket::socket(AF_INET, SOCK_STREAM, 0); From a7687c3e17245ca383ce5bcf2951a0ab54afdba1 Mon Sep 17 00:00:00 2001 From: cvwillegen Date: Sun, 3 Oct 2021 13:27:59 +0200 Subject: [PATCH 112/549] Add local MAC address to WiFi info (#2428) --- esphome/components/wifi/wifi_component.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 47cd0ef9ad..703afa99bc 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -349,6 +349,7 @@ const LogString *get_signal_bars(int8_t rssi) { void WiFiComponent::print_connect_params_() { bssid_t bssid = wifi_bssid(); + ESP_LOGCONFIG(TAG, " Local MAC: %s", get_mac_address_pretty().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], From eaa5200a35ea1b5acec49a5b61356f81c60a1d2f Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sun, 3 Oct 2021 07:10:43 -0500 Subject: [PATCH 113/549] Thermostat publish state fix (#2427) --- .../thermostat/thermostat_climate.cpp | 34 ++++++++++++------- .../thermostat/thermostat_climate.h | 8 ++--- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 6193185321..ce15c53bbe 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -18,8 +18,8 @@ void ThermostatClimate::setup() { // add a callback so that whenever the sensor state changes we can take action this->sensor_->add_on_state_callback([this](float state) { this->current_temperature = state; - // required action may have changed, recompute, refresh - this->switch_to_action_(this->compute_action_()); + // required action may have changed, recompute, refresh, we'll publish_state() later + this->switch_to_action_(this->compute_action_(), false); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); // current temperature and possibly action changed, so publish the new state this->publish_state(); @@ -34,8 +34,8 @@ void ThermostatClimate::setup() { this->mode = this->default_mode_; this->change_away_(false); } - // refresh the climate action based on the restored settings - this->switch_to_action_(this->compute_action_()); + // refresh the climate action based on the restored settings, we'll publish_state() later + this->switch_to_action_(this->compute_action_(), false); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); this->setup_complete_ = true; this->publish_state(); @@ -47,11 +47,11 @@ float ThermostatClimate::heat_deadband() { return this->heating_deadband_; } float ThermostatClimate::heat_overrun() { return this->heating_overrun_; } void ThermostatClimate::refresh() { - this->switch_to_mode_(this->mode); - this->switch_to_action_(this->compute_action_()); + this->switch_to_mode_(this->mode, false); + this->switch_to_action_(this->compute_action_(), false); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); - this->switch_to_fan_mode_(this->fan_mode.value()); - this->switch_to_swing_mode_(this->swing_mode); + this->switch_to_fan_mode_(this->fan_mode.value(), false); + this->switch_to_swing_mode_(this->swing_mode, false); this->check_temperature_change_trigger_(); this->publish_state(); } @@ -346,7 +346,7 @@ climate::ClimateAction ThermostatClimate::compute_supplemental_action_() { return target_action; } -void ThermostatClimate::switch_to_action_(climate::ClimateAction action) { +void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool publish_state) { // setup_complete_ helps us ensure an action is called immediately after boot if ((action == this->action) && this->setup_complete_) // already in target mode @@ -358,6 +358,8 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action) { // switching from OFF to IDLE or vice-versa -- this is only a visual difference. // OFF means user manually disabled, IDLE means the temperature is in target range. this->action = action; + if (publish_state) + this->publish_state(); return; } @@ -452,6 +454,8 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action) { ESP_LOGVV(TAG, "Calling FAN_ONLY action with HEATING/COOLING action"); trig_fan->trigger(); } + if (publish_state) + this->publish_state(); } } @@ -509,13 +513,15 @@ void ThermostatClimate::trigger_supplemental_action_() { } } -void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { +void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bool publish_state) { // setup_complete_ helps us ensure an action is called immediately after boot if ((fan_mode == this->prev_fan_mode_) && this->setup_complete_) // already in target mode return; this->fan_mode = fan_mode; + if (publish_state) + this->publish_state(); if (this->fan_mode_ready_()) { Trigger<> *trig = this->fan_mode_auto_trigger_; @@ -574,7 +580,7 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { } } -void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { +void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_state) { // setup_complete_ helps us ensure an action is called immediately after boot if ((mode == this->prev_mode_) && this->setup_complete_) // already in target mode @@ -615,9 +621,11 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { this->mode = mode; this->prev_mode_ = mode; this->prev_mode_trigger_ = trig; + if (publish_state) + this->publish_state(); } -void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mode) { +void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mode, bool publish_state) { // setup_complete_ helps us ensure an action is called immediately after boot if ((swing_mode == this->prev_swing_mode_) && this->setup_complete_) // already in target mode @@ -652,6 +660,8 @@ void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mo this->swing_mode = swing_mode; this->prev_swing_mode_ = swing_mode; this->prev_swing_mode_trigger_ = trig; + if (publish_state) + this->publish_state(); } bool ThermostatClimate::idle_action_ready_() { diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 60777e7c81..8d3e926752 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -160,18 +160,18 @@ class ThermostatClimate : public climate::Climate, public Component { climate::ClimateAction compute_supplemental_action_(); /// Switch the climate device to the given climate action. - void switch_to_action_(climate::ClimateAction action); + void switch_to_action_(climate::ClimateAction action, bool publish_state = true); void switch_to_supplemental_action_(climate::ClimateAction action); void trigger_supplemental_action_(); /// Switch the climate device to the given climate fan mode. - void switch_to_fan_mode_(climate::ClimateFanMode fan_mode); + void switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bool publish_state = true); /// Switch the climate device to the given climate mode. - void switch_to_mode_(climate::ClimateMode mode); + void switch_to_mode_(climate::ClimateMode mode, bool publish_state = true); /// Switch the climate device to the given climate swing mode. - void switch_to_swing_mode_(climate::ClimateSwingMode swing_mode); + void switch_to_swing_mode_(climate::ClimateSwingMode swing_mode, bool publish_state = true); /// Check if the temperature change trigger should be called. void check_temperature_change_trigger_(); From 912793eddf70f2ec59e086f22b076c82e183073f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 3 Oct 2021 14:10:53 +0200 Subject: [PATCH 114/549] Convert time to use tzdata (#2425) --- esphome/components/time/__init__.py | 160 ++++++++-------------------- requirements.txt | 4 +- 2 files changed, 49 insertions(+), 115 deletions(-) diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index d548575d72..5c2155d764 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -1,10 +1,8 @@ -import bisect -import datetime import logging -import math -import string +from importlib import resources +from typing import Optional +from datetime import timezone -import pytz import tzlocal import esphome.codegen as cg @@ -44,111 +42,47 @@ ESPTime = time_ns.struct("ESPTime") TimeHasTimeCondition = time_ns.class_("TimeHasTimeCondition", Condition) -def _tz_timedelta(td): - offset_hour = int(td.total_seconds() / (60 * 60)) - offset_minute = int(abs(td.total_seconds() / 60)) % 60 - offset_second = int(abs(td.total_seconds())) % 60 - if offset_hour == 0 and offset_minute == 0 and offset_second == 0: - return "0" - if offset_minute == 0 and offset_second == 0: - return f"{offset_hour}" - if offset_second == 0: - return f"{offset_hour}:{offset_minute}" - return f"{offset_hour}:{offset_minute}:{offset_second}" - - -# https://stackoverflow.com/a/16804556/8924614 -def _week_of_month(dt): - first_day = dt.replace(day=1) - dom = dt.day - adjusted_dom = dom + first_day.weekday() - return int(math.ceil(adjusted_dom / 7.0)) - - -def _tz_dst_str(dt): - td = datetime.timedelta(hours=dt.hour, minutes=dt.minute, seconds=dt.second) - return f"M{dt.month}.{_week_of_month(dt)}.{dt.isoweekday() % 7}/{_tz_timedelta(td)}" - - -def _safe_tzname(tz, dt): - tzname = tz.tzname(dt) - # pytz does not always return valid tznames - # For example: 'Europe/Saratov' returns '+04' - # Work around it by using a generic name for the timezone - if not all(c in string.ascii_letters for c in tzname): - return "TZ" - return tzname - - -def _non_dst_tz(tz, dt): - tzname = _safe_tzname(tz, dt) - utcoffset = tz.utcoffset(dt) - _LOGGER.info( - "Detected timezone '%s' with UTC offset %s", tzname, _tz_timedelta(utcoffset) - ) - tzbase = f"{tzname}{_tz_timedelta(-1 * utcoffset)}" - return tzbase - - -def convert_tz(pytz_obj): - tz = pytz_obj - - now = datetime.datetime.now() - first_january = datetime.datetime(year=now.year, month=1, day=1) - - if not isinstance(tz, pytz.tzinfo.DstTzInfo): - return _non_dst_tz(tz, first_january) - - # pylint: disable=protected-access - transition_times = tz._utc_transition_times - transition_info = tz._transition_info - idx = max(0, bisect.bisect_right(transition_times, now)) - if idx >= len(transition_times): - return _non_dst_tz(tz, now) - - idx1, idx2 = idx, idx + 1 - dstoffset1 = transition_info[idx1][1] - if dstoffset1 == datetime.timedelta(seconds=0): - # Normalize to 1 being DST on - idx1, idx2 = idx + 1, idx + 2 - - if idx2 >= len(transition_times): - return _non_dst_tz(tz, now) - - if transition_times[idx2].year > now.year + 1: - # Next transition is scheduled after this year - # Probably a scheduler timezone change. - return _non_dst_tz(tz, now) - - utcoffset_on, _, tzname_on = transition_info[idx1] - utcoffset_off, _, tzname_off = transition_info[idx2] - dst_begins_utc = transition_times[idx1] - dst_begins_local = dst_begins_utc + utcoffset_off - dst_ends_utc = transition_times[idx2] - dst_ends_local = dst_ends_utc + utcoffset_on - - tzbase = f"{tzname_off}{_tz_timedelta(-1 * utcoffset_off)}" - - 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", - tzname_off, - _tz_timedelta(utcoffset_off), - dst_begins_local.strftime("%d %B %X"), - dst_ends_local.strftime("%d %B %X"), - ) - return tzbase + tzext - - -def detect_tz(): +def _load_tzdata(iana_key: str) -> Optional[bytes]: + # From https://tzdata.readthedocs.io/en/latest/#examples try: - tz = tzlocal.get_localzone() - except pytz.exceptions.UnknownTimeZoneError: - _LOGGER.warning("Could not auto-detect timezone. Using UTC...") - return "UTC" + package_loc, resource = iana_key.rsplit("/", 1) + except ValueError: + return None + package = "tzdata.zoneinfo." + package_loc.replace("/", ".") - return convert_tz(tz) + try: + return resources.read_binary(package, resource) + except (FileNotFoundError, ModuleNotFoundError): + return None + + +def _extract_tz_string(tzfile: bytes) -> str: + try: + return tzfile.split(b"\n")[-2].decode() + except (IndexError, UnicodeDecodeError): + _LOGGER.error("Could not determine TZ string. Please report this issue.") + _LOGGER.error("tzfile contents: %s", tzfile, exc_info=True) + raise + + +def detect_tz() -> str: + localzone = tzlocal.get_localzone() + if localzone is timezone.utc: + return "UTC0" + if not hasattr(localzone, "key"): + raise cv.Invalid( + "Could not automatically determine timezone, please set timezone manually." + ) + iana_key = localzone.key + _LOGGER.info("Detected timezone '%s'", iana_key) + tzfile = _load_tzdata(iana_key) + if tzfile is None: + raise cv.Invalid( + "Could not automatically determine timezone, please set timezone manually." + ) + ret = _extract_tz_string(tzfile) + _LOGGER.debug(" -> TZ string %s", ret) + return ret def _parse_cron_int(value, special_mapping, message): @@ -327,15 +261,15 @@ def validate_cron_keys(value): return cv.has_at_least_one_key(*CRON_KEYS)(value) -def validate_tz(value): +def validate_tz(value: str) -> str: value = cv.string_strict(value) - try: - pytz_obj = pytz.timezone(value) - except pytz.UnknownTimeZoneError: # pylint: disable=broad-except + tzfile = _load_tzdata(value) + if tzfile is None: + # Not a IANA key, probably a TZ string return value - return convert_tz(pytz_obj) + return _extract_tz_string(tzfile) TIME_SCHEMA = cv.Schema( diff --git a/requirements.txt b/requirements.txt index 5ff9f7e282..ba2c43e5b7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,8 +3,8 @@ PyYAML==5.4.1 paho-mqtt==1.5.1 colorama==0.4.4 tornado==6.1 -tzlocal==3.0 -pytz==2021.1 +tzlocal==3.0 # from time +tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.0 esptool==3.1 From cee08debffa2f9f33a76e8948d8a6029c1854403 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sun, 3 Oct 2021 16:15:01 +0200 Subject: [PATCH 115/549] Hotfix for ESP8266 OTA issue: ERROR Error binary size (#2432) Co-authored-by: Maurice Makaay --- esphome/components/ota/ota_backend_arduino_esp8266.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.cpp b/esphome/components/ota/ota_backend_arduino_esp8266.cpp index 8e8a4f36ba..23dc0d4e21 100644 --- a/esphome/components/ota/ota_backend_arduino_esp8266.cpp +++ b/esphome/components/ota/ota_backend_arduino_esp8266.cpp @@ -16,6 +16,7 @@ OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) { bool ret = Update.begin(image_size, U_FLASH); if (ret) { esp8266::preferences_prevent_write(true); + return OTA_RESPONSE_OK; } uint8_t error = Update.getError(); From 1627dff166768ec79928fc3e65a3b18f1d7267d4 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sun, 3 Oct 2021 21:53:40 +0200 Subject: [PATCH 116/549] Disable dependency finder on ESP32 (#2435) --- esphome/components/airthings_wave_plus/sensor.py | 4 ++++ esphome/components/captive_portal/__init__.py | 6 +++++- esphome/components/esp32/__init__.py | 2 ++ esphome/components/ethernet/__init__.py | 3 +++ esphome/components/http_request/__init__.py | 5 +++++ esphome/components/mcp3008/__init__.py | 4 ++++ esphome/components/mdns/__init__.py | 9 +++++---- esphome/components/ota/__init__.py | 1 + esphome/components/spi/__init__.py | 5 ++++- esphome/components/web_server_base/__init__.py | 2 ++ 10 files changed, 35 insertions(+), 6 deletions(-) diff --git a/esphome/components/airthings_wave_plus/sensor.py b/esphome/components/airthings_wave_plus/sensor.py index 8b902ea81c..2100341536 100644 --- a/esphome/components/airthings_wave_plus/sensor.py +++ b/esphome/components/airthings_wave_plus/sensor.py @@ -1,6 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, ble_client +from esphome.core import CORE from esphome.const import ( DEVICE_CLASS_CARBON_DIOXIDE, @@ -116,3 +117,6 @@ async def to_code(config): if CONF_TVOC in config: sens = await sensor.new_sensor(config[CONF_TVOC]) cg.add(var.set_tvoc(sens)) + + if CORE.is_esp32: + cg.add_library("ESP32 BLE Arduino", None) diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index a1cc5734c1..384a3f23a0 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -3,7 +3,7 @@ import esphome.config_validation as cv from esphome.components import web_server_base from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID from esphome.const import CONF_ID -from esphome.core import coroutine_with_priority +from esphome.core import coroutine_with_priority, CORE AUTO_LOAD = ["web_server_base"] DEPENDENCIES = ["wifi"] @@ -32,3 +32,7 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID], paren) await cg.register_component(var, config) cg.add_define("USE_CAPTIVE_PORTAL") + + if CORE.is_esp32: + cg.add_library("DNSServer", None) + cg.add_library("WiFi", None) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 1316c7ccbe..704f9bb3e8 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -276,6 +276,8 @@ async def to_code(config): cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}") + cg.add_platformio_option("lib_ldf_mode", "off") + conf = config[CONF_FRAMEWORK] if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: cg.add_platformio_option( diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 384fb3dbfb..bbf64a3cd1 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -123,3 +123,6 @@ async def to_code(config): cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP]))) cg.add_define("USE_ETHERNET") + + if CORE.is_esp32: + cg.add_library("WiFi", None) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index a48b3c0acb..6e249c4247 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -92,6 +92,11 @@ async def to_code(config): 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") + + if CORE.is_esp32: + cg.add_library("WiFiClientSecure", None) + cg.add_library("HTTPClient", None) + await cg.register_component(var, config) diff --git a/esphome/components/mcp3008/__init__.py b/esphome/components/mcp3008/__init__.py index 24a48664c1..431963acfd 100644 --- a/esphome/components/mcp3008/__init__.py +++ b/esphome/components/mcp3008/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import spi from esphome.const import CONF_ID +from esphome.core import CORE DEPENDENCIES = ["spi"] AUTO_LOAD = ["sensor"] @@ -23,3 +24,6 @@ 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 CORE.is_esp32: + cg.add_library("SPI", None) diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index c0c0865643..b95469d9da 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -30,15 +30,16 @@ CONFIG_SCHEMA = cv.All( 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) + if config[CONF_DISABLED]: + return + + cg.add_define("USE_MDNS") + var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 59ab22056b..7856d35580 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -101,6 +101,7 @@ async def to_code(config): if CORE.is_esp8266: cg.add_library("Update", None) elif CORE.is_esp32 and CORE.using_arduino: + cg.add_library("Update", None) cg.add_library("Hash", None) use_state_callback = False diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index 803a45814c..3a96cce99b 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -10,7 +10,7 @@ from esphome.const import ( CONF_SPI_ID, CONF_CS_PIN, ) -from esphome.core import coroutine_with_priority +from esphome.core import coroutine_with_priority, CORE CODEOWNERS = ["@esphome/core"] spi_ns = cg.esphome_ns.namespace("spi") @@ -46,6 +46,9 @@ async def to_code(config): mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN]) cg.add(var.set_mosi(mosi)) + if CORE.is_esp32: + cg.add_library("SPI", None) + def spi_device_schema(cs_pin_required=True): """Create a schema for an SPI device. diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 8f64c473f3..95d59a863e 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -24,6 +24,8 @@ async def to_code(config): await cg.register_component(var, config) if CORE.is_esp32: + cg.add_library("WiFi", None) cg.add_library("FS", None) + cg.add_library("Update", None) # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json cg.add_library("esphome/ESPAsyncWebServer-esphome", "1.3.0") From 49f46a7cddec08d169ab23a16d6f97c0f8a8f564 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sun, 3 Oct 2021 21:55:19 +0200 Subject: [PATCH 117/549] Use size_t to fix comparision using RISC-V toolchain (#2436) --- esphome/components/ota/ota_component.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index e1188a4d31..e897d952ef 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -109,11 +109,11 @@ void OTAComponent::loop() { void OTAComponent::handle_() { OTAResponseTypes error_code = OTA_RESPONSE_ERROR_UNKNOWN; bool update_started = false; - uint32_t total = 0; + size_t total = 0; uint32_t last_progress = 0; uint8_t buf[1024]; char *sbuf = reinterpret_cast(buf); - uint32_t ota_size; + size_t ota_size; uint8_t ota_features; std::unique_ptr backend; (void) ota_features; From 46b4c970d160816e91e933a3f6af402b1812e86a Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sun, 3 Oct 2021 22:21:45 +0200 Subject: [PATCH 118/549] Fix socket abstraction for ESP-IDF v4 (#2434) --- esphome/components/socket/bsd_sockets_impl.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index aca15f118e..dcf4ea1f4e 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -96,6 +96,9 @@ class BSDSocketImpl : public Socket { break; } return ret; +#elif defined(ARDUINO_ARCH_ESP32) + // ESP-IDF v4 only has symbol lwip_readv + return ::lwip_readv(fd_, iov, iovcnt); #else return ::readv(fd_, iov, iovcnt); #endif @@ -120,6 +123,9 @@ class BSDSocketImpl : public Socket { break; } return ret; +#elif defined(ARDUINO_ARCH_ESP32) + // ESP-IDF v4 only has symbol lwip_writev + return ::lwip_writev(fd_, iov, iovcnt); #else return ::writev(fd_, iov, iovcnt); #endif From 5c06cd8eb32ccbde3d3c9605ba70d302a7ae18b1 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Mon, 4 Oct 2021 12:33:25 +0200 Subject: [PATCH 119/549] Fix I2C recovery ESP32 esp-idf (#2438) Co-authored-by: Maurice Makaay --- esphome/components/i2c/i2c_bus_arduino.cpp | 49 ++++---- esphome/components/i2c/i2c_bus_esp_idf.cpp | 125 +++++++++++++++++---- esphome/components/i2c/i2c_bus_esp_idf.h | 7 ++ 3 files changed, 137 insertions(+), 44 deletions(-) diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 539091ed9c..4b519e4873 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -12,6 +12,7 @@ static const char *const TAG = "i2c.arduino"; void ArduinoI2CBus::setup() { recover_(); + #ifdef USE_ESP32 static uint8_t next_bus_num = 0; if (next_bus_num == 0) @@ -109,11 +110,19 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn void ArduinoI2CBus::recover_() { ESP_LOGI(TAG, "Performing I2C bus recovery"); - // Activate the pull up resistor on the SCL pin. This should make the - // signal on the line HIGH. If SCL is pulled low on the I2C bus however, - // then some device is interfering with the SCL line. In that case, - // the I2C bus cannot be recovered. - pinMode(scl_pin_, INPUT_PULLUP); // NOLINT + // For the upcoming operations, target for a 100kHz toggle frequency. + // This is the maximum frequency for I2C running in standard-mode. + // The actual frequency will be lower, because of the additional + // function calls that are done, but that is no problem. + const auto half_period_usec = 1000000 / 100000 / 2; + + // Activate input and pull up resistor for the SCL pin. + pinMode(scl_pin_, INPUT_PULLUP); // NOLINT + + // This should make the signal on the line HIGH. If SCL is pulled low + // on the I2C bus however, then some device is interfering with the SCL + // line. In that case, the I2C bus cannot be recovered. + delayMicroseconds(half_period_usec); if (digitalRead(scl_pin_) == LOW) { // NOLINT ESP_LOGE(TAG, "Recovery failed: SCL is held LOW on the I2C bus"); recovery_result_ = RECOVERY_FAILED_SCL_LOW; @@ -125,25 +134,13 @@ void ArduinoI2CBus::recover_() { // device that held the bus LOW should release it sometime within // those nine clocks." // We don't really have to detect if SDA is stuck low. We'll simply send - // nine clock pulses here, just in case SDA is stuck. + // nine clock pulses here, just in case SDA is stuck. Actual checks on + // the SDA line status will be done after the clock pulses. - // Use a 100kHz toggle frequency (i.e. the maximum frequency for I2C - // running in standard-mode). The resulting frequency will be lower, - // because of the additional function calls that are done, but that - // is no problem. - const auto half_period_usec = 1000000 / 100000 / 2; - - // Make sure that switching to mode OUTPUT will make SCL low, just in - // case other code has setup the pin to output a HIGH signal. + // Make sure that switching to output mode will make SCL low, just in + // case other code has setup the pin for a HIGH signal. digitalWrite(scl_pin_, LOW); // NOLINT - // Activate the pull up resistor for SDA, so after the clock pulse cycle - // we can verify if SDA is pulled high. Also make sure that switching to - // mode OUTPUT will make SDA low. - pinMode(sda_pin_, INPUT_PULLUP); // NOLINT - digitalWrite(sda_pin_, LOW); // NOLINT - - ESP_LOGI(TAG, "Sending 9 clock pulses to drain any stuck device output"); delayMicroseconds(half_period_usec); for (auto i = 0; i < 9; i++) { // Release pull up resistor and switch to output to make the signal LOW. @@ -171,6 +168,11 @@ void ArduinoI2CBus::recover_() { } } + // Activate input and pull resistor for the SDA pin, so we can verify + // that SDA is pulled HIGH in the following step. + pinMode(sda_pin_, INPUT_PULLUP); // NOLINT + digitalWrite(sda_pin_, LOW); // NOLINT + // By now, any stuck device ought to have sent all remaining bits of its // transation, meaning that it should have freed up the SDA line, resulting // in SDA being pulled up. @@ -191,10 +193,9 @@ void ArduinoI2CBus::recover_() { // out of this state. // SCL and SDA are already high at this point, so we can generate a START // condition by making the SDA signal LOW. - ESP_LOGI(TAG, "Generate START condition to reset bus logic of I2C devices"); + delayMicroseconds(half_period_usec); pinMode(sda_pin_, INPUT); // NOLINT pinMode(sda_pin_, OUTPUT); // NOLINT - delayMicroseconds(half_period_usec); // From the specification: // "A START condition immediately followed by a STOP condition (void @@ -202,7 +203,7 @@ void ArduinoI2CBus::recover_() { // operate properly under this condition." // Finally, we'll bring the I2C bus into a starting state by generating // a STOP condition. - ESP_LOGI(TAG, "Generate STOP condition to finalize recovery"); + delayMicroseconds(half_period_usec); pinMode(sda_pin_, INPUT); // NOLINT pinMode(sda_pin_, INPUT_PULLUP); // NOLINT diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 28e71ab2a0..91fd1499e9 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -43,6 +43,17 @@ void IDFI2CBus::dump_config() { 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_); + switch (this->recovery_result_) { + case RECOVERY_COMPLETED: + ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered"); + break; + case RECOVERY_FAILED_SCL_LOW: + ESP_LOGCONFIG(TAG, " Recovery: failed, SCL is held low on the bus"); + break; + case RECOVERY_FAILED_SDA_LOW: + ESP_LOGCONFIG(TAG, " Recovery: failed, SDA is held low on the bus"); + break; + } if (this->scan_) { ESP_LOGI(TAG, "Scanning i2c bus for active devices..."); uint8_t found = 0; @@ -144,39 +155,113 @@ ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { return ERROR_OK; } +/// Perform I2C bus recovery, see: +/// https://www.nxp.com/docs/en/user-guide/UM10204.pdf +/// https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf void IDFI2CBus::recover_() { - // Perform I2C bus recovery, see - // https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf - // or see the linux kernel implementation, e.g. - // https://elixir.bootlin.com/linux/v5.14.6/source/drivers/i2c/i2c-core-base.c#L200 + ESP_LOGI(TAG, "Performing I2C bus recovery"); - // try to get about 100kHz toggle frequency - const auto half_period_usec = 1000000 / 100000 / 2; - const auto recover_scl_periods = 9; const gpio_num_t scl_pin = static_cast(scl_pin_); + const gpio_num_t sda_pin = static_cast(sda_pin_); - // configure scl as output - gpio_config_t conf{}; - conf.pin_bit_mask = 1ULL << static_cast(scl_pin_); - conf.mode = GPIO_MODE_OUTPUT; - conf.pull_up_en = GPIO_PULLUP_DISABLE; - conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - conf.intr_type = GPIO_INTR_DISABLE; + // For the upcoming operations, target for a 60kHz toggle frequency. + // 1000kHz is the maximum frequency for I2C running in standard-mode, + // but lower frequencies are not a problem. + // Note: the timing that is used here is chosen manually, to get + // results that are close to the timing that can be archieved by the + // implementation for the Arduino framework. + const auto half_period_usec = 7; - gpio_config(&conf); - - // set scl high + // Configure SCL pin for open drain input/output, with a pull up resistor. gpio_set_level(scl_pin, 1); + gpio_config_t scl_config{}; + scl_config.pin_bit_mask = 1ULL << scl_pin_; + scl_config.mode = GPIO_MODE_INPUT_OUTPUT_OD; + scl_config.pull_up_en = GPIO_PULLUP_ENABLE; + scl_config.pull_down_en = GPIO_PULLDOWN_DISABLE; + scl_config.intr_type = GPIO_INTR_DISABLE; + gpio_config(&scl_config); - // in total generate 9 falling-rising edges - for (auto i = 0; i < recover_scl_periods; i++) { - delayMicroseconds(half_period_usec); + // Configure SDA pin for open drain input/output, with a pull up resistor. + gpio_set_level(sda_pin, 1); + gpio_config_t sda_conf{}; + sda_conf.pin_bit_mask = 1ULL << sda_pin_; + sda_conf.mode = GPIO_MODE_INPUT_OUTPUT_OD; + sda_conf.pull_up_en = GPIO_PULLUP_ENABLE; + sda_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + sda_conf.intr_type = GPIO_INTR_DISABLE; + gpio_config(&sda_conf); + + // If SCL is pulled low on the I2C bus, then some device is interfering + // with the SCL line. In that case, the I2C bus cannot be recovered. + delayMicroseconds(half_period_usec); + if (gpio_get_level(scl_pin) == 0) { + ESP_LOGE(TAG, "Recovery failed: SCL is held LOW on the I2C bus"); + recovery_result_ = RECOVERY_FAILED_SCL_LOW; + return; + } + + // From the specification: + // "If the data line (SDA) is stuck LOW, send nine clock pulses. The + // device that held the bus LOW should release it sometime within + // those nine clocks." + // We don't really have to detect if SDA is stuck low. We'll simply send + // nine clock pulses here, just in case SDA is stuck. Actual checks on + // the SDA line status will be done after the clock pulses. + for (auto i = 0; i < 9; i++) { gpio_set_level(scl_pin, 0); delayMicroseconds(half_period_usec); gpio_set_level(scl_pin, 1); + delayMicroseconds(half_period_usec); + + // When SCL is kept LOW at this point, we might be looking at a device + // that applies clock stretching. Wait for the release of the SCL line, + // but not forever. There is no specification for the maximum allowed + // time. We'll stick to 500ms here. + auto wait = 20; + while (wait-- && gpio_get_level(scl_pin) == 0) { + delay(25); + } + if (gpio_get_level(scl_pin) == 0) { + ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle"); + recovery_result_ = RECOVERY_FAILED_SCL_LOW; + return; + } } + // By now, any stuck device ought to have sent all remaining bits of its + // transation, meaning that it should have freed up the SDA line, resulting + // in SDA being pulled up. + if (gpio_get_level(sda_pin) == 0) { + ESP_LOGE(TAG, "Recovery failed: SDA is held LOW after clock pulse cycle"); + recovery_result_ = RECOVERY_FAILED_SDA_LOW; + return; + } + + // From the specification: + // "I2C-bus compatible devices must reset their bus logic on receipt of + // a START or repeated START condition such that they all anticipate + // the sending of a target address, even if these START conditions are + // not positioned according to the proper format." + // While the 9 clock pulses from above might have drained all bits of a + // single byte within a transaction, a device might have more bytes to + // transmit. So here we'll generate a START condition to snap the device + // out of this state. + // SCL and SDA are already high at this point, so we can generate a START + // condition by making the SDA signal LOW. delayMicroseconds(half_period_usec); + gpio_set_level(sda_pin, 0); + + // From the specification: + // "A START condition immediately followed by a STOP condition (void + // message) is an illegal format. Many devices however are designed to + // operate properly under this condition." + // Finally, we'll bring the I2C bus into a starting state by generating + // a STOP condition. + delayMicroseconds(half_period_usec); + gpio_set_level(sda_pin, 1); + + recovery_result_ = RECOVERY_COMPLETED; } } // namespace i2c diff --git a/esphome/components/i2c/i2c_bus_esp_idf.h b/esphome/components/i2c/i2c_bus_esp_idf.h index ba5fbf25c5..13d996dbd8 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.h +++ b/esphome/components/i2c/i2c_bus_esp_idf.h @@ -9,6 +9,12 @@ namespace esphome { namespace i2c { +enum RecoveryCode { + RECOVERY_FAILED_SCL_LOW, + RECOVERY_FAILED_SDA_LOW, + RECOVERY_COMPLETED, +}; + class IDFI2CBus : public I2CBus, public Component { public: void setup() override; @@ -26,6 +32,7 @@ class IDFI2CBus : public I2CBus, public Component { private: void recover_(); + RecoveryCode recovery_result_; protected: i2c_port_t port_; From 87358e884392a717a3bd65a5b3ad39d3c2d3524a Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 4 Oct 2021 16:14:51 +0200 Subject: [PATCH 120/549] Fix esp32 no longer has Hash internal lib (#2441) --- esphome/components/ota/__init__.py | 1 - esphome/components/wifi/__init__.py | 2 ++ platformio.ini | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 7856d35580..bcfb28979d 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -102,7 +102,6 @@ async def to_code(config): cg.add_library("Update", None) elif CORE.is_esp32 and CORE.using_arduino: cg.add_library("Update", None) - cg.add_library("Hash", None) use_state_callback = False for conf in config.get(CONF_ON_STATE_CHANGE, []): diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 7d029bbea1..19e4046711 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -342,6 +342,8 @@ async def to_code(config): if CORE.is_esp8266: cg.add_library("ESP8266WiFi", None) + elif CORE.is_esp32 and CORE.using_arduino: + cg.add_library("WiFi", None) cg.add_define("USE_WIFI") diff --git a/platformio.ini b/platformio.ini index 6e605768fc..f38bbc78a9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -89,7 +89,6 @@ framework = arduino board = nodemcu-32s lib_deps = ${common:arduino.lib_deps} - Hash ; ota (Arduino built-in) esphome/AsyncTCP-esphome@1.2.2 ; async_tcp build_flags = ${common:arduino.build_flags} From 871d3b66fb5f3d8484456f4da414f56411989fad Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 4 Oct 2021 16:15:25 +0200 Subject: [PATCH 121/549] Fix restoring globals (#2442) --- esphome/cpp_generator.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index cf357f2814..937b6cceb4 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -638,7 +638,7 @@ async def process_lambda( :param return_type: The return type of the lambda. :return: The generated lambda expression. """ - from esphome.components.globals import GlobalsComponent + from esphome.components.globals import GlobalsComponent, RestoringGlobalsComponent if value is None: return @@ -648,7 +648,10 @@ async def process_lambda( if ( full_id is not None and isinstance(full_id.type, MockObjClass) - and full_id.type.inherits_from(GlobalsComponent) + and ( + full_id.type.inherits_from(GlobalsComponent) + or full_id.type.inherits_from(RestoringGlobalsComponent) + ) ): parts[i * 3 + 1] = var.value() continue From 8be40862243c7123b668ccb774f9ce3f910f4835 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 4 Oct 2021 16:59:15 +0200 Subject: [PATCH 122/549] Always upload using esptool (#2433) --- esphome/__main__.py | 35 +++++++++---- esphome/core/__init__.py | 3 ++ esphome/platformio_api.py | 102 ++++++++++++++++++++++++-------------- esphome/storage_json.py | 2 +- 4 files changed, 94 insertions(+), 48 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 6bc7e065ce..feb95e93c7 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -184,12 +184,30 @@ def compile_program(args, config): def upload_using_esptool(config, port): - path = CORE.firmware_bin + from esphome import platformio_api + first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get( "upload_speed", 460800 ) def run_esptool(baud_rate): + idedata = platformio_api.get_idedata(config) + + firmware_offset = "0x10000" if CORE.is_esp32 else "0x0" + flash_images = [ + platformio_api.FlashImage( + path=idedata.firmware_bin_path, + offset=firmware_offset, + ), + *idedata.extra_flash_images, + ] + + mcu = "esp8266" + if CORE.is_esp32: + from esphome.components.esp32 import get_esp32_variant + + mcu = get_esp32_variant().lower() + cmd = [ "esptool.py", "--before", @@ -198,14 +216,15 @@ def upload_using_esptool(config, port): "hard_reset", "--baud", str(baud_rate), - "--chip", - "esp8266", "--port", port, + "--chip", + mcu, "write_flash", - "0x0", - path, + "-z", ] + for img in flash_images: + cmd += [img.offset, img.path] if os.environ.get("ESPHOME_USE_SUBPROCESS") is None: import esptool @@ -229,11 +248,7 @@ def upload_using_esptool(config, port): def upload_program(config, args, host): # if upload is to a serial port use platformio, otherwise assume ota if get_port_type(host) == "SERIAL": - from esphome import platformio_api - - if CORE.is_esp8266: - return upload_using_esptool(config, host) - return platformio_api.run_upload(config, CORE.verbose, host) + return upload_using_esptool(config, host) from esphome import espota2 diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 8021fc2f53..8bdef3a4ea 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -542,6 +542,9 @@ class EsphomeCore: path_ = os.path.expanduser(os.path.join(*path)) return os.path.join(self.config_dir, path_) + def relative_internal_path(self, *path: str) -> str: + return self.relative_config_path(".esphome", *path) + def relative_build_path(self, *path): # pylint: disable=no-value-for-parameter path_ = os.path.expanduser(os.path.join(*path)) diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 100def3310..054c0cb1b0 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -1,12 +1,15 @@ +from dataclasses import dataclass import json -from typing import Union +from typing import List, Union +from pathlib import Path import logging import os import re import subprocess -from esphome.core import CORE +from esphome.const import KEY_CORE +from esphome.core import CORE, EsphomeError from esphome.util import run_external_command, run_external_process _LOGGER = logging.getLogger(__name__) @@ -96,36 +99,56 @@ def run_compile(config, verbose): return run_platformio_cli_run(config, verbose) -def run_upload(config, verbose, port): - return run_platformio_cli_run( - config, verbose, "-t", "upload", "--upload-port", port - ) - - -def run_idedata(config): +def _run_idedata(config): args = ["-t", "idedata"] stdout = run_platformio_cli_run(config, False, *args, capture_stdout=True) match = re.search(r'{\s*".*}', stdout) if match is None: - _LOGGER.debug("Could not match IDEData for %s", stdout) - return IDEData(None) + _LOGGER.error("Could not match idedata, please report this error") + _LOGGER.error("Stdout: %s", stdout) + raise EsphomeError + try: - return IDEData(json.loads(match.group())) + return json.loads(match.group()) except ValueError: - _LOGGER.debug("Could not load IDEData for %s", stdout, exc_info=1) - return IDEData(None) + _LOGGER.error("Could not parse idedata", exc_info=True) + _LOGGER.error("Stdout: %s", stdout) + raise -IDE_DATA = None +def _load_idedata(config): + platformio_ini = Path(CORE.relative_build_path("platformio.ini")) + temp_idedata = Path(CORE.relative_internal_path(CORE.name, "idedata.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 + + if not changed: + try: + return json.loads(temp_idedata.read_text(encoding="utf-8")) + except ValueError: + pass + + temp_idedata.parent.mkdir(exist_ok=True, parents=True) + + data = _run_idedata(config) + + temp_idedata.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") + return data -def get_idedata(config): - global IDE_DATA +KEY_IDEDATA = "idedata" - if IDE_DATA is None: - _LOGGER.info("Need to fetch platformio IDE-data, please stand by") - IDE_DATA = run_idedata(config) - return IDE_DATA + +def get_idedata(config) -> "IDEData": + if KEY_IDEDATA in CORE.data[KEY_CORE]: + return CORE.data[KEY_CORE][KEY_IDEDATA] + idedata = IDEData(_load_idedata(config)) + CORE.data[KEY_CORE][KEY_IDEDATA] = idedata + return idedata # ESP logs stack trace decoder, based on https://github.com/me-no-dev/EspExceptionDecoder @@ -261,37 +284,42 @@ def process_stacktrace(config, line, backtrace_state): return backtrace_state +@dataclass +class FlashImage: + path: str + offset: str + + class IDEData: def __init__(self, raw): - if not isinstance(raw, dict): - self.raw = {} - else: - self.raw = raw + self.raw = raw @property def firmware_elf_path(self): - return self.raw.get("prog_path") + return self.raw["prog_path"] @property - def flash_extra_images(self): + def firmware_bin_path(self) -> str: + return str(Path(self.firmware_elf_path).with_suffix(".bin")) + + @property + def extra_flash_images(self) -> List[FlashImage]: return [ - (x["path"], x["offset"]) for x in self.raw.get("flash_extra_images", []) + FlashImage(path=entry["path"], offset=entry["offset"]) + for entry in self.raw["extra"]["flash_images"] ] @property - def cc_path(self): + def cc_path(self) -> str: # For example /Users//.platformio/packages/toolchain-xtensa32/bin/xtensa-esp32-elf-gcc - return self.raw.get("cc_path") + return self.raw["cc_path"] @property - def addr2line_path(self): - cc_path = self.cc_path - if cc_path is None: - return None + def addr2line_path(self) -> str: # replace gcc at end with addr2line # Windows - if cc_path.endswith(".exe"): - return f"{cc_path[:-7]}addr2line.exe" + if self.cc_path.endswith(".exe"): + return f"{self.cc_path[:-7]}addr2line.exe" - return f"{cc_path[:-3]}addr2line" + return f"{self.cc_path[:-3]}addr2line" diff --git a/esphome/storage_json.py b/esphome/storage_json.py index e70d3d6c93..3262559116 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -16,7 +16,7 @@ _LOGGER = logging.getLogger(__name__) def storage_path(): # type: () -> str - return CORE.relative_config_path(".esphome", f"{CORE.config_filename}.json") + return CORE.relative_internal_path(f"{CORE.config_filename}.json") def ext_storage_path(base_path, config_filename): # type: (str, str) -> str From 877367677b14b397fa023430504791736d47a781 Mon Sep 17 00:00:00 2001 From: NMC Date: Mon, 4 Oct 2021 18:56:34 -0400 Subject: [PATCH 123/549] Add support for Airthing Wave Mini (#2440) --- CODEOWNERS | 1 + .../airthings_wave_mini/__init__.py | 1 + .../airthings_wave_mini.cpp | 120 ++++++++++++++++++ .../airthings_wave_mini/airthings_wave_mini.h | 65 ++++++++++ .../components/airthings_wave_mini/sensor.py | 88 +++++++++++++ tests/test2.yaml | 14 ++ 6 files changed, 289 insertions(+) create mode 100644 esphome/components/airthings_wave_mini/__init__.py create mode 100644 esphome/components/airthings_wave_mini/airthings_wave_mini.cpp create mode 100644 esphome/components/airthings_wave_mini/airthings_wave_mini.h create mode 100644 esphome/components/airthings_wave_mini/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 2972b30b33..6576c78f0e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -15,6 +15,7 @@ esphome/components/ac_dimmer/* @glmnet esphome/components/adc/* @esphome/core esphome/components/addressable_light/* @justfalter esphome/components/airthings_ble/* @jeromelaban +esphome/components/airthings_wave_mini/* @ncareau esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/am43/* @buxtronix esphome/components/am43/cover/* @buxtronix diff --git a/esphome/components/airthings_wave_mini/__init__.py b/esphome/components/airthings_wave_mini/__init__.py new file mode 100644 index 0000000000..022f35b4cf --- /dev/null +++ b/esphome/components/airthings_wave_mini/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@ncareau"] diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp new file mode 100644 index 0000000000..0ab0e65148 --- /dev/null +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp @@ -0,0 +1,120 @@ +#include "airthings_wave_mini.h" + +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +namespace esphome { +namespace airthings_wave_mini { + +static const char *const TAG = "airthings_wave_mini"; + +void AirthingsWaveMini::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 = esp32_ble_tracker::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 AirthingsWaveMini::read_sensors_(uint8_t *raw_value, uint16_t value_len) { + auto value = (WaveMiniReadings *) raw_value; + + if (sizeof(WaveMiniReadings) <= value_len) { + this->humidity_sensor_->publish_state(value->humidity / 100.0f); + this->pressure_sensor_->publish_state(value->pressure / 50.0f); + this->temperature_sensor_->publish_state(value->temperature / 100.0f - 273.15f); + 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); + } +} + +bool AirthingsWaveMini::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; } + +void AirthingsWaveMini::loop() {} + +void AirthingsWaveMini::update() { + if (this->node_state != esp32_ble_tracker::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 AirthingsWaveMini::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 AirthingsWaveMini::dump_config() { + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); + LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); +} + +AirthingsWaveMini::AirthingsWaveMini() : PollingComponent(10000) { + auto service_bt = *BLEUUID::fromString(std::string("b42e3882-ade7-11e4-89d3-123b93f75cba")).getNative(); + auto characteristic_bt = *BLEUUID::fromString(std::string("b42e3b98-ade7-11e4-89d3-123b93f75cba")).getNative(); + + service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_uuid(service_bt); + sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_uuid(characteristic_bt); +} + +void AirthingsWaveMini::setup() {} + +} // namespace airthings_wave_mini +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.h b/esphome/components/airthings_wave_mini/airthings_wave_mini.h new file mode 100644 index 0000000000..5d1964d559 --- /dev/null +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.h @@ -0,0 +1,65 @@ +#pragma once + +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +#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" + +namespace esphome { +namespace airthings_wave_mini { + +class AirthingsWaveMini : public PollingComponent, public ble_client::BLEClientNode { + public: + AirthingsWaveMini(); + + 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_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } + void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; } + void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } + + protected: + bool is_valid_voc_value_(uint16_t voc); + + void read_sensors_(uint8_t *value, uint16_t value_len); + void request_read_values_(); + + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *tvoc_sensor_{nullptr}; + + uint16_t handle_; + esp32_ble_tracker::ESPBTUUID service_uuid_; + esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_; + + struct WaveMiniReadings { + uint16_t unused01; + uint16_t temperature; + uint16_t pressure; + uint16_t humidity; + uint16_t voc; + uint16_t unused02; + uint32_t unused03; + uint32_t unused04; + }; +}; + +} // namespace airthings_wave_mini +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/airthings_wave_mini/sensor.py b/esphome/components/airthings_wave_mini/sensor.py new file mode 100644 index 0000000000..6a32cd8771 --- /dev/null +++ b/esphome/components/airthings_wave_mini/sensor.py @@ -0,0 +1,88 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, ble_client +from esphome.core import CORE + +from esphome.const import ( + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + STATE_CLASS_MEASUREMENT, + UNIT_PERCENT, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, + CONF_ID, + CONF_HUMIDITY, + CONF_TVOC, + CONF_PRESSURE, + CONF_TEMPERATURE, + UNIT_PARTS_PER_BILLION, + ICON_RADIATOR, +) + +DEPENDENCIES = ["ble_client"] + +airthings_wave_mini_ns = cg.esphome_ns.namespace("airthings_wave_mini") +AirthingsWaveMini = airthings_wave_mini_ns.class_( + "AirthingsWaveMini", cg.PollingComponent, ble_client.BLEClientNode +) + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(AirthingsWaveMini), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=2, + ), + 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=2, + device_class=DEVICE_CLASS_PRESSURE, + 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), + # Until BLEUUID reference removed + cv.only_with_arduino, +) + + +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_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_TVOC in config: + sens = await sensor.new_sensor(config[CONF_TVOC]) + cg.add(var.set_tvoc(sens)) + + if CORE.is_esp32: + cg.add_library("ESP32 BLE Arduino", None) diff --git a/tests/test2.yaml b/tests/test2.yaml index 4541fba616..364bcec28f 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -281,6 +281,17 @@ sensor: name: "Wave Plus CO2" tvoc: name: "Wave Plus VOC" + - platform: airthings_wave_mini + ble_client_id: airthingsmini01 + update_interval: 5min + temperature: + name: "Wave Mini Temperature" + humidity: + name: "Wave Mini Humidity" + pressure: + name: "Wave Mini Pressure" + tvoc: + name: "Wave Mini VOC" time: - platform: homeassistant @@ -378,6 +389,9 @@ esp32_ble_tracker: ble_client: - mac_address: 01:02:03:04:05:06 id: airthings01 + - mac_address: 01:02:03:04:05:06 + id: airthingsmini01 + airthings_ble: From 6ec546a6a481ffb449bbd086a1d937cf53e5c1b0 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Tue, 5 Oct 2021 12:00:23 +1300 Subject: [PATCH 124/549] Improved validation for Addressable Light Partition Segments (#2439) Co-authored-by: Otto Winter --- esphome/components/partition/light.py | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/esphome/components/partition/light.py b/esphome/components/partition/light.py index ada83a123e..822b7ac306 100644 --- a/esphome/components/partition/light.py +++ b/esphome/components/partition/light.py @@ -1,11 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome.components import light from esphome.const import ( CONF_ADDRESSABLE_LIGHT_ID, CONF_FROM, CONF_ID, CONF_LIGHT_ID, + CONF_NUM_LEDS, CONF_SEGMENTS, CONF_SINGLE_LIGHT_ID, CONF_TO, @@ -31,6 +33,27 @@ def validate_from_to(value): return value +def validate_segment(config): + fconf = fv.full_config.get() + + if CONF_ID in config: # only validate addressable segments + path = fconf.get_path_for_id(config[CONF_ID])[:-1] + segment_light_config = fconf.get_config_for_path(path) + + if CONF_NUM_LEDS in segment_light_config: + segment_len = segment_light_config[CONF_NUM_LEDS] + if config[CONF_FROM] >= segment_len: + raise cv.Invalid( + f"FROM ({config[CONF_FROM]}) must be less than the number of LEDs in light '{config[CONF_ID]}' ({segment_len})", + [CONF_FROM], + ) + if config[CONF_TO] >= segment_len: + raise cv.Invalid( + f"TO ({config[CONF_TO]}) must be less than the number of LEDs in light '{config[CONF_ID]}' ({segment_len})", + [CONF_TO], + ) + + ADDRESSABLE_SEGMENT_SCHEMA = cv.Schema( { cv.Required(CONF_ID): cv.use_id(light.AddressableLightState), @@ -63,6 +86,13 @@ CONFIG_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend( } ) +FINAL_VALIDATE_SCHEMA = cv.Schema( + { + cv.Required(CONF_SEGMENTS): [validate_segment], + }, + extra=cv.ALLOW_EXTRA, +) + async def to_code(config): segments = [] From e09ee8f23d7a862f8026ccaf06797878a501a42b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Oct 2021 14:49:55 +0200 Subject: [PATCH 125/549] Bump aioesphomeapi from 9.1.2 to 9.1.4 (#2443) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ba2c43e5b7..176c623691 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.0 esptool==3.1 click==8.0.1 esphome-dashboard==20210927.0 -aioesphomeapi==9.1.2 +aioesphomeapi==9.1.4 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From e22f1fc044d30cd92628d53c0eaa8e4cd438ded0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Oct 2021 14:50:18 +0200 Subject: [PATCH 126/549] Bump pytest-cov from 2.12.1 to 3.0.0 (#2444) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 1d09bb6388..85456643bc 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -6,7 +6,7 @@ pre-commit # Unit tests pytest==6.2.5 -pytest-cov==2.12.1 +pytest-cov==3.0.0 pytest-mock==3.6.1 pytest-asyncio==0.15.1 asyncmock==0.4.2 From a57580b5ab972e97e1f24f077afd5691091a374f Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Tue, 5 Oct 2021 17:56:32 +0200 Subject: [PATCH 127/549] Fix compilation error (#2447) --- esphome/components/shutdown/shutdown_switch.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/shutdown/shutdown_switch.cpp b/esphome/components/shutdown/shutdown_switch.cpp index 0e5853cc46..a5f9a92982 100644 --- a/esphome/components/shutdown/shutdown_switch.cpp +++ b/esphome/components/shutdown/shutdown_switch.cpp @@ -1,4 +1,5 @@ #include "shutdown_switch.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" #include "esphome/core/application.h" From e083d7f4d0c233e27b1c0cc9cfdb98b1dd416e1f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 6 Oct 2021 11:26:18 +1300 Subject: [PATCH 128/549] Add log line to show if API encryption is being used (#2450) --- esphome/components/api/api_server.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 8728597537..4e2899d94f 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -133,6 +133,11 @@ void APIServer::loop() { void APIServer::dump_config() { ESP_LOGCONFIG(TAG, "API Server:"); ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_); +#ifdef USE_API_NOISE + ESP_LOGCONFIG(TAG, " Using noise encryption: YES"); +#else + ESP_LOGCONFIG(TAG, " Using noise encryption: NO"); +#endif } bool APIServer::uses_password() const { return !this->password_.empty(); } bool APIServer::check_password(const std::string &password) const { From b8b30599ee150c021c0d2350093e00f3c8ab4f86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Oct 2021 11:29:30 +1300 Subject: [PATCH 129/549] Bump aioesphomeapi from 9.1.4 to 9.1.5 (#2449) 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 176c623691..202e38a21b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.0 esptool==3.1 click==8.0.1 esphome-dashboard==20210927.0 -aioesphomeapi==9.1.4 +aioesphomeapi==9.1.5 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 7bbb5213f3cbfd5265f19718b484bb5e32be8fdf Mon Sep 17 00:00:00 2001 From: Alex Iribarren Date: Wed, 6 Oct 2021 00:44:48 +0200 Subject: [PATCH 130/549] Only ping once every two seconds (#2448) --- 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 492b86384d..eb698a7de1 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -582,7 +582,7 @@ class MDNSStatusThread(threading.Thread): class PingStatusThread(threading.Thread): def run(self): with multiprocessing.Pool(processes=8) as pool: - while not STOP_EVENT.is_set(): + while not STOP_EVENT.wait(2): # Only do pings if somebody has the dashboard open def callback(ret): From 9ff824080274fdb4eff49311926a6ac13b244ed6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 6 Oct 2021 11:45:01 +1300 Subject: [PATCH 131/549] Bump esphome-dashboard to 20211006.0 (#2451) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 202e38a21b..880faeddbe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.0 esptool==3.1 click==8.0.1 -esphome-dashboard==20210927.0 +esphome-dashboard==20211006.0 aioesphomeapi==9.1.5 # esp-idf requires this, but doesn't bundle it by default From 54a173dbf1aecae345e94a7eecdc664613f251e6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 6 Oct 2021 00:57:23 +0200 Subject: [PATCH 132/549] I2C re-introduce very verbose logging (#2446) --- esphome/components/i2c/i2c_bus_arduino.cpp | 49 ++++++++++++++++++- esphome/components/i2c/i2c_bus_esp_idf.cpp | 56 +++++++++++++++++++++- 2 files changed, 101 insertions(+), 4 deletions(-) diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 4b519e4873..4afabbfa53 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -62,33 +62,75 @@ void ArduinoI2CBus::dump_config() { } } ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { - if (!initialized_) + // logging is only enabled with vv level, if warnings are shown the caller + // should log them + if (!initialized_) { + ESP_LOGVV(TAG, "i2c bus not 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) { + ESP_LOGVV(TAG, "RX %u from %02X failed with error %u", to_request, address, ret); 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(); } + +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE + char debug_buf[4]; + std::string debug_hex; + + for (size_t i = 0; i < cnt; i++) { + const auto &buf = buffers[i]; + for (size_t j = 0; j < buf.len; j++) { + snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]); + debug_hex += debug_buf; + } + } + ESP_LOGVV(TAG, "0x%02X RX %s", address, debug_hex.c_str()); +#endif + return ERROR_OK; } ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { - if (!initialized_) + // logging is only enabled with vv level, if warnings are shown the caller + // should log them + if (!initialized_) { + ESP_LOGVV(TAG, "i2c bus not initialized!"); return ERROR_NOT_INITIALIZED; + } + +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE + char debug_buf[4]; + std::string debug_hex; + + for (size_t i = 0; i < cnt; i++) { + const auto &buf = buffers[i]; + for (size_t j = 0; j < buf.len; j++) { + snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]); + debug_hex += debug_buf; + } + } + ESP_LOGVV(TAG, "0x%02X TX %s", address, debug_hex.c_str()); +#endif wire_->beginTransmission(address); + size_t written = 0; 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); + written += ret; if (ret != buf.len) { + ESP_LOGVV(TAG, "TX failed at %u", written); return ERROR_UNKNOWN; } } @@ -97,10 +139,13 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn return ERROR_OK; } else if (status == 1) { // transmit buffer not large enough + ESP_LOGVV(TAG, "TX failed: buffer not large enough"); return ERROR_UNKNOWN; } else if (status == 2 || status == 3) { + ESP_LOGVV(TAG, "TX failed: not acknowledged"); return ERROR_NOT_ACKNOWLEDGED; } + ESP_LOGVV(TAG, "TX failed: unknown error %u", status); return ERROR_UNKNOWN; } diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 91fd1499e9..f7ecfe5f7c 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -73,16 +73,22 @@ void IDFI2CBus::dump_config() { } } ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { - if (!initialized_) + // logging is only enabled with vv level, if warnings are shown the caller + // should log them + if (!initialized_) { + ESP_LOGVV(TAG, "i2c bus not 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) { + ESP_LOGVV(TAG, "RX from %02X master start failed: %s", address, esp_err_to_name(err)); i2c_cmd_link_delete(cmd); return ERROR_UNKNOWN; } err = i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_READ, true); if (err != ESP_OK) { + ESP_LOGVV(TAG, "RX from %02X address write failed: %s", address, esp_err_to_name(err)); i2c_cmd_link_delete(cmd); return ERROR_UNKNOWN; } @@ -92,12 +98,14 @@ ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { continue; err = i2c_master_read(cmd, buf.data, buf.len, i == cnt - 1 ? I2C_MASTER_LAST_NACK : I2C_MASTER_ACK); if (err != ESP_OK) { + ESP_LOGVV(TAG, "RX from %02X data read failed: %s", address, esp_err_to_name(err)); i2c_cmd_link_delete(cmd); return ERROR_UNKNOWN; } } err = i2c_master_stop(cmd); if (err != ESP_OK) { + ESP_LOGVV(TAG, "RX from %02X stop failed: %s", address, esp_err_to_name(err)); i2c_cmd_link_delete(cmd); return ERROR_UNKNOWN; } @@ -105,25 +113,64 @@ ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { i2c_cmd_link_delete(cmd); if (err == ESP_FAIL) { // transfer not acked + ESP_LOGVV(TAG, "RX from %02X failed: not acked", address); return ERROR_NOT_ACKNOWLEDGED; } else if (err == ESP_ERR_TIMEOUT) { + ESP_LOGVV(TAG, "RX from %02X failed: timeout", address); return ERROR_TIMEOUT; } else if (err != ESP_OK) { + ESP_LOGVV(TAG, "RX from %02X failed: %s", address, esp_err_to_name(err)); return ERROR_UNKNOWN; } + +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE + char debug_buf[4]; + std::string debug_hex; + + for (size_t i = 0; i < cnt; i++) { + const auto &buf = buffers[i]; + for (size_t j = 0; j < buf.len; j++) { + snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]); + debug_hex += debug_buf; + } + } + ESP_LOGVV(TAG, "0x%02X RX %s", address, debug_hex.c_str()); +#endif + return ERROR_OK; } ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { - if (!initialized_) + // logging is only enabled with vv level, if warnings are shown the caller + // should log them + if (!initialized_) { + ESP_LOGVV(TAG, "i2c bus not initialized!"); return ERROR_NOT_INITIALIZED; + } + +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE + char debug_buf[4]; + std::string debug_hex; + + for (size_t i = 0; i < cnt; i++) { + const auto &buf = buffers[i]; + for (size_t j = 0; j < buf.len; j++) { + snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]); + debug_hex += debug_buf; + } + } + ESP_LOGVV(TAG, "0x%02X TX %s", address, debug_hex.c_str()); +#endif + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); esp_err_t err = i2c_master_start(cmd); if (err != ESP_OK) { + ESP_LOGVV(TAG, "TX to %02X master start failed: %s", address, esp_err_to_name(err)); i2c_cmd_link_delete(cmd); return ERROR_UNKNOWN; } err = i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, true); if (err != ESP_OK) { + ESP_LOGVV(TAG, "TX to %02X address write failed: %s", address, esp_err_to_name(err)); i2c_cmd_link_delete(cmd); return ERROR_UNKNOWN; } @@ -133,12 +180,14 @@ ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { continue; err = i2c_master_write(cmd, buf.data, buf.len, true); if (err != ESP_OK) { + ESP_LOGVV(TAG, "TX to %02X data write failed: %s", address, esp_err_to_name(err)); i2c_cmd_link_delete(cmd); return ERROR_UNKNOWN; } } err = i2c_master_stop(cmd); if (err != ESP_OK) { + ESP_LOGVV(TAG, "TX to %02X master stop failed: %s", address, esp_err_to_name(err)); i2c_cmd_link_delete(cmd); return ERROR_UNKNOWN; } @@ -146,10 +195,13 @@ ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { i2c_cmd_link_delete(cmd); if (err == ESP_FAIL) { // transfer not acked + ESP_LOGVV(TAG, "TX to %02X failed: not acked", address); return ERROR_NOT_ACKNOWLEDGED; } else if (err == ESP_ERR_TIMEOUT) { + ESP_LOGVV(TAG, "TX to %02X failed: timeout", address); return ERROR_TIMEOUT; } else if (err != ESP_OK) { + ESP_LOGVV(TAG, "TX to %02X failed: %s", address, esp_err_to_name(err)); return ERROR_UNKNOWN; } return ERROR_OK; From 955c96731ef35dcca9c8d0fec769f166df99bc66 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Wed, 6 Oct 2021 20:44:48 +1300 Subject: [PATCH 133/549] Add Safe Mode Restart Switch (#2437) --- CODEOWNERS | 1 + esphome/components/ota/ota_component.cpp | 38 ++++++++++++++++--- esphome/components/ota/ota_component.h | 7 ++++ esphome/components/safe_mode/__init__.py | 5 +++ .../components/safe_mode/switch/__init__.py | 36 ++++++++++++++++++ .../safe_mode/switch/safe_mode_switch.cpp | 29 ++++++++++++++ .../safe_mode/switch/safe_mode_switch.h | 21 ++++++++++ esphome/const.py | 1 + tests/test1.yaml | 2 + 9 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 esphome/components/safe_mode/__init__.py create mode 100644 esphome/components/safe_mode/switch/__init__.py create mode 100644 esphome/components/safe_mode/switch/safe_mode_switch.cpp create mode 100644 esphome/components/safe_mode/switch/safe_mode_switch.h diff --git a/CODEOWNERS b/CODEOWNERS index 6576c78f0e..49cb60c177 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -126,6 +126,7 @@ esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz esphome/components/rgbct/* @jesserockz esphome/components/rtttl/* @glmnet +esphome/components/safe_mode/* @paulmonigatti esphome/components/scd4x/* @sjtrny esphome/components/script/* @esphome/core esphome/components/sdm_meter/* @jesserockz @polyfaces diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index e897d952ef..9ad3814f5c 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -89,7 +89,8 @@ void OTAComponent::dump_config() { ESP_LOGCONFIG(TAG, " Using Password."); } #endif - if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1) { + if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1 && + this->safe_mode_rtc_value_ != esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC) { 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_); } @@ -401,6 +402,27 @@ bool OTAComponent::writeall_(const uint8_t *buf, size_t len) { float OTAComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; } uint16_t OTAComponent::get_port() const { return this->port_; } void OTAComponent::set_port(uint16_t port) { this->port_ = port; } + +void OTAComponent::set_safe_mode_pending(const bool &pending) { + if (!this->has_safe_mode_) + return; + + uint32_t current_rtc = this->read_rtc_(); + + if (pending && current_rtc != esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC) { + ESP_LOGI(TAG, "Device will enter safe mode on next boot."); + this->write_rtc_(esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC); + } + + if (!pending && current_rtc == esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC) { + ESP_LOGI(TAG, "Safe mode pending has been cleared"); + this->clean_rtc(); + } +} +bool OTAComponent::get_safe_mode_pending() { + return this->has_safe_mode_ && this->read_rtc_() == esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC; +} + bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time) { this->has_safe_mode_ = true; this->safe_mode_start_time_ = millis(); @@ -409,12 +431,18 @@ bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_ 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_); + bool is_manual_safe_mode = this->safe_mode_rtc_value_ == esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC; - if (this->safe_mode_rtc_value_ >= num_attempts) { + if (is_manual_safe_mode) + ESP_LOGI(TAG, "Safe mode has been entered manually"); + else + ESP_LOGCONFIG(TAG, "There have been %u suspected unsuccessful boot attempts.", this->safe_mode_rtc_value_); + + if (this->safe_mode_rtc_value_ >= num_attempts || is_manual_safe_mode) { this->clean_rtc(); - ESP_LOGE(TAG, "Boot loop detected. Proceeding to safe mode."); + if (!is_manual_safe_mode) + ESP_LOGE(TAG, "Boot loop detected. Proceeding to safe mode."); this->status_set_error(); this->set_timeout(enable_time, []() { @@ -445,7 +473,7 @@ uint32_t OTAComponent::read_rtc_() { } void OTAComponent::clean_rtc() { this->write_rtc_(0); } void OTAComponent::on_safe_shutdown() { - if (this->has_safe_mode_) + if (this->has_safe_mode_ && this->read_rtc_() != esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC) this->clean_rtc(); } diff --git a/esphome/components/ota/ota_component.h b/esphome/components/ota/ota_component.h index f76295735e..e08e187df6 100644 --- a/esphome/components/ota/ota_component.h +++ b/esphome/components/ota/ota_component.h @@ -48,6 +48,10 @@ class OTAComponent : public Component { bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time); + /// Set to true if the next startup will enter safe mode + void set_safe_mode_pending(const bool &pending); + bool get_safe_mode_pending(); + #ifdef USE_OTA_STATE_CALLBACK void add_on_state_callback(std::function &&callback); #endif @@ -89,6 +93,9 @@ class OTAComponent : public Component { uint8_t safe_mode_num_attempts_; ESPPreferenceObject rtc_; + static const uint32_t ENTER_SAFE_MODE_MAGIC = + 0x5afe5afe; ///< a magic number to indicate that safe mode should be entered on next boot + #ifdef USE_OTA_STATE_CALLBACK CallbackManager state_callback_{}; #endif diff --git a/esphome/components/safe_mode/__init__.py b/esphome/components/safe_mode/__init__.py new file mode 100644 index 0000000000..f150d6e086 --- /dev/null +++ b/esphome/components/safe_mode/__init__.py @@ -0,0 +1,5 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@paulmonigatti"] + +safe_mode_ns = cg.esphome_ns.namespace("safe_mode") diff --git a/esphome/components/safe_mode/switch/__init__.py b/esphome/components/safe_mode/switch/__init__.py new file mode 100644 index 0000000000..0ad814ff4f --- /dev/null +++ b/esphome/components/safe_mode/switch/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.components.ota import OTAComponent +from esphome.const import ( + CONF_ID, + CONF_INVERTED, + CONF_ICON, + CONF_OTA, + ICON_RESTART_ALERT, +) +from .. import safe_mode_ns + +DEPENDENCIES = ["ota"] + +SafeModeSwitch = safe_mode_ns.class_("SafeModeSwitch", switch.Switch, cg.Component) + +CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SafeModeSwitch), + cv.GenerateID(CONF_OTA): cv.use_id(OTAComponent), + cv.Optional(CONF_INVERTED): cv.invalid( + "Safe Mode Restart switches do not support inverted mode!" + ), + cv.Optional(CONF_ICON, default=ICON_RESTART_ALERT): switch.icon, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await switch.register_switch(var, config) + + ota = await cg.get_variable(config[CONF_OTA]) + cg.add(var.set_ota(ota)) diff --git a/esphome/components/safe_mode/switch/safe_mode_switch.cpp b/esphome/components/safe_mode/switch/safe_mode_switch.cpp new file mode 100644 index 0000000000..a3979eec06 --- /dev/null +++ b/esphome/components/safe_mode/switch/safe_mode_switch.cpp @@ -0,0 +1,29 @@ +#include "safe_mode_switch.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace safe_mode { + +static const char *const TAG = "safe_mode_switch"; + +void SafeModeSwitch::set_ota(ota::OTAComponent *ota) { this->ota_ = ota; } + +void SafeModeSwitch::write_state(bool state) { + // Acknowledge + this->publish_state(false); + + if (state) { + ESP_LOGI(TAG, "Restarting device in safe mode..."); + this->ota_->set_safe_mode_pending(true); + + // Let MQTT settle a bit + delay(100); // NOLINT + App.safe_reboot(); + } +} +void SafeModeSwitch::dump_config() { LOG_SWITCH("", "Safe Mode Switch", this); } + +} // namespace safe_mode +} // namespace esphome diff --git a/esphome/components/safe_mode/switch/safe_mode_switch.h b/esphome/components/safe_mode/switch/safe_mode_switch.h new file mode 100644 index 0000000000..2772db3d84 --- /dev/null +++ b/esphome/components/safe_mode/switch/safe_mode_switch.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/ota/ota_component.h" +#include "esphome/components/switch/switch.h" + +namespace esphome { +namespace safe_mode { + +class SafeModeSwitch : public switch_::Switch, public Component { + public: + void dump_config() override; + void set_ota(ota::OTAComponent *ota); + + protected: + ota::OTAComponent *ota_; + void write_state(bool state) override; +}; + +} // namespace safe_mode +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 265576bfbe..a65285a4b5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -761,6 +761,7 @@ ICON_PULSE = "mdi:pulse" ICON_RADIATOR = "mdi:radiator" ICON_RADIOACTIVE = "mdi:radioactive" ICON_RESTART = "mdi:restart" +ICON_RESTART_ALERT = "mdi:restart-alert" ICON_ROTATE_RIGHT = "mdi:rotate-right" ICON_RULER = "mdi:ruler" ICON_SCALE = "mdi:scale" diff --git a/tests/test1.yaml b/tests/test1.yaml index 77f7da2b08..400cdb3b6b 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1905,6 +1905,8 @@ switch: state: yes - platform: restart name: 'Living Room Restart' + - platform: safe_mode + name: 'Living Room Restart (Safe Mode)' - platform: shutdown name: 'Living Room Shutdown' - platform: output From 22e3bc7cfe75501004148826c666f80fa3123016 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 6 Oct 2021 22:35:11 +1300 Subject: [PATCH 134/549] Add id() for restoring global (#2454) --- esphome/components/globals/globals_component.h | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/globals/globals_component.h b/esphome/components/globals/globals_component.h index b39c5f404b..3286e43575 100644 --- a/esphome/components/globals/globals_component.h +++ b/esphome/components/globals/globals_component.h @@ -76,6 +76,7 @@ template class GlobalVarSetAction : public Action T &id(GlobalsComponent *value) { return value->value(); } +template T &id(RestoringGlobalsComponent *value) { return value->value(); } } // namespace globals } // namespace esphome From d34a1c3ed6efa005341d3b43d03894b814ec8328 Mon Sep 17 00:00:00 2001 From: Alex Iribarren Date: Wed, 6 Oct 2021 21:56:07 +0200 Subject: [PATCH 135/549] Add timestamp to ESPHome dashboard/cli logs (#2455) --- esphome/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/log.py b/esphome/log.py index fa79efa833..abefcf6308 100644 --- a/esphome/log.py +++ b/esphome/log.py @@ -50,7 +50,7 @@ def color(col: str, msg: str, reset: bool = True) -> bool: class ESPHomeLogFormatter(logging.Formatter): def __init__(self): - super().__init__(fmt="%(levelname)s %(message)s", datefmt="%H:%M:%S", style="%") + super().__init__(fmt="%(asctime)s %(levelname)s %(message)s", style="%") def format(self, record): formatted = super().format(record) From 1c58b172358a898370de5188872ece374ecd0410 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 6 Oct 2021 22:36:12 +0200 Subject: [PATCH 136/549] API encryption switch to libsodium backend (#2456) --- esphome/components/api/__init__.py | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 3705f0d7ca..b0608a69dd 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -121,7 +121,7 @@ async def to_code(config): 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") + cg.add_library("esphome/noise-c", "0.1.3") else: cg.add_define("USE_API_PLAINTEXT") diff --git a/platformio.ini b/platformio.ini index f38bbc78a9..e038224f69 100644 --- a/platformio.ini +++ b/platformio.ini @@ -26,7 +26,7 @@ build_flags = [common] lib_deps = - esphome/noise-c@0.1.1 ; api + esphome/noise-c@0.1.3 ; api makuna/NeoPixelBus@2.6.7 ; neopixelbus build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE From 5461f87ff016cbb31a1ffe6876062dfee3f0ee63 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Thu, 7 Oct 2021 21:18:00 +0200 Subject: [PATCH 137/549] I2c fix (#2460) --- esphome/components/i2c/i2c.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index 035c483344..82ab7bd09a 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -12,7 +12,7 @@ bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t 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; + return write_register(a_register, reinterpret_cast(temp.get()), len * 2) == ERROR_OK; } I2CRegister &I2CRegister::operator=(uint8_t value) { From 9bf72ff05f28533f42020fcb6e71ae1c5239a19c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 27 Sep 2021 11:40:28 +0200 Subject: [PATCH 138/549] Re-enable TCP nodelay for ESP32 (#2390) --- esphome/components/api/api_frame_helper.cpp | 15 +++++++++++ .../components/socket/lwip_raw_tcp_impl.cpp | 27 ++++++++++--------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 15014b7937..172e1040ad 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -126,6 +126,14 @@ APIError APINoiseFrameHelper::init() { 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)); @@ -721,6 +729,13 @@ 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; diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 3c225aeb82..0fe608b62e 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -256,7 +256,7 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return -1; } - *reinterpret_cast(optval) = tcp_nagle_disabled(pcb_); + *reinterpret_cast(optval) = nodelay_; *optlen = 4; return 0; } @@ -285,11 +285,7 @@ class LWIPRawImpl : public Socket { return -1; } int val = *reinterpret_cast(optval); - if (val != 0) { - tcp_nagle_disable(pcb_); - } else { - tcp_nagle_enable(pcb_); - } + nodelay_ = val; return 0; } @@ -443,9 +439,11 @@ class LWIPRawImpl : public Socket { if (written == 0) // no need to output if nothing written return 0; - int err = internal_output(); - if (err == -1) - return -1; + if (nodelay_) { + int err = internal_output(); + if (err == -1) + return -1; + } return written; } ssize_t writev(const struct iovec *iov, int iovcnt) override { @@ -465,9 +463,11 @@ class LWIPRawImpl : public Socket { if (written == 0) // no need to output if nothing written return 0; - int err = internal_output(); - if (err == -1) - return -1; + if (nodelay_) { + int err = internal_output(); + if (err == -1) + return -1; + } return written; } int setblocking(bool blocking) override { @@ -549,6 +549,9 @@ class LWIPRawImpl : public Socket { bool rx_closed_ = false; pbuf *rx_buf_ = nullptr; size_t rx_buf_offset_ = 0; + // don't use lwip nodelay flag, it sometimes causes reconnect + // instead use it for determining whether to call lwip_output + bool nodelay_ = false; }; std::unique_ptr socket(int domain, int type, int protocol) { From e7477890cfff7b57ad6acab51ac603ea9e19c5b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Oct 2021 15:20:12 +0200 Subject: [PATCH 139/549] Bump aioesphomeapi from 9.1.1 to 9.1.2 (#2426) 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 bd90f31cbb..ee0a010a1c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,4 @@ platformio==5.2.0 esptool==3.1 click==7.1.2 esphome-dashboard==20210908.0 -aioesphomeapi==9.1.1 +aioesphomeapi==9.1.2 From 32a664eedcfbc6bef2f3ba281ad685bc22035e2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Oct 2021 14:49:55 +0200 Subject: [PATCH 140/549] Bump aioesphomeapi from 9.1.2 to 9.1.4 (#2443) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ee0a010a1c..b2708e2c87 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,4 @@ platformio==5.2.0 esptool==3.1 click==7.1.2 esphome-dashboard==20210908.0 -aioesphomeapi==9.1.2 +aioesphomeapi==9.1.4 From d9b2903d785bb5c30f5beaddd7ae28a04110146a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 6 Oct 2021 11:26:18 +1300 Subject: [PATCH 141/549] Add log line to show if API encryption is being used (#2450) --- esphome/components/api/api_server.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 33843f384b..82178e0c10 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -134,6 +134,11 @@ 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_); +#ifdef USE_API_NOISE + ESP_LOGCONFIG(TAG, " Using noise encryption: YES"); +#else + ESP_LOGCONFIG(TAG, " Using noise encryption: NO"); +#endif } bool APIServer::uses_password() const { return !this->password_.empty(); } bool APIServer::check_password(const std::string &password) const { From 95d7ad543fa2257df51d14a4b00ce4f0ea81f74c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 6 Oct 2021 22:36:12 +0200 Subject: [PATCH 142/549] API encryption switch to libsodium backend (#2456) --- esphome/components/api/__init__.py | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 3705f0d7ca..b0608a69dd 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -121,7 +121,7 @@ async def to_code(config): 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") + cg.add_library("esphome/noise-c", "0.1.3") else: cg.add_define("USE_API_PLAINTEXT") diff --git a/platformio.ini b/platformio.ini index 73d5595dcd..263172fe0b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -36,7 +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 + esphome/noise-c@0.1.3 ; used by api dudanov/MideaUART@1.1.8 ; used by midea build_flags = From fc5798fa71d0d3df6ac45582b2db250c78083078 Mon Sep 17 00:00:00 2001 From: Otto winter Date: Thu, 7 Oct 2021 22:05:30 +0200 Subject: [PATCH 143/549] Bump version to 2021.9.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f69f6e91da..25f48da264 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.2" +__version__ = "2021.9.3" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 3f2d9abfe6434cfbfc32a8b7de7c100acb95b6ae Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sat, 9 Oct 2021 01:30:21 -0700 Subject: [PATCH 144/549] Correct I2C read() return val check in bh1750 component. (#2465) Co-authored-by: Maurice Makaay --- esphome/components/bh1750/bh1750.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/bh1750/bh1750.cpp b/esphome/components/bh1750/bh1750.cpp index 3645a45bf9..951fe3670c 100644 --- a/esphome/components/bh1750/bh1750.cpp +++ b/esphome/components/bh1750/bh1750.cpp @@ -71,7 +71,7 @@ void BH1750Sensor::update() { float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; } void BH1750Sensor::read_data_() { uint16_t raw_value; - if (!this->read(reinterpret_cast(&raw_value), 2)) { + if (this->read(reinterpret_cast(&raw_value), 2) != i2c::ERROR_OK) { this->status_set_warning(); return; } From a1b28cb36e60bbc20f68cacb017881be87eaaed6 Mon Sep 17 00:00:00 2001 From: davidmonro Date: Sun, 10 Oct 2021 02:44:16 +1100 Subject: [PATCH 145/549] atm90e32: make the total_increasing class sensors actually be increasing totals. (#2459) Co-authored-by: David Monro --- esphome/components/atm90e32/atm90e32.cpp | 42 ++++++++++++++++++++---- esphome/components/atm90e32/atm90e32.h | 2 ++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/esphome/components/atm90e32/atm90e32.cpp b/esphome/components/atm90e32/atm90e32.cpp index ebceaa817c..e4b8448da6 100644 --- a/esphome/components/atm90e32/atm90e32.cpp +++ b/esphome/components/atm90e32/atm90e32.cpp @@ -265,27 +265,57 @@ float ATM90E32Component::get_power_factor_c_() { } float ATM90E32Component::get_forward_active_energy_a_() { uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYA); - return (float) val * 10 / 3200; // convert register value to WattHours + if ((UINT32_MAX - this->phase_[0].cumulative_forward_active_energy_) > val) { + this->phase_[0].cumulative_forward_active_energy_ += val; + } else { + this->phase_[0].cumulative_forward_active_energy_ = val; + } + return ((float) this->phase_[0].cumulative_forward_active_energy_ * 10 / 3200); } float ATM90E32Component::get_forward_active_energy_b_() { uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYB); - return (float) val * 10 / 3200; + if (UINT32_MAX - this->phase_[1].cumulative_forward_active_energy_ > val) { + this->phase_[1].cumulative_forward_active_energy_ += val; + } else { + this->phase_[1].cumulative_forward_active_energy_ = val; + } + return ((float) this->phase_[1].cumulative_forward_active_energy_ * 10 / 3200); } float ATM90E32Component::get_forward_active_energy_c_() { uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYC); - return (float) val * 10 / 3200; + if (UINT32_MAX - this->phase_[2].cumulative_forward_active_energy_ > val) { + this->phase_[2].cumulative_forward_active_energy_ += val; + } else { + this->phase_[2].cumulative_forward_active_energy_ = val; + } + return ((float) this->phase_[2].cumulative_forward_active_energy_ * 10 / 3200); } float ATM90E32Component::get_reverse_active_energy_a_() { uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYA); - return (float) val * 10 / 3200; + if (UINT32_MAX - this->phase_[0].cumulative_reverse_active_energy_ > val) { + this->phase_[0].cumulative_reverse_active_energy_ += val; + } else { + this->phase_[0].cumulative_reverse_active_energy_ = val; + } + return ((float) this->phase_[0].cumulative_reverse_active_energy_ * 10 / 3200); } float ATM90E32Component::get_reverse_active_energy_b_() { uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYB); - return (float) val * 10 / 3200; + if (UINT32_MAX - this->phase_[1].cumulative_reverse_active_energy_ > val) { + this->phase_[1].cumulative_reverse_active_energy_ += val; + } else { + this->phase_[1].cumulative_reverse_active_energy_ = val; + } + return ((float) this->phase_[1].cumulative_reverse_active_energy_ * 10 / 3200); } float ATM90E32Component::get_reverse_active_energy_c_() { uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYC); - return (float) val * 10 / 3200; + if (UINT32_MAX - this->phase_[2].cumulative_reverse_active_energy_ > val) { + this->phase_[2].cumulative_reverse_active_energy_ += val; + } else { + this->phase_[2].cumulative_reverse_active_energy_ = val; + } + return ((float) this->phase_[2].cumulative_reverse_active_energy_ * 10 / 3200); } float ATM90E32Component::get_frequency_() { uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ); diff --git a/esphome/components/atm90e32/atm90e32.h b/esphome/components/atm90e32/atm90e32.h index 89d62adaf6..c9662df26e 100644 --- a/esphome/components/atm90e32/atm90e32.h +++ b/esphome/components/atm90e32/atm90e32.h @@ -77,6 +77,8 @@ class ATM90E32Component : public PollingComponent, sensor::Sensor *power_factor_sensor_{nullptr}; sensor::Sensor *forward_active_energy_sensor_{nullptr}; sensor::Sensor *reverse_active_energy_sensor_{nullptr}; + uint32_t cumulative_forward_active_energy_{0}; + uint32_t cumulative_reverse_active_energy_{0}; } phase_[3]; sensor::Sensor *freq_sensor_{nullptr}; sensor::Sensor *chip_temperature_sensor_{nullptr}; From e514a1fcd446f926b1f813971d22c450b75493f0 Mon Sep 17 00:00:00 2001 From: Ryan Mounce Date: Sun, 10 Oct 2021 18:58:37 +1030 Subject: [PATCH 146/549] Use enum for Tuya fan direction datapoint (#2471) Fix regression from PR2059. Tested with Arlec DCF5242HA. --- 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 f060b18eba..d0c8809564 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -76,7 +76,7 @@ void TuyaFan::write_state() { 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_boolean_datapoint_value(*this->direction_id_, enable); + this->parent_->set_enum_datapoint_value(*this->direction_id_, enable); } if (this->speed_id_.has_value()) { ESP_LOGV(TAG, "Setting speed: %d", this->fan_->speed); From c092d92d45488dadadd5857fdb223bd0a8e92c5a Mon Sep 17 00:00:00 2001 From: definitio <37266727+definitio@users.noreply.github.com> Date: Sun, 10 Oct 2021 11:31:15 +0300 Subject: [PATCH 147/549] Fix cover state (#2468) --- esphome/components/mqtt/mqtt_cover.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index 6a0d2d1bc5..61c8fa2d94 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -70,6 +70,7 @@ void MQTTCoverComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCon root["optimistic"] = true; } if (traits.get_supports_position()) { + config.state_topic = false; root["position_topic"] = this->get_position_state_topic(); root["set_position_topic"] = this->get_position_command_topic(); } From 92b85f98e861638b70d6021b838f52f1998342cc Mon Sep 17 00:00:00 2001 From: Nate Lust Date: Sun, 10 Oct 2021 04:33:04 -0400 Subject: [PATCH 148/549] Sgp40 fix (#2462) * Sample from SGP40 sensor at the appropriate interval The spg40 sensor must be sampled at 1Hz for the VOC index algorithm to work correctly. This commit introduces a on device timer to sample correctly seperately from updating the public state of the component. * Add missing configuration values for SGP40 The SGP40 component was not printing all of it's configuration in dump_config, add in the missing store_baseline value. * Address review comments * Format according to clang-tidy * Attempt 2 at clang tidy --- esphome/components/sgp40/sgp40.cpp | 35 ++++++++++++++++++++++++------ esphome/components/sgp40/sgp40.h | 2 ++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index fddd0255b8..a3d2c74eb7 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -77,6 +77,20 @@ void SGP40Component::setup() { } this->self_test_(); + + /* The official spec for this sensor at https://docs.rs-online.com/1956/A700000007055193.pdf + indicates this sensor should be driven at 1Hz. Comments from the developers at: + https://github.com/Sensirion/embedded-sgp/issues/136 indicate the algorithm should be a bit + resilient to slight timing variations so the software timer should be accurate enough for + this. + + This block starts sampling from the sensor at 1Hz, and is done seperately from the call + to the update method. This seperation is to support getting accurate measurements but + limit the amount of communication done over wifi for power consumption or to keep the + number of records reported from being overwhelming. + */ + ESP_LOGD(TAG, "Component requires sampling of 1Hz, setting up background sampler"); + this->set_interval(1000, [this]() { this->update_voc_index(); }); } void SGP40Component::self_test_() { @@ -224,21 +238,26 @@ uint8_t SGP40Component::generate_crc_(const uint8_t *data, uint8_t datalen) { return crc; } -void SGP40Component::update() { - this->seconds_since_last_store_ += this->update_interval_ / 1000; - - uint32_t voc_index = this->measure_voc_index_(); +void SGP40Component::update_voc_index() { + this->seconds_since_last_store_ += 1; + this->voc_index_ = this->measure_voc_index_(); if (this->samples_read_ < this->samples_to_stabalize_) { this->samples_read_++; ESP_LOGD(TAG, "Sensor has not collected enough samples yet. (%d/%d) VOC index is: %u", this->samples_read_, - this->samples_to_stabalize_, voc_index); + this->samples_to_stabalize_, this->voc_index_); + return; + } +} + +void SGP40Component::update() { + if (this->samples_read_ < this->samples_to_stabalize_) { return; } - if (voc_index != UINT16_MAX) { + if (this->voc_index_ != UINT16_MAX) { this->status_clear_warning(); - this->publish_state(voc_index); + this->publish_state(this->voc_index_); } else { this->status_set_warning(); } @@ -247,6 +266,8 @@ void SGP40Component::update() { void SGP40Component::dump_config() { ESP_LOGCONFIG(TAG, "SGP40:"); LOG_I2C_DEVICE(this); + ESP_LOGCONFIG(TAG, " store_baseline: %d", this->store_baseline_); + if (this->is_failed()) { switch (this->error_code_) { case COMMUNICATION_FAILED: diff --git a/esphome/components/sgp40/sgp40.h b/esphome/components/sgp40/sgp40.h index 62936102e7..bb68a1ffcf 100644 --- a/esphome/components/sgp40/sgp40.h +++ b/esphome/components/sgp40/sgp40.h @@ -46,6 +46,7 @@ class SGP40Component : public PollingComponent, public sensor::Sensor, public i2 void setup() override; void update() override; + void update_voc_index(); void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } void set_store_baseline(bool store_baseline) { store_baseline_ = store_baseline; } @@ -72,6 +73,7 @@ class SGP40Component : public PollingComponent, public sensor::Sensor, public i2 bool store_baseline_; int32_t state0_; int32_t state1_; + int32_t voc_index_ = 0; uint8_t samples_read_ = 0; uint8_t samples_to_stabalize_ = static_cast(VOC_ALGORITHM_INITIAL_BLACKOUT) * 2; From 471b82f727e2f698b8879fb51c4161a9cf49d34b Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Sun, 10 Oct 2021 21:37:05 +1300 Subject: [PATCH 149/549] EntityBase Refactor (#2418) * Renamed Nameable to EntityBase (cpp) * Renamed NAMEABLE_SCHEMA to ENTITY_BASE_SCHEMA (Python) * Renamed cg.Nameable to cg.EntityBase (Python) * Remove redundant use of CONF_NAME from esp32_touch * Remove redundant use of CONF_NAME from mcp3008 * Updated test * Moved EntityBase from Component.h and Component.cpp * Added icon property to EntityBase * Added CONF_ICON to ENTITY_BASE_SCHEMA and added setup_entity function to cpp_helpers * Added MQTT component getters for icon and disabled_by_default * Lint * Removed icon field from MQTT components * Code generation now uses setup_entity to setENTITY_BASE_SCHEMA fields * Removed unused import * Added cstdint include * Optimisation: don't set icon if it is empty * Remove icon from NumberTraits and SelectTraits * Removed unused import * Integration and Total Daily Energy sensors now inherit icons from their parents during code generation * Minor comment correction * Removed redundant icon-handling code from sensor, switch, and text_sensor * Update esphome/components/tsl2591/tsl2591.h Co-authored-by: Oxan van Leeuwen * Added icon property to binary sensor, climate, cover, and fan component tests * Added icons for Binary Sensor, Climate, Cover, Fan, and Light to API * Consolidated EntityBase fields in MQTT components Co-authored-by: Oxan van Leeuwen --- esphome/codegen.py | 2 +- esphome/components/api/api.proto | 5 ++ esphome/components/api/api_connection.cpp | 14 ++++-- esphome/components/api/api_pb2.cpp | 46 ++++++++++++++++- esphome/components/api/api_pb2.h | 5 ++ esphome/components/binary_sensor/__init__.py | 13 ++--- .../binary_sensor/binary_sensor.cpp | 2 +- .../components/binary_sensor/binary_sensor.h | 3 +- esphome/components/climate/__init__.py | 14 ++---- esphome/components/climate/climate.cpp | 2 +- esphome/components/climate/climate.h | 3 +- esphome/components/cover/__init__.py | 14 ++---- esphome/components/cover/cover.cpp | 2 +- esphome/components/cover/cover.h | 3 +- esphome/components/esp32_camera/__init__.py | 2 +- .../components/esp32_camera/esp32_camera.cpp | 2 +- .../components/esp32_camera/esp32_camera.h | 3 +- .../components/esp32_touch/binary_sensor.py | 2 - .../components/esp32_touch/esp32_touch.cpp | 5 +- esphome/components/esp32_touch/esp32_touch.h | 2 +- esphome/components/fan/__init__.py | 13 ++--- esphome/components/fan/fan_state.cpp | 2 +- esphome/components/fan/fan_state.h | 3 +- .../integration/integration_sensor.h | 1 - esphome/components/integration/sensor.py | 17 ++++++- esphome/components/light/__init__.py | 14 +++--- esphome/components/light/light_state.cpp | 3 +- esphome/components/light/light_state.h | 5 +- esphome/components/light/types.py | 2 +- esphome/components/mcp3008/mcp3008.cpp | 6 +-- esphome/components/mcp3008/mcp3008.h | 2 +- esphome/components/mcp3008/sensor.py | 3 +- .../components/mqtt/mqtt_binary_sensor.cpp | 3 +- esphome/components/mqtt/mqtt_binary_sensor.h | 3 +- esphome/components/mqtt/mqtt_climate.cpp | 4 +- esphome/components/mqtt/mqtt_climate.h | 3 +- esphome/components/mqtt/mqtt_component.cpp | 15 +++++- esphome/components/mqtt/mqtt_component.h | 18 +++++-- esphome/components/mqtt/mqtt_cover.cpp | 4 +- esphome/components/mqtt/mqtt_cover.h | 3 +- esphome/components/mqtt/mqtt_fan.cpp | 4 +- esphome/components/mqtt/mqtt_fan.h | 4 +- esphome/components/mqtt/mqtt_light.cpp | 4 +- esphome/components/mqtt/mqtt_light.h | 4 +- esphome/components/mqtt/mqtt_number.cpp | 5 +- esphome/components/mqtt/mqtt_number.h | 4 +- esphome/components/mqtt/mqtt_select.cpp | 5 +- esphome/components/mqtt/mqtt_select.h | 4 +- esphome/components/mqtt/mqtt_sensor.cpp | 7 +-- esphome/components/mqtt/mqtt_sensor.h | 5 +- esphome/components/mqtt/mqtt_switch.cpp | 6 +-- esphome/components/mqtt/mqtt_switch.h | 4 +- esphome/components/mqtt/mqtt_text_sensor.cpp | 6 +-- esphome/components/mqtt/mqtt_text_sensor.h | 6 +-- esphome/components/number/__init__.py | 17 ++----- esphome/components/number/number.h | 10 ++-- .../remote_receiver/binary_sensor.py | 3 -- esphome/components/select/__init__.py | 17 ++----- esphome/components/select/select.h | 10 ++-- esphome/components/sensor/__init__.py | 17 ++----- esphome/components/sensor/sensor.cpp | 10 +--- esphome/components/sensor/sensor.h | 12 +---- esphome/components/switch/__init__.py | 18 ++----- esphome/components/switch/switch.cpp | 10 +--- esphome/components/switch/switch.h | 19 +------ esphome/components/text_sensor/__init__.py | 17 ++----- .../components/text_sensor/text_sensor.cpp | 10 +--- esphome/components/text_sensor/text_sensor.h | 10 +--- .../components/total_daily_energy/sensor.py | 14 ++++++ .../total_daily_energy/total_daily_energy.h | 1 - esphome/components/tsl2591/tsl2591.h | 2 +- esphome/components/web_server/web_server.cpp | 7 +-- esphome/config_validation.py | 8 +-- esphome/core/component.cpp | 20 -------- esphome/core/component.h | 34 ------------- esphome/core/entity_base.cpp | 40 +++++++++++++++ esphome/core/entity_base.h | 50 +++++++++++++++++++ esphome/core/entity_helpers.py | 32 ++++++++++++ esphome/cpp_helpers.py | 14 ++++++ esphome/cpp_types.py | 2 +- tests/test1.yaml | 3 ++ tests/test5.yaml | 1 + tests/unit_tests/test_codegen.py | 2 +- 83 files changed, 395 insertions(+), 351 deletions(-) create mode 100644 esphome/core/entity_base.cpp create mode 100644 esphome/core/entity_base.h create mode 100644 esphome/core/entity_helpers.py diff --git a/esphome/codegen.py b/esphome/codegen.py index 1b38fd9ed2..4f9f67245d 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -67,7 +67,7 @@ from esphome.cpp_types import ( # noqa NAN, esphome_ns, App, - Nameable, + EntityBase, Component, ComponentPtr, PollingComponent, diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 7648ffeaa2..5a6eba004c 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -215,6 +215,7 @@ message ListEntitiesBinarySensorResponse { string device_class = 5; bool is_status_binary_sensor = 6; bool disabled_by_default = 7; + string icon = 8; } message BinarySensorStateResponse { option (id) = 21; @@ -245,6 +246,7 @@ message ListEntitiesCoverResponse { bool supports_tilt = 7; string device_class = 8; bool disabled_by_default = 9; + string icon = 10; } enum LegacyCoverState { @@ -313,6 +315,7 @@ message ListEntitiesFanResponse { bool supports_direction = 7; int32 supported_speed_count = 8; bool disabled_by_default = 9; + string icon = 10; } enum FanSpeed { FAN_SPEED_LOW = 0; @@ -388,6 +391,7 @@ message ListEntitiesLightResponse { float max_mireds = 10; repeated string effects = 11; bool disabled_by_default = 13; + string icon = 14; } message LightStateResponse { option (id) = 24; @@ -790,6 +794,7 @@ message ListEntitiesClimateResponse { repeated ClimatePreset supported_presets = 16; repeated string supported_custom_presets = 17; bool disabled_by_default = 18; + string icon = 19; } message ClimateStateResponse { option (id) = 47; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 450375f7cd..47171ba50f 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1,4 +1,5 @@ #include "api_connection.h" +#include "esphome/core/entity_base.h" #include "esphome/core/log.h" #include "esphome/components/network/util.h" #include "esphome/core/version.h" @@ -143,8 +144,8 @@ void APIConnection::loop() { } } -std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) { - return App.get_name() + component_type + nameable->get_object_id(); +std::string get_default_unique_id(const std::string &component_type, EntityBase *entity) { + return App.get_name() + component_type + entity->get_object_id(); } DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) { @@ -180,6 +181,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_ msg.device_class = binary_sensor->get_device_class(); msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); msg.disabled_by_default = binary_sensor->is_disabled_by_default(); + msg.icon = binary_sensor->get_icon(); return this->send_list_entities_binary_sensor_response(msg); } #endif @@ -212,6 +214,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) { msg.supports_tilt = traits.get_supports_tilt(); msg.device_class = cover->get_device_class(); msg.disabled_by_default = cover->is_disabled_by_default(); + msg.icon = cover->get_icon(); return this->send_list_entities_cover_response(msg); } void APIConnection::cover_command(const CoverCommandRequest &msg) { @@ -277,6 +280,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) { msg.supports_direction = traits.supports_direction(); msg.supported_speed_count = traits.supported_speed_count(); msg.disabled_by_default = fan->is_disabled_by_default(); + msg.icon = fan->get_icon(); return this->send_list_entities_fan_response(msg); } void APIConnection::fan_command(const FanCommandRequest &msg) { @@ -339,6 +343,7 @@ bool APIConnection::send_light_info(light::LightState *light) { msg.unique_id = get_default_unique_id("light", light); msg.disabled_by_default = light->is_disabled_by_default(); + msg.icon = light->get_icon(); for (auto mode : traits.get_supported_color_modes()) msg.supported_color_modes.push_back(static_cast(mode)); @@ -529,6 +534,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { msg.unique_id = get_default_unique_id("climate", climate); msg.disabled_by_default = climate->is_disabled_by_default(); + msg.icon = climate->get_icon(); msg.supports_current_temperature = traits.get_supports_current_temperature(); msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); @@ -601,7 +607,7 @@ 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->traits.get_icon(); + msg.icon = number->get_icon(); msg.disabled_by_default = number->is_disabled_by_default(); msg.min_value = number->traits.get_min_value(); @@ -638,7 +644,7 @@ bool APIConnection::send_select_info(select::Select *select) { msg.object_id = select->get_object_id(); msg.name = select->get_name(); msg.unique_id = get_default_unique_id("select", select); - msg.icon = select->traits.get_icon(); + msg.icon = select->get_icon(); msg.disabled_by_default = select->is_disabled_by_default(); for (const auto &option : select->traits.get_options()) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 580e147245..6a87238186 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -2,7 +2,6 @@ // See scripts/api_protobuf/api_protobuf.py #include "api_pb2.h" #include "esphome/core/log.h" -#include namespace esphome { namespace api { @@ -532,6 +531,10 @@ bool ListEntitiesBinarySensorResponse::decode_length(uint32_t field_id, ProtoLen this->device_class = value.as_string(); return true; } + case 8: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -554,6 +557,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->device_class); buffer.encode_bool(6, this->is_status_binary_sensor); buffer.encode_bool(7, this->disabled_by_default); + buffer.encode_string(8, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { @@ -587,6 +591,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -678,6 +686,10 @@ bool ListEntitiesCoverResponse::decode_length(uint32_t field_id, ProtoLengthDeli this->device_class = value.as_string(); return true; } + case 10: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -702,6 +714,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->supports_tilt); buffer.encode_string(8, this->device_class); buffer.encode_bool(9, this->disabled_by_default); + buffer.encode_string(10, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { @@ -743,6 +756,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -949,6 +966,10 @@ bool ListEntitiesFanResponse::decode_length(uint32_t field_id, ProtoLengthDelimi this->unique_id = value.as_string(); return true; } + case 10: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -973,6 +994,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->supports_direction); buffer.encode_int32(8, this->supported_speed_count); buffer.encode_bool(9, this->disabled_by_default); + buffer.encode_string(10, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesFanResponse::dump_to(std::string &out) const { @@ -1015,6 +1037,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -1263,6 +1289,10 @@ bool ListEntitiesLightResponse::decode_length(uint32_t field_id, ProtoLengthDeli this->effects.push_back(value.as_string()); return true; } + case 14: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -1303,6 +1333,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(11, it, true); } buffer.encode_bool(13, this->disabled_by_default); + buffer.encode_string(14, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLightResponse::dump_to(std::string &out) const { @@ -1366,6 +1397,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -3073,6 +3108,10 @@ bool ListEntitiesClimateResponse::decode_length(uint32_t field_id, ProtoLengthDe this->supported_custom_presets.push_back(value.as_string()); return true; } + case 19: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -3130,6 +3169,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(17, it, true); } buffer.encode_bool(18, this->disabled_by_default); + buffer.encode_string(19, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesClimateResponse::dump_to(std::string &out) const { @@ -3222,6 +3262,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 1371ab5248..13a21c4772 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -269,6 +269,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage { std::string device_class{}; bool is_status_binary_sensor{false}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -304,6 +305,7 @@ class ListEntitiesCoverResponse : public ProtoMessage { bool supports_tilt{false}; std::string device_class{}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -360,6 +362,7 @@ class ListEntitiesFanResponse : public ProtoMessage { bool supports_direction{false}; int32_t supported_speed_count{0}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -424,6 +427,7 @@ class ListEntitiesLightResponse : public ProtoMessage { float max_mireds{0.0f}; std::vector effects{}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -856,6 +860,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { std::vector supported_presets{}; std::vector supported_custom_presets{}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 60ac4303a7..ec199cc5fa 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -1,15 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome.cpp_helpers import setup_entity from esphome import automation, core from esphome.automation import Condition, maybe_simple_id from esphome.components import mqtt from esphome.const import ( CONF_DELAY, CONF_DEVICE_CLASS, - CONF_DISABLED_BY_DEFAULT, CONF_FILTERS, CONF_ID, - CONF_INTERNAL, CONF_INVALID_COOLDOWN, CONF_INVERTED, CONF_MAX_LENGTH, @@ -88,7 +87,7 @@ DEVICE_CLASSES = [ IS_PLATFORM_COMPONENT = True binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor") -BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.Nameable) +BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase) BinarySensorInitiallyOff = binary_sensor_ns.class_( "BinarySensorInitiallyOff", BinarySensor ) @@ -314,7 +313,7 @@ def validate_multi_click_timing(value): device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") -BINARY_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(BinarySensor), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( @@ -375,10 +374,8 @@ BINARY_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).exten async def setup_binary_sensor_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) + if CONF_DEVICE_CLASS in config: cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) if CONF_INVERTED in config: diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index 2e1f228be6..41da83aa3e 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -42,7 +42,7 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) { } } std::string BinarySensor::device_class() { return ""; } -BinarySensor::BinarySensor(const std::string &name) : Nameable(name), state(false) {} +BinarySensor::BinarySensor(const std::string &name) : EntityBase(name), state(false) {} BinarySensor::BinarySensor() : BinarySensor("") {} void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } std::string BinarySensor::get_device_class() { diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index 195badd798..9c0d43fa98 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/components/binary_sensor/filter.h" @@ -22,7 +23,7 @@ namespace binary_sensor { * The sub classes should notify the front-end of new states via the publish_state() method which * handles inverted inputs for you. */ -class BinarySensor : public Nameable { +class BinarySensor : public EntityBase { public: explicit BinarySensor(); /** Construct a binary sensor with the specified name diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index c2f07ce423..ca1ea6a756 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -1,14 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome.cpp_helpers import setup_entity from esphome import automation from esphome.components import mqtt from esphome.const import ( CONF_AWAY, CONF_CUSTOM_FAN_MODE, CONF_CUSTOM_PRESET, - CONF_DISABLED_BY_DEFAULT, CONF_ID, - CONF_INTERNAL, CONF_MAX_TEMPERATURE, CONF_MIN_TEMPERATURE, CONF_MODE, @@ -19,7 +18,6 @@ from esphome.const import ( CONF_TEMPERATURE_STEP, CONF_VISUAL, CONF_MQTT_ID, - CONF_NAME, CONF_FAN_MODE, CONF_SWING_MODE, ) @@ -30,7 +28,7 @@ IS_PLATFORM_COMPONENT = True CODEOWNERS = ["@esphome/core"] climate_ns = cg.esphome_ns.namespace("climate") -Climate = climate_ns.class_("Climate", cg.Nameable) +Climate = climate_ns.class_("Climate", cg.EntityBase) ClimateCall = climate_ns.class_("ClimateCall") ClimateTraits = climate_ns.class_("ClimateTraits") @@ -88,7 +86,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True) # Actions ControlAction = climate_ns.class_("ControlAction", automation.Action) -CLIMATE_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( +CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(Climate), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent), @@ -105,10 +103,8 @@ CLIMATE_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ext async def setup_climate_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) + visual = config[CONF_VISUAL] if CONF_MIN_TEMPERATURE in visual: cg.add(var.set_visual_min_temperature_override(visual[CONF_MIN_TEMPERATURE])) diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index a365b933bb..34e6328d8a 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -440,7 +440,7 @@ void Climate::set_visual_max_temperature_override(float visual_max_temperature_o void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) { this->visual_temperature_step_override_ = visual_temperature_step_override; } -Climate::Climate(const std::string &name) : Nameable(name) {} +Climate::Climate(const std::string &name) : EntityBase(name) {} Climate::Climate() : Climate("") {} ClimateCall Climate::make_call() { return ClimateCall(this); } diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 418db02485..852b76686c 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" #include "esphome/core/log.h" @@ -163,7 +164,7 @@ struct ClimateDeviceRestoreState { * 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_HEAT_COOL) ...` */ -class Climate : public Nameable { +class Climate : public EntityBase { public: /// Construct a climate device with empty name (will be set later). Climate(); diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 46b8906adb..eb57637283 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -4,18 +4,16 @@ from esphome import automation from esphome.automation import maybe_simple_id, Condition from esphome.components import mqtt from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, CONF_ID, - CONF_INTERNAL, CONF_DEVICE_CLASS, CONF_STATE, CONF_POSITION, CONF_TILT, CONF_STOP, CONF_MQTT_ID, - CONF_NAME, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity IS_PLATFORM_COMPONENT = True @@ -36,7 +34,7 @@ DEVICE_CLASSES = [ cover_ns = cg.esphome_ns.namespace("cover") -Cover = cover_ns.class_("Cover", cg.Nameable) +Cover = cover_ns.class_("Cover", cg.EntityBase) COVER_OPEN = cover_ns.COVER_OPEN COVER_CLOSED = cover_ns.COVER_CLOSED @@ -65,7 +63,7 @@ CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action) CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition) CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition) -COVER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( +COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(Cover), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent), @@ -76,10 +74,8 @@ COVER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).exten async def setup_cover_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) + if CONF_DEVICE_CLASS in config: cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 863adb1d81..a8d3d691a4 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -31,7 +31,7 @@ const char *cover_operation_to_str(CoverOperation op) { } } -Cover::Cover(const std::string &name) : Nameable(name), position{COVER_OPEN} {} +Cover::Cover(const std::string &name) : EntityBase(name), position{COVER_OPEN} {} uint32_t Cover::hash_base() { return 1727367479UL; } diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 8f98a88a42..a67f8d2393 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" #include "cover_traits.h" @@ -107,7 +108,7 @@ const char *cover_operation_to_str(CoverOperation op); * to control all values of the cover. Also implement get_traits() to return what operations * the cover supports. */ -class Cover : public Nameable { +class Cover : public EntityBase { public: explicit Cover(); explicit Cover(const std::string &name); diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index de61ab43cf..7f3aebe238 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -21,7 +21,7 @@ from esphome.components.esp32 import add_idf_sdkconfig_option DEPENDENCIES = ["esp32", "api"] esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") -ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.Nameable) +ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase) ESP32CameraFrameSize = esp32_camera_ns.enum("ESP32CameraFrameSize") FRAME_SIZES = { "160X120": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_160X120, diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 05445f024b..babfda4113 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -172,7 +172,7 @@ void ESP32Camera::framebuffer_task(void *pv) { esp_camera_fb_return(framebuffer); } } -ESP32Camera::ESP32Camera(const std::string &name) : Nameable(name) { +ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { this->config_.pin_pwdn = -1; this->config_.pin_reset = -1; this->config_.pin_xclk = -1; diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 6246dc2f12..d0445607a4 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -3,6 +3,7 @@ #ifdef USE_ESP32 #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include #include @@ -50,7 +51,7 @@ enum ESP32CameraFrameSize { ESP32_CAMERA_SIZE_1600X1200, // UXGA }; -class ESP32Camera : public Component, public Nameable { +class ESP32Camera : public Component, public EntityBase { public: ESP32Camera(const std::string &name); void set_data_pins(std::array pins); diff --git a/esphome/components/esp32_touch/binary_sensor.py b/esphome/components/esp32_touch/binary_sensor.py index 93640334cd..bd3e06545d 100644 --- a/esphome/components/esp32_touch/binary_sensor.py +++ b/esphome/components/esp32_touch/binary_sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor from esphome.const import ( - CONF_NAME, CONF_PIN, CONF_THRESHOLD, CONF_ID, @@ -55,7 +54,6 @@ async def to_code(config): hub = await cg.get_variable(config[CONF_ESP32_TOUCH_ID]) var = cg.new_Pvariable( config[CONF_ID], - config[CONF_NAME], TOUCH_PADS[config[CONF_PIN]], config[CONF_THRESHOLD], config[CONF_WAKEUP_THRESHOLD], diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 801106ab6c..cb72820900 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -159,9 +159,8 @@ void ESP32TouchComponent::on_shutdown() { } } -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) {} +ESP32TouchBinarySensor::ESP32TouchBinarySensor(touch_pad_t touch_pad, uint16_t threshold, uint16_t wakeup_threshold) + : BinarySensor(), 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 c584a6d9bc..d49e4703a7 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -64,7 +64,7 @@ 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, uint16_t wakeup_threshold); + ESP32TouchBinarySensor(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_; } diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 15895976d1..52bec3b5b6 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -4,9 +4,7 @@ from esphome import automation from esphome.automation import maybe_simple_id from esphome.components import mqtt from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, CONF_ID, - CONF_INTERNAL, CONF_MQTT_ID, CONF_OSCILLATING, CONF_OSCILLATION_COMMAND_TOPIC, @@ -16,7 +14,6 @@ from esphome.const import ( CONF_SPEED_LEVEL_STATE_TOPIC, CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, - CONF_NAME, CONF_ON_SPEED_SET, CONF_ON_TURN_OFF, CONF_ON_TURN_ON, @@ -24,11 +21,12 @@ from esphome.const import ( CONF_DIRECTION, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity IS_PLATFORM_COMPONENT = True fan_ns = cg.esphome_ns.namespace("fan") -FanState = fan_ns.class_("FanState", cg.Nameable, cg.Component) +FanState = fan_ns.class_("FanState", cg.EntityBase, cg.Component) MakeFan = cg.Application.struct("MakeFan") FanDirection = fan_ns.enum("FanDirection") @@ -50,7 +48,7 @@ FanSpeedSetTrigger = fan_ns.class_("FanSpeedSetTrigger", automation.Trigger.temp 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( +FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(FanState), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent), @@ -92,10 +90,7 @@ FAN_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( async def setup_fan_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) diff --git a/esphome/components/fan/fan_state.cpp b/esphome/components/fan/fan_state.cpp index 921b8d57e7..6ff4d3a833 100644 --- a/esphome/components/fan/fan_state.cpp +++ b/esphome/components/fan/fan_state.cpp @@ -12,7 +12,7 @@ void FanState::set_traits(const FanTraits &traits) { this->traits_ = traits; } void FanState::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } -FanState::FanState(const std::string &name) : Nameable(name) {} +FanState::FanState(const std::string &name) : EntityBase(name) {} FanStateCall FanState::turn_on() { return this->make_call().set_state(true); } FanStateCall FanState::turn_off() { return this->make_call().set_state(false); } diff --git a/esphome/components/fan/fan_state.h b/esphome/components/fan/fan_state.h index af00275df0..c5a6f59ac4 100644 --- a/esphome/components/fan/fan_state.h +++ b/esphome/components/fan/fan_state.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" #include "esphome/core/log.h" @@ -66,7 +67,7 @@ class FanStateCall { optional direction_{}; }; -class FanState : public Nameable, public Component { +class FanState : public EntityBase, public Component { public: FanState() = default; /// Construct the fan state with name. diff --git a/esphome/components/integration/integration_sensor.h b/esphome/components/integration/integration_sensor.h index a3bdfbbb2b..437649c1dd 100644 --- a/esphome/components/integration/integration_sensor.h +++ b/esphome/components/integration/integration_sensor.h @@ -64,7 +64,6 @@ class IntegrationSensor : public sensor::Sensor, public Component { this->rtc_.save(&result_f); } std::string unit_of_measurement() override; - std::string icon() override { return this->sensor_->get_icon(); } int8_t accuracy_decimals() override { return this->sensor_->get_accuracy_decimals() + 2; } sensor::Sensor *sensor_; diff --git a/esphome/components/integration/sensor.py b/esphome/components/integration/sensor.py index 460dd46619..26c7c2871a 100644 --- a/esphome/components/integration/sensor.py +++ b/esphome/components/integration/sensor.py @@ -2,7 +2,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation from esphome.components import sensor -from esphome.const import CONF_ID, CONF_SENSOR, CONF_RESTORE +from esphome.const import CONF_ICON, CONF_ID, CONF_SENSOR, CONF_RESTORE +from esphome.core.entity_helpers import inherit_property_from integration_ns = cg.esphome_ns.namespace("integration") IntegrationSensor = integration_ns.class_( @@ -29,7 +30,6 @@ CONF_TIME_UNIT = "time_unit" CONF_INTEGRATION_METHOD = "integration_method" CONF_MIN_SAVE_INTERVAL = "min_save_interval" - CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(IntegrationSensor), @@ -46,6 +46,19 @@ CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( ).extend(cv.COMPONENT_SCHEMA) +FINAL_VALIDATE_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(IntegrationSensor), + cv.Optional(CONF_ICON): cv.icon, + cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), + }, + extra=cv.ALLOW_EXTRA, + ), + inherit_property_from(CONF_ICON, CONF_SENSOR), +) + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index 69cb87e539..03224d4c10 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -5,13 +5,10 @@ from esphome.components import mqtt, power_supply from esphome.const import ( CONF_COLOR_CORRECT, CONF_DEFAULT_TRANSITION_LENGTH, - CONF_DISABLED_BY_DEFAULT, CONF_EFFECTS, CONF_FLASH_TRANSITION_LENGTH, CONF_GAMMA_CORRECT, CONF_ID, - CONF_INTERNAL, - CONF_NAME, CONF_MQTT_ID, CONF_POWER_SUPPLY, CONF_RESTORE_MODE, @@ -22,6 +19,7 @@ from esphome.const import ( CONF_WARM_WHITE_COLOR_TEMPERATURE, ) from esphome.core import coroutine_with_priority +from esphome.cpp_helpers import setup_entity from .automation import light_control_to_code # noqa from .effects import ( validate_effects, @@ -54,7 +52,7 @@ RESTORE_MODES = { "RESTORE_INVERTED_DEFAULT_ON": LightRestoreMode.LIGHT_RESTORE_INVERTED_DEFAULT_ON, } -LIGHT_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( +LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(LightState), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTJSONLightComponent), @@ -126,10 +124,10 @@ def validate_color_temperature_channels(value): async def setup_light_core_(light_var, output_var, config): - cg.add(light_var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) + await setup_entity(light_var, config) + cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE])) - if CONF_INTERNAL in config: - cg.add(light_var.set_internal(config[CONF_INTERNAL])) + if CONF_DEFAULT_TRANSITION_LENGTH in config: cg.add( light_var.set_default_transition_length( @@ -167,7 +165,7 @@ async def setup_light_core_(light_var, output_var, config): async def register_light(output_var, config): - light_var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME], output_var) + light_var = cg.new_Pvariable(config[CONF_ID], output_var) cg.add(cg.App.register_light(light_var)) await cg.register_component(light_var, config) await setup_light_core_(light_var, output_var, config) diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index f888006b17..5f16585c36 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -8,7 +8,8 @@ namespace light { static const char *const TAG = "light"; -LightState::LightState(const std::string &name, LightOutput *output) : Nameable(name), output_(output) {} +LightState::LightState(const std::string &name, LightOutput *output) : EntityBase(name), output_(output) {} +LightState::LightState(LightOutput *output) : output_(output) {} LightTraits LightState::get_traits() { return this->output_->get_traits(); } LightCall LightState::turn_on() { return this->make_call().set_state(true); } diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index f73a4c3b17..ae3711234d 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/optional.h" #include "esphome/core/preferences.h" #include "light_call.h" @@ -26,11 +27,13 @@ enum LightRestoreMode { /** This class represents the communication layer between the front-end MQTT layer and the * hardware output layer. */ -class LightState : public Nameable, public Component { +class LightState : public EntityBase, public Component { public: /// Construct this LightState using the provided traits and name. LightState(const std::string &name, LightOutput *output); + LightState(LightOutput *output); + LightTraits get_traits(); /// Make a light state call diff --git a/esphome/components/light/types.py b/esphome/components/light/types.py index 66329f7cf9..cf544e5435 100644 --- a/esphome/components/light/types.py +++ b/esphome/components/light/types.py @@ -3,7 +3,7 @@ from esphome import automation # Base light_ns = cg.esphome_ns.namespace("light") -LightState = light_ns.class_("LightState", cg.Nameable, cg.Component) +LightState = light_ns.class_("LightState", cg.EntityBase, cg.Component) # Fake class for addressable lights AddressableLightState = light_ns.class_("LightState", LightState) LightOutput = light_ns.class_("LightOutput") diff --git a/esphome/components/mcp3008/mcp3008.cpp b/esphome/components/mcp3008/mcp3008.cpp index bea24b5c6a..81abc4f012 100644 --- a/esphome/components/mcp3008/mcp3008.cpp +++ b/esphome/components/mcp3008/mcp3008.cpp @@ -37,10 +37,8 @@ float MCP3008::read_data(uint8_t pin) { return data / 1023.0f; } -MCP3008Sensor::MCP3008Sensor(MCP3008 *parent, const std::string &name, uint8_t pin, float reference_voltage) - : PollingComponent(1000), parent_(parent), pin_(pin), reference_voltage_(reference_voltage) { - this->set_name(name); -} +MCP3008Sensor::MCP3008Sensor(MCP3008 *parent, uint8_t pin, float reference_voltage) + : PollingComponent(1000), parent_(parent), pin_(pin), reference_voltage_(reference_voltage) {} float MCP3008Sensor::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/mcp3008/mcp3008.h b/esphome/components/mcp3008/mcp3008.h index 6f3dc576ea..5d8b823111 100644 --- a/esphome/components/mcp3008/mcp3008.h +++ b/esphome/components/mcp3008/mcp3008.h @@ -26,7 +26,7 @@ class MCP3008 : public Component, class MCP3008Sensor : public PollingComponent, public sensor::Sensor, public voltage_sampler::VoltageSampler { public: - MCP3008Sensor(MCP3008 *parent, const std::string &name, uint8_t pin, float reference_voltage); + MCP3008Sensor(MCP3008 *parent, uint8_t pin, float reference_voltage); void set_reference_voltage(float reference_voltage) { reference_voltage_ = reference_voltage; } void setup() override; diff --git a/esphome/components/mcp3008/sensor.py b/esphome/components/mcp3008/sensor.py index 4fc9b83afb..d4b9e979ce 100644 --- a/esphome/components/mcp3008/sensor.py +++ b/esphome/components/mcp3008/sensor.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, voltage_sampler -from esphome.const import CONF_ID, CONF_NUMBER, CONF_NAME +from esphome.const import CONF_ID, CONF_NUMBER from . import mcp3008_ns, MCP3008 AUTO_LOAD = ["voltage_sampler"] @@ -29,7 +29,6 @@ async def to_code(config): var = cg.new_Pvariable( config[CONF_ID], parent, - config[CONF_NAME], config[CONF_NUMBER], config[CONF_REFERENCE_VOLTAGE], ) diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index d7322298bb..188df0f7b9 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -10,6 +10,7 @@ namespace mqtt { static const char *const TAG = "mqtt.binary_sensor"; std::string MQTTBinarySensorComponent::component_type() const { return "binary_sensor"; } +const EntityBase *MQTTBinarySensorComponent::get_entity() const { return this->binary_sensor_; } void MQTTBinarySensorComponent::setup() { this->binary_sensor_->add_on_state_callback([this](bool state) { this->publish_state(state); }); @@ -25,7 +26,6 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor this->set_custom_state_topic(mqtt::global_mqtt_client->get_availability().topic); } } -std::string MQTTBinarySensorComponent::friendly_name() const { return this->binary_sensor_->get_name(); } void MQTTBinarySensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (!this->binary_sensor_->get_device_class().empty()) @@ -43,7 +43,6 @@ bool MQTTBinarySensorComponent::send_initial_state() { return true; } } -bool MQTTBinarySensorComponent::is_internal() { return this->binary_sensor_->is_internal(); } bool MQTTBinarySensorComponent::publish_state(bool state) { if (this->binary_sensor_->is_status_binary_sensor()) return true; diff --git a/esphome/components/mqtt/mqtt_binary_sensor.h b/esphome/components/mqtt/mqtt_binary_sensor.h index c459bea1f7..0efb490367 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.h +++ b/esphome/components/mqtt/mqtt_binary_sensor.h @@ -28,11 +28,10 @@ class MQTTBinarySensorComponent : public mqtt::MQTTComponent { bool send_initial_state() override; bool publish_state(bool state); - bool is_internal() override; protected: - std::string friendly_name() const override; std::string component_type() const override; + const EntityBase *get_entity() const override; binary_sensor::BinarySensor *binary_sensor_; }; diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 8519e296b0..47b6684dec 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -212,9 +212,9 @@ void MQTTClimateComponent::setup() { } MQTTClimateComponent::MQTTClimateComponent(Climate *device) : device_(device) {} bool MQTTClimateComponent::send_initial_state() { return this->publish_state_(); } -bool MQTTClimateComponent::is_internal() { return this->device_->is_internal(); } std::string MQTTClimateComponent::component_type() const { return "climate"; } -std::string MQTTClimateComponent::friendly_name() const { return this->device_->get_name(); } +const EntityBase *MQTTClimateComponent::get_entity() const { return this->device_; } + bool MQTTClimateComponent::publish_state_() { auto traits = this->device_->get_traits(); // mode diff --git a/esphome/components/mqtt/mqtt_climate.h b/esphome/components/mqtt/mqtt_climate.h index 8b8a8e866e..40ac4c18c1 100644 --- a/esphome/components/mqtt/mqtt_climate.h +++ b/esphome/components/mqtt/mqtt_climate.h @@ -16,7 +16,6 @@ class MQTTClimateComponent : public mqtt::MQTTComponent { MQTTClimateComponent(climate::Climate *device); void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; - bool is_internal() override; std::string component_type() const override; void setup() override; @@ -38,7 +37,7 @@ class MQTTClimateComponent : public mqtt::MQTTComponent { MQTT_COMPONENT_CUSTOM_TOPIC(swing_mode, command) protected: - std::string friendly_name() const override; + const EntityBase *get_entity() const override; bool publish_state_(); diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 96cda57914..0ece4b3501 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -68,8 +68,13 @@ bool MQTTComponent::send_discovery_() { this->send_discovery(root, config); - std::string name = this->friendly_name(); - root["name"] = name; + // Fields from EntityBase + root["name"] = this->friendly_name(); + if (this->is_disabled_by_default()) + root["enabled_by_default"] = false; + if (!this->get_icon().empty()) + root["icon"] = this->get_icon(); + if (config.state_topic) root["state_topic"] = this->get_state_topic_(); if (config.command_topic) @@ -199,6 +204,12 @@ 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(); } +// Pull these properties from EntityBase if not overridden +std::string MQTTComponent::friendly_name() const { return this->get_entity()->get_name(); } +std::string MQTTComponent::get_icon() const { return this->get_entity()->get_icon(); } +bool MQTTComponent::is_disabled_by_default() const { return this->get_entity()->is_disabled_by_default(); } +bool MQTTComponent::is_internal() { return this->get_entity()->is_internal(); } + } // namespace mqtt } // namespace esphome diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index f07e752e02..657ab7b608 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -7,6 +7,7 @@ #include #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "mqtt_client.h" namespace esphome { @@ -73,7 +74,7 @@ class MQTTComponent : public Component { virtual bool send_initial_state() = 0; - virtual bool is_internal() = 0; + virtual bool is_internal(); /// Set whether state message should be retained. void set_retain(bool retain); @@ -148,8 +149,10 @@ class MQTTComponent : public Component { */ std::string get_default_topic_for_(const std::string &suffix) const; - /// Get the friendly name of this MQTT component. - virtual std::string friendly_name() const = 0; + /** + * Gets the Entity served by this MQTT component. + */ + virtual const EntityBase *get_entity() const = 0; /** A unique ID for this MQTT component, empty for no unique id. See unique ID requirements: * https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements @@ -158,6 +161,15 @@ class MQTTComponent : public Component { */ virtual std::string unique_id(); + /// Get the friendly name of this MQTT component. + virtual std::string friendly_name() const; + + /// Get the icon field of this component + virtual std::string get_icon() const; + + /// Get whether the underlying Entity is disabled by default + virtual bool is_disabled_by_default() const; + /// Get the MQTT topic that new states will be shared to. const std::string get_state_topic_() const; diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index 61c8fa2d94..e8bc7f0e30 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -84,9 +84,9 @@ void MQTTCoverComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCon } std::string MQTTCoverComponent::component_type() const { return "cover"; } -std::string MQTTCoverComponent::friendly_name() const { return this->cover_->get_name(); } +const EntityBase *MQTTCoverComponent::get_entity() const { return this->cover_; } + bool MQTTCoverComponent::send_initial_state() { return this->publish_state(); } -bool MQTTCoverComponent::is_internal() { return this->cover_->is_internal(); } bool MQTTCoverComponent::publish_state() { auto traits = this->cover_->get_traits(); bool success = true; diff --git a/esphome/components/mqtt/mqtt_cover.h b/esphome/components/mqtt/mqtt_cover.h index b8c9f2617c..149d46ac85 100644 --- a/esphome/components/mqtt/mqtt_cover.h +++ b/esphome/components/mqtt/mqtt_cover.h @@ -24,7 +24,6 @@ class MQTTCoverComponent : public mqtt::MQTTComponent { MQTT_COMPONENT_CUSTOM_TOPIC(tilt, state) bool send_initial_state() override; - bool is_internal() override; bool publish_state(); @@ -32,7 +31,7 @@ class MQTTCoverComponent : public mqtt::MQTTComponent { protected: std::string component_type() const override; - std::string friendly_name() const override; + const EntityBase *get_entity() const override; cover::Cover *cover_; }; diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index ed1ab605aa..898183cc58 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -16,6 +16,7 @@ MQTTFanComponent::MQTTFanComponent(FanState *state) : MQTTComponent(), state_(st FanState *MQTTFanComponent::get_state() const { return this->state_; } std::string MQTTFanComponent::component_type() const { return "fan"; } +const EntityBase *MQTTFanComponent::get_entity() const { return this->state_; } void MQTTFanComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { @@ -113,7 +114,7 @@ void MQTTFanComponent::dump_config() { } bool MQTTFanComponent::send_initial_state() { return this->publish_state(); } -std::string MQTTFanComponent::friendly_name() const { return this->state_->get_name(); } + void MQTTFanComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (this->state_->get_traits().supports_oscillation()) { root["oscillation_command_topic"] = this->get_oscillation_command_topic(); @@ -126,7 +127,6 @@ void MQTTFanComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfi root["speed_state_topic"] = this->get_speed_state_topic(); } } -bool MQTTFanComponent::is_internal() { return this->state_->is_internal(); } bool MQTTFanComponent::publish_state() { const char *state_s = this->state_->state ? "ON" : "OFF"; ESP_LOGD(TAG, "'%s' Sending state %s.", this->state_->get_name().c_str(), state_s); diff --git a/esphome/components/mqtt/mqtt_fan.h b/esphome/components/mqtt/mqtt_fan.h index 00263e13eb..a160d5366b 100644 --- a/esphome/components/mqtt/mqtt_fan.h +++ b/esphome/components/mqtt/mqtt_fan.h @@ -39,10 +39,8 @@ class MQTTFanComponent : public mqtt::MQTTComponent { fan::FanState *get_state() const; - bool is_internal() override; - protected: - std::string friendly_name() const override; + const EntityBase *get_entity() const override; fan::FanState *state_; }; diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index d028b1b037..a88358a6b2 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -13,6 +13,7 @@ static const char *const TAG = "mqtt.light"; using namespace esphome::light; std::string MQTTJSONLightComponent::component_type() const { return "light"; } +const EntityBase *MQTTJSONLightComponent::get_entity() const { return this->state_; } void MQTTJSONLightComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject &root) { @@ -32,7 +33,7 @@ bool MQTTJSONLightComponent::publish_state_() { [this](JsonObject &root) { LightJSONSchema::dump_json(*this->state_, root); }); } LightState *MQTTJSONLightComponent::get_state() const { return this->state_; } -std::string MQTTJSONLightComponent::friendly_name() const { return this->state_->get_name(); } + void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { root["schema"] = "json"; auto traits = this->state_->get_traits(); @@ -70,7 +71,6 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover } } bool MQTTJSONLightComponent::send_initial_state() { return this->publish_state_(); } -bool MQTTJSONLightComponent::is_internal() { return this->state_->is_internal(); } void MQTTJSONLightComponent::dump_config() { ESP_LOGCONFIG(TAG, "MQTT Light '%s':", this->state_->get_name().c_str()); LOG_MQTT_COMPONENT(true, true) diff --git a/esphome/components/mqtt/mqtt_light.h b/esphome/components/mqtt/mqtt_light.h index b0f3145900..192cba39b6 100644 --- a/esphome/components/mqtt/mqtt_light.h +++ b/esphome/components/mqtt/mqtt_light.h @@ -25,11 +25,9 @@ class MQTTJSONLightComponent : public mqtt::MQTTComponent { bool send_initial_state() override; - bool is_internal() override; - protected: - std::string friendly_name() const override; std::string component_type() const override; + const EntityBase *get_entity() const override; bool publish_state_(); diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index faa38056a9..674fd77bdf 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -33,13 +33,11 @@ void MQTTNumberComponent::dump_config() { } std::string MQTTNumberComponent::component_type() const { return "number"; } +const EntityBase *MQTTNumberComponent::get_entity() const { return this->number_; } -std::string MQTTNumberComponent::friendly_name() const { return this->number_->get_name(); } void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { 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"] = traits.get_min_value(); root["max"] = traits.get_max_value(); root["step"] = traits.get_step(); @@ -53,7 +51,6 @@ bool MQTTNumberComponent::send_initial_state() { return true; } } -bool MQTTNumberComponent::is_internal() { return this->number_->is_internal(); } bool MQTTNumberComponent::publish_state(float value) { char buffer[64]; snprintf(buffer, sizeof(buffer), "%f", value); diff --git a/esphome/components/mqtt/mqtt_number.h b/esphome/components/mqtt/mqtt_number.h index 46dc221e9e..66622d7c29 100644 --- a/esphome/components/mqtt/mqtt_number.h +++ b/esphome/components/mqtt/mqtt_number.h @@ -28,15 +28,13 @@ class MQTTNumberComponent : public mqtt::MQTTComponent { void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; - bool is_internal() override; bool publish_state(float value); protected: /// Override for MQTTComponent, returns "number". std::string component_type() const override; - - std::string friendly_name() const override; + const EntityBase *get_entity() const override; number::Number *number_; }; diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index 467a0cd84c..b499636006 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -28,13 +28,11 @@ void MQTTSelectComponent::dump_config() { } std::string MQTTSelectComponent::component_type() const { return "select"; } +const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_; } -std::string MQTTSelectComponent::friendly_name() const { return this->select_->get_name(); } void MQTTSelectComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { const auto &traits = select_->traits; // https://www.home-assistant.io/integrations/select.mqtt/ - if (!traits.get_icon().empty()) - root["icon"] = traits.get_icon(); JsonArray &options = root.createNestedArray("options"); for (const auto &option : traits.get_options()) options.add(option); @@ -48,7 +46,6 @@ bool MQTTSelectComponent::send_initial_state() { return true; } } -bool MQTTSelectComponent::is_internal() { return this->select_->is_internal(); } bool MQTTSelectComponent::publish_state(const std::string &value) { return this->publish(this->get_state_topic_(), value); } diff --git a/esphome/components/mqtt/mqtt_select.h b/esphome/components/mqtt/mqtt_select.h index 0115c0a41e..d77d0cf513 100644 --- a/esphome/components/mqtt/mqtt_select.h +++ b/esphome/components/mqtt/mqtt_select.h @@ -28,15 +28,13 @@ class MQTTSelectComponent : public mqtt::MQTTComponent { void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; - bool is_internal() override; bool publish_state(const std::string &value); protected: /// Override for MQTTComponent, returns "select". std::string component_type() const override; - - std::string friendly_name() const override; + const EntityBase *get_entity() const override; select::Select *select_; }; diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 72ec7c54ee..78710ff403 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -30,6 +30,7 @@ void MQTTSensorComponent::dump_config() { } std::string MQTTSensorComponent::component_type() const { return "sensor"; } +const EntityBase *MQTTSensorComponent::get_entity() const { return this->sensor_; } uint32_t MQTTSensorComponent::get_expire_after() const { if (this->expire_after_.has_value()) @@ -38,7 +39,7 @@ uint32_t MQTTSensorComponent::get_expire_after() const { } void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire_after_ = expire_after; } void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } -std::string MQTTSensorComponent::friendly_name() const { return this->sensor_->get_name(); } + void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (!this->sensor_->get_device_class().empty()) root["device_class"] = this->sensor_->get_device_class(); @@ -49,9 +50,6 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo if (this->get_expire_after() > 0) root["expire_after"] = this->get_expire_after() / 1000; - if (!this->sensor_->get_icon().empty()) - root["icon"] = this->sensor_->get_icon(); - if (this->sensor_->get_force_update()) root["force_update"] = true; @@ -67,7 +65,6 @@ bool MQTTSensorComponent::send_initial_state() { return true; } } -bool MQTTSensorComponent::is_internal() { return this->sensor_->is_internal(); } bool MQTTSensorComponent::publish_state(float value) { int8_t accuracy = this->sensor_->get_accuracy_decimals(); return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); diff --git a/esphome/components/mqtt/mqtt_sensor.h b/esphome/components/mqtt/mqtt_sensor.h index 2385529f4f..22609fdfef 100644 --- a/esphome/components/mqtt/mqtt_sensor.h +++ b/esphome/components/mqtt/mqtt_sensor.h @@ -41,14 +41,11 @@ class MQTTSensorComponent : public mqtt::MQTTComponent { bool publish_state(float value); bool send_initial_state() override; - bool is_internal() override; protected: /// Override for MQTTComponent, returns "sensor". std::string component_type() const override; - - std::string friendly_name() const override; - + const EntityBase *get_entity() const override; std::string unique_id() override; sensor::Sensor *sensor_; diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index b13ddd5d9d..16cf102f7e 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -41,15 +41,13 @@ void MQTTSwitchComponent::dump_config() { } std::string MQTTSwitchComponent::component_type() const { return "switch"; } +const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; } void MQTTSwitchComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { - if (!this->switch_->get_icon().empty()) - root["icon"] = this->switch_->get_icon(); if (this->switch_->assumed_state()) root["optimistic"] = true; } bool MQTTSwitchComponent::send_initial_state() { return this->publish_state(this->switch_->state); } -bool MQTTSwitchComponent::is_internal() { return this->switch_->is_internal(); } -std::string MQTTSwitchComponent::friendly_name() const { return this->switch_->get_name(); } + bool MQTTSwitchComponent::publish_state(bool state) { const char *state_s = state ? "ON" : "OFF"; return this->publish(this->get_state_topic_(), state_s); diff --git a/esphome/components/mqtt/mqtt_switch.h b/esphome/components/mqtt/mqtt_switch.h index 9959d21872..a0a7a23220 100644 --- a/esphome/components/mqtt/mqtt_switch.h +++ b/esphome/components/mqtt/mqtt_switch.h @@ -23,15 +23,13 @@ class MQTTSwitchComponent : public mqtt::MQTTComponent { void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; - bool is_internal() override; bool publish_state(bool state); protected: - std::string friendly_name() const override; - /// "switch" component type. std::string component_type() const override; + const EntityBase *get_entity() const override; switch_::Switch *switch_; }; diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index fd96cd0902..7b89915649 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -13,9 +13,6 @@ using namespace esphome::text_sensor; MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : MQTTComponent(), sensor_(sensor) {} void MQTTTextSensor::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { - if (!this->sensor_->get_icon().empty()) - root["icon"] = this->sensor_->get_icon(); - config.command_topic = false; } void MQTTTextSensor::setup() { @@ -35,9 +32,8 @@ bool MQTTTextSensor::send_initial_state() { return true; } } -bool MQTTTextSensor::is_internal() { return this->sensor_->is_internal(); } std::string MQTTTextSensor::component_type() const { return "sensor"; } -std::string MQTTTextSensor::friendly_name() const { return this->sensor_->get_name(); } +const EntityBase *MQTTTextSensor::get_entity() const { return this->sensor_; } std::string MQTTTextSensor::unique_id() { return this->sensor_->unique_id(); } } // namespace mqtt diff --git a/esphome/components/mqtt/mqtt_text_sensor.h b/esphome/components/mqtt/mqtt_text_sensor.h index 1d3f95b894..83743245cc 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.h +++ b/esphome/components/mqtt/mqtt_text_sensor.h @@ -25,13 +25,9 @@ class MQTTTextSensor : public mqtt::MQTTComponent { bool send_initial_state() override; - bool is_internal() override; - protected: std::string component_type() const override; - - std::string friendly_name() const override; - + const EntityBase *get_entity() const override; std::string unique_id() override; text_sensor::TextSensor *sensor_; diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 88153492d3..2856a25ee7 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -6,25 +6,21 @@ from esphome.components import mqtt from esphome.const import ( CONF_ABOVE, CONF_BELOW, - CONF_DISABLED_BY_DEFAULT, - CONF_ICON, CONF_ID, - CONF_INTERNAL, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, CONF_TRIGGER_ID, - CONF_NAME, CONF_MQTT_ID, CONF_VALUE, - ICON_EMPTY, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True number_ns = cg.esphome_ns.namespace("number") -Number = number_ns.class_("Number", cg.Nameable) +Number = number_ns.class_("Number", cg.EntityBase) NumberPtr = Number.operator("ptr") # Triggers @@ -46,11 +42,10 @@ NumberInRangeCondition = number_ns.class_( icon = cv.icon -NUMBER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent), cv.GenerateID(): cv.declare_id(Number), - cv.Optional(CONF_ICON, default=ICON_EMPTY): icon, cv.Optional(CONF_ON_VALUE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(NumberStateTrigger), @@ -71,12 +66,8 @@ NUMBER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( async def setup_number_core_( var, config, *, min_value: float, max_value: float, step: Optional[float] ): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) - 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: diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index 945f174510..ed104fb477 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" namespace esphome { @@ -9,8 +10,8 @@ namespace number { #define LOG_NUMBER(prefix, type, obj) \ if ((obj) != nullptr) { \ 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()); \ + if (!(obj)->get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ } \ } @@ -40,21 +41,18 @@ class NumberTraits { 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 { +class Number : public EntityBase { public: float state; diff --git a/esphome/components/remote_receiver/binary_sensor.py b/esphome/components/remote_receiver/binary_sensor.py index 62c1d9cb27..218b40d6cc 100644 --- a/esphome/components/remote_receiver/binary_sensor.py +++ b/esphome/components/remote_receiver/binary_sensor.py @@ -1,6 +1,4 @@ -import esphome.codegen as cg from esphome.components import binary_sensor, remote_base -from esphome.const import CONF_NAME DEPENDENCIES = ["remote_receiver"] @@ -9,5 +7,4 @@ CONFIG_SCHEMA = remote_base.validate_binary_sensor async def to_code(config): var = await remote_base.build_binary_sensor(config) - cg.add(var.set_name(config[CONF_NAME])) await binary_sensor.register_binary_sensor(var, config) diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index d3ab344926..c156a63a86 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -4,24 +4,20 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import mqtt from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, - CONF_ICON, CONF_ID, - CONF_INTERNAL, CONF_ON_VALUE, CONF_OPTION, CONF_TRIGGER_ID, - CONF_NAME, CONF_MQTT_ID, - ICON_EMPTY, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True select_ns = cg.esphome_ns.namespace("select") -Select = select_ns.class_("Select", cg.Nameable) +Select = select_ns.class_("Select", cg.EntityBase) SelectPtr = Select.operator("ptr") # Triggers @@ -35,11 +31,10 @@ SelectSetAction = select_ns.class_("SelectSetAction", automation.Action) icon = cv.icon -SELECT_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent), cv.GenerateID(): cv.declare_id(Select), - cv.Optional(CONF_ICON, default=ICON_EMPTY): icon, cv.Optional(CONF_ON_VALUE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SelectStateTrigger), @@ -50,12 +45,8 @@ SELECT_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( async def setup_select_core_(var, config, *, options: List[str]): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) - cg.add(var.traits.set_icon(config[CONF_ICON])) cg.add(var.traits.set_options(options)) for conf in config.get(CONF_ON_VALUE, []): diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index 0dec74b627..6113cca1fd 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -3,6 +3,7 @@ #include #include #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" namespace esphome { @@ -11,8 +12,8 @@ namespace select { #define LOG_SELECT(prefix, type, obj) \ if ((obj) != nullptr) { \ 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()); \ + if (!(obj)->get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ } \ } @@ -38,19 +39,16 @@ class SelectTraits { public: void set_options(std::vector options) { this->options_ = std::move(options); } const std::vector get_options() const { return this->options_; } - void set_icon(std::string icon) { icon_ = std::move(icon); } - const std::string &get_icon() const { return icon_; } protected: std::vector options_; - std::string icon_; }; /** Base-class for all selects. * * A select can use publish_state to send out a new value. */ -class Select : public Nameable { +class Select : public EntityBase { public: std::string state; diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index ce4aafb3b0..cb74a41119 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -10,13 +10,11 @@ from esphome.const import ( CONF_ACCURACY_DECIMALS, CONF_ALPHA, CONF_BELOW, - CONF_DISABLED_BY_DEFAULT, CONF_EXPIRE_AFTER, CONF_FILTERS, CONF_FROM, CONF_ICON, CONF_ID, - CONF_INTERNAL, CONF_ON_RAW_VALUE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, @@ -27,7 +25,6 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_UNIT_OF_MEASUREMENT, CONF_WINDOW_SIZE, - CONF_NAME, CONF_MQTT_ID, CONF_FORCE_UPDATE, DEVICE_CLASS_EMPTY, @@ -59,6 +56,7 @@ from esphome.const import ( DEVICE_CLASS_VOLTAGE, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity from esphome.util import Registry CODEOWNERS = ["@esphome/core"] @@ -136,7 +134,7 @@ def validate_datapoint(value): # Base sensor_ns = cg.esphome_ns.namespace("sensor") -Sensor = sensor_ns.class_("Sensor", cg.Nameable) +Sensor = sensor_ns.class_("Sensor", cg.EntityBase) SensorPtr = Sensor.operator("ptr") # Triggers @@ -180,12 +178,11 @@ validate_accuracy_decimals = cv.int_ validate_icon = cv.icon validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") -SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSensorComponent), cv.GenerateID(): cv.declare_id(Sensor), cv.Optional(CONF_UNIT_OF_MEASUREMENT): validate_unit_of_measurement, - cv.Optional(CONF_ICON): validate_icon, cv.Optional(CONF_ACCURACY_DECIMALS): validate_accuracy_decimals, cv.Optional(CONF_DEVICE_CLASS): validate_device_class, cv.Optional(CONF_STATE_CLASS): validate_state_class, @@ -495,18 +492,14 @@ async def build_filters(config): async def setup_sensor_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) + if CONF_DEVICE_CLASS in config: cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) if CONF_STATE_CLASS in config: cg.add(var.set_state_class(config[CONF_STATE_CLASS])) if CONF_UNIT_OF_MEASUREMENT in config: cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT])) - if CONF_ICON in 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])) cg.add(var.set_force_update(config[CONF_FORCE_UPDATE])) diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 0dc0275715..793ae170c3 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -18,7 +18,7 @@ std::string state_class_to_string(StateClass state_class) { } } -Sensor::Sensor(const std::string &name) : Nameable(name), state(NAN), raw_state(NAN) {} +Sensor::Sensor(const std::string &name) : EntityBase(name), state(NAN), raw_state(NAN) {} Sensor::Sensor() : Sensor("") {} std::string Sensor::get_unit_of_measurement() { @@ -31,14 +31,6 @@ void Sensor::set_unit_of_measurement(const std::string &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_; diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index d284f931b1..6cab46f7f9 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -2,6 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/components/sensor/filter.h" @@ -43,7 +44,7 @@ std::string state_class_to_string(StateClass state_class); * * A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy. */ -class Sensor : public Nameable { +class Sensor : public EntityBase { public: explicit Sensor(); explicit Sensor(const std::string &name); @@ -53,11 +54,6 @@ class Sensor : public Nameable { /// Manually set the unit of measurement. void set_unit_of_measurement(const std::string &unit_of_measurement); - /// 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); - /// Get the accuracy in decimals, using the manual override if set. int8_t get_accuracy_decimals(); /// Manually set the accuracy in decimals. @@ -157,9 +153,6 @@ class Sensor : public Nameable { /// Override this to set the default unit of measurement. virtual std::string unit_of_measurement(); // NOLINT - /// Override this to set the default icon. - virtual std::string icon(); // NOLINT - /// Override this to set the default accuracy in decimals. virtual int8_t accuracy_decimals(); // NOLINT @@ -178,7 +171,6 @@ class Sensor : public Nameable { Filter *filter_list_{nullptr}; ///< Store all active filters. 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 diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index 8aa213a9f6..88341e0add 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -4,24 +4,21 @@ from esphome import automation from esphome.automation import Condition, maybe_simple_id from esphome.components import mqtt from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, - CONF_ICON, CONF_ID, - CONF_INTERNAL, CONF_INVERTED, CONF_ON_TURN_OFF, CONF_ON_TURN_ON, CONF_TRIGGER_ID, CONF_MQTT_ID, - CONF_NAME, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True switch_ns = cg.esphome_ns.namespace("switch_") -Switch = switch_ns.class_("Switch", cg.Nameable) +Switch = switch_ns.class_("Switch", cg.EntityBase) SwitchPtr = Switch.operator("ptr") ToggleAction = switch_ns.class_("ToggleAction", automation.Action) @@ -39,10 +36,9 @@ SwitchTurnOffTrigger = switch_ns.class_( icon = cv.icon -SWITCH_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( +SWITCH_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSwitchComponent), - cv.Optional(CONF_ICON): icon, cv.Optional(CONF_INVERTED): cv.boolean, cv.Optional(CONF_ON_TURN_ON): automation.validate_automation( { @@ -59,12 +55,8 @@ SWITCH_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).exte async def setup_switch_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) - if CONF_ICON in config: - cg.add(var.set_icon(config[CONF_ICON])) + await setup_entity(var, config) + if CONF_INVERTED in config: cg.add(var.set_inverted(config[CONF_INVERTED])) for conf in config.get(CONF_ON_TURN_ON, []): diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index 0e12f5af0f..e4d20719e1 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -6,17 +6,9 @@ namespace switch_ { static const char *const TAG = "switch"; -std::string Switch::icon() { return ""; } -Switch::Switch(const std::string &name) : Nameable(name), state(false) {} +Switch::Switch(const std::string &name) : EntityBase(name), state(false) {} Switch::Switch() : Switch("") {} -std::string Switch::get_icon() { - if (this->icon_.has_value()) - return *this->icon_; - return this->icon(); -} - -void Switch::set_icon(const std::string &icon) { this->icon_ = icon; } void Switch::turn_on() { ESP_LOGD(TAG, "'%s' Turning ON.", this->get_name().c_str()); this->write_state(!this->inverted_); diff --git a/esphome/components/switch/switch.h b/esphome/components/switch/switch.h index 8cfae3b6f8..071393003a 100644 --- a/esphome/components/switch/switch.h +++ b/esphome/components/switch/switch.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/preferences.h" #include "esphome/core/helpers.h" @@ -26,7 +27,7 @@ namespace switch_ { * A switch is basically just a combination of a binary sensor (for reporting switch values) * and a write_state method that writes a state to the hardware. */ -class Switch : public Nameable { +class Switch : public EntityBase { public: explicit Switch(); explicit Switch(const std::string &name); @@ -70,12 +71,6 @@ class Switch : public Nameable { */ void set_inverted(bool inverted); - /// Set the icon for this switch. "" for no icon. - void set_icon(const std::string &icon); - - /// Get the icon for this switch. Using icon() if not manually set - std::string get_icon(); - /** Set callback for state changes. * * @param callback The void(bool) callback. @@ -104,18 +99,8 @@ class Switch : public Nameable { */ virtual void write_state(bool state) = 0; - /** Override this to set the Home Assistant icon for this switch. - * - * Return "" to disable this feature. - * - * @return The icon of this switch, for example "mdi:fan". - */ - virtual std::string icon(); // NOLINT - uint32_t hash_base() override; - optional icon_{}; ///< The icon shown here. Not set means use default from switch. Empty means no icon. - CallbackManager state_callback_{}; bool inverted_{false}; Deduplicator publish_dedup_; diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index cdb4b85e9a..5c739e1d0a 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -3,21 +3,18 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import mqtt from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, CONF_FILTERS, - CONF_ICON, CONF_ID, - CONF_INTERNAL, CONF_ON_VALUE, CONF_ON_RAW_VALUE, CONF_TRIGGER_ID, CONF_MQTT_ID, - CONF_NAME, CONF_STATE, CONF_FROM, CONF_TO, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity from esphome.util import Registry @@ -25,7 +22,7 @@ IS_PLATFORM_COMPONENT = True # pylint: disable=invalid-name text_sensor_ns = cg.esphome_ns.namespace("text_sensor") -TextSensor = text_sensor_ns.class_("TextSensor", cg.Nameable) +TextSensor = text_sensor_ns.class_("TextSensor", cg.EntityBase) TextSensorPtr = TextSensor.operator("ptr") TextSensorStateTrigger = text_sensor_ns.class_( @@ -111,10 +108,9 @@ async def substitute_filter_to_code(config, filter_id): icon = cv.icon -TEXT_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor), - cv.Optional(CONF_ICON): icon, cv.Optional(CONF_FILTERS): validate_filters, cv.Optional(CONF_ON_VALUE): automation.validate_automation( { @@ -137,12 +133,7 @@ async def build_filters(config): async def setup_text_sensor_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) - if CONF_ICON in config: - cg.add(var.set_icon(config[CONF_ICON])) + await setup_entity(var, config) if config.get(CONF_FILTERS): # must exist and not be empty filters = await build_filters(config[CONF_FILTERS]) diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 774f3a8cb6..0bcab90843 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -7,7 +7,7 @@ namespace text_sensor { static const char *const TAG = "text_sensor"; TextSensor::TextSensor() : TextSensor("") {} -TextSensor::TextSensor(const std::string &name) : Nameable(name) {} +TextSensor::TextSensor(const std::string &name) : EntityBase(name) {} void TextSensor::publish_state(const std::string &state) { this->raw_state = state; @@ -68,14 +68,6 @@ void TextSensor::internal_send_state_to_frontend(const std::string &state) { this->callback_.call(state); } -void TextSensor::set_icon(const std::string &icon) { this->icon_ = icon; } -std::string TextSensor::get_icon() { - if (this->icon_.has_value()) - return *this->icon_; - return this->icon(); -} -std::string TextSensor::icon() { return ""; } - std::string TextSensor::unique_id() { return ""; } bool TextSensor::has_state() { return this->has_state_; } uint32_t TextSensor::hash_base() { return 334300109UL; } diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index 7804deedb6..4bd77131d7 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/components/text_sensor/filter.h" @@ -18,7 +19,7 @@ namespace text_sensor { } \ } -class TextSensor : public Nameable { +class TextSensor : public EntityBase { public: explicit TextSensor(); explicit TextSensor(const std::string &name); @@ -30,8 +31,6 @@ class TextSensor : public Nameable { void publish_state(const std::string &state); - void set_icon(const std::string &icon); - /// Add a filter to the filter chain. Will be appended to the back. void add_filter(Filter *filter); @@ -53,10 +52,6 @@ class TextSensor : public Nameable { // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) - std::string get_icon(); - - virtual std::string icon(); - virtual std::string unique_id(); bool has_state(); @@ -71,7 +66,6 @@ class TextSensor : public Nameable { Filter *filter_list_{nullptr}; ///< Store all active filters. - optional icon_; bool has_state_{false}; }; diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index 46eaac98eb..6a8a416b81 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -2,12 +2,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, time from esphome.const import ( + CONF_ICON, CONF_ID, CONF_TIME_ID, DEVICE_CLASS_ENERGY, CONF_METHOD, STATE_CLASS_TOTAL_INCREASING, ) +from esphome.core.entity_helpers import inherit_property_from DEPENDENCIES = ["time"] @@ -45,6 +47,18 @@ CONFIG_SCHEMA = ( .extend(cv.COMPONENT_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(TotalDailyEnergy), + cv.Optional(CONF_ICON): cv.icon, + cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), + }, + extra=cv.ALLOW_EXTRA, + ), + inherit_property_from(CONF_ICON, CONF_POWER_ID), +) + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) diff --git a/esphome/components/total_daily_energy/total_daily_energy.h b/esphome/components/total_daily_energy/total_daily_energy.h index 9d2396d6e3..fedceafbd3 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.h +++ b/esphome/components/total_daily_energy/total_daily_energy.h @@ -25,7 +25,6 @@ class TotalDailyEnergy : public sensor::Sensor, public Component { void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } std::string unit_of_measurement() override { return this->parent_->get_unit_of_measurement() + "h"; } - std::string icon() override { return this->parent_->get_icon(); } int8_t accuracy_decimals() override { return this->parent_->get_accuracy_decimals() + 2; } void loop() override; diff --git a/esphome/components/tsl2591/tsl2591.h b/esphome/components/tsl2591/tsl2591.h index d377d082a8..19352a15c5 100644 --- a/esphome/components/tsl2591/tsl2591.h +++ b/esphome/components/tsl2591/tsl2591.h @@ -224,7 +224,7 @@ class TSL2591Component : public PollingComponent, public i2c::I2CDevice { float get_setup_priority() const override; protected: - const char *name_; // TODO: extend esphome::Nameable + const char *name_; sensor::Sensor *full_spectrum_sensor_; sensor::Sensor *infrared_sensor_; sensor::Sensor *visible_sensor_; diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index d72262b4d3..e99431be36 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -3,6 +3,7 @@ #include "web_server.h" #include "esphome/core/log.h" #include "esphome/core/application.h" +#include "esphome/core/entity_base.h" #include "esphome/core/util.h" #include "esphome/components/json/json_util.h" #include "esphome/components/network/util.h" @@ -28,8 +29,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, - const std::function &action_func = nullptr) { +void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action, + const std::function &action_func = nullptr) { if (obj->is_internal()) return; stream->print(""); stream.print(""); diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 0d8f4f3b64..fcec74b245 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -18,6 +18,7 @@ from esphome.const import ( CONF_COMMAND_TOPIC, CONF_DISABLED_BY_DEFAULT, CONF_DISCOVERY, + CONF_ICON, CONF_ID, CONF_INTERNAL, CONF_NAME, @@ -1476,7 +1477,7 @@ class OnlyWith(Optional): pass -def _nameable_validator(config): +def _entity_base_validator(config): if CONF_NAME not in config and CONF_ID not in config: raise Invalid("At least one of 'id:' or 'name:' is required!") if CONF_NAME not in config: @@ -1587,15 +1588,16 @@ MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend( } ) -NAMEABLE_SCHEMA = Schema( +ENTITY_BASE_SCHEMA = Schema( { Optional(CONF_NAME): string, Optional(CONF_INTERNAL): boolean, Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean, + Optional(CONF_ICON): icon, } ) -NAMEABLE_SCHEMA.add_extra(_nameable_validator) +ENTITY_BASE_SCHEMA.add_extra(_entity_base_validator) COMPONENT_SCHEMA = Schema({Optional(CONF_SETUP_PRIORITY): float_}) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index c85b445b08..5692194a91 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -177,26 +177,6 @@ void PollingComponent::call_setup() { uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; } void PollingComponent::set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } -const std::string &Nameable::get_name() const { return this->name_; } -void Nameable::set_name(const std::string &name) { - this->name_ = name; - this->calc_object_id_(); -} -Nameable::Nameable(std::string name) : name_(std::move(name)) { this->calc_object_id_(); } - -const std::string &Nameable::get_object_id() { return this->object_id_; } -bool Nameable::is_internal() const { return this->internal_; } -void Nameable::set_internal(bool internal) { this->internal_ = internal; } -void Nameable::calc_object_id_() { - this->object_id_ = sanitize_string_allowlist(to_lowercase_underscore(this->name_), HOSTNAME_CHARACTER_ALLOWLIST); - // FNV-1 hash - this->object_id_hash_ = fnv1_hash(this->object_id_); -} -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) : started_(millis()), component_(component) {} WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() { diff --git a/esphome/core/component.h b/esphome/core/component.h index 85256c0f0f..a1afc17c2c 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -264,40 +264,6 @@ class PollingComponent : public Component { uint32_t update_interval_; }; -/// Helper class that enables naming of objects so that it doesn't have to be re-implement every time. -class Nameable { - public: - Nameable() : Nameable("") {} - explicit Nameable(std::string name); - const std::string &get_name() const; - void set_name(const std::string &name); - /// Get the sanitized name of this nameable as an ID. Caching it internally. - const std::string &get_object_id(); - uint32_t get_object_id_hash(); - - bool is_internal() const; - void set_internal(bool internal); - - /** Check if this object is declared to be disabled by default. - * - * That means that when the device gets added to Home Assistant (or other clients) it should - * not be added to the default view by default, and a user action is necessary to manually add it. - */ - bool is_disabled_by_default() const; - void set_disabled_by_default(bool disabled_by_default); - - protected: - virtual uint32_t hash_base() = 0; - - void calc_object_id_(); - - std::string name_; - std::string object_id_; - uint32_t object_id_hash_; - bool internal_{false}; - bool disabled_by_default_{false}; -}; - class WarnIfComponentBlockingGuard { public: WarnIfComponentBlockingGuard(Component *component); diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp new file mode 100644 index 0000000000..bc94da85fe --- /dev/null +++ b/esphome/core/entity_base.cpp @@ -0,0 +1,40 @@ +#include "esphome/core/entity_base.h" +#include "esphome/core/helpers.h" + +namespace esphome { + +static const char *const TAG = "entity_base"; + +EntityBase::EntityBase(std::string name) : name_(std::move(name)) { this->calc_object_id_(); } + +// Entity Name +const std::string &EntityBase::get_name() const { return this->name_; } +void EntityBase::set_name(const std::string &name) { + this->name_ = name; + this->calc_object_id_(); +} + +// Entity Internal +bool EntityBase::is_internal() const { return this->internal_; } +void EntityBase::set_internal(bool internal) { this->internal_ = internal; } + +// Entity Disabled by Default +bool EntityBase::is_disabled_by_default() const { return this->disabled_by_default_; } +void EntityBase::set_disabled_by_default(bool disabled_by_default) { this->disabled_by_default_ = disabled_by_default; } + +// Entity Icon +const std::string &EntityBase::get_icon() const { return this->icon_; } +void EntityBase::set_icon(const std::string &name) { this->icon_ = name; } + +// Entity Object ID +const std::string &EntityBase::get_object_id() { return this->object_id_; } + +// Calculate Object ID Hash from Entity Name +void EntityBase::calc_object_id_() { + this->object_id_ = sanitize_string_allowlist(to_lowercase_underscore(this->name_), HOSTNAME_CHARACTER_ALLOWLIST); + // FNV-1 hash + this->object_id_hash_ = fnv1_hash(this->object_id_); +} +uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; } + +} // namespace esphome diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h new file mode 100644 index 0000000000..263747b721 --- /dev/null +++ b/esphome/core/entity_base.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +namespace esphome { + +// The generic Entity base class that provides an interface common to all Entities. +class EntityBase { + public: + EntityBase() : EntityBase("") {} + explicit EntityBase(std::string name); + + // Get/set the name of this Entity + const std::string &get_name() const; + void set_name(const std::string &name); + + // Get the sanitized name of this Entity as an ID. Caching it internally. + const std::string &get_object_id(); + + // Get the unique Object ID of this Entity + uint32_t get_object_id_hash(); + + // Get/set whether this Entity should be hidden from outside of ESPHome + bool is_internal() const; + void set_internal(bool internal); + + // Check if this object is declared to be disabled by default. + // That means that when the device gets added to Home Assistant (or other clients) it should + // not be added to the default view by default, and a user action is necessary to manually add it. + bool is_disabled_by_default() const; + void set_disabled_by_default(bool disabled_by_default); + + // Get/set this entity's icon + const std::string &get_icon() const; + void set_icon(const std::string &name); + + protected: + virtual uint32_t hash_base() = 0; + void calc_object_id_(); + + std::string name_; + std::string object_id_; + std::string icon_; + uint32_t object_id_hash_; + bool internal_{false}; + bool disabled_by_default_{false}; +}; + +} // namespace esphome diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py new file mode 100644 index 0000000000..b2dbe2116e --- /dev/null +++ b/esphome/core/entity_helpers.py @@ -0,0 +1,32 @@ +import esphome.final_validate as fv + +from esphome.const import CONF_ID + + +def inherit_property_from(property_to_inherit, parent_id_property): + """Validator that inherits a configuration property from another entity, for use with FINAL_VALIDATE_SCHEMA. + + If a property is already set, it will not be inherited. + + Keyword arguments: + property_to_inherit -- the name of the property to inherit, e.g. CONF_ICON + parent_id_property -- the name of the property that holds the ID of the parent, e.g. CONF_POWER_ID + """ + + def inherit_property(config): + if property_to_inherit not in config: + fconf = fv.full_config.get() + + # Get config for the parent entity + path = fconf.get_path_for_id(config[parent_id_property])[:-1] + parent_config = fconf.get_config_for_path(path) + + # If parent sensor has the property set, inherit it + if property_to_inherit in parent_config: + path = fconf.get_path_for_id(config[CONF_ID])[:-1] + this_config = fconf.get_config_for_path(path) + this_config[property_to_inherit] = parent_config[property_to_inherit] + + return config + + return inherit_property diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index a2eafaa0e8..5b081698ad 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -1,6 +1,10 @@ import logging from esphome.const import ( + CONF_DISABLED_BY_DEFAULT, + CONF_ICON, + CONF_INTERNAL, + CONF_NAME, CONF_SETUP_PRIORITY, CONF_UPDATE_INTERVAL, CONF_TYPE_ID, @@ -90,6 +94,16 @@ async def register_parented(var, value): add(var.set_parent(paren)) +async def setup_entity(var, config): + """Set up generic properties of an Entity""" + add(var.set_name(config[CONF_NAME])) + add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) + if CONF_INTERNAL in config: + add(var.set_internal(config[CONF_INTERNAL])) + if CONF_ICON in config: + add(var.set_icon(config[CONF_ICON])) + + def extract_registry_entry_config(registry, full_config): # type: (Registry, ConfigType) -> RegistryEntry key, config = next((k, v) for k, v in full_config.items() if k in registry) diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 7a8eb8e04c..888c319024 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -19,7 +19,7 @@ const_char_ptr = global_ns.namespace("const char *") NAN = global_ns.namespace("NAN") esphome_ns = global_ns # using namespace esphome; App = esphome_ns.App -Nameable = esphome_ns.class_("Nameable") +EntityBase = esphome_ns.class_("EntityBase") Component = esphome_ns.class_("Component") ComponentPtr = Component.operator("ptr") PollingComponent = esphome_ns.class_("PollingComponent", Component) diff --git a/tests/test1.yaml b/tests/test1.yaml index 400cdb3b6b..c0bfbb8f0c 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1708,6 +1708,7 @@ climate: name: Anova cooker ble_client_id: ble_blah unit_of_measurement: c + icon: mdi:stove script: - id: climate_custom @@ -1986,6 +1987,7 @@ fan: direction_output: gpio_26 - platform: speed id: fan_speed + icon: mdi:weather-windy output: pca_6 speed_count: 10 name: 'Living Room Fan 2' @@ -2287,6 +2289,7 @@ cover: name: 'Test AM43' id: am43_test ble_client_id: ble_foo + icon: mdi:blinds debug: diff --git a/tests/test5.yaml b/tests/test5.yaml index aca8434fbf..72df3ed212 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -51,6 +51,7 @@ binary_sensor: - platform: gpio pin: GPIO0 id: io0_button + icon: mdi:gesture-tap-button tlc5947: data_pin: GPIO12 diff --git a/tests/unit_tests/test_codegen.py b/tests/unit_tests/test_codegen.py index 9f402465fa..32d82b3062 100644 --- a/tests/unit_tests/test_codegen.py +++ b/tests/unit_tests/test_codegen.py @@ -59,7 +59,7 @@ from esphome import codegen as cg "NAN", "esphome_ns", "App", - "Nameable", + "EntityBase", "Component", "ComponentPtr", # from cpp_types From c3b8c84131bc4ae4d62bc44f102abb0e3e6caccc Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Sun, 10 Oct 2021 03:53:58 -0500 Subject: [PATCH 150/549] Fix below freezing temperature for Inkbird sensors (#2466) --- esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp index c01fc274f4..76013e28ff 100644 --- a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp @@ -72,7 +72,7 @@ bool InkbirdIbstH1Mini::parse_device(const esp32_ble_tracker::ESPBTDevice &devic auto external_temperature = NAN; // Read bluetooth data into variable - auto measured_temperature = mnf_data.uuid.get_uuid().uuid.uuid16 / 100.0f; + auto measured_temperature = ((int16_t) mnf_data.uuid.get_uuid().uuid.uuid16) / 100.0f; // Set temperature or external_temperature based on which sensor is in use if (mnf_data.data[2] == 0) { From a1f9b0d7f2f746633e51881ba694522ab00bd568 Mon Sep 17 00:00:00 2001 From: definitio <37266727+definitio@users.noreply.github.com> Date: Sun, 10 Oct 2021 18:54:07 +0300 Subject: [PATCH 151/549] Add configuration for cover topics (#2472) --- esphome/components/cover/__init__.py | 32 +++++++++++++++++++++++++++- esphome/const.py | 4 ++++ tests/test1.yaml | 6 ++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index eb57637283..137aaac872 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -8,7 +8,11 @@ from esphome.const import ( CONF_DEVICE_CLASS, CONF_STATE, CONF_POSITION, + CONF_POSITION_COMMAND_TOPIC, + CONF_POSITION_STATE_TOPIC, CONF_TILT, + CONF_TILT_COMMAND_TOPIC, + CONF_TILT_STATE_TOPIC, CONF_STOP, CONF_MQTT_ID, ) @@ -68,7 +72,18 @@ COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ex cv.GenerateID(): cv.declare_id(Cover), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent), cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True), - # TODO: MQTT topic options + cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_TILT_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_TILT_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), } ) @@ -83,6 +98,21 @@ async def setup_cover_core_(var, config): mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) + if CONF_POSITION_STATE_TOPIC in config: + cg.add( + mqtt_.set_custom_position_state_topic(config[CONF_POSITION_STATE_TOPIC]) + ) + if CONF_POSITION_COMMAND_TOPIC in config: + cg.add( + mqtt_.set_custom_position_command_topic( + config[CONF_POSITION_COMMAND_TOPIC] + ) + ) + if CONF_TILT_STATE_TOPIC in config: + cg.add(mqtt_.set_custom_tilt_state_topic(config[CONF_TILT_STATE_TOPIC])) + if CONF_TILT_COMMAND_TOPIC in config: + cg.add(mqtt_.set_custom_tilt_command_topic(config[CONF_TILT_COMMAND_TOPIC])) + async def register_cover(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/const.py b/esphome/const.py index a65285a4b5..ac9e759ffd 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -497,6 +497,8 @@ CONF_PMC_4_0 = "pmc_4_0" CONF_PORT = "port" CONF_POSITION = "position" CONF_POSITION_ACTION = "position_action" +CONF_POSITION_COMMAND_TOPIC = "position_command_topic" +CONF_POSITION_STATE_TOPIC = "position_state_topic" CONF_POWER = "power" CONF_POWER_FACTOR = "power_factor" CONF_POWER_ON_VALUE = "power_on_value" @@ -654,7 +656,9 @@ CONF_THRESHOLD = "threshold" CONF_THROTTLE = "throttle" CONF_TILT = "tilt" CONF_TILT_ACTION = "tilt_action" +CONF_TILT_COMMAND_TOPIC = "tilt_command_topic" CONF_TILT_LAMBDA = "tilt_lambda" +CONF_TILT_STATE_TOPIC = "tilt_state_topic" CONF_TIME = "time" CONF_TIME_ID = "time_id" CONF_TIMEOUT = "timeout" diff --git a/tests/test1.yaml b/tests/test1.yaml index c0bfbb8f0c..537ef6b1c4 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2285,6 +2285,12 @@ cover: id: template_cover state: CLOSED assumed_state: no + has_position: yes + position_state_topic: position/state/topic + position_command_topic: position/command/topic + tilt_lambda: !lambda 'return 0.5;' + tilt_state_topic: tilt/state/topic + tilt_command_topic: tilt/command/topic - platform: am43 name: 'Test AM43' id: am43_test From 42739f0b22d02b97b6545d77e8731303447457ab Mon Sep 17 00:00:00 2001 From: definitio <37266727+definitio@users.noreply.github.com> Date: Sun, 10 Oct 2021 18:55:22 +0300 Subject: [PATCH 152/549] Add configuration for climate topics (#2473) --- esphome/components/climate/__init__.py | 145 ++++++++++++++++++++++++- esphome/const.py | 16 +++ tests/test1.yaml | 16 +++ 3 files changed, 174 insertions(+), 3 deletions(-) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index ca1ea6a756..7ff769e5cb 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -4,22 +4,38 @@ from esphome.cpp_helpers import setup_entity from esphome import automation from esphome.components import mqtt from esphome.const import ( + CONF_ACTION_STATE_TOPIC, CONF_AWAY, + CONF_AWAY_COMMAND_TOPIC, + CONF_AWAY_STATE_TOPIC, + CONF_CURRENT_TEMPERATURE_STATE_TOPIC, CONF_CUSTOM_FAN_MODE, CONF_CUSTOM_PRESET, + CONF_FAN_MODE, + CONF_FAN_MODE_COMMAND_TOPIC, + CONF_FAN_MODE_STATE_TOPIC, CONF_ID, CONF_MAX_TEMPERATURE, CONF_MIN_TEMPERATURE, CONF_MODE, + CONF_MODE_COMMAND_TOPIC, + CONF_MODE_STATE_TOPIC, CONF_PRESET, + CONF_SWING_MODE, + CONF_SWING_MODE_COMMAND_TOPIC, + CONF_SWING_MODE_STATE_TOPIC, CONF_TARGET_TEMPERATURE, + CONF_TARGET_TEMPERATURE_COMMAND_TOPIC, + CONF_TARGET_TEMPERATURE_STATE_TOPIC, CONF_TARGET_TEMPERATURE_HIGH, + CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC, + CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC, CONF_TARGET_TEMPERATURE_LOW, + CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC, + CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC, CONF_TEMPERATURE_STEP, CONF_VISUAL, CONF_MQTT_ID, - CONF_FAN_MODE, - CONF_SWING_MODE, ) from esphome.core import CORE, coroutine_with_priority @@ -97,7 +113,54 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA). cv.Optional(CONF_TEMPERATURE_STEP): cv.temperature, } ), - # TODO: MQTT topic options + cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_AWAY_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_AWAY_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_CURRENT_TEMPERATURE_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_FAN_MODE_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_FAN_MODE_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_MODE_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_MODE_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_SWING_MODE_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), } ) @@ -117,6 +180,82 @@ async def setup_climate_core_(var, config): mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) + if CONF_ACTION_STATE_TOPIC in config: + cg.add(mqtt_.set_custom_action_state_topic(config[CONF_ACTION_STATE_TOPIC])) + if CONF_AWAY_COMMAND_TOPIC in config: + cg.add(mqtt_.set_custom_away_command_topic(config[CONF_AWAY_COMMAND_TOPIC])) + if CONF_AWAY_STATE_TOPIC in config: + cg.add(mqtt_.set_custom_away_state_topic(config[CONF_AWAY_STATE_TOPIC])) + if CONF_CURRENT_TEMPERATURE_STATE_TOPIC in config: + cg.add( + mqtt_.set_custom_current_temperature_state_topic( + config[CONF_CURRENT_TEMPERATURE_STATE_TOPIC] + ) + ) + if CONF_FAN_MODE_COMMAND_TOPIC in config: + cg.add( + mqtt_.set_custom_fan_mode_command_topic( + config[CONF_FAN_MODE_COMMAND_TOPIC] + ) + ) + if CONF_FAN_MODE_STATE_TOPIC in config: + cg.add( + mqtt_.set_custom_fan_mode_state_topic(config[CONF_FAN_MODE_STATE_TOPIC]) + ) + if CONF_MODE_COMMAND_TOPIC in config: + cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC])) + if CONF_MODE_STATE_TOPIC in config: + cg.add(mqtt_.set_custom_state_topic(config[CONF_MODE_STATE_TOPIC])) + + if CONF_SWING_MODE_COMMAND_TOPIC in config: + cg.add( + mqtt_.set_custom_swing_mode_command_topic( + config[CONF_SWING_MODE_COMMAND_TOPIC] + ) + ) + if CONF_SWING_MODE_STATE_TOPIC in config: + cg.add( + mqtt_.set_custom_swing_mode_state_topic( + config[CONF_SWING_MODE_STATE_TOPIC] + ) + ) + if CONF_TARGET_TEMPERATURE_COMMAND_TOPIC in config: + cg.add( + mqtt_.set_custom_target_temperature_command_topic( + config[CONF_TARGET_TEMPERATURE_COMMAND_TOPIC] + ) + ) + if CONF_TARGET_TEMPERATURE_STATE_TOPIC in config: + cg.add( + mqtt_.set_custom_target_temperature_state_topic( + config[CONF_TARGET_TEMPERATURE_STATE_TOPIC] + ) + ) + if CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC in config: + cg.add( + mqtt_.set_custom_target_temperature_high_command_topic( + config[CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC] + ) + ) + if CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC in config: + cg.add( + mqtt_.set_custom_target_temperature_high_state_topic( + config[CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC] + ) + ) + if CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC in config: + cg.add( + mqtt_.set_custom_target_temperature_low_command_topic( + config[CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC] + ) + ) + if CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC in config: + cg.add( + mqtt_.set_custom_target_temperature_state_topic( + config[CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC] + ) + ) + async def register_climate(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/const.py b/esphome/const.py index ac9e759ffd..780f4f6e22 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -39,6 +39,7 @@ CONF_ACCELERATION_Z = "acceleration_z" CONF_ACCURACY = "accuracy" CONF_ACCURACY_DECIMALS = "accuracy_decimals" CONF_ACTION_ID = "action_id" +CONF_ACTION_STATE_TOPIC = "action_state_topic" CONF_ACTIVE_POWER = "active_power" CONF_ADDRESS = "address" CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id" @@ -60,7 +61,9 @@ CONF_AUTOCONF = "autoconf" CONF_AUTOMATION_ID = "automation_id" CONF_AVAILABILITY = "availability" CONF_AWAY = "away" +CONF_AWAY_COMMAND_TOPIC = "away_command_topic" CONF_AWAY_CONFIG = "away_config" +CONF_AWAY_STATE_TOPIC = "away_state_topic" CONF_BACKLIGHT_PIN = "backlight_pin" CONF_BASELINE = "baseline" CONF_BATTERY_LEVEL = "battery_level" @@ -141,6 +144,7 @@ CONF_CSS_URL = "css_url" CONF_CURRENT = "current" CONF_CURRENT_OPERATION = "current_operation" CONF_CURRENT_RESISTOR = "current_resistor" +CONF_CURRENT_TEMPERATURE_STATE_TOPIC = "current_temperature_state_topic" CONF_CUSTOM_FAN_MODE = "custom_fan_mode" CONF_CUSTOM_FAN_MODES = "custom_fan_modes" CONF_CUSTOM_PRESET = "custom_preset" @@ -209,6 +213,7 @@ CONF_FALLING_EDGE = "falling_edge" CONF_FAMILY = "family" CONF_FAN_MODE = "fan_mode" CONF_FAN_MODE_AUTO_ACTION = "fan_mode_auto_action" +CONF_FAN_MODE_COMMAND_TOPIC = "fan_mode_command_topic" CONF_FAN_MODE_DIFFUSE_ACTION = "fan_mode_diffuse_action" CONF_FAN_MODE_FOCUS_ACTION = "fan_mode_focus_action" CONF_FAN_MODE_HIGH_ACTION = "fan_mode_high_action" @@ -217,6 +222,7 @@ CONF_FAN_MODE_MEDIUM_ACTION = "fan_mode_medium_action" CONF_FAN_MODE_MIDDLE_ACTION = "fan_mode_middle_action" CONF_FAN_MODE_OFF_ACTION = "fan_mode_off_action" CONF_FAN_MODE_ON_ACTION = "fan_mode_on_action" +CONF_FAN_MODE_STATE_TOPIC = "fan_mode_state_topic" CONF_FAN_ONLY_ACTION = "fan_only_action" CONF_FAN_ONLY_ACTION_USES_FAN_MODE_TIMER = "fan_only_action_uses_fan_mode_timer" CONF_FAN_ONLY_COOLING = "fan_only_cooling" @@ -378,6 +384,8 @@ CONF_MINUTE = "minute" CONF_MINUTES = "minutes" CONF_MISO_PIN = "miso_pin" CONF_MODE = "mode" +CONF_MODE_COMMAND_TOPIC = "mode_command_topic" +CONF_MODE_STATE_TOPIC = "mode_state_topic" CONF_MODEL = "model" CONF_MOISTURE = "moisture" CONF_MONTHS = "months" @@ -636,6 +644,8 @@ CONF_SUPPORTS_HEAT = "supports_heat" CONF_SWING_BOTH_ACTION = "swing_both_action" CONF_SWING_HORIZONTAL_ACTION = "swing_horizontal_action" CONF_SWING_MODE = "swing_mode" +CONF_SWING_MODE_COMMAND_TOPIC = "swing_mode_command_topic" +CONF_SWING_MODE_STATE_TOPIC = "swing_mode_state_topic" CONF_SWING_OFF_ACTION = "swing_off_action" CONF_SWING_VERTICAL_ACTION = "swing_vertical_action" CONF_SWITCH_DATAPOINT = "switch_datapoint" @@ -646,8 +656,14 @@ CONF_TAG = "tag" CONF_TARGET = "target" CONF_TARGET_TEMPERATURE = "target_temperature" CONF_TARGET_TEMPERATURE_CHANGE_ACTION = "target_temperature_change_action" +CONF_TARGET_TEMPERATURE_COMMAND_TOPIC = "target_temperature_command_topic" CONF_TARGET_TEMPERATURE_HIGH = "target_temperature_high" +CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC = "target_temperature_high_command_topic" +CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC = "target_temperature_high_state_topic" CONF_TARGET_TEMPERATURE_LOW = "target_temperature_low" +CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC = "target_temperature_low_command_topic" +CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC = "target_temperature_low_state_topic" +CONF_TARGET_TEMPERATURE_STATE_TOPIC = "target_temperature_state_topic" CONF_TEMPERATURE = "temperature" CONF_TEMPERATURE_STEP = "temperature_step" CONF_TEXT_SENSORS = "text_sensors" diff --git a/tests/test1.yaml b/tests/test1.yaml index 537ef6b1c4..058da35d2c 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1642,6 +1642,22 @@ climate: sensor: ${sensorname}_sensor - platform: tcl112 name: TCL112 Climate + action_state_topic: action/state/topic + away_command_topic: away/command/topic + away_state_topic: away/state/topic + current_temperature_state_topic: current/temperature/state/topic + fan_mode_command_topic: fan_mode/mode/command/topic + fan_mode_state_topic: fan_mode/mode/state/topic + mode_command_topic: mode/command/topic + mode_state_topic: mode/state/topic + swing_mode_command_topic: swing_mode/command/topic + swing_mode_state_topic: swing_mode/state/topic + target_temperature_command_topic: target/temperature/command/topic + target_temperature_high_command_topic: target/temperature/high/command/topic + target_temperature_high_state_topic: target/temperature/high/state/topic + target_temperature_low_command_topic: target/temperature/low/command/topic + target_temperature_low_state_topic: target/temperature/low/state/topic + target_temperature_state_topic: target/temperature/state/topic - platform: coolix name: Coolix Climate With Sensor supports_heat: True From 2c517e3e8ccf727f9e4ae948dcec38bd5c1dc46f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 11 Oct 2021 10:38:45 +1300 Subject: [PATCH 153/549] Use arduino btStart for arduino framework (#2457) --- esphome/components/esp32_ble/ble.cpp | 7 +++++++ .../components/esp32_ble_beacon/esp32_ble_beacon.cpp | 11 +++++++++++ .../esp32_ble_tracker/esp32_ble_tracker.cpp | 7 +++++++ 3 files changed, 25 insertions(+) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 143be06e3b..ecd591d169 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -56,6 +56,12 @@ bool ESP32BLE::ble_setup_() { return false; } +#ifdef USE_ARDUINO + if (!btStart()) { + ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); + return false; + } +#else 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) { @@ -80,6 +86,7 @@ bool ESP32BLE::ble_setup_() { return false; } } +#endif esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp index 96afadd19a..f6bab8e6df 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp @@ -12,6 +12,10 @@ #include #include "esphome/core/hal.h" +#ifdef USE_ARDUINO +#include +#endif + namespace esphome { namespace esp32_ble_beacon { @@ -70,6 +74,12 @@ void ESP32BLEBeacon::ble_setup() { return; } +#ifdef USE_ARDUINO + if (!btStart()) { + ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); + return; + } +#else 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) { @@ -94,6 +104,7 @@ void ESP32BLEBeacon::ble_setup() { return; } } +#endif esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 9e987a994a..95176bb179 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -132,6 +132,12 @@ bool ESP32BLETracker::ble_setup() { return false; } +#ifdef USE_ARDUINO + if (!btStart()) { + ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); + return false; + } +#else 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) { @@ -156,6 +162,7 @@ bool ESP32BLETracker::ble_setup() { return false; } } +#endif esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); From 11d286675510ed01430cd75938dc8c6d34bf4dc1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 16:45:27 +0200 Subject: [PATCH 154/549] Bump click from 8.0.1 to 8.0.3 (#2481) 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 880faeddbe..3912f099df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.0 esptool==3.1 -click==8.0.1 +click==8.0.3 esphome-dashboard==20211006.0 aioesphomeapi==9.1.5 From 3cb4b4ca039914c53b8b2c8f26c0a17b5eeb2a1c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 16:52:42 +0200 Subject: [PATCH 155/549] Bump flake8 from 3.9.2 to 4.0.1 (#2483) 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 85456643bc..8ebcf24d4d 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,5 +1,5 @@ pylint==2.11.1 -flake8==3.9.2 +flake8==4.0.1 black==21.9b0 pexpect==4.8.0 pre-commit From 55e9560e7400f43015de29cbd74015e5c2619111 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 16:58:51 +0200 Subject: [PATCH 156/549] Bump platformio from 5.2.0 to 5.2.1 (#2482) 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 3912f099df..4e32753ba3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ tornado==6.1 tzlocal==3.0 # from time tzdata>=2021.1 # from time pyserial==3.5 -platformio==5.2.0 +platformio==5.2.1 esptool==3.1 click==8.0.3 esphome-dashboard==20211006.0 From ea56a39e11aaaef20a2bdf1e48a8931330651f26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 17:21:04 +0200 Subject: [PATCH 157/549] Bump esphome-dashboard from 20211006.0 to 20211011.1 (#2484) 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 4e32753ba3..23a00d3755 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 esptool==3.1 click==8.0.3 -esphome-dashboard==20211006.0 +esphome-dashboard==20211011.1 aioesphomeapi==9.1.5 # esp-idf requires this, but doesn't bundle it by default From 039fbc677de4d4d7625413f22b06cdc01ffb270a Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Mon, 11 Oct 2021 22:44:05 +0100 Subject: [PATCH 158/549] Replace deprecated COLOR_BLACK constant (#2487) --- esphome/components/ili9341/ili9341_display.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/ili9341/ili9341_display.cpp b/esphome/components/ili9341/ili9341_display.cpp index 88f8bac272..ab5586fa28 100644 --- a/esphome/components/ili9341/ili9341_display.cpp +++ b/esphome/components/ili9341/ili9341_display.cpp @@ -142,7 +142,7 @@ void ILI9341Display::fill(Color color) { } void ILI9341Display::fill_internal_(Color color) { - if (color.raw_32 == COLOR_BLACK.raw_32) { + if (color.raw_32 == Color::BLACK.raw_32) { memset(transfer_buffer_, 0, sizeof(transfer_buffer_)); } else { uint8_t *dst = transfer_buffer_; From 85461a752acb7075ee9a4dc973bb31473f95ad7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Mon, 11 Oct 2021 23:56:35 +0200 Subject: [PATCH 159/549] Fix color temperature persistence on CWWW lights (#2486) --- esphome/components/light/light_call.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index dc1e7d39fb..9858590850 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -532,7 +532,8 @@ LightCall &LightCall::set_white_if_supported(float white) { return *this; } LightCall &LightCall::set_color_temperature_if_supported(float color_temperature) { - if (this->get_active_color_mode_() & ColorCapability::COLOR_TEMPERATURE) + if (this->get_active_color_mode_() & ColorCapability::COLOR_TEMPERATURE || + this->get_active_color_mode_() & ColorCapability::COLD_WARM_WHITE) this->set_color_temperature(color_temperature); return *this; } From d7ad1558856a9e795117e038d21b5616d9d1d86a Mon Sep 17 00:00:00 2001 From: niklasweber Date: Tue, 12 Oct 2021 00:11:04 +0200 Subject: [PATCH 160/549] Fix reset on http_request without network connection (#2474) * Fix reset problem when http_request is sent without network connection (#2501) * Fix format --- esphome/components/http_request/http_request.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index f88ee19e5c..309977a915 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -3,6 +3,7 @@ #include "http_request.h" #include "esphome/core/macros.h" #include "esphome/core/log.h" +#include "esphome/components/network/util.h" namespace esphome { namespace http_request { @@ -28,6 +29,13 @@ void HttpRequestComponent::set_url(std::string url) { } void HttpRequestComponent::send(const std::vector &response_triggers) { + if (!network::is_connected()) { + this->client_.end(); + this->status_set_warning(); + ESP_LOGW(TAG, "HTTP Request failed; Not connected to network"); + return; + } + bool begin_status = false; const String url = this->url_.c_str(); #ifdef USE_ESP32 From 04ec1c8b56d49353865f8eb8f616c40d4ee598f0 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Mon, 11 Oct 2021 23:14:04 +0100 Subject: [PATCH 161/549] Consolidate CONF_RAW_DATA_ID to const.py (#2491) --- esphome/components/animation/__init__.py | 4 +--- esphome/components/font/__init__.py | 3 +-- esphome/components/image/__init__.py | 11 ++++++++--- esphome/const.py | 1 + 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index 3ae3aa94f9..3f03e5c185 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -5,7 +5,7 @@ from esphome.components import display, font import esphome.components.image as espImage import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE +from esphome.const import CONF_FILE, CONF_ID, CONF_RAW_DATA_ID, CONF_RESIZE, CONF_TYPE from esphome.core import CORE, HexInt _LOGGER = logging.getLogger(__name__) @@ -15,8 +15,6 @@ MULTI_CONF = True Animation_ = display.display_ns.class_("Animation") -CONF_RAW_DATA_ID = "raw_data_id" - ANIMATION_SCHEMA = cv.Schema( { cv.Required(CONF_ID): cv.declare_id(Animation_), diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 7225bf5bb9..6af5be45d4 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -4,7 +4,7 @@ from esphome import core from esphome.components import display import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_SIZE +from esphome.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_RAW_DATA_ID, CONF_SIZE from esphome.core import CORE, HexInt DEPENDENCIES = ["display"] @@ -74,7 +74,6 @@ def validate_truetype_file(value): DEFAULT_GLYPHS = ( ' !"%()+=,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' ) -CONF_RAW_DATA_ID = "raw_data_id" CONF_RAW_GLYPH_ID = "raw_glyph_id" FONT_SCHEMA = cv.Schema( diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index b946a86bc4..a721263dff 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -4,7 +4,14 @@ from esphome import core from esphome.components import display, font import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE, CONF_DITHER +from esphome.const import ( + CONF_DITHER, + CONF_FILE, + CONF_ID, + CONF_RAW_DATA_ID, + CONF_RESIZE, + CONF_TYPE, +) from esphome.core import CORE, HexInt _LOGGER = logging.getLogger(__name__) @@ -21,8 +28,6 @@ IMAGE_TYPE = { Image_ = display.display_ns.class_("Image") -CONF_RAW_DATA_ID = "raw_data_id" - IMAGE_SCHEMA = cv.Schema( { cv.Required(CONF_ID): cv.declare_id(Image_), diff --git a/esphome/const.py b/esphome/const.py index 780f4f6e22..a9eec3e249 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -533,6 +533,7 @@ CONF_RANGE_FROM = "range_from" CONF_RANGE_TO = "range_to" CONF_RATE = "rate" CONF_RAW = "raw" +CONF_RAW_DATA_ID = "raw_data_id" CONF_RC_CODE_1 = "rc_code_1" CONF_RC_CODE_2 = "rc_code_2" CONF_REACTIVE_POWER = "reactive_power" From 6a5eb43454dcbbcf8d41338fc2a526f77373b6e2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 12 Oct 2021 11:56:47 +1300 Subject: [PATCH 162/549] Update Airthings BLE (#2453) --- .../airthings_wave_mini.cpp | 19 ++----- .../airthings_wave_mini/airthings_wave_mini.h | 16 +++--- .../components/airthings_wave_mini/sensor.py | 8 +-- .../airthings_wave_plus.cpp | 19 ++----- .../airthings_wave_plus/airthings_wave_plus.h | 16 +++--- .../components/airthings_wave_plus/sensor.py | 8 +-- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 57 +++++++++++++++++++ .../esp32_ble_tracker/esp32_ble_tracker.h | 2 + 8 files changed, 89 insertions(+), 56 deletions(-) diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp index 0ab0e65148..6b6418f7e6 100644 --- a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp @@ -1,6 +1,6 @@ #include "airthings_wave_mini.h" -#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#ifdef USE_ESP32 namespace esphome { namespace airthings_wave_mini { @@ -75,8 +75,6 @@ void AirthingsWaveMini::read_sensors_(uint8_t *raw_value, uint16_t value_len) { bool AirthingsWaveMini::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; } -void AirthingsWaveMini::loop() {} - void AirthingsWaveMini::update() { if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { if (!parent()->enabled) { @@ -104,17 +102,12 @@ void AirthingsWaveMini::dump_config() { LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); } -AirthingsWaveMini::AirthingsWaveMini() : PollingComponent(10000) { - auto service_bt = *BLEUUID::fromString(std::string("b42e3882-ade7-11e4-89d3-123b93f75cba")).getNative(); - auto characteristic_bt = *BLEUUID::fromString(std::string("b42e3b98-ade7-11e4-89d3-123b93f75cba")).getNative(); - - service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_uuid(service_bt); - sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_uuid(characteristic_bt); -} - -void AirthingsWaveMini::setup() {} +AirthingsWaveMini::AirthingsWaveMini() + : PollingComponent(10000), + service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)), + sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {} } // namespace airthings_wave_mini } // namespace esphome -#endif // USE_ESP32_FRAMEWORK_ARDUINO +#endif // USE_ESP32 diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.h b/esphome/components/airthings_wave_mini/airthings_wave_mini.h index 5d1964d559..128774f9cb 100644 --- a/esphome/components/airthings_wave_mini/airthings_wave_mini.h +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.h @@ -1,28 +1,28 @@ #pragma once -#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#ifdef USE_ESP32 +#include #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/component.h" +#include "esphome/core/log.h" namespace esphome { namespace airthings_wave_mini { +static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba"; +static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba"; + class AirthingsWaveMini : public PollingComponent, public ble_client::BLEClientNode { public: AirthingsWaveMini(); - 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; @@ -62,4 +62,4 @@ class AirthingsWaveMini : public PollingComponent, public ble_client::BLEClientN } // namespace airthings_wave_mini } // namespace esphome -#endif // USE_ESP32_FRAMEWORK_ARDUINO +#endif // USE_ESP32 diff --git a/esphome/components/airthings_wave_mini/sensor.py b/esphome/components/airthings_wave_mini/sensor.py index 6a32cd8771..d38354fa84 100644 --- a/esphome/components/airthings_wave_mini/sensor.py +++ b/esphome/components/airthings_wave_mini/sensor.py @@ -1,7 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, ble_client -from esphome.core import CORE from esphome.const import ( DEVICE_CLASS_HUMIDITY, @@ -58,10 +57,8 @@ CONFIG_SCHEMA = cv.All( ), } ) - .extend(cv.polling_component_schema("5mins")) + .extend(cv.polling_component_schema("5min")) .extend(ble_client.BLE_CLIENT_SCHEMA), - # Until BLEUUID reference removed - cv.only_with_arduino, ) @@ -83,6 +80,3 @@ async def to_code(config): if CONF_TVOC in config: sens = await sensor.new_sensor(config[CONF_TVOC]) cg.add(var.set_tvoc(sens)) - - if CORE.is_esp32: - cg.add_library("ESP32 BLE Arduino", None) diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index 0eaffbd889..79f2cb7741 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 USE_ESP32_FRAMEWORK_ARDUINO +#ifdef USE_ESP32 namespace esphome { namespace airthings_wave_plus { @@ -96,8 +96,6 @@ bool AirthingsWavePlus::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && v bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && co2 <= 16383; } -void AirthingsWavePlus::loop() {} - void AirthingsWavePlus::update() { if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { if (!parent()->enabled) { @@ -128,17 +126,12 @@ void AirthingsWavePlus::dump_config() { 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_ = esp32_ble_tracker::ESPBTUUID::from_uuid(service_bt); - sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_uuid(characteristic_bt); -} - -void AirthingsWavePlus::setup() {} +AirthingsWavePlus::AirthingsWavePlus() + : PollingComponent(10000), + service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)), + sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {} } // namespace airthings_wave_plus } // namespace esphome -#endif // USE_ESP32_FRAMEWORK_ARDUINO +#endif // USE_ESP32 diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.h b/esphome/components/airthings_wave_plus/airthings_wave_plus.h index 5677f05a62..9dd6ed92d5 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.h +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.h @@ -1,28 +1,28 @@ #pragma once -#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#ifdef USE_ESP32 +#include #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/component.h" +#include "esphome/core/log.h" namespace esphome { namespace airthings_wave_plus { +static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba"; +static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba"; + class AirthingsWavePlus : public PollingComponent, public ble_client::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; @@ -72,4 +72,4 @@ class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientN } // namespace airthings_wave_plus } // namespace esphome -#endif // USE_ESP32_FRAMEWORK_ARDUINO +#endif // USE_ESP32 diff --git a/esphome/components/airthings_wave_plus/sensor.py b/esphome/components/airthings_wave_plus/sensor.py index 2100341536..727fbe15fb 100644 --- a/esphome/components/airthings_wave_plus/sensor.py +++ b/esphome/components/airthings_wave_plus/sensor.py @@ -1,7 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, ble_client -from esphome.core import CORE from esphome.const import ( DEVICE_CLASS_CARBON_DIOXIDE, @@ -83,10 +82,8 @@ CONFIG_SCHEMA = cv.All( ), } ) - .extend(cv.polling_component_schema("5mins")) + .extend(cv.polling_component_schema("5min")) .extend(ble_client.BLE_CLIENT_SCHEMA), - # Until BLEUUID reference removed - cv.only_with_arduino, ) @@ -117,6 +114,3 @@ async def to_code(config): if CONF_TVOC in config: sens = await sensor.new_sensor(config[CONF_TVOC]) cg.add(var.set_tvoc(sens)) - - if CORE.is_esp32: - cg.add_library("ESP32 BLE Arduino", None) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 95176bb179..65749f5124 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -317,6 +317,63 @@ ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) { ret.uuid_.uuid.uuid128[i] = data[i]; return ret; } +ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { + ESPBTUUID ret; + if (data.length() == 4) { + 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]; + + 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]; + + 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 + // investigated (lack of time) + ret.uuid_.len = ESP_UUID_LEN_128; + memcpy(ret.uuid_.uuid.uuid128, (uint8_t *) data.data(), 16); + } else if (data.length() == 36) { + // If the length of the string is 36 bytes then we will assume it is a long hex string in + // UUID format. + ret.uuid_.len = ESP_UUID_LEN_128; + int n = 0; + 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]; + + if (msb > '9') + msb -= 7; + if (lsb > '9') + lsb -= 7; + ret.uuid_.uuid.uuid128[15 - n++] = ((msb & 0x0F) << 4) | (lsb & 0x0F); + i += 2; + } + } else { + ESP_LOGE(TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes - %s", data.c_str()); + } + return ret; +} ESPBTUUID ESPBTUUID::from_uuid(esp_bt_uuid_t uuid) { ESPBTUUID ret; ret.uuid_.len = uuid.len; diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 71885a564f..1308119df5 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -25,6 +25,8 @@ class ESPBTUUID { static ESPBTUUID from_raw(const uint8_t *data); + static ESPBTUUID from_raw(const std::string &data); + static ESPBTUUID from_uuid(esp_bt_uuid_t uuid); ESPBTUUID as_128bit() const; From b4f57972fb4e7f20e0d83632366f4ee32f348b7b Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Mon, 11 Oct 2021 21:39:21 -0500 Subject: [PATCH 163/549] Add on_open and on_closed triggers to cover (#2488) --- esphome/components/cover/__init__.py | 27 +++++++++++++++++++++++++++ esphome/components/cover/automation.h | 23 +++++++++++++++++++++++ tests/test1.yaml | 6 ++++++ 3 files changed, 56 insertions(+) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 137aaac872..0fd27f3f27 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -15,6 +15,7 @@ from esphome.const import ( CONF_TILT_STATE_TOPIC, CONF_STOP, CONF_MQTT_ID, + CONF_TRIGGER_ID, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_helpers import setup_entity @@ -67,6 +68,15 @@ CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action) CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition) CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition) +# Triggers +CoverOpenTrigger = cover_ns.class_("CoverOpenTrigger", automation.Trigger.template()) +CoverClosedTrigger = cover_ns.class_( + "CoverClosedTrigger", automation.Trigger.template() +) + +CONF_ON_OPEN = "on_open" +CONF_ON_CLOSED = "on_closed" + COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(Cover), @@ -84,6 +94,16 @@ COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ex cv.Optional(CONF_TILT_STATE_TOPIC): cv.All( cv.requires_component("mqtt"), cv.subscribe_topic ), + cv.Optional(CONF_ON_OPEN): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger), + } + ), + cv.Optional(CONF_ON_CLOSED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger), + } + ), } ) @@ -94,6 +114,13 @@ async def setup_cover_core_(var, config): if CONF_DEVICE_CLASS in config: cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) + for conf in config.get(CONF_ON_OPEN, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_CLOSED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index 79bca6826e..6406ba52cb 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -99,6 +99,7 @@ template class CoverIsOpenCondition : public Condition { protected: Cover *cover_; }; + template class CoverIsClosedCondition : public Condition { public: CoverIsClosedCondition(Cover *cover) : cover_(cover) {} @@ -108,5 +109,27 @@ template class CoverIsClosedCondition : public Condition Cover *cover_; }; +class CoverOpenTrigger : public Trigger<> { + public: + CoverOpenTrigger(Cover *a_cover) { + a_cover->add_on_state_callback([this, a_cover]() { + if (a_cover->is_fully_open()) { + this->trigger(); + } + }); + } +}; + +class CoverClosedTrigger : public Trigger<> { + public: + CoverClosedTrigger(Cover *a_cover) { + a_cover->add_on_state_callback([this, a_cover]() { + if (a_cover->is_fully_closed()) { + this->trigger(); + } + }); + } +}; + } // namespace cover } // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index 058da35d2c..130022c14d 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2307,6 +2307,12 @@ cover: tilt_lambda: !lambda 'return 0.5;' tilt_state_topic: tilt/state/topic tilt_command_topic: tilt/command/topic + on_open: + then: + - lambda: 'ESP_LOGD("cover", "open");' + on_closed: + then: + - lambda: 'ESP_LOGD("cover", "closed");' - platform: am43 name: 'Test AM43' id: am43_test From d13134135bfde3cf2ddbc5f8df890737a83516f1 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Tue, 12 Oct 2021 13:51:41 +0200 Subject: [PATCH 164/549] Fix LoadProhibited crash for logger baud_rate 0 (#2498) Co-authored-by: Maurice Makaay --- esphome/components/logger/logger.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 4352b7e208..2d85969bf3 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -111,14 +111,15 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) { this->set_null_terminator_(); const char *msg = this->tx_buffer_ + offset; + if (this->baud_rate_ > 0) { #ifdef USE_ARDUINO - if (this->baud_rate_ > 0) this->hw_serial_->println(msg); #endif // USE_ARDUINO #ifdef USE_ESP_IDF - uart_write_bytes(uart_num_, msg, strlen(msg)); - uart_write_bytes(uart_num_, "\n", 1); + 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 From a3eb2a7ee0d16e1e0b7d3e05636372300791ab29 Mon Sep 17 00:00:00 2001 From: Rob Deutsch Date: Wed, 13 Oct 2021 05:38:19 +1100 Subject: [PATCH 165/549] Added heatpumpir support (#1343) Co-authored-by: Otto winter Co-authored-by: Oxan van Leeuwen --- CODEOWNERS | 1 + esphome/components/heatpumpir/__init__.py | 0 esphome/components/heatpumpir/climate.py | 114 +++++++++++ esphome/components/heatpumpir/heatpumpir.cpp | 183 ++++++++++++++++++ esphome/components/heatpumpir/heatpumpir.h | 116 +++++++++++ .../heatpumpir/ir_sender_esphome.cpp | 32 +++ .../components/heatpumpir/ir_sender_esphome.h | 27 +++ platformio.ini | 1 + tests/test1.yaml | 7 + 9 files changed, 481 insertions(+) create mode 100644 esphome/components/heatpumpir/__init__.py create mode 100644 esphome/components/heatpumpir/climate.py create mode 100644 esphome/components/heatpumpir/heatpumpir.cpp create mode 100644 esphome/components/heatpumpir/heatpumpir.h create mode 100644 esphome/components/heatpumpir/ir_sender_esphome.cpp create mode 100644 esphome/components/heatpumpir/ir_sender_esphome.h diff --git a/CODEOWNERS b/CODEOWNERS index 49cb60c177..4c3084d463 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -64,6 +64,7 @@ esphome/components/graph/* @synco esphome/components/havells_solar/* @sourabhjaiswal esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/light/* @DotNetDann +esphome/components/heatpumpir/* @rob-deutsch esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/homeassistant/* @OttoWinter esphome/components/hrxl_maxsonar_wr/* @netmikey diff --git a/esphome/components/heatpumpir/__init__.py b/esphome/components/heatpumpir/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py new file mode 100644 index 0000000000..36e56aa5da --- /dev/null +++ b/esphome/components/heatpumpir/climate.py @@ -0,0 +1,114 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate_ir +from esphome.const import ( + CONF_ID, + CONF_MAX_TEMPERATURE, + CONF_MIN_TEMPERATURE, + CONF_PROTOCOL, + CONF_VISUAL, +) + +CODEOWNERS = ["@rob-deutsch"] + +AUTO_LOAD = ["climate_ir"] + +heatpumpir_ns = cg.esphome_ns.namespace("heatpumpir") +HeatpumpIRClimate = heatpumpir_ns.class_("HeatpumpIRClimate", climate_ir.ClimateIR) + +Protocol = heatpumpir_ns.enum("Protocol") +PROTOCOLS = { + "aux": Protocol.PROTOCOL_AUX, + "ballu": Protocol.PROTOCOL_BALLU, + "carrier_mca": Protocol.PROTOCOL_CARRIER_MCA, + "carrier_nqv": Protocol.PROTOCOL_CARRIER_NQV, + "daikin_arc417": Protocol.PROTOCOL_DAIKIN_ARC417, + "daikin_arc480": Protocol.PROTOCOL_DAIKIN_ARC480, + "daikin": Protocol.PROTOCOL_DAIKIN, + "fuego": Protocol.PROTOCOL_FUEGO, + "fujitsu_awyz": Protocol.PROTOCOL_FUJITSU_AWYZ, + "gree": Protocol.PROTOCOL_GREE, + "greeya": Protocol.PROTOCOL_GREEYAA, + "greeyan": Protocol.PROTOCOL_GREEYAN, + "hisense_aud": Protocol.PROTOCOL_HISENSE_AUD, + "hitachi": Protocol.PROTOCOL_HITACHI, + "hyundai": Protocol.PROTOCOL_HYUNDAI, + "ivt": Protocol.PROTOCOL_IVT, + "midea": Protocol.PROTOCOL_MIDEA, + "mitsubishi_fa": Protocol.PROTOCOL_MITSUBISHI_FA, + "mitsubishi_fd": Protocol.PROTOCOL_MITSUBISHI_FD, + "mitsubishi_fe": Protocol.PROTOCOL_MITSUBISHI_FE, + "mitsubishi_heavy_fdtc": Protocol.PROTOCOL_MITSUBISHI_HEAVY_FDTC, + "mitsubishi_heavy_zj": Protocol.PROTOCOL_MITSUBISHI_HEAVY_ZJ, + "mitsubishi_heavy_zm": Protocol.PROTOCOL_MITSUBISHI_HEAVY_ZM, + "mitsubishi_heavy_zmp": Protocol.PROTOCOL_MITSUBISHI_HEAVY_ZMP, + "mitsubishi_heavy_kj": Protocol.PROTOCOL_MITSUBISHI_KJ, + "mitsubishi_msc": Protocol.PROTOCOL_MITSUBISHI_MSC, + "mitsubishi_msy": Protocol.PROTOCOL_MITSUBISHI_MSY, + "mitsubishi_sez": Protocol.PROTOCOL_MITSUBISHI_SEZ, + "panasonic_ckp": Protocol.PROTOCOL_PANASONIC_CKP, + "panasonic_dke": Protocol.PROTOCOL_PANASONIC_DKE, + "panasonic_jke": Protocol.PROTOCOL_PANASONIC_JKE, + "panasonic_lke": Protocol.PROTOCOL_PANASONIC_LKE, + "panasonic_nke": Protocol.PROTOCOL_PANASONIC_NKE, + "samsung_aqv": Protocol.PROTOCOL_SAMSUNG_AQV, + "samsung_fjm": Protocol.PROTOCOL_SAMSUNG_FJM, + "sharp": Protocol.PROTOCOL_SHARP, + "toshiba_daiseikai": Protocol.PROTOCOL_TOSHIBA_DAISEIKAI, + "toshiba": Protocol.PROTOCOL_TOSHIBA, +} + +CONF_HORIZONTAL_DEFAULT = "horizontal_default" +HorizontalDirections = heatpumpir_ns.enum("HorizontalDirections") +HORIZONTAL_DIRECTIONS = { + "auto": HorizontalDirections.HORIZONTAL_DIRECTION_AUTO, + "middle": HorizontalDirections.HORIZONTAL_DIRECTION_MIDDLE, + "left": HorizontalDirections.HORIZONTAL_DIRECTION_LEFT, + "mleft": HorizontalDirections.HORIZONTAL_DIRECTION_MLEFT, + "mright": HorizontalDirections.HORIZONTAL_DIRECTION_MRIGHT, + "right": HorizontalDirections.HORIZONTAL_DIRECTION_RIGHT, +} + +CONF_VERTICAL_DEFAULT = "vertical_default" +VerticalDirections = heatpumpir_ns.enum("VerticalDirections") +VERTICAL_DIRECTIONS = { + "auto": VerticalDirections.VERTICAL_DIRECTION_AUTO, + "up": VerticalDirections.VERTICAL_DIRECTION_UP, + "mup": VerticalDirections.VERTICAL_DIRECTION_MUP, + "middle": VerticalDirections.VERTICAL_DIRECTION_MIDDLE, + "mdown": VerticalDirections.VERTICAL_DIRECTION_MDOWN, + "down": VerticalDirections.VERTICAL_DIRECTION_DOWN, +} + +CONFIG_SCHEMA = cv.All( + climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(HeatpumpIRClimate), + cv.Required(CONF_PROTOCOL): cv.enum(PROTOCOLS), + cv.Required(CONF_HORIZONTAL_DEFAULT): cv.enum(HORIZONTAL_DIRECTIONS), + cv.Required(CONF_VERTICAL_DEFAULT): cv.enum(VERTICAL_DIRECTIONS), + cv.Required(CONF_MIN_TEMPERATURE): cv.temperature, + cv.Required(CONF_MAX_TEMPERATURE): cv.temperature, + } + ), + cv.only_with_arduino, +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + if CONF_VISUAL not in config: + config[CONF_VISUAL] = {} + visual = config[CONF_VISUAL] + if CONF_MAX_TEMPERATURE not in visual: + visual[CONF_MAX_TEMPERATURE] = config[CONF_MAX_TEMPERATURE] + if CONF_MIN_TEMPERATURE not in visual: + visual[CONF_MIN_TEMPERATURE] = config[CONF_MIN_TEMPERATURE] + yield climate_ir.register_climate_ir(var, config) + cg.add(var.set_protocol(config[CONF_PROTOCOL])) + cg.add(var.set_horizontal_default(config[CONF_HORIZONTAL_DEFAULT])) + cg.add(var.set_vertical_default(config[CONF_VERTICAL_DEFAULT])) + cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE])) + cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE])) + + cg.add_library("tonia/HeatpumpIR", "1.0.15") diff --git a/esphome/components/heatpumpir/heatpumpir.cpp b/esphome/components/heatpumpir/heatpumpir.cpp new file mode 100644 index 0000000000..8d9fc962c0 --- /dev/null +++ b/esphome/components/heatpumpir/heatpumpir.cpp @@ -0,0 +1,183 @@ +#include "heatpumpir.h" + +#ifdef USE_ARDUINO + +#include +#include "ir_sender_esphome.h" +#include "HeatpumpIRFactory.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace heatpumpir { + +static const char *const TAG = "heatpumpir.climate"; + +const std::map> PROTOCOL_CONSTRUCTOR_MAP = { + {PROTOCOL_AUX, []() { return new AUXHeatpumpIR(); }}, // NOLINT + {PROTOCOL_BALLU, []() { return new BalluHeatpumpIR(); }}, // NOLINT + {PROTOCOL_CARRIER_MCA, []() { return new CarrierMCAHeatpumpIR(); }}, // NOLINT + {PROTOCOL_CARRIER_NQV, []() { return new CarrierNQVHeatpumpIR(); }}, // NOLINT + {PROTOCOL_DAIKIN_ARC417, []() { return new DaikinHeatpumpARC417IR(); }}, // NOLINT + {PROTOCOL_DAIKIN_ARC480, []() { return new DaikinHeatpumpARC480A14IR(); }}, // NOLINT + {PROTOCOL_DAIKIN, []() { return new DaikinHeatpumpIR(); }}, // NOLINT + {PROTOCOL_FUEGO, []() { return new FuegoHeatpumpIR(); }}, // NOLINT + {PROTOCOL_FUJITSU_AWYZ, []() { return new FujitsuHeatpumpIR(); }}, // NOLINT + {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }}, // NOLINT + {PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }}, // NOLINT + {PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT + {PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT + {PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT + {PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT + {PROTOCOL_IVT, []() { return new IVTHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MIDEA, []() { return new MideaHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_FA, []() { return new MitsubishiFAHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_FD, []() { return new MitsubishiFDHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_FE, []() { return new MitsubishiFEHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_HEAVY_FDTC, []() { return new MitsubishiHeavyFDTCHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_HEAVY_ZJ, []() { return new MitsubishiHeavyZJHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_HEAVY_ZM, []() { return new MitsubishiHeavyZMHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_HEAVY_ZMP, []() { return new MitsubishiHeavyZMPHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_KJ, []() { return new MitsubishiKJHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_MSC, []() { return new MitsubishiMSCHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_MSY, []() { return new MitsubishiMSYHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_SEZ, []() { return new MitsubishiSEZKDXXHeatpumpIR(); }}, // NOLINT + {PROTOCOL_PANASONIC_CKP, []() { return new PanasonicCKPHeatpumpIR(); }}, // NOLINT + {PROTOCOL_PANASONIC_DKE, []() { return new PanasonicDKEHeatpumpIR(); }}, // NOLINT + {PROTOCOL_PANASONIC_JKE, []() { return new PanasonicJKEHeatpumpIR(); }}, // NOLINT + {PROTOCOL_PANASONIC_LKE, []() { return new PanasonicLKEHeatpumpIR(); }}, // NOLINT + {PROTOCOL_PANASONIC_NKE, []() { return new PanasonicNKEHeatpumpIR(); }}, // NOLINT + {PROTOCOL_SAMSUNG_AQV, []() { return new SamsungAQVHeatpumpIR(); }}, // NOLINT + {PROTOCOL_SAMSUNG_FJM, []() { return new SamsungFJMHeatpumpIR(); }}, // NOLINT + {PROTOCOL_SHARP, []() { return new SharpHeatpumpIR(); }}, // NOLINT + {PROTOCOL_TOSHIBA_DAISEIKAI, []() { return new ToshibaDaiseikaiHeatpumpIR(); }}, // NOLINT + {PROTOCOL_TOSHIBA, []() { return new ToshibaHeatpumpIR(); }}, // NOLINT +}; + +void HeatpumpIRClimate::setup() { + auto protocol_constructor = PROTOCOL_CONSTRUCTOR_MAP.find(protocol_); + if (protocol_constructor == PROTOCOL_CONSTRUCTOR_MAP.end()) { + ESP_LOGE(TAG, "Invalid protocol"); + return; + } + this->heatpump_ir_ = protocol_constructor->second(); + climate_ir::ClimateIR::setup(); +} + +void HeatpumpIRClimate::transmit_state() { + uint8_t power_mode_cmd; + uint8_t operating_mode_cmd; + uint8_t temperature_cmd; + uint8_t fan_speed_cmd; + + uint8_t swing_v_cmd; + switch (default_vertical_direction_) { + case VERTICAL_DIRECTION_AUTO: + swing_v_cmd = VDIR_AUTO; + break; + case VERTICAL_DIRECTION_UP: + swing_v_cmd = VDIR_UP; + break; + case VERTICAL_DIRECTION_MUP: + swing_v_cmd = VDIR_MUP; + break; + case VERTICAL_DIRECTION_MIDDLE: + swing_v_cmd = VDIR_MIDDLE; + break; + case VERTICAL_DIRECTION_MDOWN: + swing_v_cmd = VDIR_MDOWN; + break; + case VERTICAL_DIRECTION_DOWN: + swing_v_cmd = VDIR_DOWN; + break; + default: + ESP_LOGE(TAG, "Invalid default vertical direction"); + return; + } + if ((this->swing_mode == climate::CLIMATE_SWING_VERTICAL) || (this->swing_mode == climate::CLIMATE_SWING_BOTH)) { + swing_v_cmd = VDIR_SWING; + } + + uint8_t swing_h_cmd; + switch (default_horizontal_direction_) { + case HORIZONTAL_DIRECTION_AUTO: + swing_h_cmd = HDIR_AUTO; + break; + case HORIZONTAL_DIRECTION_MIDDLE: + swing_h_cmd = HDIR_MIDDLE; + break; + case HORIZONTAL_DIRECTION_LEFT: + swing_h_cmd = HDIR_LEFT; + break; + case HORIZONTAL_DIRECTION_MLEFT: + swing_h_cmd = HDIR_MLEFT; + break; + case HORIZONTAL_DIRECTION_MRIGHT: + swing_h_cmd = HDIR_MRIGHT; + break; + case HORIZONTAL_DIRECTION_RIGHT: + swing_h_cmd = HDIR_RIGHT; + break; + default: + ESP_LOGE(TAG, "Invalid default horizontal direction"); + return; + } + if ((this->swing_mode == climate::CLIMATE_SWING_HORIZONTAL) || (this->swing_mode == climate::CLIMATE_SWING_BOTH)) { + swing_h_cmd = HDIR_SWING; + } + + switch (this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO)) { + case climate::CLIMATE_FAN_LOW: + fan_speed_cmd = FAN_2; + break; + case climate::CLIMATE_FAN_MEDIUM: + fan_speed_cmd = FAN_3; + break; + case climate::CLIMATE_FAN_HIGH: + fan_speed_cmd = FAN_4; + break; + case climate::CLIMATE_FAN_AUTO: + default: + fan_speed_cmd = FAN_AUTO; + break; + } + + switch (this->mode) { + case climate::CLIMATE_MODE_COOL: + power_mode_cmd = POWER_ON; + operating_mode_cmd = MODE_COOL; + break; + case climate::CLIMATE_MODE_HEAT: + power_mode_cmd = POWER_ON; + operating_mode_cmd = MODE_HEAT; + break; + case climate::CLIMATE_MODE_AUTO: + power_mode_cmd = POWER_ON; + operating_mode_cmd = MODE_AUTO; + break; + case climate::CLIMATE_MODE_FAN_ONLY: + power_mode_cmd = POWER_ON; + operating_mode_cmd = MODE_FAN; + break; + case climate::CLIMATE_MODE_DRY: + power_mode_cmd = POWER_ON; + operating_mode_cmd = MODE_DRY; + break; + case climate::CLIMATE_MODE_OFF: + default: + power_mode_cmd = POWER_OFF; + operating_mode_cmd = MODE_AUTO; + break; + } + + temperature_cmd = (uint8_t) clamp(this->target_temperature, this->min_temperature_, this->max_temperature_); + + IRSenderESPHome esp_sender(0, this->transmitter_); + + heatpump_ir_->send(esp_sender, power_mode_cmd, operating_mode_cmd, fan_speed_cmd, temperature_cmd, swing_v_cmd, + swing_h_cmd); +} + +} // namespace heatpumpir +} // namespace esphome + +#endif diff --git a/esphome/components/heatpumpir/heatpumpir.h b/esphome/components/heatpumpir/heatpumpir.h new file mode 100644 index 0000000000..e2d2b45dc4 --- /dev/null +++ b/esphome/components/heatpumpir/heatpumpir.h @@ -0,0 +1,116 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "esphome/components/climate_ir/climate_ir.h" + +// Forward-declare HeatpumpIR class from library. We cannot include its header here because it has unnamespaced defines +// that conflict with ESPHome. +class HeatpumpIR; + +namespace esphome { +namespace heatpumpir { + +// Simple enum to represent protocols. +enum Protocol { + PROTOCOL_AUX, + PROTOCOL_BALLU, + PROTOCOL_CARRIER_MCA, + PROTOCOL_CARRIER_NQV, + PROTOCOL_DAIKIN_ARC417, + PROTOCOL_DAIKIN_ARC480, + PROTOCOL_DAIKIN, + PROTOCOL_FUEGO, + PROTOCOL_FUJITSU_AWYZ, + PROTOCOL_GREE, + PROTOCOL_GREEYAA, + PROTOCOL_GREEYAN, + PROTOCOL_HISENSE_AUD, + PROTOCOL_HITACHI, + PROTOCOL_HYUNDAI, + PROTOCOL_IVT, + PROTOCOL_MIDEA, + PROTOCOL_MITSUBISHI_FA, + PROTOCOL_MITSUBISHI_FD, + PROTOCOL_MITSUBISHI_FE, + PROTOCOL_MITSUBISHI_HEAVY_FDTC, + PROTOCOL_MITSUBISHI_HEAVY_ZJ, + PROTOCOL_MITSUBISHI_HEAVY_ZM, + PROTOCOL_MITSUBISHI_HEAVY_ZMP, + PROTOCOL_MITSUBISHI_KJ, + PROTOCOL_MITSUBISHI_MSC, + PROTOCOL_MITSUBISHI_MSY, + PROTOCOL_MITSUBISHI_SEZ, + PROTOCOL_PANASONIC_CKP, + PROTOCOL_PANASONIC_DKE, + PROTOCOL_PANASONIC_JKE, + PROTOCOL_PANASONIC_LKE, + PROTOCOL_PANASONIC_NKE, + PROTOCOL_SAMSUNG_AQV, + PROTOCOL_SAMSUNG_FJM, + PROTOCOL_SHARP, + PROTOCOL_TOSHIBA_DAISEIKAI, + PROTOCOL_TOSHIBA, +}; + +// Simple enum to represent horizontal directios +enum HorizontalDirection { + HORIZONTAL_DIRECTION_AUTO = 0, + HORIZONTAL_DIRECTION_MIDDLE = 1, + HORIZONTAL_DIRECTION_LEFT = 2, + HORIZONTAL_DIRECTION_MLEFT = 3, + HORIZONTAL_DIRECTION_MRIGHT = 4, + HORIZONTAL_DIRECTION_RIGHT = 5, +}; + +// Simple enum to represent vertical directions +enum VerticalDirection { + VERTICAL_DIRECTION_AUTO = 0, + VERTICAL_DIRECTION_UP = 1, + VERTICAL_DIRECTION_MUP = 2, + VERTICAL_DIRECTION_MIDDLE = 3, + VERTICAL_DIRECTION_MDOWN = 4, + VERTICAL_DIRECTION_DOWN = 5, +}; + +// Temperature +const float TEMP_MIN = 0; // Celsius +const float TEMP_MAX = 100; // Celsius + +class HeatpumpIRClimate : public climate_ir::ClimateIR { + public: + HeatpumpIRClimate() + : climate_ir::ClimateIR( + TEMP_MIN, TEMP_MAX, 1.0f, true, true, + std::set{climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH, climate::CLIMATE_FAN_AUTO}, + std::set{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL, + climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_BOTH}) {} + void setup() override; + void set_protocol(Protocol protocol) { this->protocol_ = protocol; } + void set_horizontal_default(HorizontalDirection horizontal_direction) { + this->default_horizontal_direction_ = horizontal_direction; + } + void set_vertical_default(VerticalDirection vertical_direction) { + this->default_vertical_direction_ = vertical_direction; + } + + void set_max_temperature(float temperature) { this->max_temperature_ = temperature; } + void set_min_temperature(float temperature) { this->min_temperature_ = temperature; } + + protected: + HeatpumpIR *heatpump_ir_; + /// Transmit via IR the state of this climate controller. + void transmit_state() override; + Protocol protocol_; + HorizontalDirection default_horizontal_direction_; + VerticalDirection default_vertical_direction_; + + float max_temperature_; + float min_temperature_; +}; + +} // namespace heatpumpir +} // namespace esphome + +#endif diff --git a/esphome/components/heatpumpir/ir_sender_esphome.cpp b/esphome/components/heatpumpir/ir_sender_esphome.cpp new file mode 100644 index 0000000000..24c7933563 --- /dev/null +++ b/esphome/components/heatpumpir/ir_sender_esphome.cpp @@ -0,0 +1,32 @@ +#include "ir_sender_esphome.h" + +#ifdef USE_ARDUINO + +namespace esphome { +namespace heatpumpir { + +void IRSenderESPHome::setFrequency(int frequency) { // NOLINT(readability-identifier-naming) + auto data = transmit_.get_data(); + data->set_carrier_frequency(1000 * frequency); +} + +// Send an IR 'mark' symbol, i.e. transmitter ON +void IRSenderESPHome::mark(int mark_length) { + auto data = transmit_.get_data(); + data->mark(mark_length); +} + +// Send an IR 'space' symbol, i.e. transmitter OFF +void IRSenderESPHome::space(int space_length) { + if (space_length) { + auto data = transmit_.get_data(); + data->space(space_length); + } else { + transmit_.perform(); + } +} + +} // namespace heatpumpir +} // namespace esphome + +#endif diff --git a/esphome/components/heatpumpir/ir_sender_esphome.h b/esphome/components/heatpumpir/ir_sender_esphome.h new file mode 100644 index 0000000000..24e8ba9883 --- /dev/null +++ b/esphome/components/heatpumpir/ir_sender_esphome.h @@ -0,0 +1,27 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "esphome/components/remote_base/remote_base.h" +#include "esphome/components/remote_transmitter/remote_transmitter.h" +#include // arduino-heatpump library + +namespace esphome { +namespace heatpumpir { + +class IRSenderESPHome : public IRSender { + public: + IRSenderESPHome(uint8_t pin, remote_transmitter::RemoteTransmitterComponent *transmitter) + : IRSender(pin), transmit_(transmitter->transmit()){}; + void setFrequency(int frequency) override; // NOLINT(readability-identifier-naming) + void space(int space_length) override; + void mark(int mark_length) override; + + protected: + remote_transmitter::RemoteTransmitterComponent::TransmitCall transmit_; +}; + +} // namespace heatpumpir +} // namespace esphome + +#endif diff --git a/platformio.ini b/platformio.ini index e038224f69..9cc7477d51 100644 --- a/platformio.ini +++ b/platformio.ini @@ -49,6 +49,7 @@ lib_deps = glmnet/Dsmr@0.5 ; dsmr rweather/Crypto@0.2.0 ; dsmr dudanov/MideaUART@1.1.8 ; midea + tonia/HeatpumpIR@^1.0.15 ; heatpumpir build_flags = ${common.build_flags} -DUSE_ARDUINO diff --git a/tests/test1.yaml b/tests/test1.yaml index 130022c14d..fd142a63fd 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1681,6 +1681,13 @@ climate: name: Toshiba Climate - platform: hitachi_ac344 name: Hitachi Climate + - platform: heatpumpir + protocol: mitsubishi_heavy_zm + horizontal_default: left + vertical_default: up + name: HeatpumpIR Climate + min_temperature: 18 + max_temperature: 30 - platform: midea id: midea_unit uart_id: uart0 From 1184bbc976e27aebbfcdaee27219d2141c22efd2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 12 Oct 2021 21:22:38 +0200 Subject: [PATCH 166/549] Reduce IRAM usage in test3 (#2499) --- tests/test2.yaml | 6 ++++++ tests/test3.yaml | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test2.yaml b/tests/test2.yaml index 364bcec28f..7e71d1ab4e 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -61,6 +61,12 @@ mcp3008: - id: 'mcp3008_hub' cs_pin: GPIO12 +output: + - platform: ac_dimmer + id: dimmer1 + gate_pin: GPIO5 + zero_cross_pin: GPIO12 + sensor: - platform: homeassistant entity_id: sensor.hello_world diff --git a/tests/test3.yaml b/tests/test3.yaml index b261d6cc8e..9d2839a026 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -261,8 +261,6 @@ logger: level: DEBUG esp8266_store_log_strings_in_flash: true -web_server: - deep_sleep: run_duration: 20s sleep_duration: 50s @@ -1092,10 +1090,6 @@ output: return {s}; outputs: - id: custom_float - - platform: ac_dimmer - id: dimmer1 - gate_pin: GPIO5 - zero_cross_pin: GPIO12 - platform: slow_pwm pin: GPIO5 id: my_slow_pwm From 34db9d9ef2b0748abee992958e783ec88a69398b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Oct 2021 08:23:24 +1300 Subject: [PATCH 167/549] Add optional timeout for wait_until action (#2282) --- esphome/automation.py | 7 +++++++ esphome/core/base_automation.h | 12 ++++++++++++ tests/test1.yaml | 5 ++++- tests/test3.yaml | 12 +++++++----- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/esphome/automation.py b/esphome/automation.py index 71c564b906..0768bf8869 100644 --- a/esphome/automation.py +++ b/esphome/automation.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_ELSE, CONF_ID, CONF_THEN, + CONF_TIMEOUT, CONF_TRIGGER_ID, CONF_TYPE_ID, CONF_TIME, @@ -244,6 +245,9 @@ def validate_wait_until(value): schema = cv.Schema( { cv.Required(CONF_CONDITION): validate_potentially_and_condition, + cv.Optional(CONF_TIMEOUT): cv.templatable( + cv.positive_time_period_milliseconds + ), } ) if isinstance(value, dict) and CONF_CONDITION in value: @@ -255,6 +259,9 @@ def validate_wait_until(value): async def wait_until_action_to_code(config, action_id, template_arg, args): conditions = await build_condition(config[CONF_CONDITION], template_arg, args) var = cg.new_Pvariable(action_id, template_arg, conditions) + if CONF_TIMEOUT in config: + template_ = await cg.templatable(config[CONF_TIMEOUT], args, cg.uint32) + cg.add(var.set_timeout_value(template_)) await cg.register_component(var, {}) return var diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index fa49786d1d..d97d369d33 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -228,6 +228,8 @@ template class WaitUntilAction : public Action, public Co public: WaitUntilAction(Condition *condition) : condition_(condition) {} + TEMPLATABLE_VALUE(uint32_t, timeout_value) + void play_complex(Ts... x) override { this->num_running_++; // Check if we can continue immediately. @@ -238,6 +240,12 @@ template class WaitUntilAction : public Action, public Co return; } this->var_ = std::make_tuple(x...); + + if (this->timeout_value_.has_value()) { + auto f = std::bind(&WaitUntilAction::play_next_, this, x...); + this->set_timeout("timeout", this->timeout_value_.value(x...), f); + } + this->loop(); } @@ -249,6 +257,8 @@ template class WaitUntilAction : public Action, public Co return; } + this->cancel_timeout("timeout"); + this->play_next_tuple_(this->var_); } @@ -257,6 +267,8 @@ template class WaitUntilAction : public Action, public Co void play(Ts... x) override { /* ignore - see play_complex */ } + void stop() override { this->cancel_timeout("timeout"); } + protected: Condition *condition_; std::tuple var_{}; diff --git a/tests/test1.yaml b/tests/test1.yaml index fd142a63fd..540a715dde 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -364,8 +364,11 @@ sensor: then: - lambda: >- ESP_LOGD("main", "Got value range %f", x); + - wait_until: wifi.connected - wait_until: - binary_sensor.is_on: binary_sensor1 + condition: + binary_sensor.is_on: binary_sensor1 + timeout: 1s on_raw_value: - lambda: >- ESP_LOGD("main", "Got raw value %f", x); diff --git a/tests/test3.yaml b/tests/test3.yaml index 9d2839a026..73e314c94c 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -5,10 +5,13 @@ esphome: board: d1_mini build_path: build/test3 on_boot: - - wait_until: - - api.connected - - wifi.connected - - time.has_time + - if: + condition: + - api.connected + - wifi.connected + - time.has_time + then: + - logger.log: "Have time" includes: - custom.h @@ -1291,4 +1294,3 @@ dsmr: daly_bms: update_interval: 20s uart_id: uart1 - From c33077bc61a214df23d82753ae9077898c280786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Trevi=C3=B1o?= Date: Tue, 12 Oct 2021 21:42:51 +0200 Subject: [PATCH 168/549] Improves ct_clamp component accuracy (#2283) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafa Treviño --- .../components/ct_clamp/ct_clamp_sensor.cpp | 22 ++++++++++++------- esphome/components/ct_clamp/ct_clamp_sensor.h | 1 + 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/esphome/components/ct_clamp/ct_clamp_sensor.cpp b/esphome/components/ct_clamp/ct_clamp_sensor.cpp index 0052b1426d..51b0f1318c 100644 --- a/esphome/components/ct_clamp/ct_clamp_sensor.cpp +++ b/esphome/components/ct_clamp/ct_clamp_sensor.cpp @@ -31,19 +31,20 @@ void CTClampSensor::update() { return; } - 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); + const float rms_ac_dc_squared = this->sample_squared_sum_ / this->num_samples_; + const float rms_dc = this->sample_sum_ / this->num_samples_; + const float rms_ac = std::sqrt(rms_ac_dc_squared - rms_dc * rms_dc); + ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %d different samples (%d SPS)", this->name_.c_str(), rms_ac, + this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_); + this->publish_state(rms_ac); }); // Set sampling values - this->is_sampling_ = true; + this->last_value_ = 0.0; this->num_samples_ = 0; this->sample_sum_ = 0.0f; this->sample_squared_sum_ = 0.0f; + this->is_sampling_ = true; } void CTClampSensor::loop() { @@ -55,9 +56,14 @@ void CTClampSensor::loop() { if (std::isnan(value)) return; + // Assuming a sine wave, avoid requesting values faster than the ADC can provide them + if (this->last_value_ == value) + return; + this->last_value_ = value; + + this->num_samples_++; this->sample_sum_ += value; this->sample_squared_sum_ += value * value; - this->num_samples_++; } } // namespace ct_clamp diff --git a/esphome/components/ct_clamp/ct_clamp_sensor.h b/esphome/components/ct_clamp/ct_clamp_sensor.h index 10601ab852..db4dc1ea57 100644 --- a/esphome/components/ct_clamp/ct_clamp_sensor.h +++ b/esphome/components/ct_clamp/ct_clamp_sensor.h @@ -43,6 +43,7 @@ class CTClampSensor : public sensor::Sensor, public PollingComponent { * https://en.wikipedia.org/wiki/Root_mean_square */ + float last_value_ = 0.0f; float sample_sum_ = 0.0f; float sample_squared_sum_ = 0.0f; uint32_t num_samples_ = 0; From 4406a08fa7b88ef9b7a97e273c1c1d127d0c19aa Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Oct 2021 09:06:52 +1300 Subject: [PATCH 169/549] Allow multiple pn532_spi entries (#2489) --- esphome/components/pn532_spi/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/pn532_spi/__init__.py b/esphome/components/pn532_spi/__init__.py index 2683f34ad5..8a8ab1b175 100644 --- a/esphome/components/pn532_spi/__init__.py +++ b/esphome/components/pn532_spi/__init__.py @@ -6,6 +6,7 @@ from esphome.const import CONF_ID AUTO_LOAD = ["pn532"] CODEOWNERS = ["@OttoWinter", "@jesserockz"] DEPENDENCIES = ["spi"] +MULTI_CONF = True pn532_spi_ns = cg.esphome_ns.namespace("pn532_spi") PN532Spi = pn532_spi_ns.class_("PN532Spi", pn532.PN532, spi.SPIDevice) From 3dee057826cbccbdb90a0fe92b6664eb029c7d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mayoral=20Mart=C3=ADnez?= Date: Wed, 13 Oct 2021 00:35:30 +0200 Subject: [PATCH 170/549] Add throttle_average sensor filter (#2485) --- esphome/components/sensor/__init__.py | 10 ++++++++++ esphome/components/sensor/filter.cpp | 25 +++++++++++++++++++++++++ esphome/components/sensor/filter.h | 20 ++++++++++++++++++++ tests/test1.yaml | 1 + 4 files changed, 56 insertions(+) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index cb74a41119..4b2e9dc019 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -160,6 +160,7 @@ SlidingWindowMovingAverageFilter = sensor_ns.class_( ExponentialMovingAverageFilter = sensor_ns.class_( "ExponentialMovingAverageFilter", Filter ) +ThrottleAverageFilter = sensor_ns.class_("ThrottleAverageFilter", Filter, cg.Component) LambdaFilter = sensor_ns.class_("LambdaFilter", Filter) OffsetFilter = sensor_ns.class_("OffsetFilter", Filter) MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter) @@ -381,6 +382,15 @@ async def exponential_moving_average_filter_to_code(config, filter_id): return cg.new_Pvariable(filter_id, config[CONF_ALPHA], config[CONF_SEND_EVERY]) +@FILTER_REGISTRY.register( + "throttle_average", ThrottleAverageFilter, cv.positive_time_period_milliseconds +) +async def throttle_average_filter_to_code(config, filter_id): + var = cg.new_Pvariable(filter_id, config) + await cg.register_component(var, {}) + return var + + @FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda) async def lambda_filter_to_code(config, filter_id): lambda_ = await cg.process_lambda( diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index 63801e7996..321e3a4a4f 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -187,6 +187,31 @@ 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; } +// ThrottleAverageFilter +ThrottleAverageFilter::ThrottleAverageFilter(uint32_t time_period) : time_period_(time_period) {} + +optional ThrottleAverageFilter::new_value(float value) { + ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::new_value(value=%f)", this, value); + if (!std::isnan(value)) { + this->sum_ += value; + this->n_++; + } + return {}; +} +void ThrottleAverageFilter::setup() { + this->set_interval("throttle_average", this->time_period_, [this]() { + ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::interval(sum=%f, n=%i)", this, this->sum_, this->n_); + if (this->n_ == 0) { + this->output(NAN); + } else { + this->output(this->sum_ / this->n_); + this->sum_ = 0.0f; + this->n_ = 0; + } + }); +} +float ThrottleAverageFilter::get_setup_priority() const { return setup_priority::HARDWARE; } + // LambdaFilter LambdaFilter::LambdaFilter(lambda_filter_t lambda_filter) : lambda_filter_(std::move(lambda_filter)) {} const lambda_filter_t &LambdaFilter::get_lambda_filter() const { return this->lambda_filter_; } diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 29a6813ea9..d595e419a6 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -178,6 +178,26 @@ class ExponentialMovingAverageFilter : public Filter { float alpha_; }; +/** Simple throttle average filter. + * + * It takes the average of all the values received in a period of time. + */ +class ThrottleAverageFilter : public Filter, public Component { + public: + explicit ThrottleAverageFilter(uint32_t time_period); + + void setup() override; + + optional new_value(float value) override; + + float get_setup_priority() const override; + + protected: + uint32_t time_period_; + float sum_{0.0f}; + unsigned int n_{0}; +}; + using lambda_filter_t = std::function(float)>; /** This class allows for creation of simple template filters. diff --git a/tests/test1.yaml b/tests/test1.yaml index 540a715dde..157ccfc5d1 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -343,6 +343,7 @@ sensor: - exponential_moving_average: alpha: 0.1 send_every: 15 + - throttle_average: 60s - throttle: 1s - heartbeat: 5s - debounce: 0.1s From fe5a6847b5d51bd0e4120a4697eeaf7b2c468d92 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Oct 2021 16:40:46 +1300 Subject: [PATCH 171/549] Bump version to 2021.11.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a9eec3e249..4f7d95d694 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.0-dev" +__version__ = "2021.11.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From bb86db869a24ec88c0e0fd1d662d6f93ad5799be Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Oct 2021 22:09:38 +1300 Subject: [PATCH 172/549] Fix bad merge --- esphome/components/api/api_connection.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 81ff11fcd9..47171ba50f 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -161,19 +161,6 @@ void APIConnection::on_disconnect_response(const DisconnectResponse &value) { // pass } -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_) From 534ce11d54437d1a8426be2392743ae712321ab0 Mon Sep 17 00:00:00 2001 From: razorback16 Date: Wed, 13 Oct 2021 09:45:41 -0700 Subject: [PATCH 173/549] TCS34725 BugFix and GA factor (#2445) - Fixed endianness bug on tcs34725 data read - Fixed lux adjustments based on gain, integration time and GA factor - Added glass attenuation factor to allow using this sensor behind semi transparent glass Co-authored-by: Razorback16 --- esphome/components/tcs34725/sensor.py | 19 ++- esphome/components/tcs34725/tcs34725.cpp | 180 +++++++++++++++++++---- esphome/components/tcs34725/tcs34725.h | 27 +++- tests/test3.yaml | 2 +- 4 files changed, 197 insertions(+), 31 deletions(-) diff --git a/esphome/components/tcs34725/sensor.py b/esphome/components/tcs34725/sensor.py index 6c74c86faf..fcc56e395f 100644 --- a/esphome/components/tcs34725/sensor.py +++ b/esphome/components/tcs34725/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_GAIN, CONF_ID, CONF_ILLUMINANCE, + CONF_GLASS_ATTENUATION_FACTOR, CONF_INTEGRATION_TIME, DEVICE_CLASS_ILLUMINANCE, ICON_LIGHTBULB, @@ -34,8 +35,20 @@ TCS34725_INTEGRATION_TIMES = { "24ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_24MS, "50ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_50MS, "101ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_101MS, + "120ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_120MS, "154ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_154MS, - "700ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_700MS, + "180ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_180MS, + "199ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_199MS, + "240ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_240MS, + "300ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_300MS, + "360ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_360MS, + "401ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_401MS, + "420ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_420MS, + "480ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_480MS, + "499ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_499MS, + "540ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_540MS, + "600ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_600MS, + "614ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_614MS, } TCS34725Gain = tcs34725_ns.enum("TCS34725Gain") @@ -79,6 +92,9 @@ CONFIG_SCHEMA = ( TCS34725_INTEGRATION_TIMES, lower=True ), cv.Optional(CONF_GAIN, default="1X"): cv.enum(TCS34725_GAINS, upper=True), + cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=1.0): cv.float_range( + min=1.0 + ), } ) .extend(cv.polling_component_schema("60s")) @@ -93,6 +109,7 @@ async def to_code(config): cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) cg.add(var.set_gain(config[CONF_GAIN])) + cg.add(var.set_glass_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR])) if CONF_RED_CHANNEL in config: sens = await sensor.new_sensor(config[CONF_RED_CHANNEL]) diff --git a/esphome/components/tcs34725/tcs34725.cpp b/esphome/components/tcs34725/tcs34725.cpp index 564d3dcda7..f7ffe2a97d 100644 --- a/esphome/components/tcs34725/tcs34725.cpp +++ b/esphome/components/tcs34725/tcs34725.cpp @@ -26,10 +26,8 @@ void TCS34725Component::setup() { return; } - uint8_t integration_reg = this->integration_time_; - uint8_t gain_reg = this->gain_; - if (!this->write_byte(TCS34725_REGISTER_ATIME, integration_reg) || - !this->write_byte(TCS34725_REGISTER_CONTROL, gain_reg)) { + if (!this->write_byte(TCS34725_REGISTER_ATIME, this->integration_reg_) || + !this->write_byte(TCS34725_REGISTER_CONTROL, this->gain_reg_)) { this->mark_failed(); return; } @@ -61,6 +59,114 @@ void TCS34725Component::dump_config() { LOG_SENSOR(" ", "Color Temperature", this->color_temperature_sensor_); } float TCS34725Component::get_setup_priority() const { return setup_priority::DATA; } + +/*! + * @brief Converts the raw R/G/B values to color temperature in degrees + * Kelvin using the algorithm described in DN40 from Taos (now AMS). + * @param r + * Red value + * @param g + * Green value + * @param b + * Blue value + * @param c + * Clear channel value + * @return Color temperature in degrees Kelvin + */ +void TCS34725Component::calculate_temperature_and_lux_(uint16_t r, uint16_t g, uint16_t b, uint16_t c) { + float r2, g2, b2; /* RGB values minus IR component */ + float sat; /* Digital saturation level */ + float ir; /* Inferred IR content */ + + this->illuminance_ = 0; // Assign 0 value before calculation + this->color_temperature_ = 0; + + const float ga = this->glass_attenuation_; // Glass Attenuation Factor + static const float DF = 310.f; // Device Factor + static const float R_COEF = 0.136f; // + static const float G_COEF = 1.f; // used in lux computation + static const float B_COEF = -0.444f; // + static const float CT_COEF = 3810.f; // Color Temperature Coefficient + static const float CT_OFFSET = 1391.f; // Color Temperatuer Offset + + if (c == 0) { + return; + } + + /* Analog/Digital saturation: + * + * (a) As light becomes brighter, the clear channel will tend to + * saturate first since R+G+B is approximately equal to C. + * (b) The TCS34725 accumulates 1024 counts per 2.4ms of integration + * time, up to a maximum values of 65535. This means analog + * saturation can occur up to an integration time of 153.6ms + * (64*2.4ms=153.6ms). + * (c) If the integration time is > 153.6ms, digital saturation will + * occur before analog saturation. Digital saturation occurs when + * the count reaches 65535. + */ + if ((256 - this->integration_reg_) > 63) { + /* Track digital saturation */ + sat = 65535.f; + } else { + /* Track analog saturation */ + sat = 1024.f * (256.f - this->integration_reg_); + } + + /* Ripple rejection: + * + * (a) An integration time of 50ms or multiples of 50ms are required to + * reject both 50Hz and 60Hz ripple. + * (b) If an integration time faster than 50ms is required, you may need + * to average a number of samples over a 50ms period to reject ripple + * from fluorescent and incandescent light sources. + * + * Ripple saturation notes: + * + * (a) If there is ripple in the received signal, the value read from C + * will be less than the max, but still have some effects of being + * saturated. This means that you can be below the 'sat' value, but + * still be saturating. At integration times >150ms this can be + * ignored, but <= 150ms you should calculate the 75% saturation + * level to avoid this problem. + */ + if (this->integration_time_ < 150) { + /* Adjust sat to 75% to avoid analog saturation if atime < 153.6ms */ + sat -= sat / 4.f; + } + + /* Check for saturation and mark the sample as invalid if true */ + if (c >= sat) { + return; + } + + /* AMS RGB sensors have no IR channel, so the IR content must be */ + /* calculated indirectly. */ + ir = ((r + g + b) > c) ? (r + g + b - c) / 2 : 0; + + /* Remove the IR component from the raw RGB values */ + r2 = r - ir; + g2 = g - ir; + b2 = b - ir; + + if (r2 == 0) { + return; + } + + // Lux Calculation (DN40 3.2) + + float g1 = R_COEF * r2 + G_COEF * g2 + B_COEF * b2; + float cpl = (this->integration_time_ * this->gain_) / (ga * DF); + this->illuminance_ = g1 / cpl; + + // Color Temperature Calculation (DN40) + /* A simple method of measuring color temp is to use the ratio of blue */ + /* to red light, taking IR cancellation into account. */ + this->color_temperature_ = (CT_COEF * b2) / /** Color temp coefficient. */ + r2 + + CT_OFFSET; /** Color temp offset. */ +} + void TCS34725Component::update() { uint16_t raw_c; uint16_t raw_r; @@ -74,6 +180,12 @@ void TCS34725Component::update() { return; } + // May need to fix endianness as the data read over I2C is big-endian, but most ESP platforms are little-endian + raw_c = i2c::i2ctohs(raw_c); + raw_r = i2c::i2ctohs(raw_r); + raw_g = i2c::i2ctohs(raw_g); + raw_b = i2c::i2ctohs(raw_b); + const float channel_c = raw_c / 655.35f; const float channel_r = raw_r / 655.35f; const float channel_g = raw_g / 655.35f; @@ -87,38 +199,54 @@ void TCS34725Component::update() { if (this->blue_sensor_ != nullptr) this->blue_sensor_->publish_state(channel_b); - // Formulae taken from Adafruit TCS35725 library - float illuminance = (-0.32466f * channel_r) + (1.57837f * channel_g) + (-0.73191f * channel_b); + if (this->illuminance_sensor_ || this->color_temperature_sensor_) { + calculate_temperature_and_lux_(raw_r, raw_g, raw_b, raw_c); + } + if (this->illuminance_sensor_ != nullptr) - this->illuminance_sensor_->publish_state(illuminance); + this->illuminance_sensor_->publish_state(this->illuminance_); - // Color temperature - // 1. Convert RGB to XYZ color space - const float x = (-0.14282f * raw_r) + (1.54924f * raw_g) + (-0.95641f * raw_b); - const float y = (-0.32466f * raw_r) + (1.57837f * raw_g) + (-0.73191f * raw_b); - const float z = (-0.68202f * raw_r) + (0.77073f * raw_g) + (0.56332f * raw_b); - - // 2. Calculate chromacity coordinates - const float xc = (x) / (x + y + z); - const float yc = (y) / (x + y + z); - - // 3. Use McCamy's formula to determine the color temperature - const float n = (xc - 0.3320f) / (0.1858f - yc); - - // 4. final color temperature in Kelvin. - const float color_temperature = (449.0f * powf(n, 3.0f)) + (3525.0f * powf(n, 2.0f)) + (6823.3f * n) + 5520.33f; if (this->color_temperature_sensor_ != nullptr) - this->color_temperature_sensor_->publish_state(color_temperature); + this->color_temperature_sensor_->publish_state(this->color_temperature_); ESP_LOGD(TAG, "Got R=%.1f%%,G=%.1f%%,B=%.1f%%,C=%.1f%% Illuminance=%.1flx Color Temperature=%.1fK", channel_r, - channel_g, channel_b, channel_c, illuminance, color_temperature); + channel_g, channel_b, channel_c, this->illuminance_, this->color_temperature_); this->status_clear_warning(); } void TCS34725Component::set_integration_time(TCS34725IntegrationTime integration_time) { - this->integration_time_ = integration_time; + this->integration_reg_ = integration_time; + this->integration_time_ = (256.f - integration_time) * 2.4f; +} +void TCS34725Component::set_gain(TCS34725Gain gain) { + this->gain_reg_ = gain; + switch (gain) { + case TCS34725Gain::TCS34725_GAIN_1X: + this->gain_ = 1.f; + break; + case TCS34725Gain::TCS34725_GAIN_4X: + this->gain_ = 4.f; + break; + case TCS34725Gain::TCS34725_GAIN_16X: + this->gain_ = 16.f; + break; + case TCS34725Gain::TCS34725_GAIN_60X: + this->gain_ = 60.f; + break; + default: + this->gain_ = 1.f; + break; + } +} + +void TCS34725Component::set_glass_attenuation_factor(float ga) { + // The Glass Attenuation (FA) factor used to compensate for lower light + // levels at the device due to the possible presence of glass. The GA is + // the inverse of the glass transmissivity (T), so GA = 1/T. A transmissivity + // of 50% gives GA = 1 / 0.50 = 2. If no glass is present, use GA = 1. + // See Application Note: DN40-Rev 1.0 + this->glass_attenuation_ = ga; } -void TCS34725Component::set_gain(TCS34725Gain gain) { this->gain_ = gain; } } // namespace tcs34725 } // namespace esphome diff --git a/esphome/components/tcs34725/tcs34725.h b/esphome/components/tcs34725/tcs34725.h index b914db0eb0..47ed2959c6 100644 --- a/esphome/components/tcs34725/tcs34725.h +++ b/esphome/components/tcs34725/tcs34725.h @@ -12,8 +12,20 @@ enum TCS34725IntegrationTime { TCS34725_INTEGRATION_TIME_24MS = 0xF6, TCS34725_INTEGRATION_TIME_50MS = 0xEB, TCS34725_INTEGRATION_TIME_101MS = 0xD5, + TCS34725_INTEGRATION_TIME_120MS = 0xCE, TCS34725_INTEGRATION_TIME_154MS = 0xC0, - TCS34725_INTEGRATION_TIME_700MS = 0x00, + TCS34725_INTEGRATION_TIME_180MS = 0xB5, + TCS34725_INTEGRATION_TIME_199MS = 0xAD, + TCS34725_INTEGRATION_TIME_240MS = 0x9C, + TCS34725_INTEGRATION_TIME_300MS = 0x83, + TCS34725_INTEGRATION_TIME_360MS = 0x6A, + TCS34725_INTEGRATION_TIME_401MS = 0x59, + TCS34725_INTEGRATION_TIME_420MS = 0x51, + TCS34725_INTEGRATION_TIME_480MS = 0x38, + TCS34725_INTEGRATION_TIME_499MS = 0x30, + TCS34725_INTEGRATION_TIME_540MS = 0x1F, + TCS34725_INTEGRATION_TIME_600MS = 0x06, + TCS34725_INTEGRATION_TIME_614MS = 0x00, }; enum TCS34725Gain { @@ -27,6 +39,7 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice { public: void set_integration_time(TCS34725IntegrationTime integration_time); void set_gain(TCS34725Gain gain); + void set_glass_attenuation_factor(float ga); void set_clear_sensor(sensor::Sensor *clear_sensor) { clear_sensor_ = clear_sensor; } void set_red_sensor(sensor::Sensor *red_sensor) { red_sensor_ = red_sensor; } @@ -49,8 +62,16 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *blue_sensor_{nullptr}; sensor::Sensor *illuminance_sensor_{nullptr}; sensor::Sensor *color_temperature_sensor_{nullptr}; - TCS34725IntegrationTime integration_time_{TCS34725_INTEGRATION_TIME_2_4MS}; - TCS34725Gain gain_{TCS34725_GAIN_1X}; + float integration_time_{2.4}; + float gain_{1.0}; + float glass_attenuation_{1.0}; + float illuminance_; + float color_temperature_; + + private: + void calculate_temperature_and_lux_(uint16_t r, uint16_t g, uint16_t b, uint16_t c); + uint8_t integration_reg_{TCS34725_INTEGRATION_TIME_2_4MS}; + uint8_t gain_reg_{TCS34725_GAIN_1X}; }; } // namespace tcs34725 diff --git a/tests/test3.yaml b/tests/test3.yaml index 73e314c94c..4c76967842 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -403,7 +403,7 @@ sensor: name: Illuminance color_temperature: name: Color Temperature - integration_time: 700ms + integration_time: 614ms gain: 60x - platform: custom lambda: |- From 859e5083925edc541d4c20509f9adfe7497b3231 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Wed, 13 Oct 2021 18:50:27 +0200 Subject: [PATCH 174/549] change millis() to micros() in feed_wdt for 3ms check (#2492) --- esphome/core/application.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index a4d61f819c..f67fc826cf 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -109,8 +109,8 @@ void Application::loop() { void IRAM_ATTR HOT Application::feed_wdt() { static uint32_t last_feed = 0; - uint32_t now = millis(); - if (now - last_feed > 3) { + uint32_t now = micros(); + if (now - last_feed > 3000) { arch_feed_wdt(); last_feed = now; #ifdef USE_STATUS_LED From e06b6d7140ade52fa956de83b9a2944141a945aa Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 13 Oct 2021 21:22:57 +0200 Subject: [PATCH 175/549] Add ESP32 IDF as a test env for PRs (#2494) Co-authored-by: Maurice Makaay --- .github/PULL_REQUEST_TEMPLATE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index aa90ef365f..25411c19f5 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -16,6 +16,7 @@ Quick description and explanation of changes ## Test Environment - [ ] ESP32 +- [ ] ESP32 IDF - [ ] ESP8266 ## Example entry for `config.yaml`: From 05388d2dfcb6f2b49e13cefb2dae04f478d9f1d4 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 13 Oct 2021 21:53:00 +0200 Subject: [PATCH 176/549] Fix light state remaining on after turn off with transition (#2509) --- esphome/components/light/transformers.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h index 90646f4e61..c22846ceb1 100644 --- a/esphome/components/light/transformers.h +++ b/esphome/components/light/transformers.h @@ -18,10 +18,13 @@ class LightTransitionTransformer : public LightTransformer { this->start_values_.set_brightness(0.0f); } - // When turning light off from on state, use source state and only decrease brightness to zero. + // When turning light off from on state, use source state and only decrease brightness to zero. Use a second + // variable for transition end state, as overwriting target_values breaks LightState logic. 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); + this->end_values_ = LightColorValues(this->start_values_); + this->end_values_.set_brightness(0.0f); + } else { + this->end_values_ = LightColorValues(this->target_values_); } // When changing color mode, go through off state, as color modes are orthogonal and there can't be two active. @@ -43,7 +46,7 @@ class LightTransitionTransformer : public LightTransformer { } 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_; + LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->end_values_; if (this->changing_color_mode_) p = p < 0.5f ? p * 2 : (p - 0.5) * 2; @@ -57,6 +60,7 @@ class LightTransitionTransformer : public LightTransformer { static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); } bool changing_color_mode_{false}; + LightColorValues end_values_{}; LightColorValues intermediate_values_{}; }; From 867fecd157f45f591c11ae7e40c646f1111695d3 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 14 Oct 2021 08:59:52 +1300 Subject: [PATCH 177/549] Fix: Light flash not restoring previous LightState (#2383) * Update light state when transformer has finished * Revert writing direct to output * Correct handling of zero-length light transformers * Allow transformers to handle zero-length transitions, and check more boundary conditions when transitioning back to start state * Removed log.h * Fixed race condition between LightFlashTransformer.apply() and is_finished() * clang-format * Step progress from 0.0f to 1.0f at t=start_time for zero-length transforms to avoid divide-by-zero --- .../components/light/addressable_light.cpp | 2 +- esphome/components/light/light_transformer.h | 10 ++++- esphome/components/light/transformers.h | 37 ++++++++++--------- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index f3e6c0ef1d..a8e0c7b762 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -79,7 +79,7 @@ optional AddressableLightTransformer::apply() { // dynamically-calculated alpha values to match the look. float denom = (1.0f - smoothed_progress); - float alpha = denom == 0.0f ? 0.0f : (smoothed_progress - this->last_transition_progress_) / denom; + float alpha = denom == 0.0f ? 1.0f : (smoothed_progress - this->last_transition_progress_) / denom; // 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. diff --git a/esphome/components/light/light_transformer.h b/esphome/components/light/light_transformer.h index dd904d0eed..35b045d5b4 100644 --- a/esphome/components/light/light_transformer.h +++ b/esphome/components/light/light_transformer.h @@ -39,7 +39,15 @@ class LightTransformer { protected: /// 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); } + float get_progress_() { + uint32_t now = esphome::millis(); + if (now < this->start_time_) + return 0.0f; + if (now >= this->start_time_ + this->length_) + return 1.0f; + + return clamp((now - this->start_time_) / float(this->length_), 0.0f, 1.0f); + } uint32_t start_time_; uint32_t length_; diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h index c22846ceb1..a557bd39b1 100644 --- a/esphome/components/light/transformers.h +++ b/esphome/components/light/transformers.h @@ -73,9 +73,7 @@ class LightFlashTransformer : public LightTransformer { 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; + this->begun_lightstate_restore_ = false; // first transition to original target this->transformer_ = this->state_.get_output()->create_default_transition(); @@ -83,40 +81,45 @@ class LightFlashTransformer : public LightTransformer { } optional apply() override { - // transition transformer does not handle 0 length as progress returns nan - if (this->transition_length_ == 0) - return this->target_values_; + optional result = {}; + + if (this->transformer_ == nullptr && 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->begun_lightstate_restore_ = true; + } if (this->transformer_ != nullptr) { - if (!this->transformer_->is_finished()) { - return this->transformer_->apply(); - } else { + result = this->transformer_->apply(); + + if (this->transformer_->is_finished()) { this->transformer_->stop(); this->transformer_ = nullptr; } } - 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_); - } - - // once transition is complete, don't change states until next transition - return optional(); + return result; } // Restore the original values after the flash. void stop() override { + if (this->transformer_ != nullptr) { + this->transformer_->stop(); + this->transformer_ = nullptr; + } this->state_.current_values = this->get_start_values(); this->state_.remote_values = this->get_start_values(); this->state_.publish_state(); } + bool is_finished() override { return this->begun_lightstate_restore_ && LightTransformer::is_finished(); } + protected: LightState &state_; uint32_t transition_length_; std::unique_ptr transformer_{nullptr}; + bool begun_lightstate_restore_; }; } // namespace light From 6bbb5e9b56bb71353aac609cea7fe325cd9b4c60 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 13 Oct 2021 22:21:43 +0200 Subject: [PATCH 178/549] Disallow using UART2 for logger on ESP-32 variants that lack it (#2510) --- esphome/components/esp32/__init__.py | 6 +++++- esphome/components/logger/__init__.py | 7 +++++++ esphome/components/logger/logger.cpp | 8 +++----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 704f9bb3e8..09eabe1fa7 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -21,12 +21,16 @@ from esphome.core import CORE, HexInt import esphome.config_validation as cv import esphome.codegen as cg -from .const import ( +from .const import ( # noqa KEY_BOARD, KEY_ESP32, KEY_SDKCONFIG_OPTIONS, KEY_VARIANT, + VARIANT_ESP32, + VARIANT_ESP32S2, + VARIANT_ESP32S3, VARIANT_ESP32C3, + VARIANT_ESP32H2, VARIANTS, ) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index bc1bc6bb41..fe2a3ec8f8 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -19,6 +19,7 @@ from esphome.const import ( CONF_TX_BUFFER_SIZE, ) from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority +from esphome.components.esp32 import get_esp32_variant, VARIANT_ESP32S2, VARIANT_ESP32C3 CODEOWNERS = ["@esphome/core"] logger_ns = cg.esphome_ns.namespace("logger") @@ -52,6 +53,10 @@ LOG_LEVEL_SEVERITY = [ "VERY_VERBOSE", ] +ESP32_REDUCED_VARIANTS = [VARIANT_ESP32C3, VARIANT_ESP32S2] + +UART_SELECTION_ESP32_REDUCED = ["UART0", "UART1"] + UART_SELECTION_ESP32 = ["UART0", "UART1", "UART2"] UART_SELECTION_ESP8266 = ["UART0", "UART0_SWAP", "UART1"] @@ -75,6 +80,8 @@ is_log_level = cv.one_of(*LOG_LEVELS, upper=True) def uart_selection(value): if CORE.is_esp32: + if get_esp32_variant() in ESP32_REDUCED_VARIANTS: + return cv.one_of(*UART_SELECTION_ESP32_REDUCED, upper=True)(value) return cv.one_of(*UART_SELECTION_ESP32, upper=True)(value) if CORE.is_esp8266: return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 2d85969bf3..b38c7f1a69 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -153,13 +153,9 @@ void Logger::pre_setup() { case UART_SELECTION_UART1: this->hw_serial_ = &Serial1; break; -#ifdef USE_ESP32 +#if defined(USE_ESP32) && !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32S2 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 - // UART0-UART1) this->hw_serial_ = &Serial2; -#endif break; #endif } @@ -173,9 +169,11 @@ void Logger::pre_setup() { case UART_SELECTION_UART1: uart_num_ = UART_NUM_1; break; +#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32S2 case UART_SELECTION_UART2: uart_num_ = UART_NUM_2; break; +#endif } uart_config_t uart_config{}; uart_config.baud_rate = (int) baud_rate_; From 07b309e65d38bf420b89975aee56675314ab7a1d Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 14 Oct 2021 20:58:35 +1300 Subject: [PATCH 179/549] Fix BME680_BSEC compilation issue with ESP32 (#2516) --- esphome/components/bme680_bsec/__init__.py | 8 +++++++- tests/test1.yaml | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/esphome/components/bme680_bsec/__init__.py b/esphome/components/bme680_bsec/__init__.py index d258819aa4..38da18d702 100644 --- a/esphome/components/bme680_bsec/__init__.py +++ b/esphome/components/bme680_bsec/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c from esphome.const import CONF_ID +from esphome.core import CORE CODEOWNERS = ["@trvrnrth"] DEPENDENCIES = ["i2c"] @@ -44,7 +45,8 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional( CONF_STATE_SAVE_INTERVAL, default="6hours" ): cv.positive_time_period_minutes, - } + }, + cv.only_with_arduino, ).extend(i2c.i2c_device_schema(0x76)) @@ -60,5 +62,9 @@ async def to_code(config): var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds) ) + if CORE.is_esp32: + # Although this component does not use SPI, the BSEC library requires the SPI library + cg.add_library("SPI", None) + cg.add_define("USE_BSEC") cg.add_library("BSEC Software Library", "1.6.1480") diff --git a/tests/test1.yaml b/tests/test1.yaml index 157ccfc5d1..62fc781eca 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -265,6 +265,9 @@ wled: adalight: +bme680_bsec: + i2c_id: i2c_bus + esp32_ble_tracker: ble_client: @@ -478,6 +481,19 @@ sensor: duration: 150ms update_interval: 15s i2c_id: i2c_bus + - platform: bme680_bsec + temperature: + name: "BME680 Temperature" + pressure: + name: "BME680 Pressure" + humidity: + name: "BME680 Humidity" + iaq: + name: "BME680 IAQ" + co2_equivalent: + name: "BME680 CO2 Equivalent" + breath_voc_equivalent: + name: "BME680 Breath VOC Equivalent" - platform: bmp085 temperature: name: 'Outside Temperature' From 7e482901d93d56ee517690309da87aa75933023b Mon Sep 17 00:00:00 2001 From: Dmitriy Lopatko Date: Thu, 14 Oct 2021 10:00:53 +0200 Subject: [PATCH 180/549] add missing include in sgp30 (#2517) --- esphome/components/sgp30/sgp30.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 1a64a12907..87cf0fa61a 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -1,4 +1,5 @@ #include "sgp30.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" #include "esphome/core/application.h" #include From 4896f870f0d6755bcdd570a47f0323b9e3640645 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 14 Oct 2021 21:04:50 +1300 Subject: [PATCH 181/549] Fix: Color modes not being correctly used in light partitions (#2513) --- .../light/addressable_light_wrapper.h | 89 +++++++++++++++++-- 1 file changed, 80 insertions(+), 9 deletions(-) diff --git a/esphome/components/light/addressable_light_wrapper.h b/esphome/components/light/addressable_light_wrapper.h index cd5bcabd47..d358502430 100644 --- a/esphome/components/light/addressable_light_wrapper.h +++ b/esphome/components/light/addressable_light_wrapper.h @@ -16,24 +16,94 @@ class AddressableLightWrapper : public light::AddressableLight { void clear_effect_data() override { this->wrapper_state_[4] = 0; } - light::LightTraits get_traits() override { return this->light_state_->get_traits(); } + light::LightTraits get_traits() override { + LightTraits traits; + + // Choose which color mode to use. + // This is ordered by how closely each color mode matches the underlying RGBW data structure used in LightPartition. + ColorMode color_mode_precedence[] = {ColorMode::RGB_WHITE, + ColorMode::RGB_COLD_WARM_WHITE, + ColorMode::RGB_COLOR_TEMPERATURE, + ColorMode::RGB, + ColorMode::WHITE, + ColorMode::COLD_WARM_WHITE, + ColorMode::COLOR_TEMPERATURE, + ColorMode::BRIGHTNESS, + ColorMode::ON_OFF, + ColorMode::UNKNOWN}; + + LightTraits parent_traits = this->light_state_->get_traits(); + for (auto cm : color_mode_precedence) { + if (parent_traits.supports_color_mode(cm)) { + this->color_mode_ = cm; + break; + } + } + + // Report a color mode that's compatible with both the partition and the underlying light + switch (this->color_mode_) { + case ColorMode::RGB_WHITE: + case ColorMode::RGB_COLD_WARM_WHITE: + case ColorMode::RGB_COLOR_TEMPERATURE: + traits.set_supported_color_modes({light::ColorMode::RGB_WHITE}); + break; + + case ColorMode::RGB: + traits.set_supported_color_modes({light::ColorMode::RGB}); + break; + + case ColorMode::WHITE: + case ColorMode::COLD_WARM_WHITE: + case ColorMode::COLOR_TEMPERATURE: + case ColorMode::BRIGHTNESS: + traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS}); + break; + + case ColorMode::ON_OFF: + traits.set_supported_color_modes({light::ColorMode::ON_OFF}); + break; + + default: + traits.set_supported_color_modes({light::ColorMode::UNKNOWN}); + } + + return traits; + } void write_state(light::LightState *state) override { + // Don't overwrite state if the underlying light is turned on + if (this->light_state_->remote_values.is_on()) { + this->mark_shown_(); + return; + } + 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); + + float color_brightness = fmaxf(r, fmaxf(g, b)); + float brightness = fmaxf(color_brightness, w); + if (brightness == 0.0f) { + call.set_state(false); + } else { + color_brightness /= brightness; + w /= brightness; + + call.set_state(true); + call.set_color_mode_if_supported(this->color_mode_); + call.set_brightness_if_supported(brightness); + call.set_color_brightness_if_supported(color_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_warm_white_if_supported(w); + call.set_cold_white_if_supported(w); + } call.set_transition_length_if_supported(0); call.set_publish(false); call.set_save(false); @@ -50,6 +120,7 @@ class AddressableLightWrapper : public light::AddressableLight { light::LightState *light_state_; uint8_t *wrapper_state_; + ColorMode color_mode_{ColorMode::UNKNOWN}; }; } // namespace light From 882302450956b3070a13cd7bb701dba91b59f132 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Thu, 14 Oct 2021 11:24:57 +0200 Subject: [PATCH 182/549] Add pressure compensation during runtime (#2493) Co-authored-by: Oxan van Leeuwen --- esphome/components/scd4x/scd4x.cpp | 133 ++++++++++++++++++----------- esphome/components/scd4x/scd4x.h | 9 +- esphome/components/scd4x/sensor.py | 9 +- 3 files changed, 97 insertions(+), 54 deletions(-) diff --git a/esphome/components/scd4x/scd4x.cpp b/esphome/components/scd4x/scd4x.cpp index c91fd5e882..eacb39edf1 100644 --- a/esphome/components/scd4x/scd4x.cpp +++ b/esphome/components/scd4x/scd4x.cpp @@ -1,4 +1,5 @@ #include "scd4x.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" namespace esphome { @@ -38,6 +39,7 @@ void SCD4XComponent::setup() { return; } + uint32_t stop_measurement_delay = 0; // In order to query the device periodic measurement must be ceased if (raw_read_status[0]) { ESP_LOGD(TAG, "Sensor has data available, stopping periodic measurement"); @@ -46,68 +48,72 @@ void SCD4XComponent::setup() { this->mark_failed(); return; } + // According to the SCD4x datasheet the sensor will only respond to other commands after waiting 500 ms after + // issuing the stop_periodic_measurement command + stop_measurement_delay = 500; } + this->set_timeout(stop_measurement_delay, [this]() { + if (!this->write_command_(SCD4X_CMD_GET_SERIAL_NUMBER)) { + ESP_LOGE(TAG, "Failed to write get serial command"); + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } - if (!this->write_command_(SCD4X_CMD_GET_SERIAL_NUMBER)) { - ESP_LOGE(TAG, "Failed to write get serial command"); - this->error_code_ = COMMUNICATION_FAILED; - this->mark_failed(); - return; - } + uint16_t raw_serial_number[3]; + if (!this->read_data_(raw_serial_number, 3)) { + ESP_LOGE(TAG, "Failed to read serial number"); + this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED; + this->mark_failed(); + return; + } + ESP_LOGD(TAG, "Serial number %02d.%02d.%02d", (uint16_t(raw_serial_number[0]) >> 8), + uint16_t(raw_serial_number[0] & 0xFF), (uint16_t(raw_serial_number[1]) >> 8)); - uint16_t raw_serial_number[3]; - if (!this->read_data_(raw_serial_number, 3)) { - ESP_LOGE(TAG, "Failed to read serial number"); - this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED; - this->mark_failed(); - return; - } - ESP_LOGD(TAG, "Serial number %02d.%02d.%02d", (uint16_t(raw_serial_number[0]) >> 8), - uint16_t(raw_serial_number[0] & 0xFF), (uint16_t(raw_serial_number[1]) >> 8)); - - if (!this->write_command_(SCD4X_CMD_TEMPERATURE_OFFSET, - (uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) { - ESP_LOGE(TAG, "Error setting temperature offset."); - this->error_code_ = MEASUREMENT_INIT_FAILED; - this->mark_failed(); - return; - } - - // If pressure compensation available use it - // else use altitude - if (ambient_pressure_compensation_) { - if (!this->write_command_(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, ambient_pressure_compensation_)) { - ESP_LOGE(TAG, "Error setting ambient pressure compensation."); + if (!this->write_command_(SCD4X_CMD_TEMPERATURE_OFFSET, + (uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) { + ESP_LOGE(TAG, "Error setting temperature offset."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); return; } - } else { - if (!this->write_command_(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { - ESP_LOGE(TAG, "Error setting altitude compensation."); + + // If pressure compensation available use it + // else use altitude + if (ambient_pressure_compensation_) { + if (!this->update_ambient_pressure_compensation_(ambient_pressure_)) { + ESP_LOGE(TAG, "Error setting ambient pressure compensation."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + } else { + if (!this->write_command_(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { + ESP_LOGE(TAG, "Error setting altitude compensation."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + } + + if (!this->write_command_(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) { + ESP_LOGE(TAG, "Error setting automatic self calibration."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); return; } - } - if (!this->write_command_(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) { - ESP_LOGE(TAG, "Error setting automatic self calibration."); - this->error_code_ = MEASUREMENT_INIT_FAILED; - this->mark_failed(); - return; - } + // Finally start sensor measurements + if (!this->write_command_(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) { + ESP_LOGE(TAG, "Error starting continuous measurements."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } - // Finally start sensor measurements - if (!this->write_command_(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) { - ESP_LOGE(TAG, "Error starting continuous measurements."); - this->error_code_ = MEASUREMENT_INIT_FAILED; - this->mark_failed(); - return; - } - - initialized_ = true; - ESP_LOGD(TAG, "Sensor initialized"); + initialized_ = true; + ESP_LOGD(TAG, "Sensor initialized"); + }); }); } @@ -150,6 +156,13 @@ void SCD4XComponent::update() { return; } + if (this->ambient_pressure_source_ != nullptr) { + float pressure = this->ambient_pressure_source_->state / 1000.0f; + if (!std::isnan(pressure)) { + set_ambient_pressure_compensation(this->ambient_pressure_source_->state / 1000.0f); + } + } + // Check if data is ready if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) { this->status_set_warning(); @@ -191,6 +204,28 @@ void SCD4XComponent::update() { this->status_clear_warning(); } +// Note pressure in bar here. Convert to hPa +void SCD4XComponent::set_ambient_pressure_compensation(float pressure_in_bar) { + ambient_pressure_compensation_ = true; + uint16_t new_ambient_pressure = (uint16_t)(pressure_in_bar * 1000); + // remove millibar from comparison to avoid frequent updates +/- 10 millibar doesn't matter + if (initialized_ && (new_ambient_pressure / 10 != ambient_pressure_ / 10)) { + update_ambient_pressure_compensation_(new_ambient_pressure); + ambient_pressure_ = new_ambient_pressure; + } else { + ESP_LOGD(TAG, "ambient pressure compensation skipped - no change required"); + } +} + +bool SCD4XComponent::update_ambient_pressure_compensation_(uint16_t pressure_in_hpa) { + if (this->write_command_(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, pressure_in_hpa)) { + ESP_LOGD(TAG, "setting ambient pressure compensation to %d hPa", pressure_in_hpa); + return true; + } else { + ESP_LOGE(TAG, "Error setting ambient pressure compensation."); + return false; + } +} uint8_t SCD4XComponent::sht_crc_(uint8_t data1, uint8_t data2) { uint8_t bit; diff --git a/esphome/components/scd4x/scd4x.h b/esphome/components/scd4x/scd4x.h index 3c428b8623..4fe2bf14cc 100644 --- a/esphome/components/scd4x/scd4x.h +++ b/esphome/components/scd4x/scd4x.h @@ -18,10 +18,8 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { void set_automatic_self_calibration(bool asc) { enable_asc_ = asc; } void set_altitude_compensation(uint16_t altitude) { altitude_compensation_ = altitude; } - void set_ambient_pressure_compensation(float pressure) { - ambient_pressure_compensation_ = true; - ambient_pressure_ = (uint16_t)(pressure * 1000); - } + void set_ambient_pressure_compensation(float pressure_in_bar); + void set_ambient_pressure_source(sensor::Sensor *pressure) { ambient_pressure_source_ = pressure; } void set_temperature_offset(float offset) { temperature_offset_ = offset; }; void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; } @@ -33,6 +31,7 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { bool read_data_(uint16_t *data, uint8_t len); bool write_command_(uint16_t command); bool write_command_(uint16_t command, uint16_t data); + bool update_ambient_pressure_compensation_(uint16_t pressure_in_hpa); ERRORCODE error_code_; @@ -47,6 +46,8 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *co2_sensor_{nullptr}; sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; + // used for compensation + sensor::Sensor *ambient_pressure_source_{nullptr}; }; } // namespace scd4x diff --git a/esphome/components/scd4x/sensor.py b/esphome/components/scd4x/sensor.py index 0b1a960f6f..3e814ffe78 100644 --- a/esphome/components/scd4x/sensor.py +++ b/esphome/components/scd4x/sensor.py @@ -29,6 +29,7 @@ CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" CONF_ALTITUDE_COMPENSATION = "altitude_compensation" CONF_AMBIENT_PRESSURE_COMPENSATION = "ambient_pressure_compensation" CONF_TEMPERATURE_OFFSET = "temperature_offset" +CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE = "ambient_pressure_compensation_source" CONFIG_SCHEMA = ( cv.Schema( @@ -62,6 +63,9 @@ CONFIG_SCHEMA = ( ), cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION): cv.pressure, cv.Optional(CONF_TEMPERATURE_OFFSET, default="4°C"): cv.temperature, + cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE): cv.use_id( + sensor.Sensor + ), } ) .extend(cv.polling_component_schema("60s")) @@ -92,7 +96,10 @@ async def to_code(config): cg.add(getattr(var, funcName)(config[key])) for key, funcName in SENSOR_MAP.items(): - if key in config: sens = await sensor.new_sensor(config[key]) cg.add(getattr(var, funcName)(sens)) + + if CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE in config: + sens = await cg.get_variable(config[CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE]) + cg.add(var.set_ambient_pressure_source(sens)) From 63d6b610b82f77b88d42dc0865c1b7ae32709967 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 14 Oct 2021 11:25:10 +0200 Subject: [PATCH 183/549] Don't define UART_SELECTION_UART2 when UART2 is unavailable (#2512) --- esphome/components/logger/logger.cpp | 4 ++-- esphome/components/logger/logger.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index b38c7f1a69..97ad4c2cb9 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -153,7 +153,7 @@ void Logger::pre_setup() { case UART_SELECTION_UART1: this->hw_serial_ = &Serial1; break; -#if defined(USE_ESP32) && !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32S2 +#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) case UART_SELECTION_UART2: this->hw_serial_ = &Serial2; break; @@ -169,7 +169,7 @@ void Logger::pre_setup() { case UART_SELECTION_UART1: uart_num_ = UART_NUM_1; break; -#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32S2 +#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) case UART_SELECTION_UART2: uart_num_ = UART_NUM_2; break; diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index e6fa6e2058..8756bc2387 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -24,7 +24,7 @@ namespace logger { enum UARTSelection { UART_SELECTION_UART0 = 0, UART_SELECTION_UART1, -#ifdef USE_ESP32 +#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) UART_SELECTION_UART2, #endif #ifdef USE_ESP8266 From 1308236429d1564ed7cb14ee38c5d904f0c22916 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 14 Oct 2021 23:31:52 +1300 Subject: [PATCH 184/549] Revert "Added test for bme680_bsec" (#2518) This reverts commit 7f6a50d291b14935b17802b4dce52135fad1e49e due to BSEC library license restrictions. --- tests/test1.yaml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/test1.yaml b/tests/test1.yaml index 62fc781eca..157ccfc5d1 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -265,9 +265,6 @@ wled: adalight: -bme680_bsec: - i2c_id: i2c_bus - esp32_ble_tracker: ble_client: @@ -481,19 +478,6 @@ sensor: duration: 150ms update_interval: 15s i2c_id: i2c_bus - - platform: bme680_bsec - temperature: - name: "BME680 Temperature" - pressure: - name: "BME680 Pressure" - humidity: - name: "BME680 Humidity" - iaq: - name: "BME680 IAQ" - co2_equivalent: - name: "BME680 CO2 Equivalent" - breath_voc_equivalent: - name: "BME680 Breath VOC Equivalent" - platform: bmp085 temperature: name: 'Outside Temperature' From 7178f10bdaa793401ff2c2fc376e7b7d12fd91bc Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Fri, 15 Oct 2021 02:26:26 -0500 Subject: [PATCH 185/549] Fix Nextion HTTPClient error for ESP32 (#2524) --- esphome/components/nextion/display.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index f4b35fd56f..d95810bfbe 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -8,7 +8,7 @@ from esphome.const import ( CONF_BRIGHTNESS, CONF_TRIGGER_ID, ) - +from esphome.core import CORE from . import Nextion, nextion_ns, nextion_ref from .base_component import ( CONF_ON_SLEEP, @@ -76,6 +76,9 @@ async def to_code(config): if CONF_TFT_URL in config: cg.add_define("USE_NEXTION_TFT_UPLOAD") cg.add(var.set_tft_url(config[CONF_TFT_URL])) + if CORE.is_esp32: + cg.add_library("WiFiClientSecure", None) + cg.add_library("HTTPClient", None) if CONF_TOUCH_SLEEP_TIMEOUT in config: cg.add(var.set_touch_sleep_timeout_internal(config[CONF_TOUCH_SLEEP_TIMEOUT])) From 6beb9e568a3108a27a980478e7b5388e0ae4cad7 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Fri, 15 Oct 2021 09:27:56 +0200 Subject: [PATCH 186/549] Fix bug in register name definition (#2526) --- esphome/components/modbus_controller/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 7a69029dab..6b452ea25c 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -38,7 +38,7 @@ ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType") ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType") MODBUS_REGISTER_TYPE = { "coil": ModbusRegisterType.COIL, - "discrete_input": ModbusRegisterType.DISCRETE, + "discrete_input": ModbusRegisterType.DISCRETE_INPUT, "holding": ModbusRegisterType.HOLDING, "read": ModbusRegisterType.READ, } From dc15d1c8ecea36a21cf648322385a42c9f5fe703 Mon Sep 17 00:00:00 2001 From: Dmitriy Lopatko Date: Fri, 15 Oct 2021 16:52:03 +0200 Subject: [PATCH 187/549] use no hold master mode for si7021/htu21d (#2528) --- esphome/components/htu21d/htu21d.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/htu21d/htu21d.cpp b/esphome/components/htu21d/htu21d.cpp index b53284ae3f..a38ec73019 100644 --- a/esphome/components/htu21d/htu21d.cpp +++ b/esphome/components/htu21d/htu21d.cpp @@ -9,8 +9,8 @@ static const char *const TAG = "htu21d"; static const uint8_t HTU21D_ADDRESS = 0x40; static const uint8_t HTU21D_REGISTER_RESET = 0xFE; -static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xE3; -static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xE5; +static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xF3; +static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xF5; static const uint8_t HTU21D_REGISTER_STATUS = 0xE7; void HTU21DComponent::setup() { From 935992bcb32bad838aa90d7eab03dcd6f749d9fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Oct 2021 19:38:09 +0200 Subject: [PATCH 188/549] Bump pyyaml from 5.4.1 to 6.0 (#2521) Bumps [pyyaml](https://github.com/yaml/pyyaml) from 5.4.1 to 6.0. - [Release notes](https://github.com/yaml/pyyaml/releases) - [Changelog](https://github.com/yaml/pyyaml/blob/master/CHANGES) - [Commits](https://github.com/yaml/pyyaml/compare/5.4.1...6.0) --- updated-dependencies: - dependency-name: pyyaml 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 23a00d3755..b21bed87a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ voluptuous==0.12.2 -PyYAML==5.4.1 +PyYAML==6.0 paho-mqtt==1.5.1 colorama==0.4.4 tornado==6.1 From 85d2f24447dc79029b715d6082add1935d53b1ca Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Fri, 15 Oct 2021 19:51:42 +0200 Subject: [PATCH 189/549] Clarify statement at the cmd wizard tool, for new users (#2519) * Clarify next steps for the install wizard * Update wizard.py * Link to relevant section of guide * Formatting Co-authored-by: Oxan van Leeuwen --- esphome/wizard.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/wizard.py b/esphome/wizard.py index 5c35fac73a..6c87b66453 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -367,10 +367,9 @@ def wizard(path): ) safe_print() safe_print("Next steps:") + safe_print(" > Follow the rest of the getting started guide:") safe_print( - ' > Check your Home Assistant "integrations" screen. If all goes well, you ' - "should see your ESP being discovered automatically." + " > https://esphome.io/guides/getting_started_command_line.html#adding-some-features" ) - safe_print(" > Then follow the rest of the getting started guide:") - safe_print(" > https://esphome.io/guides/getting_started_command_line.html") + safe_print(" > to learn how to customize ESPHome and install it to your device.") return 0 From 884b7201de5256cd5692d1c992654d429866f53a Mon Sep 17 00:00:00 2001 From: Tom Matheussen Date: Fri, 15 Oct 2021 20:46:58 +0200 Subject: [PATCH 190/549] Continue ethernet setup if hostname fails (#2430) --- esphome/components/ethernet/ethernet_component.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index d55db0a7d8..00f68df2b4 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -168,7 +168,9 @@ void EthernetComponent::start_connect_() { 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"); + if (err != ERR_OK) { + ESP_LOGW(TAG, "tcpip_adapter_set_hostname failed: %s", esp_err_to_name(err)); + } tcpip_adapter_ip_info_t info; if (this->manual_ip_.has_value()) { From 653a3d5d11a42e93158e7b65058806197713cc97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Oct 2021 22:05:04 +0200 Subject: [PATCH 191/549] Bump aioesphomeapi from 9.1.5 to 10.0.0 (#2508) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen --- esphome/components/api/client.py | 1 - requirements.txt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index 4a3944d33e..b2920f239b 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -23,7 +23,6 @@ async def async_run_logs(config, address): _LOGGER.info("Starting log output from %s using esphome API", address) zc = zeroconf.Zeroconf() cli = APIClient( - asyncio.get_event_loop(), address, port, password, diff --git a/requirements.txt b/requirements.txt index b21bed87a6..afc75efcc7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.1 esptool==3.1 click==8.0.3 esphome-dashboard==20211011.1 -aioesphomeapi==9.1.5 +aioesphomeapi==10.0.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From c82d5d63e37cb2eda25f5316160cfbebd0a06381 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Fri, 15 Oct 2021 22:05:11 +0200 Subject: [PATCH 192/549] Move TemplatableValue helper class to automation.h (#2511) --- .../components/api/homeassistant_service.h | 16 +++++- esphome/core/automation.h | 50 +++++++++++++--- esphome/core/helpers.h | 57 ------------------- 3 files changed, 58 insertions(+), 65 deletions(-) diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index 8a72765195..26269bcae4 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -8,6 +8,18 @@ namespace esphome { namespace api { +template class TemplatableStringValue : public TemplatableValue { + public: + TemplatableStringValue() : TemplatableValue() {} + + template::value, int> = 0> + TemplatableStringValue(F value) : TemplatableValue(value) {} + + template::value, int> = 0> + TemplatableStringValue(F f) + : TemplatableValue([f](X... x) -> std::string { return to_string(f(x...)); }) {} +}; + template class TemplatableKeyValuePair { public: template TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {} @@ -19,7 +31,8 @@ template class HomeAssistantServiceCallAction : public Action void set_service(T service) { this->service_ = service; } + template void add_data(std::string key, T value) { this->data_.push_back(TemplatableKeyValuePair(key, value)); } @@ -58,6 +71,7 @@ template class HomeAssistantServiceCallAction : public Action service_{}; std::vector> data_; std::vector> data_template_; std::vector> variables_; diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 6d79480f0f..e5460bef34 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -17,14 +17,50 @@ namespace esphome { #define TEMPLATABLE_VALUE(type, name) TEMPLATABLE_VALUE_(type, name) -#define TEMPLATABLE_STRING_VALUE_(name) \ - protected: \ - TemplatableStringValue name##_{}; \ -\ - public: \ - template void set_##name(V name) { this->name##_ = name; } +template class TemplatableValue { + public: + TemplatableValue() : type_(EMPTY) {} -#define TEMPLATABLE_STRING_VALUE(name) TEMPLATABLE_STRING_VALUE_(name) + template::value, int> = 0> + TemplatableValue(F value) : type_(VALUE), value_(value) {} + + template::value, int> = 0> + TemplatableValue(F f) : type_(LAMBDA), f_(f) {} + + bool has_value() { return this->type_ != EMPTY; } + + T value(X... x) { + if (this->type_ == LAMBDA) { + return this->f_(x...); + } + // return value also when empty + return this->value_; + } + + optional optional_value(X... x) { + if (!this->has_value()) { + return {}; + } + return this->value(x...); + } + + T value_or(X... x, T default_value) { + if (!this->has_value()) { + return default_value; + } + return this->value(x...); + } + + protected: + enum { + EMPTY, + VALUE, + LAMBDA, + } type_; + + T value_{}; + std::function f_{}; +}; /** Base class for all automation conditions. * diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 61cc9a9e4a..905cb76170 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -255,63 +255,6 @@ struct is_callable // NOLINT static constexpr auto value = decltype(test(nullptr))::value; // NOLINT }; -template class TemplatableValue { - public: - TemplatableValue() : type_(EMPTY) {} - - template::value, int> = 0> - TemplatableValue(F value) : type_(VALUE), value_(value) {} - - template::value, int> = 0> - TemplatableValue(F f) : type_(LAMBDA), f_(f) {} - - bool has_value() { return this->type_ != EMPTY; } - - T value(X... x) { - if (this->type_ == LAMBDA) { - return this->f_(x...); - } - // return value also when empty - return this->value_; - } - - optional optional_value(X... x) { - if (!this->has_value()) { - return {}; - } - return this->value(x...); - } - - T value_or(X... x, T default_value) { - if (!this->has_value()) { - return default_value; - } - return this->value(x...); - } - - protected: - enum { - EMPTY, - VALUE, - LAMBDA, - } type_; - - T value_{}; - std::function f_{}; -}; - -template class TemplatableStringValue : public TemplatableValue { - public: - TemplatableStringValue() : TemplatableValue() {} - - template::value, int> = 0> - TemplatableStringValue(F value) : TemplatableValue(value) {} - - template::value, int> = 0> - TemplatableStringValue(F f) - : TemplatableValue([f](X... x) -> std::string { return to_string(f(x...)); }) {} -}; - void delay_microseconds_accurate(uint32_t usec); template class Deduplicator { From 384f8d97d8d6ef665c72d635ce1e04c108fa2cff Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Fri, 15 Oct 2021 22:06:32 +0200 Subject: [PATCH 193/549] OTA firmware MD5 check + password support for esp-idf (#2507) Co-authored-by: Maurice Makaay --- CODEOWNERS | 1 + esphome/components/md5/__init__.py | 1 + esphome/components/md5/md5.cpp | 51 ++++++++++++++++ esphome/components/md5/md5.h | 58 +++++++++++++++++++ esphome/components/ota/__init__.py | 12 +--- .../ota/ota_backend_arduino_esp32.h | 1 + .../components/ota/ota_backend_esp_idf.cpp | 12 +++- esphome/components/ota/ota_backend_esp_idf.h | 3 + esphome/components/ota/ota_component.cpp | 27 ++++----- esphome/core/defines.h | 1 + 10 files changed, 139 insertions(+), 28 deletions(-) create mode 100644 esphome/components/md5/__init__.py create mode 100644 esphome/components/md5/md5.cpp create mode 100644 esphome/components/md5/md5.h diff --git a/CODEOWNERS b/CODEOWNERS index 4c3084d463..a7cf3a1b68 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -89,6 +89,7 @@ esphome/components/mcp23x17_base/* @jesserockz esphome/components/mcp23xxx_base/* @jesserockz esphome/components/mcp2515/* @danielschramm @mvturnho esphome/components/mcp9808/* @k7hpn +esphome/components/md5/* @esphome/core esphome/components/mdns/* @esphome/core esphome/components/midea/* @dudanov esphome/components/mitsubishi/* @RubyBailey diff --git a/esphome/components/md5/__init__.py b/esphome/components/md5/__init__.py new file mode 100644 index 0000000000..f70ffa9520 --- /dev/null +++ b/esphome/components/md5/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@esphome/core"] diff --git a/esphome/components/md5/md5.cpp b/esphome/components/md5/md5.cpp new file mode 100644 index 0000000000..c6ff783439 --- /dev/null +++ b/esphome/components/md5/md5.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "md5.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace md5 { + +void MD5Digest::init() { + memset(this->digest_, 0, 16); + MD5Init(&this->ctx_); +} + +void MD5Digest::add(const uint8_t *data, size_t len) { MD5Update(&this->ctx_, data, len); } + +void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); } + +void MD5Digest::get_bytes(uint8_t *output) { memcpy(output, this->digest_, 16); } + +void MD5Digest::get_hex(char *output) { + for (size_t i = 0; i < 16; i++) { + sprintf(output + i * 2, "%02x", this->digest_[i]); + } +} + +bool MD5Digest::equals_bytes(const char *expected) { + for (size_t i = 0; i < 16; i++) { + if (expected[i] != this->digest_[i]) { + return false; + } + } + return true; +} + +bool MD5Digest::equals_hex(const char *expected) { + for (size_t i = 0; i < 16; i++) { + auto high = parse_hex(expected[i * 2]); + auto low = parse_hex(expected[i * 2 + 1]); + if (!high.has_value() || !low.has_value()) { + return false; + } + auto value = (*high << 4) | *low; + if (value != this->digest_[i]) { + return false; + } + } + return true; +} + +} // namespace md5 +} // namespace esphome diff --git a/esphome/components/md5/md5.h b/esphome/components/md5/md5.h new file mode 100644 index 0000000000..e40f419347 --- /dev/null +++ b/esphome/components/md5/md5.h @@ -0,0 +1,58 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_ESP_IDF +#include "esp32/rom/md5_hash.h" +#define MD5_CTX_TYPE MD5Context +#endif + +#if defined(USE_ARDUINO) && defined(USE_ESP32) +#include "rom/md5_hash.h" +#define MD5_CTX_TYPE MD5Context +#endif + +#if defined(USE_ARDUINO) && defined(USE_ESP8266) +#include +#define MD5_CTX_TYPE md5_context_t +#endif + +namespace esphome { +namespace md5 { + +class MD5Digest { + public: + MD5Digest() = default; + ~MD5Digest() = default; + + /// Initialize a new MD5 digest computation. + void init(); + + /// Add bytes of data for the digest. + void add(const uint8_t *data, size_t len); + void add(const char *data, size_t len) { this->add((const uint8_t *) data, len); } + + /// Compute the digest, based on the provided data. + void calculate(); + + /// Retrieve the MD5 digest as bytes. + /// The output must be able to hold 16 bytes or more. + void get_bytes(uint8_t *output); + + /// Retrieve the MD5 digest as hex characters. + /// The output must be able to hold 32 bytes or more. + void get_hex(char *output); + + /// Compare the digest against a provided byte-encoded digest (16 bytes). + bool equals_bytes(const char *expected); + + /// Compare the digest against a provided hex-encoded digest (32 bytes). + bool equals_hex(const char *expected); + + protected: + MD5_CTX_TYPE ctx_{}; + uint8_t digest_[16]; +}; + +} // namespace md5 +} // namespace esphome diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index bcfb28979d..53b282c43e 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -15,7 +15,7 @@ from esphome.core import CORE, coroutine_with_priority CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] -AUTO_LOAD = ["socket"] +AUTO_LOAD = ["socket", "md5"] CONF_ON_STATE_CHANGE = "on_state_change" CONF_ON_BEGIN = "on_begin" @@ -35,20 +35,12 @@ 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): cv.All(cv.string, validate_password_support), + cv.Optional(CONF_PASSWORD): cv.string, cv.Optional( CONF_REBOOT_TIMEOUT, default="5min" ): cv.positive_time_period_milliseconds, diff --git a/esphome/components/ota/ota_backend_arduino_esp32.h b/esphome/components/ota/ota_backend_arduino_esp32.h index 8343bdf94f..6b712502fb 100644 --- a/esphome/components/ota/ota_backend_arduino_esp32.h +++ b/esphome/components/ota/ota_backend_arduino_esp32.h @@ -9,6 +9,7 @@ namespace esphome { namespace ota { class ArduinoESP32OTABackend : public OTABackend { + public: OTAResponseTypes begin(size_t image_size) override; void set_update_md5(const char *md5) override; OTAResponseTypes write(uint8_t *data, size_t len) override; diff --git a/esphome/components/ota/ota_backend_esp_idf.cpp b/esphome/components/ota/ota_backend_esp_idf.cpp index 4eb17d82f1..336b3798d9 100644 --- a/esphome/components/ota/ota_backend_esp_idf.cpp +++ b/esphome/components/ota/ota_backend_esp_idf.cpp @@ -4,6 +4,7 @@ #include "ota_backend_esp_idf.h" #include "ota_component.h" #include +#include "esphome/components/md5/md5.h" namespace esphome { namespace ota { @@ -24,15 +25,15 @@ OTAResponseTypes IDFOTABackend::begin(size_t image_size) { } return OTA_RESPONSE_ERROR_UNKNOWN; } + this->md5_.init(); return OTA_RESPONSE_OK; } -void IDFOTABackend::set_update_md5(const char *md5) { - // pass -} +void IDFOTABackend::set_update_md5(const char *expected_md5) { memcpy(this->expected_bin_md5_, expected_md5, 32); } OTAResponseTypes IDFOTABackend::write(uint8_t *data, size_t len) { esp_err_t err = esp_ota_write(this->update_handle_, data, len); + this->md5_.add(data, len); if (err != ESP_OK) { if (err == ESP_ERR_OTA_VALIDATE_FAILED) { return OTA_RESPONSE_ERROR_MAGIC; @@ -45,6 +46,11 @@ OTAResponseTypes IDFOTABackend::write(uint8_t *data, size_t len) { } OTAResponseTypes IDFOTABackend::end() { + this->md5_.calculate(); + if (!this->md5_.equals_hex(this->expected_bin_md5_)) { + this->abort(); + return OTA_RESPONSE_ERROR_UPDATE_END; + } esp_err_t err = esp_ota_end(this->update_handle_); this->update_handle_ = 0; if (err == ESP_OK) { diff --git a/esphome/components/ota/ota_backend_esp_idf.h b/esphome/components/ota/ota_backend_esp_idf.h index d6e2e2742a..49c6e124fa 100644 --- a/esphome/components/ota/ota_backend_esp_idf.h +++ b/esphome/components/ota/ota_backend_esp_idf.h @@ -5,6 +5,7 @@ #include "ota_component.h" #include "ota_backend.h" #include +#include "esphome/components/md5/md5.h" namespace esphome { namespace ota { @@ -20,6 +21,8 @@ class IDFOTABackend : public OTABackend { private: esp_ota_handle_t update_handle_{0}; const esp_partition_t *partition_; + md5::MD5Digest md5_{}; + char expected_bin_md5_[32]; }; } // namespace ota diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 9ad3814f5c..89bee17452 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -8,15 +8,12 @@ #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/util.h" +#include "esphome/components/md5/md5.h" #include "esphome/components/network/util.h" #include #include -#ifdef USE_OTA_PASSWORD -#include -#endif - namespace esphome { namespace ota { @@ -173,12 +170,12 @@ void OTAComponent::handle_() { if (!this->password_.empty()) { buf[0] = OTA_RESPONSE_REQUEST_AUTH; this->writeall_(buf, 1); - MD5Builder md5_builder{}; - md5_builder.begin(); + md5::MD5Digest md5{}; + md5.init(); sprintf(sbuf, "%08X", random_uint32()); - md5_builder.add(sbuf); - md5_builder.calculate(); - md5_builder.getChars(sbuf); + md5.add(sbuf, 8); + md5.calculate(); + md5.get_hex(sbuf); ESP_LOGV(TAG, "Auth: Nonce is %s", sbuf); // Send nonce, 32 bytes hex MD5 @@ -188,10 +185,10 @@ void OTAComponent::handle_() { } // prepare challenge - md5_builder.begin(); - md5_builder.add(this->password_.c_str()); + md5.init(); + md5.add(this->password_.c_str(), this->password_.length()); // add nonce - md5_builder.add(sbuf); + md5.add(sbuf, 32); // Receive cnonce, 32 bytes hex MD5 if (!this->readall_(buf, 32)) { @@ -201,11 +198,11 @@ void OTAComponent::handle_() { sbuf[32] = '\0'; ESP_LOGV(TAG, "Auth: CNonce is %s", sbuf); // add cnonce - md5_builder.add(sbuf); + md5.add(sbuf, 32); // calculate result - md5_builder.calculate(); - md5_builder.getChars(sbuf); + md5.calculate(); + md5.get_hex(sbuf); ESP_LOGV(TAG, "Auth: Result is %s", sbuf); // Receive result, 32 bytes hex MD5 diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 7c2261920a..b44987a768 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -26,6 +26,7 @@ #define USE_LOGGER #define USE_MDNS #define USE_NUMBER +#define USE_OTA_PASSWORD #define USE_OTA_STATE_CALLBACK #define USE_POWER_SUPPLY #define USE_PROMETHEUS From 7cca67390201f391198f063baf5cd897dd99adac Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Fri, 15 Oct 2021 22:06:49 +0200 Subject: [PATCH 194/549] [esp-idf fix] increase FreeRTOS ticker loop from 100Hz to 1kHz (#2527) Co-authored-by: Otto Winter --- esphome/components/esp32/__init__.py | 2 ++ sdkconfig.defaults | 1 + 2 files changed, 3 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 09eabe1fa7..68653d68ff 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -302,6 +302,8 @@ async def to_code(config): ) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True) + # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms + add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000) cg.add_platformio_option("board_build.partitions", "partitions.csv") diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 6b2d6f8f2e..26db4705b8 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -8,6 +8,7 @@ CONFIG_COMPILER_OPTIMIZATION_SIZE=y CONFIG_PARTITION_TABLE_CUSTOM=y #CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_SINGLE_APP=n +CONFIG_FREERTOS_HZ=1000 # esp32_ble CONFIG_BT_ENABLED=y From 94d518a41871f941944eeaf66ceed4d497666fc2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Fri, 15 Oct 2021 22:07:05 +0200 Subject: [PATCH 195/549] Replace framework version_hint with source option (#2529) --- esphome/components/esp32/__init__.py | 134 +++++++++++-------------- esphome/components/esp8266/__init__.py | 75 ++++++-------- esphome/core/config.py | 11 +- 3 files changed, 97 insertions(+), 123 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 68653d68ff..8a4b8b478a 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -7,6 +7,7 @@ from esphome.helpers import write_file_if_changed from esphome.const import ( CONF_BOARD, CONF_FRAMEWORK, + CONF_SOURCE, CONF_TYPE, CONF_VARIANT, CONF_VERSION, @@ -53,7 +54,7 @@ def set_core_data(config): 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] + config[CONF_FRAMEWORK][CONF_VERSION] ) CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD] CORE.data[KEY_ESP32][KEY_VARIANT] = config[CONF_VARIANT] @@ -94,6 +95,13 @@ def _format_framework_arduino_version(ver: cv.Version) -> str: return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" +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" + + # 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 @@ -123,119 +131,97 @@ 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, - ), + "dev": (cv.Version(2, 0, 0), "https://github.com/espressif/arduino-esp32.git"), + "latest": (cv.Version(1, 0, 6), None), + "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), } - 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] + + if value[CONF_VERSION] in lookups: + if CONF_SOURCE in value: + raise cv.Invalid( + "Framework version needs to be explicitly specified when custom source is used." + ) + + version, source = lookups[value[CONF_VERSION]] 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 + version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) + source = value.get(CONF_SOURCE, None) - 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") + value[CONF_VERSION] = str(version) + value[CONF_SOURCE] = source or _format_framework_arduino_version(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) + platform_version = value.get(CONF_PLATFORM_VERSION, ARDUINO_PLATFORM_VERSION) + value[CONF_PLATFORM_VERSION] = str(platform_version) - if cv.Version.parse(ver_hint_s) != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: + if version != 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" + "The selected Arduino framework version is not the recommended one. " + "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, - ), + "dev": (cv.Version(4, 3, 1), "https://github.com/espressif/esp-idf.git"), + "latest": (cv.Version(4, 3, 0), None), + "recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None), } - 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] + + if value[CONF_VERSION] in lookups: + if CONF_SOURCE in value: + raise cv.Invalid( + "Framework version needs to be explicitly specified when custom source is used." + ) + + version, source = lookups[value[CONF_VERSION]] 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 + version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) + source = value.get(CONF_SOURCE, None) - 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") + if version < cv.Version(4, 0, 0): + raise cv.Invalid("Only ESP-IDF 4.0+ is supported.") - 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: + value[CONF_VERSION] = str(version) + value[CONF_SOURCE] = source or _format_framework_espidf_version(version) + + platform_version = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION) + value[CONF_PLATFORM_VERSION] = str(platform_version) + + if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: _LOGGER.warning( - "The selected esp-idf framework version is not the recommended one" + "The selected ESP-IDF framework version is not the recommended one. " + "If there are connectivity or build issues please remove the manual version." ) - _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_SOURCE): cv.string_strict, 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_SOURCE): cv.string_strict, + cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { cv.string_strict: cv.string_strict }, - cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, cv.Optional(CONF_ADVANCED, default={}): cv.Schema( { cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean, @@ -293,7 +279,7 @@ async def to_code(config): cg.add_build_flag("-Wno-nonnull-compare") cg.add_platformio_option( "platform_packages", - [f"platformio/framework-espidf @ {conf[CONF_VERSION]}"], + [f"platformio/framework-espidf @ {conf[CONF_SOURCE]}"], ) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) @@ -325,7 +311,7 @@ async def to_code(config): cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") cg.add_platformio_option( "platform_packages", - [f"platformio/framework-arduinoespressif32 @ {conf[CONF_VERSION]}"], + [f"platformio/framework-arduinoespressif32 @ {conf[CONF_SOURCE]}"], ) cg.add_platformio_option("board_build.partitions", "partitions.csv") diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 93a461ba1f..a5323db8bf 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -4,6 +4,7 @@ from esphome.const import ( CONF_BOARD, CONF_BOARD_FLASH_MODE, CONF_FRAMEWORK, + CONF_SOURCE, CONF_VERSION, KEY_CORE, KEY_FRAMEWORK_VERSION, @@ -31,7 +32,7 @@ def set_core_data(config): 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] + config[CONF_FRAMEWORK][CONF_VERSION] ) CORE.data[KEY_ESP8266][KEY_BOARD] = config[CONF_BOARD] return config @@ -70,66 +71,50 @@ 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, - ), + "dev": (cv.Version(3, 0, 2), "https://github.com/esp8266/Arduino.git"), + "latest": (cv.Version(3, 0, 2), None), + "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), } - 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] + + if value[CONF_VERSION] in lookups: + if CONF_SOURCE in value: + raise cv.Invalid( + "Framework version needs to be explicitly specified when custom source is used." + ) + + version, source = lookups[value[CONF_VERSION]] 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) + version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) + source = value.get(CONF_SOURCE, None) - value[CONF_VERSION] = ver_value + value[CONF_VERSION] = str(version) + value[CONF_SOURCE] = source or _format_framework_arduino_version(version) - 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 + platform_version = value.get(CONF_PLATFORM_VERSION) + if platform_version is None: + if version >= cv.Version(3, 0, 0): + platform_version = ARDUINO_3_PLATFORM_VERSION + elif version >= cv.Version(2, 5, 0): + platform_version = ARDUINO_2_PLATFORM_VERSION else: - plat_ver = cv.Version(1, 8, 0) - value[CONF_PLATFORM_VERSION] = str(plat_ver) + platform_version = cv.Version(1, 8, 0) + value[CONF_PLATFORM_VERSION] = str(platform_version) - if cv.Version.parse(ver_hint_s) != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: + if version != 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" + "The selected Arduino framework version is not the recommended one. " + "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_SOURCE): cv.string_strict, cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, } ), @@ -167,7 +152,7 @@ async def to_code(config): cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO") cg.add_platformio_option( "platform_packages", - [f"platformio/framework-arduinoespressif8266 @ {conf[CONF_VERSION]}"], + [f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"], ) cg.add_platformio_option( "platform", f"platformio/espressif8266 @ {conf[CONF_PLATFORM_VERSION]}" diff --git a/esphome/core/config.py b/esphome/core/config.py index bbdfcf124c..c495fefddd 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -23,6 +23,7 @@ from esphome.const import ( CONF_PLATFORMIO_OPTIONS, CONF_PRIORITY, CONF_PROJECT, + CONF_SOURCE, CONF_TRIGGER_ID, CONF_TYPE, CONF_VERSION, @@ -181,10 +182,12 @@ def preload_core_config(config, result): 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), - } + plat_conf[CONF_FRAMEWORK] = {CONF_TYPE: "arduino"} + try: + cv.Version.parse(conf[CONF_ARDUINO_VERSION]) + plat_conf[CONF_FRAMEWORK][CONF_VERSION] = conf.pop(CONF_ARDUINO_VERSION) + except ValueError: + plat_conf[CONF_FRAMEWORK][CONF_SOURCE] = 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 From 65d2b37496577b35d6b2a73931731f6d3a8bca83 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 17 Oct 2021 08:53:49 +0200 Subject: [PATCH 196/549] Fix bitshift on read in ADE7953 (#2537) --- esphome/components/ade7953/ade7953.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/ade7953/ade7953.h b/esphome/components/ade7953/ade7953.h index c6fb383ed8..bb160cd8eb 100644 --- a/esphome/components/ade7953/ade7953.h +++ b/esphome/components/ade7953/ade7953.h @@ -76,9 +76,9 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent { 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; + *value |= ((uint32_t) recv[1]) << 16; + *value |= ((uint32_t) recv[2]) << 8; + *value |= ((uint32_t) recv[3]); return i2c::ERROR_OK; } From 0991ab3543d67c5d7dfbe64bcf4aedd44cb5430f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sun, 17 Oct 2021 19:54:09 +1300 Subject: [PATCH 197/549] Allow downloading all bin files from backend in dashboard (#2514) Co-authored-by: Otto Winter --- esphome/__main__.py | 29 +++++++++++- esphome/dashboard/dashboard.py | 82 +++++++++++++++++++++++++++++----- 2 files changed, 99 insertions(+), 12 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index feb95e93c7..1b9c601091 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -180,7 +180,11 @@ def compile_program(args, config): from esphome import platformio_api _LOGGER.info("Compiling app...") - return platformio_api.run_compile(config, CORE.verbose) + rc = platformio_api.run_compile(config, CORE.verbose) + if rc != 0: + return rc + idedata = platformio_api.get_idedata(config) + return 0 if idedata is not None else 1 def upload_using_esptool(config, port): @@ -458,6 +462,21 @@ def command_update_all(args): return failed +def command_idedata(args, config): + from esphome import platformio_api + import json + + logging.disable(logging.INFO) + logging.disable(logging.WARNING) + + idedata = platformio_api.get_idedata(config) + if idedata is None: + return 1 + + print(json.dumps(idedata.raw, indent=2) + "\n") + return 0 + + PRE_CONFIG_ACTIONS = { "wizard": command_wizard, "version": command_version, @@ -475,6 +494,7 @@ POST_CONFIG_ACTIONS = { "clean-mqtt": command_clean_mqtt, "mqtt-fingerprint": command_mqtt_fingerprint, "clean": command_clean, + "idedata": command_idedata, } @@ -650,6 +670,11 @@ def parse_args(argv): "configuration", help="Your YAML configuration file directories.", nargs="+" ) + parser_idedata = subparsers.add_parser("idedata") + parser_idedata.add_argument( + "configuration", help="Your YAML configuration file(s).", nargs=1 + ) + # Keep backward compatibility with the old command line format of # esphome . # @@ -762,7 +787,7 @@ def run_esphome(argv): config = read_config(dict(args.substitution) if args.substitution else {}) if config is None: - return 1 + return 2 CORE.config = config if args.command not in POST_CONFIG_ACTIONS: diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index eb698a7de1..501666b100 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -9,6 +9,7 @@ import json import logging import multiprocessing import os +from pathlib import Path import secrets import shutil import subprocess @@ -26,7 +27,7 @@ import tornado.process import tornado.web import tornado.websocket -from esphome import const, util +from esphome import const, platformio_api, util from esphome.helpers import mkdir_p, get_bool_env, run_system_command from esphome.storage_json import ( EsphomeStorageJSON, @@ -398,17 +399,45 @@ class DownloadBinaryRequestHandler(BaseHandler): @authenticated @bind_config def get(self, configuration=None): - # pylint: disable=no-value-for-parameter - storage_path = ext_storage_path(settings.config_dir, configuration) - storage_json = StorageJSON.load(storage_path) - if storage_json is None: - self.send_error() + type = self.get_argument("type", "firmware.bin") + + if type == "firmware.bin": + storage_path = ext_storage_path(settings.config_dir, configuration) + storage_json = StorageJSON.load(storage_path) + if storage_json is None: + self.send_error(404) + return + filename = f"{storage_json.name}.bin" + path = storage_json.firmware_bin_path + + else: + args = ["esphome", "idedata", settings.rel_path(configuration)] + rc, stdout, _ = run_system_command(*args) + + if rc != 0: + self.send_error(404 if rc == 2 else 500) + return + + idedata = platformio_api.IDEData(json.loads(stdout)) + + found = False + for image in idedata.extra_flash_images: + if image.path.endswith(type): + path = image.path + filename = type + found = True + break + + if not found: + self.send_error(404) + return + + self.set_header("Content-Type", "application/octet-stream") + self.set_header("Content-Disposition", f'attachment; filename="{filename}"') + if not Path(path).is_file(): + self.send_error(404) return - path = storage_json.firmware_bin_path - self.set_header("Content-Type", "application/octet-stream") - filename = f"{storage_json.name}.bin" - self.set_header("Content-Disposition", f'attachment; filename="{filename}"') with open(path, "rb") as f: while True: data = f.read(16384) @@ -418,6 +447,38 @@ class DownloadBinaryRequestHandler(BaseHandler): self.finish() +class ManifestRequestHandler(BaseHandler): + @authenticated + @bind_config + def get(self, configuration=None): + args = ["esphome", "idedata", settings.rel_path(configuration)] + rc, stdout, _ = run_system_command(*args) + + if rc != 0: + self.send_error(404 if rc == 2 else 500) + return + + idedata = platformio_api.IDEData(json.loads(stdout)) + + firmware_offset = "0x10000" if idedata.extra_flash_images else "0x0" + flash_images = [ + { + "path": f"./download.bin?configuration={configuration}&type=firmware.bin", + "offset": firmware_offset, + } + ] + [ + { + "path": f"./download.bin?configuration={configuration}&type={os.path.basename(image.path)}", + "offset": image.offset, + } + for image in idedata.extra_flash_images + ] + + self.set_header("Content-Type", "application/json") + self.write(json.dumps(flash_images)) + self.finish() + + def _list_dashboard_entries(): files = settings.list_yaml_files() return [DashboardEntry(file) for file in files] @@ -862,6 +923,7 @@ def make_app(debug=get_bool_env(ENV_DEV)): (f"{rel}info", InfoRequestHandler), (f"{rel}edit", EditRequestHandler), (f"{rel}download.bin", DownloadBinaryRequestHandler), + (f"{rel}manifest.json", ManifestRequestHandler), (f"{rel}serial-ports", SerialPortRequestHandler), (f"{rel}ping", PingRequestHandler), (f"{rel}delete", DeleteRequestHandler), From 12fce7a08d615b3bd52f62a6452d34e247c31636 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 17 Oct 2021 00:26:59 -0700 Subject: [PATCH 198/549] Bump dashboard to 20211015.0 (#2525) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index afc75efcc7..6615d4bd3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 esptool==3.1 click==8.0.3 -esphome-dashboard==20211011.1 +esphome-dashboard==20211015.0 aioesphomeapi==10.0.0 # esp-idf requires this, but doesn't bundle it by default From 5425e45851169ee58edc175b12a31417317dcba0 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 17 Oct 2021 21:01:51 +0200 Subject: [PATCH 199/549] Only show timestamp for dashboard access logs (#2540) --- esphome/__main__.py | 7 ++++++- esphome/log.py | 14 ++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 1b9c601091..97059154fd 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -758,7 +758,12 @@ def run_esphome(argv): args = parse_args(argv) CORE.dashboard = args.dashboard - setup_log(args.verbose, args.quiet) + setup_log( + args.verbose, + args.quiet, + # Show timestamp for dashboard access logs + args.command == "dashboard", + ) if args.deprecated_argv_suggestion is not None and args.command != "vscode": _LOGGER.warning( "Calling ESPHome with the configuration before the command is deprecated " diff --git a/esphome/log.py b/esphome/log.py index abefcf6308..e7ba0fdd82 100644 --- a/esphome/log.py +++ b/esphome/log.py @@ -49,8 +49,10 @@ def color(col: str, msg: str, reset: bool = True) -> bool: class ESPHomeLogFormatter(logging.Formatter): - def __init__(self): - super().__init__(fmt="%(asctime)s %(levelname)s %(message)s", style="%") + def __init__(self, *, include_timestamp: bool): + fmt = "%(asctime)s " if include_timestamp else "" + fmt += "%(levelname)s %(message)s" + super().__init__(fmt=fmt, style="%") def format(self, record): formatted = super().format(record) @@ -64,7 +66,9 @@ class ESPHomeLogFormatter(logging.Formatter): return f"{prefix}{formatted}{Style.RESET_ALL}" -def setup_log(debug=False, quiet=False): +def setup_log( + debug: bool = False, quiet: bool = False, include_timestamp: bool = False +) -> None: import colorama if debug: @@ -79,4 +83,6 @@ def setup_log(debug=False, quiet=False): logging.getLogger("urllib3").setLevel(logging.WARNING) colorama.init() - logging.getLogger().handlers[0].setFormatter(ESPHomeLogFormatter()) + logging.getLogger().handlers[0].setFormatter( + ESPHomeLogFormatter(include_timestamp=include_timestamp) + ) From 644ce2a26c8a161a3005471d5faae0dd0cd925b6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 18 Oct 2021 09:55:35 +1300 Subject: [PATCH 200/549] Fix const used for IDF recommended version (#2542) --- esphome/components/esp32/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 8a4b8b478a..d36471f0d4 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -190,7 +190,7 @@ def _esp_idf_check_versions(value): platform_version = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION) value[CONF_PLATFORM_VERSION] = str(platform_version) - if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: + if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION: _LOGGER.warning( "The selected ESP-IDF framework version is not the recommended one. " "If there are connectivity or build issues please remove the manual version." From 6b9c084162496aacf5896edfa0b9867593017d6b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 18 Oct 2021 09:56:31 +1300 Subject: [PATCH 201/549] Fix Bluetooth setup_priorities (#2458) Co-authored-by: Otto Winter --- esphome/components/ble_client/ble_client.cpp | 2 ++ esphome/components/ble_client/ble_client.h | 1 + esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp | 2 +- esphome/components/esp32_ble_server/ble_server.cpp | 2 +- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp | 2 ++ esphome/components/esp32_ble_tracker/esp32_ble_tracker.h | 1 + 6 files changed, 8 insertions(+), 2 deletions(-) diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 8ff516d735..e6cdb0c23d 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -11,6 +11,8 @@ namespace ble_client { static const char *const TAG = "ble_client"; +float BLEClient::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } + void BLEClient::setup() { auto ret = esp_ble_gattc_app_register(this->app_id); if (ret) { diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index 4a17ccb79b..23123914e8 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -81,6 +81,7 @@ class BLEClient : public espbt::ESPBTClient, public Component { void setup() override; void dump_config() override; void loop() override; + float get_setup_priority() const 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; diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp index f6bab8e6df..955bc8595f 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp @@ -57,7 +57,7 @@ void ESP32BLEBeacon::setup() { ); } -float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::DATA; } +float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::BLUETOOTH; } void ESP32BLEBeacon::ble_core_task(void *params) { ble_setup(); diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index 0b91c238c3..e0fb80f94b 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -154,7 +154,7 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga } } -float BLEServer::get_setup_priority() const { return setup_priority::BLUETOOTH - 10; } +float BLEServer::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 65749f5124..303cb34aa7 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -40,6 +40,8 @@ uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) { return u; } +float ESP32BLETracker::get_setup_priority() const { return setup_priority::BLUETOOTH; } + void ESP32BLETracker::setup() { global_esp32_ble_tracker = this; this->scan_result_lock_ = xSemaphoreCreateMutex(); diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 1308119df5..02e102f06c 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -171,6 +171,7 @@ class ESP32BLETracker : public Component { /// Setup the FreeRTOS task and the Bluetooth stack. void setup() override; void dump_config() override; + float get_setup_priority() const override; void loop() override; From ced11bc707a8aaecdd2d2d6df22d64372e0d3ba8 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 18 Oct 2021 02:36:18 +0200 Subject: [PATCH 202/549] Autodetect ESP32 variant (#2530) Co-authored-by: Otto winter --- esphome/components/esp32/__init__.py | 25 ++++-- esphome/components/esp32/boards.py | 124 ++++++++++++++++++++++++++ esphome/components/logger/__init__.py | 3 +- 3 files changed, 144 insertions(+), 8 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index d36471f0d4..db24b9aa29 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -27,13 +27,10 @@ from .const import ( # noqa KEY_ESP32, KEY_SDKCONFIG_OPTIONS, KEY_VARIANT, - VARIANT_ESP32, - VARIANT_ESP32S2, - VARIANT_ESP32S3, VARIANT_ESP32C3, - VARIANT_ESP32H2, VARIANTS, ) +from .boards import BOARD_TO_VARIANT # force import gpio to register pin schema from .gpio import esp32_pin_to_code # noqa @@ -199,6 +196,21 @@ def _esp_idf_check_versions(value): return value +def _detect_variant(value): + if CONF_VARIANT not in value: + board = value[CONF_BOARD] + if board not in BOARD_TO_VARIANT: + raise cv.Invalid( + "This board is unknown, please set the variant manually", + path=[CONF_BOARD], + ) + + value = value.copy() + value[CONF_VARIANT] = BOARD_TO_VARIANT[board] + + return value + + CONF_PLATFORM_VERSION = "platform_version" ARDUINO_FRAMEWORK_SCHEMA = cv.All( @@ -250,12 +262,11 @@ 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_VARIANT): cv.one_of(*VARIANTS, upper=True), cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA, } ), + _detect_variant, set_core_data, ) diff --git a/esphome/components/esp32/boards.py b/esphome/components/esp32/boards.py index ddf4bf2026..7f7bb2259f 100644 --- a/esphome/components/esp32/boards.py +++ b/esphome/components/esp32/boards.py @@ -1,3 +1,5 @@ +from .const import VARIANT_ESP32, VARIANT_ESP32S2, VARIANT_ESP32C3 + ESP32_BASE_PINS = { "TX": 1, "RX": 3, @@ -925,3 +927,125 @@ ESP32_BOARD_PINS = { }, "xinabox_cw02": {"LED": 27}, } + +""" +BOARD_TO_VARIANT generated with: + +git clone https://github.com/platformio/platform-espressif32 +for x in platform-espressif32/boards/*.json; do + mcu=$(jq -r .build.mcu <"$x"); + fname=$(basename "$x") + board="${fname%.*}" + variant=$(echo "$mcu" | tr '[:lower:]' '[:upper:]') + echo " \"$board\": VARIANT_${variant}," +done | sort +""" + +BOARD_TO_VARIANT = { + "alksesp32": VARIANT_ESP32, + "az-delivery-devkit-v4": VARIANT_ESP32, + "bpi-bit": VARIANT_ESP32, + "briki_abc_esp32": VARIANT_ESP32, + "briki_mbc-wb_esp32": VARIANT_ESP32, + "d-duino-32": VARIANT_ESP32, + "esp320": VARIANT_ESP32, + "esp32-c3-devkitm-1": VARIANT_ESP32C3, + "esp32cam": VARIANT_ESP32, + "esp32-devkitlipo": VARIANT_ESP32, + "esp32dev": VARIANT_ESP32, + "esp32doit-devkit-v1": VARIANT_ESP32, + "esp32doit-espduino": VARIANT_ESP32, + "esp32-evb": VARIANT_ESP32, + "esp32-gateway": VARIANT_ESP32, + "esp32-poe-iso": VARIANT_ESP32, + "esp32-poe": VARIANT_ESP32, + "esp32-pro": VARIANT_ESP32, + "esp32-s2-kaluga-1": VARIANT_ESP32S2, + "esp32-s2-saola-1": VARIANT_ESP32S2, + "esp32thing_plus": VARIANT_ESP32, + "esp32thing": VARIANT_ESP32, + "esp32vn-iot-uno": VARIANT_ESP32, + "espea32": VARIANT_ESP32, + "espectro32": VARIANT_ESP32, + "espino32": VARIANT_ESP32, + "esp-wrover-kit": VARIANT_ESP32, + "etboard": VARIANT_ESP32, + "featheresp32-s2": VARIANT_ESP32S2, + "featheresp32": VARIANT_ESP32, + "firebeetle32": VARIANT_ESP32, + "fm-devkit": VARIANT_ESP32, + "frogboard": VARIANT_ESP32, + "healthypi4": VARIANT_ESP32, + "heltec_wifi_kit_32_v2": VARIANT_ESP32, + "heltec_wifi_kit_32": VARIANT_ESP32, + "heltec_wifi_lora_32_V2": VARIANT_ESP32, + "heltec_wifi_lora_32": VARIANT_ESP32, + "heltec_wireless_stick_lite": VARIANT_ESP32, + "heltec_wireless_stick": VARIANT_ESP32, + "honeylemon": VARIANT_ESP32, + "hornbill32dev": VARIANT_ESP32, + "hornbill32minima": VARIANT_ESP32, + "imbrios-logsens-v1p1": VARIANT_ESP32, + "inex_openkb": VARIANT_ESP32, + "intorobot": VARIANT_ESP32, + "iotaap_magnolia": VARIANT_ESP32, + "iotbusio": VARIANT_ESP32, + "iotbusproteus": VARIANT_ESP32, + "kits-edu": VARIANT_ESP32, + "labplus_mpython": VARIANT_ESP32, + "lolin32_lite": VARIANT_ESP32, + "lolin32": VARIANT_ESP32, + "lolin_d32_pro": VARIANT_ESP32, + "lolin_d32": VARIANT_ESP32, + "lopy4": VARIANT_ESP32, + "lopy": VARIANT_ESP32, + "m5stack-atom": VARIANT_ESP32, + "m5stack-core2": VARIANT_ESP32, + "m5stack-core-esp32": VARIANT_ESP32, + "m5stack-coreink": VARIANT_ESP32, + "m5stack-fire": VARIANT_ESP32, + "m5stack-grey": VARIANT_ESP32, + "m5stack-timer-cam": VARIANT_ESP32, + "m5stick-c": VARIANT_ESP32, + "magicbit": VARIANT_ESP32, + "mgbot-iotik32a": VARIANT_ESP32, + "mgbot-iotik32b": VARIANT_ESP32, + "mhetesp32devkit": VARIANT_ESP32, + "mhetesp32minikit": VARIANT_ESP32, + "microduino-core-esp32": VARIANT_ESP32, + "nano32": VARIANT_ESP32, + "nina_w10": VARIANT_ESP32, + "node32s": VARIANT_ESP32, + "nodemcu-32s": VARIANT_ESP32, + "nscreen-32": VARIANT_ESP32, + "odroid_esp32": VARIANT_ESP32, + "onehorse32dev": VARIANT_ESP32, + "oroca_edubot": VARIANT_ESP32, + "pico32": VARIANT_ESP32, + "piranha_esp32": VARIANT_ESP32, + "pocket_32": VARIANT_ESP32, + "pycom_gpy": VARIANT_ESP32, + "qchip": VARIANT_ESP32, + "quantum": VARIANT_ESP32, + "sensesiot_weizen": VARIANT_ESP32, + "sg-o_airMon": VARIANT_ESP32, + "s_odi_ultra": VARIANT_ESP32, + "sparkfun_lora_gateway_1-channel": VARIANT_ESP32, + "tinypico": VARIANT_ESP32, + "ttgo-lora32-v1": VARIANT_ESP32, + "ttgo-lora32-v21": VARIANT_ESP32, + "ttgo-lora32-v2": VARIANT_ESP32, + "ttgo-t1": VARIANT_ESP32, + "ttgo-t7-v13-mini32": VARIANT_ESP32, + "ttgo-t7-v14-mini32": VARIANT_ESP32, + "ttgo-t-beam": VARIANT_ESP32, + "ttgo-t-watch": VARIANT_ESP32, + "turta_iot_node": VARIANT_ESP32, + "vintlabs-devkit-v1": VARIANT_ESP32, + "wemosbat": VARIANT_ESP32, + "wemos_d1_mini32": VARIANT_ESP32, + "wesp32": VARIANT_ESP32, + "widora-air": VARIANT_ESP32, + "wifiduino32": VARIANT_ESP32, + "xinabox_cw02": VARIANT_ESP32, +} diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index fe2a3ec8f8..20a0b0f792 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -19,7 +19,8 @@ from esphome.const import ( CONF_TX_BUFFER_SIZE, ) from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority -from esphome.components.esp32 import get_esp32_variant, VARIANT_ESP32S2, VARIANT_ESP32C3 +from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32.const import VARIANT_ESP32S2, VARIANT_ESP32C3 CODEOWNERS = ["@esphome/core"] logger_ns = cg.esphome_ns.namespace("logger") From 03cfd78c591b660db15ae649bb530e60adf2797c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 19 Oct 2021 15:16:39 +1300 Subject: [PATCH 203/549] Bump dashboard to 20211019.0 (#2549) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6615d4bd3d..23642adf9a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 esptool==3.1 click==8.0.3 -esphome-dashboard==20211015.0 +esphome-dashboard==20211019.0 aioesphomeapi==10.0.0 # esp-idf requires this, but doesn't bundle it by default From 5b5ead872b677bc21e5eb62ac660d388b05894d6 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 19 Oct 2021 12:56:49 +0200 Subject: [PATCH 204/549] Fix ADC pin validation on ESP32-C3 (#2551) --- esphome/components/adc/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 9a0407d0f4..26ef504c1a 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -35,7 +35,7 @@ def validate_adc_pin(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 + elif 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 From e2a812fa4b43489bfc3813c1982a8ac50784a2c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Oct 2021 20:28:48 +0200 Subject: [PATCH 205/549] Bump pytest-asyncio from 0.15.1 to 0.16.0 (#2547) 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 8ebcf24d4d..e40d51f4bf 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -8,6 +8,6 @@ pre-commit pytest==6.2.5 pytest-cov==3.0.0 pytest-mock==3.6.1 -pytest-asyncio==0.15.1 +pytest-asyncio==0.16.0 asyncmock==0.4.2 hypothesis==5.49.0 From f5441a87e3f308c76fef424551bc9fea27f6d8ad Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Tue, 19 Oct 2021 23:10:24 +0200 Subject: [PATCH 206/549] ignore exception when not waiting for a response (#2552) --- esphome/components/modbus/modbus.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 1f6d868baf..45c5bfb603 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -96,23 +96,27 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { ESP_LOGW(TAG, "Modbus CRC Check failed! %02X!=%02X", computed_crc, remote_crc); return false; } - - waiting_for_response = 0; std::vector data(this->rx_buffer_.begin() + data_offset, this->rx_buffer_.begin() + data_offset + data_len); - bool found = false; for (auto *device : this->devices_) { if (device->address_ == address) { // Is it an error response? if ((function_code & 0x80) == 0x80) { - ESP_LOGW(TAG, "Modbus error function code: 0x%X exception: %d", function_code, raw[2]); - device->on_modbus_error(function_code & 0x7F, raw[2]); + ESP_LOGD(TAG, "Modbus error function code: 0x%X exception: %d", function_code, raw[2]); + if (waiting_for_response != 0) { + device->on_modbus_error(function_code & 0x7F, raw[2]); + } else { + // Ignore modbus exception not related to a pending command + ESP_LOGD(TAG, "Ignoring Modbus error - not expecting a response"); + } } else { device->on_modbus_data(data); } found = true; } } + waiting_for_response = 0; + if (!found) { ESP_LOGW(TAG, "Got Modbus frame from unknown address 0x%02X! ", address); } @@ -196,6 +200,7 @@ void Modbus::send_raw(const std::vector &payload) { if (this->flow_control_pin_ != nullptr) this->flow_control_pin_->digital_write(false); waiting_for_response = payload[0]; + ESP_LOGV(TAG, "Modbus write raw: %s", hexencode(payload).c_str()); last_send_ = millis(); } From cb48394e8a776beb8f873cd70a2c0e3c9b28dcd3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Oct 2021 10:53:49 +1300 Subject: [PATCH 207/549] Bump dashboard to 20211020.0 (#2556) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 23642adf9a..08f3e8e4e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 esptool==3.1 click==8.0.3 -esphome-dashboard==20211019.0 +esphome-dashboard==20211020.0 aioesphomeapi==10.0.0 # esp-idf requires this, but doesn't bundle it by default From 8b11e5aeb1ea4072a76412cb9da3fd2345159766 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Oct 2021 13:25:00 +1300 Subject: [PATCH 208/549] Fix HA addon so it does not have logout button (#2558) --- 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 501666b100..63378a38b5 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -597,7 +597,7 @@ class MainRequestHandler(BaseHandler): get_template_path("index"), begin=begin, **template_args(), - login_enabled=settings.using_auth, + login_enabled=settings.using_password, ) From bcc77c73e1fb42ab0b3bbbf2ad704dbe98457ab1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Oct 2021 16:17:00 +1300 Subject: [PATCH 209/549] Bump esphome-dashboard to 20211020.1 (#2559) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 08f3e8e4e5..a32cb1e123 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 esptool==3.1 click==8.0.3 -esphome-dashboard==20211020.0 +esphome-dashboard==20211020.1 aioesphomeapi==10.0.0 # esp-idf requires this, but doesn't bundle it by default From e79f7ce29016a68df3cc2b685dd8ef4dbf3081d0 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Wed, 20 Oct 2021 19:44:51 +0200 Subject: [PATCH 210/549] [ESP32] ADC auto-range setting (#2541) --- esphome/components/adc/adc_sensor.cpp | 160 ++++++++++++++++---------- esphome/components/adc/adc_sensor.h | 4 + esphome/components/adc/sensor.py | 6 +- 3 files changed, 110 insertions(+), 60 deletions(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index c8f8b0e0f6..0c24c615f3 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -1,5 +1,6 @@ #include "adc_sensor.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" #ifdef USE_ESP8266 #ifdef USE_ADC_SENSOR_VCC @@ -16,8 +17,6 @@ namespace adc { static const char *const TAG = "adc"; #ifdef USE_ESP32 -void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } - inline adc1_channel_t gpio_to_adc1(uint8_t pin) { #if CONFIG_IDF_TARGET_ESP32 switch (pin) { @@ -57,6 +56,8 @@ inline adc1_channel_t gpio_to_adc1(uint8_t pin) { } #endif } +void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } +void ADCSensor::set_autorange(bool autorange) { this->autorange_ = autorange; } #endif void ADCSensor::setup() { @@ -66,6 +67,8 @@ void ADCSensor::setup() { #endif #ifdef USE_ESP32 + if (this->autorange_) + this->attenuation_ = ADC_ATTEN_DB_11; 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 @@ -84,22 +87,25 @@ void ADCSensor::dump_config() { #endif #ifdef USE_ESP32 LOG_PIN(" Pin: ", pin_); - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); - break; - case ADC_ATTEN_DB_2_5: - ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); - break; - case ADC_ATTEN_DB_6: - ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); - break; - 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; - } + if (autorange_) + ESP_LOGCONFIG(TAG, " Attenuation: auto"); + else + switch (this->attenuation_) { + case ADC_ATTEN_DB_0: + ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); + break; + case ADC_ATTEN_DB_2_5: + ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); + break; + case ADC_ATTEN_DB_6: + ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); + break; + 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); } @@ -109,56 +115,92 @@ void ADCSensor::update() { ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v); this->publish_state(value_v); } -float ADCSensor::sample() { +uint16_t ADCSensor::read_raw_() { #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_) { - case ADC_ATTEN_DB_0: - value_v *= 1.1; - break; - case ADC_ATTEN_DB_2_5: - value_v *= 1.5; - break; - case ADC_ATTEN_DB_6: - value_v *= 2.2; - break; - case ADC_ATTEN_DB_11: - value_v *= 3.9; - break; - default: // This is to satisfy the unused ADC_ATTEN_MAX - break; - } -#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - value_v *= 0.84; - break; - case ADC_ATTEN_DB_2_5: - value_v *= 1.13; - break; - case ADC_ATTEN_DB_6: - value_v *= 1.56; - break; - case ADC_ATTEN_DB_11: - value_v *= 3.0; - break; - default: // This is to satisfy the unused ADC_ATTEN_MAX - break; - } -#endif - return value_v; + return adc1_get_raw(gpio_to_adc1(pin_->get_pin())); #endif #ifdef USE_ESP8266 #ifdef USE_ADC_SENSOR_VCC - return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance) + return ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) #else - return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT + return analogRead(this->pin_->get_pin()); // NOLINT #endif #endif } +uint32_t ADCSensor::raw_to_microvolts_(uint16_t raw) { +#ifdef USE_ESP32 +#if CONFIG_IDF_TARGET_ESP32 + switch (this->attenuation_) { + case ADC_ATTEN_DB_0: + return raw * 269; // 1e6 * 1.1 / 4095 + case ADC_ATTEN_DB_2_5: + return raw * 366; // 1e6 * 1.5 / 4095 + case ADC_ATTEN_DB_6: + return raw * 537; // 1e6 * 2.2 / 4095 + case ADC_ATTEN_DB_11: + return raw * 952; // 1e6 * 3.9 / 4095 + default: // This is to satisfy the unused ADC_ATTEN_MAX + return raw * 244; // 1e6 * 1.0 / 4095 + } +#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 + switch (this->attenuation_) { + case ADC_ATTEN_DB_0: + return raw * 205; // 1e6 * 0.84 / 4095 + case ADC_ATTEN_DB_2_5: + return raw * 276; // 1e6 * 1.13 / 4095 + case ADC_ATTEN_DB_6: + return raw * 381; // 1e6 * 1.56 / 4095 + case ADC_ATTEN_DB_11: + return raw * 733; // 1e6 * 3.0 / 4095 + default: // This is to satisfy the unused ADC_ATTEN_MAX + return raw * 244; // 1e6 * 1.0 / 4095 + } +#endif +#endif + +#ifdef USE_ESP8266 + return raw * 977; // 1e6 / 1024 +#endif +} +float ADCSensor::sample() { + int raw = this->read_raw_(); + uint32_t v = this->raw_to_microvolts_(raw); +#ifdef USE_ESP32 + if (autorange_) { + int raw11 = raw, raw6 = 4095, raw2 = 4095, raw0 = 4095; + uint32_t v11 = v, v6 = 0, v2 = 0, v0 = 0; + if (raw11 < 4095) { // Progressively read all attenuation ranges + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_6); + raw6 = this->read_raw_(); + v6 = this->raw_to_microvolts_(raw6); + if (raw6 < 4095) { + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_2_5); + raw2 = this->read_raw_(); + v2 = this->raw_to_microvolts_(raw2); + if (raw2 < 4095) { + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_0); + raw0 = this->read_raw_(); + v0 = this->raw_to_microvolts_(raw0); + } + } + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_11); + } // Contribution coefficients (normalized to 2048) + uint16_t c11 = clamp(raw11, 0, 2048); // high 1, middle 1, low 0 + uint16_t c6 = (2048 - abs(raw6 - 2048)); // high 0, middle 1, low 0 + uint16_t c2 = (2048 - abs(raw2 - 2048)); // high 0, middle 1, low 0 + uint16_t c0 = clamp(4095 - raw0, 0, 2048); // high 0, middle 1, low 1 + uint32_t csum = c11 + c6 + c2 + c0; // sum to normalize the final result + if (csum > 0) + v = (v11 * c11) + (v6 * c6) + (v2 * c2) + (v0 * c0); + else // in case of error, this keeps the 11db output (v) + csum = 1; + csum *= 1e6; // include the 1e6 microvolts->volts conversion factor + return (float) v / (float) csum; // normalize, convert & return + } +#endif + return v / (float) 1e6; // convert from microvolts to volts +} #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 b8c702be4e..fafb0d5ca0 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -18,6 +18,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage #ifdef USE_ESP32 /// Set the attenuation for this pin. Only available on the ESP32. void set_attenuation(adc_atten_t attenuation); + void set_autorange(bool autorange); #endif /// Update adc values. @@ -36,9 +37,12 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage protected: InternalGPIOPin *pin_; + uint16_t read_raw_(); + uint32_t raw_to_microvolts_(uint16_t raw); #ifdef USE_ESP32 adc_atten_t attenuation_{ADC_ATTEN_DB_0}; + bool autorange_{false}; #endif }; diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 26ef504c1a..0265f52d31 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -21,6 +21,7 @@ ATTENUATION_MODES = { "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, + "auto": "auto", } @@ -92,4 +93,7 @@ async def to_code(config): cg.add(var.set_pin(pin)) if CONF_ATTENUATION in config: - cg.add(var.set_attenuation(config[CONF_ATTENUATION])) + if config[CONF_ATTENUATION] == "auto": + cg.add(var.set_autorange(cg.global_ns.true)) + else: + cg.add(var.set_attenuation(config[CONF_ATTENUATION])) From e4d17e0b15433e264023e00eb4c776bc4f543a33 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 21 Oct 2021 06:45:10 +1300 Subject: [PATCH 211/549] A few esp32_ble_server/improv fixes (#2562) --- .../components/esp32_ble_server/ble_server.cpp | 17 +++++++++-------- .../components/esp32_ble_server/ble_server.h | 14 ++++++++------ .../esp32_improv/esp32_improv_component.h | 2 +- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index e0fb80f94b..15bea07021 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -98,19 +98,20 @@ bool BLEServer::create_device_characteristics_() { return true; } -BLEService *BLEServer::create_service(const uint8_t *uuid, bool advertise) { +std::shared_ptr BLEServer::create_service(const uint8_t *uuid, bool advertise) { return this->create_service(ESPBTUUID::from_raw(uuid), advertise); } -BLEService *BLEServer::create_service(uint16_t uuid, bool advertise) { +std::shared_ptr BLEServer::create_service(uint16_t uuid, bool advertise) { return this->create_service(ESPBTUUID::from_uint16(uuid), advertise); } -BLEService *BLEServer::create_service(const std::string &uuid, bool advertise) { +std::shared_ptr BLEServer::create_service(const std::string &uuid, bool advertise) { return this->create_service(ESPBTUUID::from_raw(uuid), advertise); } -BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles, uint8_t inst_id) { +std::shared_ptr 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); // NOLINT(cppcoreguidelines-owning-memory) - this->services_.push_back(service); + std::shared_ptr service = std::make_shared(uuid, num_handles, inst_id); + this->services_.emplace_back(service); if (advertise) { esp32_ble::global_ble->get_advertising()->add_service_uuid(uuid); } @@ -149,12 +150,12 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga break; } - for (auto *service : this->services_) { + for (const auto &service : this->services_) { service->gatts_event_handler(event, gatts_if, param); } } -float BLEServer::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } +float BLEServer::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH + 10; } void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); } diff --git a/esphome/components/esp32_ble_server/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h index 9f7e8b8fc0..d275eeab01 100644 --- a/esphome/components/esp32_ble_server/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -11,6 +11,7 @@ #include "esphome/core/preferences.h" #include +#include #ifdef USE_ESP32 @@ -43,10 +44,11 @@ class BLEServer : public Component { void set_manufacturer(const std::string &manufacturer) { this->manufacturer_ = manufacturer; } void set_model(const std::string &model) { this->model_ = model; } - BLEService *create_service(const uint8_t *uuid, bool advertise = false); - BLEService *create_service(uint16_t uuid, bool advertise = false); - BLEService *create_service(const std::string &uuid, bool advertise = false); - BLEService *create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15, uint8_t inst_id = 0); + std::shared_ptr create_service(const uint8_t *uuid, bool advertise = false); + std::shared_ptr create_service(uint16_t uuid, bool advertise = false); + std::shared_ptr create_service(const std::string &uuid, bool advertise = false); + std::shared_ptr create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15, + uint8_t inst_id = 0); esp_gatt_if_t get_gatts_if() { return this->gatts_if_; } uint32_t get_connected_client_count() { return this->connected_clients_; } @@ -74,8 +76,8 @@ class BLEServer : public Component { uint32_t connected_clients_{0}; std::map clients_; - std::vector services_; - BLEService *device_information_service_; + std::vector> services_; + std::shared_ptr device_information_service_; std::vector service_components_; diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index 53cda5f399..3a5d150fbe 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -48,7 +48,7 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent { std::vector incoming_data_; wifi::WiFiAP connecting_sta_; - BLEService *service_; + std::shared_ptr service_; BLECharacteristic *status_; BLECharacteristic *error_; BLECharacteristic *rpc_; From 7cfede5b835cb72a652cf07ab3ce6129bfba778f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 21 Oct 2021 07:08:34 +1300 Subject: [PATCH 212/549] Bump esphome-dashboard to 20211021.0 (#2564) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a32cb1e123..b946019f18 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 esptool==3.1 click==8.0.3 -esphome-dashboard==20211020.1 +esphome-dashboard==20211021.0 aioesphomeapi==10.0.0 # esp-idf requires this, but doesn't bundle it by default From 64a45dc6a67d261023a5823f5cfddfc0e4fcdab7 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 20 Oct 2021 20:15:09 +0200 Subject: [PATCH 213/549] Move running process log line to debug level (#2565) --- esphome/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/util.py b/esphome/util.py index 527e370ad8..0f168cade3 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -178,7 +178,7 @@ def run_external_command( orig_argv = sys.argv orig_exit = sys.exit # mock sys.exit full_cmd = " ".join(shlex_quote(x) for x in cmd) - _LOGGER.info("Running: %s", full_cmd) + _LOGGER.debug("Running: %s", full_cmd) orig_stdout = sys.stdout sys.stdout = RedirectText(sys.stdout, filter_lines=filter_lines) @@ -214,7 +214,7 @@ def run_external_command( def run_external_process(*cmd, **kwargs): full_cmd = " ".join(shlex_quote(x) for x in cmd) - _LOGGER.info("Running: %s", full_cmd) + _LOGGER.debug("Running: %s", full_cmd) filter_lines = kwargs.get("filter_lines") capture_stdout = kwargs.get("capture_stdout", False) From 15b596841858af676c4adb3d0abee2e87e92947e Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 20 Oct 2021 20:31:13 +0200 Subject: [PATCH 214/549] Revert nextion clang-tidy changes (#2566) --- .../binary_sensor/nextion_binarysensor.cpp | 4 +- .../binary_sensor/nextion_binarysensor.h | 3 +- esphome/components/nextion/nextion.cpp | 73 ++++++++++++------- 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 | 14 ++-- .../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 +- 13 files changed, 78 insertions(+), 70 deletions(-) diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp index c5bfa78efe..bf6e74cb38 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(shared_from_this()); + this->nextion_->add_to_get_queue(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(shared_from_this(), (int) state); + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); } } diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.h b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h index b86ee74013..b6b23ada85 100644 --- a/esphome/components/nextion/binary_sensor/nextion_binarysensor.h +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h @@ -10,8 +10,7 @@ class NextionBinarySensor; class NextionBinarySensor : public NextionComponent, public binary_sensor::BinarySensorInitiallyOff, - public PollingComponent, - public std::enable_shared_from_this { + public PollingComponent { public: NextionBinarySensor(NextionBase *nextion) { this->nextion_ = nextion; } diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index d56c370412..f23f55c9bb 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,9 +257,8 @@ bool Nextion::remove_from_q_(bool report_empty) { return false; } - auto nb = std::move(this->nextion_queue_.front()); - this->nextion_queue_.pop_front(); - auto &component = nb->component; + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; ESP_LOGN(TAG, "Removing %s from the queue", component->get_variable_name().c_str()); @@ -267,8 +266,10 @@ bool Nextion::remove_from_q_(bool report_empty) { if (component->get_variable_name() == "sleep_wake") { this->is_sleeping_ = false; } + delete component; // NOLINT(cppcoreguidelines-owning-memory) } - + delete nb; // NOLINT(cppcoreguidelines-owning-memory) + this->nextion_queue_.pop_front(); return true; } @@ -357,7 +358,7 @@ void Nextion::process_nextion_commands_() { int index = 0; int found = -1; for (auto &nb : this->nextion_queue_) { - auto &component = nb->component; + 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!", @@ -368,6 +369,9 @@ void Nextion::process_nextion_commands_() { found = index; + delete component; // NOLINT(cppcoreguidelines-owning-memory) + delete nb; // NOLINT(cppcoreguidelines-owning-memory) + break; } ++index; @@ -464,9 +468,8 @@ void Nextion::process_nextion_commands_() { break; } - auto nb = std::move(this->nextion_queue_.front()); - this->nextion_queue_.pop_front(); - auto &component = nb->component; + 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", @@ -477,6 +480,9 @@ void Nextion::process_nextion_commands_() { component->set_state_from_string(to_process, true, false); } + delete nb; // NOLINT(cppcoreguidelines-owning-memory) + this->nextion_queue_.pop_front(); + break; } // 0x71 0x01 0x02 0x03 0x04 0xFF 0xFF 0xFF @@ -505,9 +511,8 @@ void Nextion::process_nextion_commands_() { ++dataindex; } - auto nb = std::move(this->nextion_queue_.front()); - this->nextion_queue_.pop_front(); - auto &component = nb->component; + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; if (component->get_queue_type() != NextionQueueType::SENSOR && component->get_queue_type() != NextionQueueType::BINARY_SENSOR && @@ -521,6 +526,9 @@ void Nextion::process_nextion_commands_() { component->set_state_from_int(value, true, false); } + delete nb; // NOLINT(cppcoreguidelines-owning-memory) + this->nextion_queue_.pop_front(); + break; } @@ -682,7 +690,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 @@ -699,6 +707,8 @@ void Nextion::process_nextion_commands_() { component->get_wave_buffer().begin() + buffer_to_send); } found = index; + delete component; // NOLINT(cppcoreguidelines-owning-memory) + delete nb; // NOLINT(cppcoreguidelines-owning-memory) break; } ++index; @@ -727,7 +737,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++) { - auto &component = this->nextion_queue_[i]->component; + 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", @@ -744,8 +754,11 @@ void Nextion::process_nextion_commands_() { if (component->get_variable_name() == "sleep_wake") { this->is_sleeping_ = false; } + delete component; // NOLINT(cppcoreguidelines-owning-memory) } + delete this->nextion_queue_[i]; // NOLINT(cppcoreguidelines-owning-memory) + this->nextion_queue_.erase(this->nextion_queue_.begin() + i); i--; @@ -899,16 +912,18 @@ 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) { - auto nextion_queue = make_unique(); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; - nextion_queue->component = make_unique(); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + nextion_queue->component = new nextion::NextionComponentBase; nextion_queue->component->set_variable_name(variable_name); nextion_queue->queue_time = millis(); - ESP_LOGN(TAG, "Add to queue type: NORESULT component %s", nextion_queue->component->get_variable_name().c_str()); + this->nextion_queue_.push_back(nextion_queue); - this->nextion_queue_.push_back(std::move(nextion_queue)); + ESP_LOGN(TAG, "Add to queue type: NORESULT component %s", nextion_queue->component->get_variable_name().c_str()); } /** @@ -979,7 +994,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(std::shared_ptr component, int state_value) { +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); } @@ -1007,8 +1022,7 @@ 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(std::shared_ptr component, - const std::string &state_value) { +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); } @@ -1028,11 +1042,12 @@ void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &varia state_value.c_str()); } -void Nextion::add_to_get_queue(std::shared_ptr component) { +void Nextion::add_to_get_queue(NextionComponentBase *component) { if ((!this->is_setup() && !this->ignore_is_setup_)) return; - auto nextion_queue = make_unique(); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; nextion_queue->component = component; nextion_queue->queue_time = millis(); @@ -1043,7 +1058,7 @@ void Nextion::add_to_get_queue(std::shared_ptr component) std::string command = "get " + component->get_variable_name_to_send(); if (this->send_command_(command)) { - this->nextion_queue_.push_back(std::move(nextion_queue)); + this->nextion_queue_.push_back(nextion_queue); } } @@ -1055,13 +1070,15 @@ void Nextion::add_to_get_queue(std::shared_ptr component) * @param buffer_to_send The buffer size * @param buffer_size The buffer data */ -void Nextion::add_addt_command_to_queue(std::shared_ptr component) { +void Nextion::add_addt_command_to_queue(NextionComponentBase *component) { if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) return; - auto nextion_queue = make_unique(); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; - nextion_queue->component = std::make_shared(); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + 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() @@ -1070,7 +1087,7 @@ void Nextion::add_addt_command_to_queue(std::shared_ptr co 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(std::move(nextion_queue)); + this->nextion_queue_.push_back(nextion_queue); } } diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 1bee41f6cf..285b3ac9a3 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -707,18 +707,17 @@ 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(std::shared_ptr component, int state_value) override; + 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(std::shared_ptr component, - const std::string &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(std::shared_ptr component) override; + void add_to_get_queue(NextionComponentBase *component) override; - void add_addt_command_to_queue(std::shared_ptr component) override; + void add_addt_command_to_queue(NextionComponentBase *component) override; void update_components_by_prefix(const std::string &prefix); @@ -729,7 +728,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 d91c70c960..a24fd74060 100644 --- a/esphome/components/nextion/nextion_base.h +++ b/esphome/components/nextion/nextion_base.h @@ -24,19 +24,18 @@ class NextionBase; class NextionBase { public: - 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(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(std::shared_ptr component, - const std::string &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(std::shared_ptr component) = 0; + virtual void add_addt_command_to_queue(NextionComponentBase *component) = 0; - virtual void add_to_get_queue(std::shared_ptr 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; diff --git a/esphome/components/nextion/nextion_component_base.h b/esphome/components/nextion/nextion_component_base.h index 2725d5a30c..71ad803bc4 100644 --- a/esphome/components/nextion/nextion_component_base.h +++ b/esphome/components/nextion/nextion_component_base.h @@ -1,6 +1,5 @@ #pragma once #include -#include #include "esphome/core/defines.h" namespace esphome { @@ -23,7 +22,7 @@ class NextionComponentBase; class NextionQueue { public: virtual ~NextionQueue() = default; - std::shared_ptr component; + NextionComponentBase *component; uint32_t queue_time = 0; }; diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index cebdbec31a..cd1c073320 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -281,12 +281,14 @@ void Nextion::upload_tft() { #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 + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size]; + 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 (std::nothrow) uint8_t[chunk_size]; // NOLINT(cppcoreguidelines-owning-memory) + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + this->transfer_buffer_ = new uint8_t[chunk_size]; if (!this->transfer_buffer_) this->upload_end_(); @@ -330,7 +332,8 @@ 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(); // NOLINT(cppcoreguidelines-owning-memory) + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + this->wifi_client_secure_ = new BearSSL::WiFiClientSecure(); this->wifi_client_secure_->setInsecure(); this->wifi_client_secure_->setBufferSizes(512, 512); } @@ -338,7 +341,8 @@ WiFiClient *Nextion::get_wifi_client_() { } if (this->wifi_client_ == nullptr) { - this->wifi_client_ = new WiFiClient(); // NOLINT(cppcoreguidelines-owning-memory) + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + this->wifi_client_ = new WiFiClient(); } return this->wifi_client_; } diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp index e983ebcc6f..4b7532d32d 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(shared_from_this()); + this->nextion_->add_to_get_queue(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(shared_from_this(), (int) state_value); + this->nextion_->add_no_result_to_queue_with_set(this, (int) state_value); } else { - this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state); + this->nextion_->add_no_result_to_queue_with_set(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(shared_from_this()); + this->nextion_->add_addt_command_to_queue(this); } } // namespace nextion diff --git a/esphome/components/nextion/sensor/nextion_sensor.h b/esphome/components/nextion/sensor/nextion_sensor.h index 068ff0451b..e4dde9a513 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.h +++ b/esphome/components/nextion/sensor/nextion_sensor.h @@ -8,10 +8,7 @@ namespace esphome { namespace nextion { class NextionSensor; -class NextionSensor : public NextionComponent, - public sensor::Sensor, - public PollingComponent, - public std::enable_shared_from_this { +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); }; diff --git a/esphome/components/nextion/switch/nextion_switch.cpp b/esphome/components/nextion/switch/nextion_switch.cpp index 0bd958e0d8..1f32ad3425 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(shared_from_this()); + this->nextion_->add_to_get_queue(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(shared_from_this(), (int) state); + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); } } if (publish) { diff --git a/esphome/components/nextion/switch/nextion_switch.h b/esphome/components/nextion/switch/nextion_switch.h index d7783e5c51..1548287473 100644 --- a/esphome/components/nextion/switch/nextion_switch.h +++ b/esphome/components/nextion/switch/nextion_switch.h @@ -8,10 +8,7 @@ namespace esphome { namespace nextion { class NextionSwitch; -class NextionSwitch : public NextionComponent, - public switch_::Switch, - public PollingComponent, - public std::enable_shared_from_this { +class NextionSwitch : public NextionComponent, public switch_::Switch, public PollingComponent { 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 fa7cb35025..08f032df74 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(shared_from_this()); + this->nextion_->add_to_get_queue(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(shared_from_this(), state); + this->nextion_->add_no_result_to_queue_with_set(this, state); } } diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.h b/esphome/components/nextion/text_sensor/nextion_textsensor.h index 762797727d..5716d0a008 100644 --- a/esphome/components/nextion/text_sensor/nextion_textsensor.h +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.h @@ -8,10 +8,7 @@ namespace esphome { namespace nextion { class NextionTextSensor; -class NextionTextSensor : public NextionComponent, - public text_sensor::TextSensor, - public PollingComponent, - public std::enable_shared_from_this { +class NextionTextSensor : public NextionComponent, public text_sensor::TextSensor, public PollingComponent { public: NextionTextSensor(NextionBase *nextion) { this->nextion_ = nextion; } void update() override; From c51b5095014f7f303e5407ad48dee798b69469a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Oct 2021 22:59:46 +0200 Subject: [PATCH 215/549] Bump paho-mqtt from 1.5.1 to 1.6.0 (#2568) Bumps [paho-mqtt](https://github.com/eclipse/paho.mqtt.python) from 1.5.1 to 1.6.0. - [Release notes](https://github.com/eclipse/paho.mqtt.python/releases) - [Changelog](https://github.com/eclipse/paho.mqtt.python/blob/master/ChangeLog.txt) - [Commits](https://github.com/eclipse/paho.mqtt.python/commits) --- updated-dependencies: - dependency-name: paho-mqtt 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.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b946019f18..7a2eeb58d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ voluptuous==0.12.2 PyYAML==6.0 -paho-mqtt==1.5.1 +paho-mqtt==1.6.0 colorama==0.4.4 tornado==6.1 tzlocal==3.0 # from time From 34606b0f1fb8fcb3b453d3f44c7121caeeeab4f3 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Thu, 21 Oct 2021 12:23:21 +0200 Subject: [PATCH 216/549] Fix MDNS for ESP8266 devices (#2571) Co-authored-by: Maurice Makaay Co-authored-by: Otto winter Co-authored-by: Maurice Makaay --- esphome/components/mdns/mdns_component.cpp | 6 +++--- esphome/components/mdns/mdns_component.h | 4 ++++ esphome/components/mdns/mdns_esp8266.cpp | 16 ++++++++++++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 372d980eb0..631af9eba9 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -23,7 +23,7 @@ std::vector MDNSComponent::compile_services_() { #ifdef USE_API if (api::global_api_server != nullptr) { MDNSService service{}; - service.service_type = "esphomelib"; + service.service_type = "_esphomelib"; service.proto = "_tcp"; service.port = api::global_api_server->get_port(); service.txt_records.push_back({"version", ESPHOME_VERSION}); @@ -57,7 +57,7 @@ std::vector MDNSComponent::compile_services_() { #ifdef USE_PROMETHEUS { MDNSService service{}; - service.service_type = "prometheus-http"; + service.service_type = "_prometheus-http"; service.proto = "_tcp"; service.port = WEBSERVER_PORT; res.push_back(service); @@ -68,7 +68,7 @@ std::vector MDNSComponent::compile_services_() { // 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.service_type = "_http"; service.proto = "_tcp"; service.port = WEBSERVER_PORT; service.txt_records.push_back({"version", ESPHOME_VERSION}); diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h index 985947d99c..679fb1a768 100644 --- a/esphome/components/mdns/mdns_component.h +++ b/esphome/components/mdns/mdns_component.h @@ -13,7 +13,11 @@ struct MDNSTXTRecord { }; struct MDNSService { + // service name _including_ underscore character prefix + // as defined in RFC6763 Section 7 std::string service_type; + // second label indicating protocol _including_ underscore character prefix + // as defined in RFC6763 Section 7, like "_tcp" or "_udp" std::string proto; uint16_t port; std::vector txt_records; diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index 48f31f1bbf..1a73e4b5b3 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -17,9 +17,21 @@ void MDNSComponent::setup() { auto services = compile_services_(); for (const auto &service : services) { - MDNS.addService(service.service_type.c_str(), service.proto.c_str(), service.port); + // Strip the leading underscore from the proto and service_type. While it is + // part of the wire protocol to have an underscore, and for example ESP-IDF + // expects the underscore to be there, the ESP8266 implementation always adds + // the underscore itself. + auto proto = service.proto.c_str(); + while (*proto == '_') { + proto++; + } + auto service_type = service.service_type.c_str(); + while (*service_type == '_') { + service_type++; + } + MDNS.addService(service_type, proto, 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()); + MDNS.addServiceTxt(service_type, proto, record.key.c_str(), record.value.c_str()); } } } From 7f34561e53b1146f47a4288bd2992aa073c7bb2d Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 12:23:37 +0200 Subject: [PATCH 217/549] Fix ESP8266 GPIO0 Pullup Validation (#2572) --- esphome/components/esp8266/gpio.py | 4 ++-- tests/test3.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp8266/gpio.py b/esphome/components/esp8266/gpio.py index 0ebfbd6f69..fa5c94dff5 100644 --- a/esphome/components/esp8266/gpio.py +++ b/esphome/components/esp8266/gpio.py @@ -107,9 +107,9 @@ def validate_supports(value): raise cv.Invalid( "Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN] ) - if is_pullup and num == 0: + if is_pullup and num == 16: raise cv.Invalid( - "GPIO Pin 0 does not support pullup pin mode. " + "GPIO Pin 16 does not support pullup pin mode. " "Please choose another pin.", [CONF_MODE, CONF_PULLUP], ) diff --git a/tests/test3.yaml b/tests/test3.yaml index 4c76967842..836e895374 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1150,7 +1150,7 @@ servo: ttp229_lsf: ttp229_bsf: - sdo_pin: D0 + sdo_pin: D2 scl_pin: D1 sim800l: From e39f314e7ab30756d23a269996c2f01885dd5f0e Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 12:24:01 +0200 Subject: [PATCH 218/549] Fix wifi ble coexistence check (#2573) --- esphome/components/wifi/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 19e4046711..faf3cca280 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -159,8 +159,15 @@ def final_validate_power_esp32_ble(value): "esp32_ble_server", "esp32_ble_tracker", ]: + if conflicting not in fv.full_config.get(): + continue + try: - cv.require_framework_version(esp32_arduino=cv.Version(1, 0, 5))(None) + # Only arduino 1.0.5+ and esp-idf impacted + cv.require_framework_version( + esp32_arduino=cv.Version(1, 0, 5), + esp_idf=cv.Version(4, 0, 0), + )(None) except cv.Invalid: pass else: From f41f7994a3e05cac5012e71744547fbb4e8e991a Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 14:20:23 +0200 Subject: [PATCH 219/549] Arduino global delay/millis/... symbols workaround (#2575) --- esphome/core/config.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/esphome/core/config.py b/esphome/core/config.py index c495fefddd..3c53d81784 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -206,6 +206,31 @@ def include_file(path, basename): cg.add_global(cg.RawStatement(f'#include "{basename}"')) +ARDUINO_GLUE_CODE = """\ +#define yield() esphome::yield() +#define millis() esphome::millis() +#define delay(x) esphome::delay(x) +#define delayMicroseconds(x) esphome::delayMicroseconds(x) +""" + + +@coroutine_with_priority(-999.0) +async def add_arduino_global_workaround(): + # The Arduino framework defined these itself in the global + # namespace. For the esphome codebase that is not a problem, + # but when custom code + # 1. writes `millis()` for example AND + # 2. has `using namespace esphome;` like our guides suggest + # Then the compiler will complain that the call is ambiguous + # Define a hacky macro so that the call is never ambiguous + # and always uses the esphome namespace one. + # See also https://github.com/esphome/issues/issues/2510 + # Priority -999 so that it runs before adding includes, as those + # also might reference these symbols + for line in ARDUINO_GLUE_CODE.splitlines(): + cg.add_global(cg.RawStatement(line)) + + @coroutine_with_priority(-1000.0) async def add_includes(includes): # Add includes at the very end, so that the included files can access global variables @@ -287,6 +312,9 @@ async def to_code(config): cg.add_build_flag("-Wno-unused-but-set-variable") cg.add_build_flag("-Wno-sign-compare") + if CORE.using_arduino: + CORE.add_job(add_arduino_global_workaround) + if config[CONF_INCLUDES]: CORE.add_job(add_includes, config[CONF_INCLUDES]) From 1caabb641927c787cc17aabe4d1ab7bbd6ac2249 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 14:20:57 +0200 Subject: [PATCH 220/549] Fix ESP8266 OTA adds unnecessary Update library (#2579) --- esphome/components/ota/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 53b282c43e..b3d3b7ad23 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -90,9 +90,7 @@ async def to_code(config): ) cg.add(RawExpression(f"if ({condition}) return")) - if CORE.is_esp8266: - cg.add_library("Update", None) - elif CORE.is_esp32 and CORE.using_arduino: + if CORE.is_esp32 and CORE.using_arduino: cg.add_library("Update", None) use_state_callback = False From c615dc573a223b7a20a363d868bf075d3a50ebfb Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 14:39:36 +0200 Subject: [PATCH 221/549] Fix ESP8266 dallas GPIO16 INPUT_PULLUP (#2581) --- esphome/components/esp8266/gpio.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index cb703c18e1..7805889b40 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -50,6 +50,13 @@ void ESP8266GPIOPin::pin_mode(gpio::Flags flags) { mode = OUTPUT; } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { mode = INPUT_PULLUP; + if (pin_ == 16) { + // GPIO16 doesn't have a pullup, so pinMode would fail. + // However, sometimes this method is called with pullup mode anyway + // for example from dallas one_wire. For those cases convert this + // to a INPUT mode. + mode = INPUT; + } } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { mode = INPUT_PULLDOWN_16; } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { From c248ba40438a3c1c83c5187ed80064db161072ad Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 14:53:08 +0200 Subject: [PATCH 222/549] Fix platformio version in Dockerfile doesn't match requirements (#2582) --- docker/Dockerfile | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index e66c3e1d95..7928ff8901 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -43,7 +43,7 @@ RUN \ # Ubuntu python3-pip is missing wheel pip3 install --no-cache-dir \ wheel==0.36.2 \ - platformio==5.2.0 \ + platformio==5.2.1 \ # Change some platformio settings && platformio settings set enable_telemetry No \ && platformio settings set check_libraries_interval 1000000 \ diff --git a/requirements.txt b/requirements.txt index 7a2eeb58d6..69058e108b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ tornado==6.1 tzlocal==3.0 # from time tzdata>=2021.1 # from time pyserial==3.5 -platformio==5.2.1 +platformio==5.2.1 # When updating platformio, also update Dockerfile esptool==3.1 click==8.0.3 esphome-dashboard==20211021.0 From 156104d5f5d83970970a15c1f2e663533495a150 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 15:29:32 +0200 Subject: [PATCH 223/549] Fix platformio_install_deps no longer installing all lib_deps (#2584) --- docker/platformio_install_deps.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docker/platformio_install_deps.py b/docker/platformio_install_deps.py index 5625bd4d01..c7b11cf321 100755 --- a/docker/platformio_install_deps.py +++ b/docker/platformio_install_deps.py @@ -8,6 +8,23 @@ import sys 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] + +libs = [] +# Extract from every lib_deps key in all sections +for section in config.sections(): + conf = config[section] + if "lib_deps" not in conf: + continue + for lib_dep in conf["lib_deps"].splitlines(): + if not lib_dep: + # Empty line or comment + continue + if lib_dep.startswith("${"): + # Extending from another section + continue + if "@" not in lib_dep: + # No version pinned, this is an internal lib + continue + libs.append(lib_dep) subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs]) From cac5b356db7deab24f5f66375b8ad4a6ee28d289 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 16:01:33 +0200 Subject: [PATCH 224/549] ESP32 ADC use factory calibration data (#2574) --- esphome/components/adc/adc_sensor.cpp | 224 +++++++++++--------------- esphome/components/adc/adc_sensor.h | 8 +- esphome/components/adc/sensor.py | 91 +++++++++-- 3 files changed, 177 insertions(+), 146 deletions(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 0c24c615f3..9b7d0437e1 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -16,50 +16,6 @@ namespace adc { static const char *const TAG = "adc"; -#ifdef USE_ESP32 -inline adc1_channel_t gpio_to_adc1(uint8_t pin) { -#if CONFIG_IDF_TARGET_ESP32 - 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; - } -#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 - switch (pin) { - case 0: - return ADC1_CHANNEL_0; - case 1: - return ADC1_CHANNEL_1; - case 2: - return ADC1_CHANNEL_2; - case 3: - return ADC1_CHANNEL_3; - case 4: - return ADC1_CHANNEL_4; - default: - return ADC1_CHANNEL_MAX; - } -#endif -} -void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } -void ADCSensor::set_autorange(bool autorange) { this->autorange_ = autorange; } -#endif - void ADCSensor::setup() { ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); #ifndef USE_ADC_SENSOR_VCC @@ -67,15 +23,36 @@ void ADCSensor::setup() { #endif #ifdef USE_ESP32 - if (this->autorange_) - this->attenuation_ = ADC_ATTEN_DB_11; - 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_->get_pin())); -#endif + if (!autorange_) { + adc1_config_channel_atten(channel_, attenuation_); + } + + // load characteristics for each attenuation + for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) { + auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_BIT_12, + 1100, // default vref + &cal_characteristics_[i]); + switch (cal_value) { + case ESP_ADC_CAL_VAL_EFUSE_VREF: + ESP_LOGV(TAG, "Using eFuse Vref for calibration"); + break; + case ESP_ADC_CAL_VAL_EFUSE_TP: + ESP_LOGV(TAG, "Using two-point eFuse Vref for calibration"); + break; + case ESP_ADC_CAL_VAL_DEFAULT_VREF: + default: + break; + } + } + + // adc_gpio_init doesn't exist on ESP32-C3 or ESP32-H2 +#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32H2) + adc_gpio_init(ADC_UNIT_1, (adc_channel_t) channel_); #endif +#endif // USE_ESP32 } + void ADCSensor::dump_config() { LOG_SENSOR("", "ADC Sensor", this); #ifdef USE_ESP8266 @@ -84,7 +61,8 @@ void ADCSensor::dump_config() { #else LOG_PIN(" Pin: ", pin_); #endif -#endif +#endif // USE_ESP8266 + #ifdef USE_ESP32 LOG_PIN(" Pin: ", pin_); if (autorange_) @@ -106,101 +84,81 @@ void ADCSensor::dump_config() { default: // This is to satisfy the unused ADC_ATTEN_MAX break; } -#endif +#endif // USE_ESP32 LOG_UPDATE_INTERVAL(this); } + float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } void ADCSensor::update() { float value_v = this->sample(); ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v); this->publish_state(value_v); } -uint16_t ADCSensor::read_raw_() { -#ifdef USE_ESP32 - return adc1_get_raw(gpio_to_adc1(pin_->get_pin())); -#endif #ifdef USE_ESP8266 -#ifdef USE_ADC_SENSOR_VCC - return ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) -#else - return analogRead(this->pin_->get_pin()); // NOLINT -#endif -#endif -} -uint32_t ADCSensor::raw_to_microvolts_(uint16_t raw) { -#ifdef USE_ESP32 -#if CONFIG_IDF_TARGET_ESP32 - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - return raw * 269; // 1e6 * 1.1 / 4095 - case ADC_ATTEN_DB_2_5: - return raw * 366; // 1e6 * 1.5 / 4095 - case ADC_ATTEN_DB_6: - return raw * 537; // 1e6 * 2.2 / 4095 - case ADC_ATTEN_DB_11: - return raw * 952; // 1e6 * 3.9 / 4095 - default: // This is to satisfy the unused ADC_ATTEN_MAX - return raw * 244; // 1e6 * 1.0 / 4095 - } -#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - return raw * 205; // 1e6 * 0.84 / 4095 - case ADC_ATTEN_DB_2_5: - return raw * 276; // 1e6 * 1.13 / 4095 - case ADC_ATTEN_DB_6: - return raw * 381; // 1e6 * 1.56 / 4095 - case ADC_ATTEN_DB_11: - return raw * 733; // 1e6 * 3.0 / 4095 - default: // This is to satisfy the unused ADC_ATTEN_MAX - return raw * 244; // 1e6 * 1.0 / 4095 - } -#endif -#endif - -#ifdef USE_ESP8266 - return raw * 977; // 1e6 / 1024 -#endif -} float ADCSensor::sample() { - int raw = this->read_raw_(); - uint32_t v = this->raw_to_microvolts_(raw); -#ifdef USE_ESP32 - if (autorange_) { - int raw11 = raw, raw6 = 4095, raw2 = 4095, raw0 = 4095; - uint32_t v11 = v, v6 = 0, v2 = 0, v0 = 0; - if (raw11 < 4095) { // Progressively read all attenuation ranges - adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_6); - raw6 = this->read_raw_(); - v6 = this->raw_to_microvolts_(raw6); - if (raw6 < 4095) { - adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_2_5); - raw2 = this->read_raw_(); - v2 = this->raw_to_microvolts_(raw2); - if (raw2 < 4095) { - adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_0); - raw0 = this->read_raw_(); - v0 = this->raw_to_microvolts_(raw0); - } - } - adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_11); - } // Contribution coefficients (normalized to 2048) - uint16_t c11 = clamp(raw11, 0, 2048); // high 1, middle 1, low 0 - uint16_t c6 = (2048 - abs(raw6 - 2048)); // high 0, middle 1, low 0 - uint16_t c2 = (2048 - abs(raw2 - 2048)); // high 0, middle 1, low 0 - uint16_t c0 = clamp(4095 - raw0, 0, 2048); // high 0, middle 1, low 1 - uint32_t csum = c11 + c6 + c2 + c0; // sum to normalize the final result - if (csum > 0) - v = (v11 * c11) + (v6 * c6) + (v2 * c2) + (v0 * c0); - else // in case of error, this keeps the 11db output (v) - csum = 1; - csum *= 1e6; // include the 1e6 microvolts->volts conversion factor - return (float) v / (float) csum; // normalize, convert & return - } +#ifdef USE_ADC_SENSOR_VCC + return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance) +#else + return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT #endif - return v / (float) 1e6; // convert from microvolts to volts } +#endif + +#ifdef USE_ESP32 +float ADCSensor::sample() { + if (!autorange_) { + int raw = adc1_get_raw(channel_); + if (raw == -1) { + return NAN; + } + uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]); + return mv / 1000.0f; + } + + int raw11, raw6 = 4095, raw2 = 4095, raw0 = 4095; + adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11); + raw11 = adc1_get_raw(channel_); + if (raw11 < 4095) { + adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6); + raw6 = adc1_get_raw(channel_); + if (raw6 < 4095) { + adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5); + raw2 = adc1_get_raw(channel_); + if (raw2 < 4095) { + adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0); + raw0 = adc1_get_raw(channel_); + } + } + } + + if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) { + return NAN; + } + // prevent divide by zero + if (raw0 == 0 && raw2 == 0 && raw6 == 0 && raw11 == 0) { + return 0; + } + + uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]); + uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]); + uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]); + uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]); + + // Contribution of each value, in range 0-2048 + uint32_t c11 = std::min(raw11, 2048); + uint32_t c6 = 2048 - std::abs(raw6 - 2048); + uint32_t c2 = 2048 - std::abs(raw2 - 2048); + uint32_t c0 = std::min(4095 - raw0, 2048); + // max theoretical csum value is 2048*4 = 8192 + uint32_t csum = c11 + c6 + c2 + c0; + + // each mv is max 3900; so max value is 3900*2048*4, fits in unsigned + uint32_t mv_scaled = (mv11 * c11) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0); + return mv_scaled / (float) (csum * 1000U); +} +#endif // USE_ESP32 + #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 fafb0d5ca0..9984c72819 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -8,6 +8,7 @@ #ifdef USE_ESP32 #include "driver/adc.h" +#include #endif namespace esphome { @@ -17,8 +18,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage public: #ifdef USE_ESP32 /// Set the attenuation for this pin. Only available on the ESP32. - void set_attenuation(adc_atten_t attenuation); - void set_autorange(bool autorange); + void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; } + void set_channel(adc1_channel_t channel) { channel_ = channel; } + void set_autorange(bool autorange) { autorange_ = autorange; } #endif /// Update adc values. @@ -42,7 +44,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage #ifdef USE_ESP32 adc_atten_t attenuation_{ADC_ATTEN_DB_0}; + adc1_channel_t channel_{}; bool autorange_{false}; + esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {}; #endif }; diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 0265f52d31..9fdddaa0a6 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -6,12 +6,21 @@ from esphome.const import ( CONF_ATTENUATION, CONF_ID, CONF_INPUT, + CONF_NUMBER, CONF_PIN, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT, UNIT_VOLT, ) from esphome.core import CORE +from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32.const import ( + VARIANT_ESP32, + VARIANT_ESP32C3, + VARIANT_ESP32H2, + VARIANT_ESP32S2, + VARIANT_ESP32S3, +) AUTO_LOAD = ["voltage_sampler"] @@ -24,21 +33,77 @@ ATTENUATION_MODES = { "auto": "auto", } +adc1_channel_t = cg.global_ns.enum("adc1_channel_t") + +# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h +# pin to adc1 channel mapping +ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { + VARIANT_ESP32: { + 36: adc1_channel_t.ADC1_CHANNEL_0, + 37: adc1_channel_t.ADC1_CHANNEL_1, + 38: adc1_channel_t.ADC1_CHANNEL_2, + 39: adc1_channel_t.ADC1_CHANNEL_3, + 32: adc1_channel_t.ADC1_CHANNEL_4, + 33: adc1_channel_t.ADC1_CHANNEL_5, + 34: adc1_channel_t.ADC1_CHANNEL_6, + 35: adc1_channel_t.ADC1_CHANNEL_7, + }, + VARIANT_ESP32S2: { + 1: adc1_channel_t.ADC1_CHANNEL_0, + 2: adc1_channel_t.ADC1_CHANNEL_1, + 3: adc1_channel_t.ADC1_CHANNEL_2, + 4: adc1_channel_t.ADC1_CHANNEL_3, + 5: adc1_channel_t.ADC1_CHANNEL_4, + 6: adc1_channel_t.ADC1_CHANNEL_5, + 7: adc1_channel_t.ADC1_CHANNEL_6, + 8: adc1_channel_t.ADC1_CHANNEL_7, + 9: adc1_channel_t.ADC1_CHANNEL_8, + 10: adc1_channel_t.ADC1_CHANNEL_9, + }, + VARIANT_ESP32S3: { + 1: adc1_channel_t.ADC1_CHANNEL_0, + 2: adc1_channel_t.ADC1_CHANNEL_1, + 3: adc1_channel_t.ADC1_CHANNEL_2, + 4: adc1_channel_t.ADC1_CHANNEL_3, + 5: adc1_channel_t.ADC1_CHANNEL_4, + 6: adc1_channel_t.ADC1_CHANNEL_5, + 7: adc1_channel_t.ADC1_CHANNEL_6, + 8: adc1_channel_t.ADC1_CHANNEL_7, + 9: adc1_channel_t.ADC1_CHANNEL_8, + 10: adc1_channel_t.ADC1_CHANNEL_9, + }, + VARIANT_ESP32C3: { + 0: adc1_channel_t.ADC1_CHANNEL_0, + 1: adc1_channel_t.ADC1_CHANNEL_1, + 2: adc1_channel_t.ADC1_CHANNEL_2, + 3: adc1_channel_t.ADC1_CHANNEL_3, + 4: adc1_channel_t.ADC1_CHANNEL_4, + }, + VARIANT_ESP32H2: { + 0: adc1_channel_t.ADC1_CHANNEL_0, + 1: adc1_channel_t.ADC1_CHANNEL_1, + 2: adc1_channel_t.ADC1_CHANNEL_2, + 3: adc1_channel_t.ADC1_CHANNEL_3, + 4: adc1_channel_t.ADC1_CHANNEL_4, + }, +} + def validate_adc_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.") - elif not (32 <= value <= 39): # ADC1 - raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.") - elif CORE.is_esp8266: + variant = get_esp32_variant() + if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL: + raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported") + + if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]: + raise cv.Invalid(f"{variant} doesn't support ADC on this pin") + return pins.internal_gpio_input_pin_schema(value) + + if CORE.is_esp8266: from esphome.components.esp8266.gpio import CONF_ANALOG value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})( @@ -50,10 +115,8 @@ def validate_adc_pin(value): 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) + raise NotImplementedError adc_ns = cg.esphome_ns.namespace("adc") @@ -97,3 +160,9 @@ async def to_code(config): cg.add(var.set_autorange(cg.global_ns.true)) else: cg.add(var.set_attenuation(config[CONF_ATTENUATION])) + + if CORE.is_esp32: + variant = get_esp32_variant() + pin_num = config[CONF_PIN][CONF_NUMBER] + chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num] + cg.add(var.set_channel(chan)) From f2ebfe7aef849b3d39c43a24566a03b64e35e7b5 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Thu, 21 Oct 2021 16:02:28 +0200 Subject: [PATCH 225/549] Add mDNS config dump (#2576) --- esphome/components/mdns/mdns_component.cpp | 29 ++++++++++++++----- esphome/components/mdns/mdns_component.h | 6 ++-- .../components/mdns/mdns_esp32_arduino.cpp | 9 +++--- esphome/components/mdns/mdns_esp8266.cpp | 11 ++++--- esphome/components/mdns/mdns_esp_idf.cpp | 11 +++---- 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 631af9eba9..13f65edfc4 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -13,13 +13,16 @@ namespace esphome { namespace mdns { +static const char *const TAG = "mdns"; + #ifndef WEBSERVER_PORT #define WEBSERVER_PORT 80 // NOLINT #endif -std::vector MDNSComponent::compile_services_() { - std::vector res; +void MDNSComponent::compile_records_() { + this->hostname_ = App.get_name(); + this->services_.clear(); #ifdef USE_API if (api::global_api_server != nullptr) { MDNSService service{}; @@ -50,7 +53,7 @@ std::vector MDNSComponent::compile_services_() { service.txt_records.push_back({"package_import_url", dashboard_import::get_package_import_url()}); #endif - res.push_back(service); + this->services_.push_back(service); } #endif // USE_API @@ -60,11 +63,11 @@ std::vector MDNSComponent::compile_services_() { service.service_type = "_prometheus-http"; service.proto = "_tcp"; service.port = WEBSERVER_PORT; - res.push_back(service); + this->services_.push_back(service); } #endif - if (res.empty()) { + if (this->services_.empty()) { // Publish "http" service if not using native API // This is just to have *some* mDNS service so that .local resolution works MDNSService service{}; @@ -72,11 +75,21 @@ std::vector MDNSComponent::compile_services_() { service.proto = "_tcp"; service.port = WEBSERVER_PORT; service.txt_records.push_back({"version", ESPHOME_VERSION}); - res.push_back(service); + this->services_.push_back(service); + } +} + +void MDNSComponent::dump_config() { + ESP_LOGCONFIG(TAG, "mDNS:"); + ESP_LOGCONFIG(TAG, " Hostname: %s", this->hostname_.c_str()); + ESP_LOGV(TAG, " Services:"); + for (const auto &service : this->services_) { + ESP_LOGV(TAG, " - %s, %s, %d", service.service_type.c_str(), service.proto.c_str(), service.port); + for (const auto &record : service.txt_records) { + ESP_LOGV(TAG, " TXT: %s = %s", record.key.c_str(), record.value.c_str()); + } } - 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 index 679fb1a768..45614d509a 100644 --- a/esphome/components/mdns/mdns_component.h +++ b/esphome/components/mdns/mdns_component.h @@ -26,6 +26,7 @@ struct MDNSService { class MDNSComponent : public Component { public: void setup() override; + void dump_config() override; #if defined(USE_ESP8266) && defined(USE_ARDUINO) void loop() override; @@ -33,8 +34,9 @@ class MDNSComponent : public Component { float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } protected: - std::vector compile_services_(); - std::string compile_hostname_(); + std::vector services_{}; + std::string hostname_; + void compile_records_(); }; } // namespace mdns diff --git a/esphome/components/mdns/mdns_esp32_arduino.cpp b/esphome/components/mdns/mdns_esp32_arduino.cpp index 4d13b7321a..6a66beef92 100644 --- a/esphome/components/mdns/mdns_esp32_arduino.cpp +++ b/esphome/components/mdns/mdns_esp32_arduino.cpp @@ -7,13 +7,12 @@ namespace esphome { namespace mdns { -static const char *const TAG = "mdns"; - void MDNSComponent::setup() { - MDNS.begin(compile_hostname_().c_str()); + this->compile_records_(); - auto services = compile_services_(); - for (const auto &service : services) { + MDNS.begin(this->hostname_.c_str()); + + for (const auto &service : this->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()); diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index 1a73e4b5b3..ff305f907a 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -9,14 +9,13 @@ 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); + this->compile_records_(); - auto services = compile_services_(); - for (const auto &service : services) { + network::IPAddress addr = network::get_ip_address(); + MDNS.begin(this->hostname_.c_str(), (uint32_t) addr); + + for (const auto &service : this->services_) { // Strip the leading underscore from the proto and service_type. While it is // part of the wire protocol to have an underscore, and for example ESP-IDF // expects the underscore to be there, the ESP8266 implementation always adds diff --git a/esphome/components/mdns/mdns_esp_idf.cpp b/esphome/components/mdns/mdns_esp_idf.cpp index 17874f1ffe..40d9f1d5f3 100644 --- a/esphome/components/mdns/mdns_esp_idf.cpp +++ b/esphome/components/mdns/mdns_esp_idf.cpp @@ -11,18 +11,19 @@ namespace mdns { static const char *const TAG = "mdns"; void MDNSComponent::setup() { + this->compile_records_(); + esp_err_t err = mdns_init(); if (err != ESP_OK) { - ESP_LOGW(TAG, "MDNS init failed: %s", esp_err_to_name(err)); + 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()); + mdns_hostname_set(this->hostname_.c_str()); + mdns_instance_name_set(this->hostname_.c_str()); - auto services = compile_services_(); - for (const auto &service : services) { + for (const auto &service : this->services_) { std::vector txt_records; for (const auto &record : service.txt_records) { mdns_txt_item_t it{}; From eccdef82116b8878541ca96555b6f64857012e1c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 18:53:08 +0200 Subject: [PATCH 226/549] Fix mDNS ESP8266 log not included (#2589) --- esphome/components/mdns/mdns_component.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 13f65edfc4..915c640b06 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" #include "esphome/core/version.h" #include "esphome/core/application.h" +#include "esphome/core/log.h" #ifdef USE_API #include "esphome/components/api/api_server.h" From ca59dd13022ac9b217baf86046213dbd51cd6fb6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 18:57:03 +0200 Subject: [PATCH 227/549] Fix HeatpumpIR pin (#2585) --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 9cc7477d51..6a8b342314 100644 --- a/platformio.ini +++ b/platformio.ini @@ -49,7 +49,7 @@ lib_deps = glmnet/Dsmr@0.5 ; dsmr rweather/Crypto@0.2.0 ; dsmr dudanov/MideaUART@1.1.8 ; midea - tonia/HeatpumpIR@^1.0.15 ; heatpumpir + tonia/HeatpumpIR@1.0.15 ; heatpumpir build_flags = ${common.build_flags} -DUSE_ARDUINO From 8735d3b83e41b5b72084290db3bc1803680e9019 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 21 Oct 2021 18:59:49 +0200 Subject: [PATCH 228/549] Fix PlatformIO version for latest Arduino framework (#2590) --- esphome/components/esp8266/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index a5323db8bf..ddaeee6ab7 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -65,7 +65,7 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 7, 4) # - 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) +ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 2, 0) def _arduino_check_versions(value): From c0fc5b48aec6c99e9e95bb74f050c54f54a9fe68 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 21 Oct 2021 19:55:19 +0200 Subject: [PATCH 229/549] Fix pin/component switchup in SX1509 pin configuration (#2593) --- esphome/components/sx1509/__init__.py | 4 ++-- esphome/core/helpers.cpp | 1 + tests/test4.yaml | 10 ++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/esphome/components/sx1509/__init__.py b/esphome/components/sx1509/__init__.py index f1b7d5f424..879ced2fb3 100644 --- a/esphome/components/sx1509/__init__.py +++ b/esphome/components/sx1509/__init__.py @@ -80,8 +80,8 @@ def validate_mode(value): CONF_SX1509 = "sx1509" SX1509_PIN_SCHEMA = cv.All( { - cv.GenerateID(): cv.declare_id(SX1509Component), - cv.Required(CONF_SX1509): cv.use_id(SX1509GPIOPin), + cv.GenerateID(): cv.declare_id(SX1509GPIOPin), + cv.Required(CONF_SX1509): cv.use_id(SX1509Component), cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), cv.Optional(CONF_MODE, default={}): cv.All( { diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 780df3ca6d..bc97259a71 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -358,6 +358,7 @@ template T clamp(const T val, const T min, const T max) { return max; return val; } +template uint8_t clamp(uint8_t, uint8_t, uint8_t); template float clamp(float, float, float); template int clamp(int, int, int); diff --git a/tests/test4.yaml b/tests/test4.yaml index 4f2025ad74..bc249c5ecb 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -59,6 +59,10 @@ tuya: pipsolar: id: inverter0 +sx1509: + - id: sx1509_hub + address: 0x3E + sensor: - platform: homeassistant entity_id: sensor.hello_world @@ -209,6 +213,7 @@ sensor: - or: - throttle: "20min" - delta: 0.02 + # # platform sensor.apds9960 requires component apds9960 # @@ -308,6 +313,11 @@ binary_sensor: y_max: 212 on_state: - lambda: 'ESP_LOGI("main", "key0: %s", (x ? "touch" : "release"));' + - platform: gpio + name: GPIO SX1509 test + pin: + sx1509: sx1509_hub + number: 3 climate: - platform: tuya From 27d7d7ca699bf4a8008d9a36e22f956e16c1870b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 21 Oct 2021 19:56:47 +0200 Subject: [PATCH 230/549] Fix old-style `arduino_version` on ESP8266 and with magic values (#2591) --- esphome/const.py | 5 ++++- esphome/core/config.py | 9 +++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/esphome/const.py b/esphome/const.py index 4f7d95d694..e00eebb1a4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,10 @@ __version__ = "2021.11.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" -TARGET_PLATFORMS = ["esp32", "esp8266"] +PLATFORM_ESP32 = "esp32" +PLATFORM_ESP8266 = "esp8266" + +TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266] TARGET_FRAMEWORKS = ["arduino", "esp-idf"] # See also https://github.com/platformio/platform-espressif8266/releases diff --git a/esphome/core/config.py b/esphome/core/config.py index 3c53d81784..235e0eeb84 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -29,6 +29,7 @@ from esphome.const import ( CONF_VERSION, KEY_CORE, TARGET_PLATFORMS, + PLATFORM_ESP8266, ) from esphome.core import CORE, coroutine_with_priority from esphome.helpers import copy_file_if_changed, walk_files @@ -182,9 +183,13 @@ def preload_core_config(config, result): 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"} + plat_conf[CONF_FRAMEWORK] = {} + if plat != PLATFORM_ESP8266: + plat_conf[CONF_FRAMEWORK][CONF_TYPE] = "arduino" + try: - cv.Version.parse(conf[CONF_ARDUINO_VERSION]) + if conf[CONF_ARDUINO_VERSION] not in ("recommended", "latest", "dev"): + cv.Version.parse(conf[CONF_ARDUINO_VERSION]) plat_conf[CONF_FRAMEWORK][CONF_VERSION] = conf.pop(CONF_ARDUINO_VERSION) except ValueError: plat_conf[CONF_FRAMEWORK][CONF_SOURCE] = conf.pop(CONF_ARDUINO_VERSION) From f1f2640d0e752c053dc7cf26810ec8922d1985be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:01:03 +0200 Subject: [PATCH 231/549] Bump esphome-dashboard from 20211021.0 to 20211021.1 (#2594) 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 69058e108b..9a64272df5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 # When updating platformio, also update Dockerfile esptool==3.1 click==8.0.3 -esphome-dashboard==20211021.0 +esphome-dashboard==20211021.1 aioesphomeapi==10.0.0 # esp-idf requires this, but doesn't bundle it by default From f408f074c49c7127892640c3bcb919896e5000ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:01:27 +0200 Subject: [PATCH 232/549] Bump platformio from 5.2.1 to 5.2.2 (#2569) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen --- docker/Dockerfile | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 7928ff8901..7c4a6a270a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -43,7 +43,7 @@ RUN \ # Ubuntu python3-pip is missing wheel pip3 install --no-cache-dir \ wheel==0.36.2 \ - platformio==5.2.1 \ + platformio==5.2.2 \ # Change some platformio settings && platformio settings set enable_telemetry No \ && platformio settings set check_libraries_interval 1000000 \ diff --git a/requirements.txt b/requirements.txt index 9a64272df5..1eb76e51e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ tornado==6.1 tzlocal==3.0 # from time tzdata>=2021.1 # from time pyserial==3.5 -platformio==5.2.1 # When updating platformio, also update Dockerfile +platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.1 click==8.0.3 esphome-dashboard==20211021.1 From 07a9cb910fd13e98e0e5cb67f9590db163d74db4 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 21 Oct 2021 20:07:37 +0200 Subject: [PATCH 233/549] Fix validation of addressable light IDs (#2588) --- esphome/components/light/addressable_light.h | 6 ++++++ esphome/components/light/types.py | 5 ++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index fea7508515..2b2c0ca7e4 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -22,6 +22,12 @@ using ESPColor ESPDEPRECATED("esphome::light::ESPColor is deprecated, use esphom /// Convert the color information from a `LightColorValues` object to a `Color` object (does not apply brightness). Color color_from_light_color_values(LightColorValues val); +/// Use a custom state class for addressable lights, to allow type system to discriminate between addressable and +/// non-addressable lights. +class AddressableLightState : public LightState { + using LightState::LightState; +}; + class AddressableLight : public LightOutput, public Component { public: virtual int32_t size() const = 0; diff --git a/esphome/components/light/types.py b/esphome/components/light/types.py index cf544e5435..bc20cd5555 100644 --- a/esphome/components/light/types.py +++ b/esphome/components/light/types.py @@ -4,10 +4,9 @@ from esphome import automation # Base light_ns = cg.esphome_ns.namespace("light") LightState = light_ns.class_("LightState", cg.EntityBase, cg.Component) -# Fake class for addressable lights -AddressableLightState = light_ns.class_("LightState", LightState) +AddressableLightState = light_ns.class_("AddressableLightState", LightState) LightOutput = light_ns.class_("LightOutput") -AddressableLight = light_ns.class_("AddressableLight", cg.Component) +AddressableLight = light_ns.class_("AddressableLight", LightOutput, cg.Component) AddressableLightRef = AddressableLight.operator("ref") Color = cg.esphome_ns.class_("Color") From 4765173778a7fde84bd77449f49174b72729fdc8 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 20:07:44 +0200 Subject: [PATCH 234/549] Update docker base images (#2583) --- docker/Dockerfile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 7c4a6a270a..0d30bb0267 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,12 +5,12 @@ # One of "docker", "hassio" ARG BASEIMGTYPE=docker -FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.0 AS base-hassio-amd64 -FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.0 AS base-hassio-arm64 -FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.0 AS base-hassio-armv7 -FROM debian:bullseye-20210902-slim AS base-docker-amd64 -FROM debian:bullseye-20210902-slim AS base-docker-arm64 -FROM debian:bullseye-20210902-slim AS base-docker-armv7 +FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.1 AS base-hassio-amd64 +FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.1 AS base-hassio-arm64 +FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.1 AS base-hassio-armv7 +FROM debian:bullseye-20211011-slim AS base-docker-amd64 +FROM debian:bullseye-20211011-slim AS base-docker-arm64 +FROM debian:bullseye-20211011-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 5389382798129f7ae78d7074f89b1f3a86c2f2ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:09:29 +0200 Subject: [PATCH 235/549] Bump paho-mqtt from 1.6.0 to 1.6.1 (#2596) 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 1eb76e51e0..3b6378c06f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ voluptuous==0.12.2 PyYAML==6.0 -paho-mqtt==1.6.0 +paho-mqtt==1.6.1 colorama==0.4.4 tornado==6.1 tzlocal==3.0 # from time From a88c0224065513176eeeac85f07bf3297df33809 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 22 Oct 2021 07:53:06 +1300 Subject: [PATCH 236/549] Logging a proper url allows terminals to make it clickable (#2554) --- 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 63378a38b5..084dd84a07 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -963,7 +963,7 @@ def start_web_server(args): server.add_socket(socket) else: _LOGGER.info( - "Starting dashboard web server on port %s and configuration dir %s...", + "Starting dashboard web server on http://0.0.0.0:%s and configuration dir %s...", args.port, settings.config_dir, ) From b141aea4c0936e7983ac31c5cd80c34569142420 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:54:12 +0200 Subject: [PATCH 237/549] Bump aioesphomeapi from 10.0.0 to 10.0.3 (#2595) 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 3b6378c06f..fa95f2156c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.1 click==8.0.3 esphome-dashboard==20211021.1 -aioesphomeapi==10.0.0 +aioesphomeapi==10.0.3 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 1468acfced9e43e3201bec46e4819630e5cbbc1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 22:46:05 +0200 Subject: [PATCH 238/549] Bump tzlocal from 3.0 to 4.0.1 (#2553) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen --- esphome/components/time/__init__.py | 8 ++------ requirements.txt | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index 5c2155d764..2d73d0aef9 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -1,7 +1,6 @@ import logging from importlib import resources from typing import Optional -from datetime import timezone import tzlocal @@ -66,14 +65,11 @@ def _extract_tz_string(tzfile: bytes) -> str: def detect_tz() -> str: - localzone = tzlocal.get_localzone() - if localzone is timezone.utc: - return "UTC0" - if not hasattr(localzone, "key"): + iana_key = tzlocal.get_localzone_name() + if iana_key is None: raise cv.Invalid( "Could not automatically determine timezone, please set timezone manually." ) - iana_key = localzone.key _LOGGER.info("Detected timezone '%s'", iana_key) tzfile = _load_tzdata(iana_key) if tzfile is None: diff --git a/requirements.txt b/requirements.txt index fa95f2156c..3ed53d0c90 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ PyYAML==6.0 paho-mqtt==1.6.1 colorama==0.4.4 tornado==6.1 -tzlocal==3.0 # from time +tzlocal==4.0.1 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile From 68c854706752f387d51caf5adbf2d9091cf04b58 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 22:48:28 +0200 Subject: [PATCH 239/549] Add IDF support to dallas (#2578) --- esphome/components/dallas/__init__.py | 26 ++--- .../components/dallas/dallas_component.cpp | 66 +++++------ esphome/components/dallas/dallas_component.h | 4 +- esphome/components/dallas/esp_one_wire.cpp | 103 ++++++++++-------- esphome/components/dallas/esp_one_wire.h | 7 +- esphome/components/esp32/gpio_arduino.cpp | 37 ++++--- esphome/components/esp32/gpio_idf.cpp | 87 +++++++++------ esphome/components/esp32/gpio_idf.h | 3 +- esphome/components/esp8266/gpio.cpp | 50 +++++---- esphome/core/gpio.h | 1 + 10 files changed, 201 insertions(+), 183 deletions(-) diff --git a/esphome/components/dallas/__init__.py b/esphome/components/dallas/__init__.py index 2dbc69b8e2..0f71399a7c 100644 --- a/esphome/components/dallas/__init__.py +++ b/esphome/components/dallas/__init__.py @@ -6,26 +6,20 @@ from esphome.const import CONF_ID, CONF_PIN MULTI_CONF = True AUTO_LOAD = ["sensor"] -CONF_ONE_WIRE_ID = "one_wire_id" dallas_ns = cg.esphome_ns.namespace("dallas") DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent) -ESPOneWire = dallas_ns.class_("ESPOneWire") -CONFIG_SCHEMA = cv.All( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(DallasComponent), - cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire), - cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, - } - ).extend(cv.polling_component_schema("60s")), - # pin_mode call logs in esp-idf, but InterruptLock is active -> crash - cv.only_with_arduino, -) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(DallasComponent), + cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, + } +).extend(cv.polling_component_schema("60s")) async def to_code(config): - pin = await cg.gpio_pin_expression(config[CONF_PIN]) - one_wire = cg.new_Pvariable(config[CONF_ONE_WIRE_ID], pin) - var = cg.new_Pvariable(config[CONF_ID], one_wire) + var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + + pin = await cg.gpio_pin_expression(config[CONF_PIN]) + cg.add(var.set_pin(pin)) diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 0fc4108687..8d7f2e4deb 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -31,12 +31,11 @@ uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion() const { void DallasComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up DallasComponent..."); - yield(); + pin_->setup(); + one_wire_ = new ESPOneWire(pin_); // NOLINT(cppcoreguidelines-owning-memory) + std::vector raw_sensors; - { - InterruptLock lock; - raw_sensors = this->one_wire_->search_vec(); - } + raw_sensors = this->one_wire_->search_vec(); for (auto &address : raw_sensors) { std::string s = uint64_to_string(address); @@ -70,7 +69,7 @@ void DallasComponent::setup() { } void DallasComponent::dump_config() { ESP_LOGCONFIG(TAG, "DallasComponent:"); - LOG_PIN(" Pin: ", this->one_wire_->get_pin()); + LOG_PIN(" Pin: ", this->pin_); LOG_UPDATE_INTERVAL(this); if (this->found_sensors_.empty()) { @@ -102,15 +101,12 @@ void DallasComponent::update() { this->status_clear_warning(); bool result; - { - InterruptLock lock; - if (!this->one_wire_->reset()) { - result = false; - } else { - result = true; - this->one_wire_->skip(); - this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION); - } + if (!this->one_wire_->reset()) { + result = false; + } else { + result = true; + this->one_wire_->skip(); + this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION); } if (!result) { @@ -121,11 +117,7 @@ void DallasComponent::update() { for (auto *sensor : this->sensors_) { this->set_timeout(sensor->get_address_name(), sensor->millis_to_wait_for_conversion(), [this, sensor] { - bool res; - { - InterruptLock lock; - res = sensor->read_scratch_pad(); - } + bool res = sensor->read_scratch_pad(); if (!res) { ESP_LOGW(TAG, "'%s' - Resetting bus for read failed!", sensor->get_name().c_str()); @@ -146,7 +138,6 @@ void DallasComponent::update() { }); } } -DallasComponent::DallasComponent(ESPOneWire *one_wire) : one_wire_(one_wire) {} void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; } uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; } @@ -162,7 +153,7 @@ const std::string &DallasTemperatureSensor::get_address_name() { return this->address_name_; } bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() { - ESPOneWire *wire = this->parent_->one_wire_; + auto *wire = this->parent_->one_wire_; if (!wire->reset()) { return false; } @@ -176,11 +167,7 @@ bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() { return true; } bool DallasTemperatureSensor::setup_sensor() { - bool r; - { - InterruptLock lock; - r = this->read_scratch_pad(); - } + bool r = this->read_scratch_pad(); if (!r) { ESP_LOGE(TAG, "Reading scratchpad failed: reset"); @@ -214,21 +201,18 @@ bool DallasTemperatureSensor::setup_sensor() { break; } - ESPOneWire *wire = this->parent_->one_wire_; - { - InterruptLock lock; - if (wire->reset()) { - wire->select(this->address_); - wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD); - wire->write8(this->scratch_pad_[2]); // high alarm temp - wire->write8(this->scratch_pad_[3]); // low alarm temp - wire->write8(this->scratch_pad_[4]); // resolution - wire->reset(); + auto *wire = this->parent_->one_wire_; + if (wire->reset()) { + wire->select(this->address_); + wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD); + wire->write8(this->scratch_pad_[2]); // high alarm temp + wire->write8(this->scratch_pad_[3]); // low alarm temp + wire->write8(this->scratch_pad_[4]); // resolution + wire->reset(); - // write value to EEPROM - wire->select(this->address_); - wire->write8(0x48); - } + // write value to EEPROM + wire->select(this->address_); + wire->write8(0x48); } delay(20); // allow it to finish operation diff --git a/esphome/components/dallas/dallas_component.h b/esphome/components/dallas/dallas_component.h index 8d405f6eab..37c098283a 100644 --- a/esphome/components/dallas/dallas_component.h +++ b/esphome/components/dallas/dallas_component.h @@ -11,8 +11,7 @@ class DallasTemperatureSensor; class DallasComponent : public PollingComponent { public: - explicit DallasComponent(ESPOneWire *one_wire); - + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void register_sensor(DallasTemperatureSensor *sensor); void setup() override; @@ -24,6 +23,7 @@ class DallasComponent : public PollingComponent { protected: friend DallasTemperatureSensor; + InternalGPIOPin *pin_; ESPOneWire *one_wire_; std::vector sensors_; std::vector found_sensors_; diff --git a/esphome/components/dallas/esp_one_wire.cpp b/esphome/components/dallas/esp_one_wire.cpp index 9278b83f7f..a0ab10f8a4 100644 --- a/esphome/components/dallas/esp_one_wire.cpp +++ b/esphome/components/dallas/esp_one_wire.cpp @@ -10,115 +10,123 @@ static const char *const TAG = "dallas.one_wire"; const uint8_t ONE_WIRE_ROM_SELECT = 0x55; const int ONE_WIRE_ROM_SEARCH = 0xF0; -ESPOneWire::ESPOneWire(GPIOPin *pin) : pin_(pin) {} +ESPOneWire::ESPOneWire(InternalGPIOPin *pin) { pin_ = pin->to_isr(); } bool HOT IRAM_ATTR ESPOneWire::reset() { - uint8_t retries = 125; + // See reset here: + // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html + InterruptLock lock; - // Wait for communication to clear - this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + // Wait for communication to clear (delay G) + pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + uint8_t retries = 125; do { if (--retries == 0) return false; delayMicroseconds(2); - } while (!this->pin_->digital_read()); + } while (!pin_.digital_read()); - // Send 480µs LOW TX reset pulse - this->pin_->pin_mode(gpio::FLAG_OUTPUT); - this->pin_->digital_write(false); + // Send 480µs LOW TX reset pulse (drive bus low, delay H) + pin_.pin_mode(gpio::FLAG_OUTPUT); + pin_.digital_write(false); delayMicroseconds(480); - // Switch into RX mode, letting the pin float - 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 + // Release the bus, delay I + pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); delayMicroseconds(70); - bool r = !this->pin_->digital_read(); + // sample bus, 0=device(s) present, 1=no device present + bool r = !pin_.digital_read(); + // delay J delayMicroseconds(410); return r; } void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) { - // Initiate write/read by pulling low. - this->pin_->pin_mode(gpio::FLAG_OUTPUT); - this->pin_->digital_write(false); + // See write 1/0 bit here: + // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html + InterruptLock lock; - // bus sampled within 15µs and 60µs after pulling LOW. - if (bit) { - // pull high/release within 15µs - delayMicroseconds(10); - this->pin_->digital_write(true); - // in total minimum of 60µs long - delayMicroseconds(55); - } else { - // continue pulling LOW for at least 60µs - delayMicroseconds(65); - this->pin_->digital_write(true); - // grace period, 1µs recovery time - delayMicroseconds(5); - } + // drive bus low + pin_.pin_mode(gpio::FLAG_OUTPUT); + pin_.digital_write(false); + + uint32_t delay0 = bit ? 10 : 65; + uint32_t delay1 = bit ? 55 : 5; + + // delay A/C + delayMicroseconds(delay0); + // release bus + pin_.digital_write(true); + // delay B/D + delayMicroseconds(delay1); } bool HOT IRAM_ATTR ESPOneWire::read_bit() { - // Initiate read slot by pulling LOW for at least 1µs - this->pin_->pin_mode(gpio::FLAG_OUTPUT); - this->pin_->digital_write(false); + // See read bit here: + // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html + InterruptLock lock; + + // drive bus low, delay A + pin_.pin_mode(gpio::FLAG_OUTPUT); + pin_.digital_write(false); delayMicroseconds(3); - // release bus, we have to sample within 15µs of pulling low - this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + // release bus, delay E + pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); delayMicroseconds(10); - bool r = this->pin_->digital_read(); - // read time slot at least 60µs long + 1µs recovery time between slots + // sample bus to read bit from peer + bool r = pin_.digital_read(); + + // delay F delayMicroseconds(53); return r; } -void IRAM_ATTR ESPOneWire::write8(uint8_t val) { +void ESPOneWire::write8(uint8_t val) { for (uint8_t i = 0; i < 8; i++) { this->write_bit(bool((1u << i) & val)); } } -void IRAM_ATTR ESPOneWire::write64(uint64_t val) { +void ESPOneWire::write64(uint64_t val) { for (uint8_t i = 0; i < 64; i++) { this->write_bit(bool((1ULL << i) & val)); } } -uint8_t IRAM_ATTR ESPOneWire::read8() { +uint8_t 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 IRAM_ATTR ESPOneWire::read64() { +uint64_t ESPOneWire::read64() { uint64_t ret = 0; for (uint8_t i = 0; i < 8; i++) { ret |= (uint64_t(this->read_bit()) << i); } return ret; } -void IRAM_ATTR ESPOneWire::select(uint64_t address) { +void ESPOneWire::select(uint64_t address) { this->write8(ONE_WIRE_ROM_SELECT); this->write64(address); } -void IRAM_ATTR ESPOneWire::reset_search() { +void ESPOneWire::reset_search() { this->last_discrepancy_ = 0; this->last_device_flag_ = false; this->last_family_discrepancy_ = 0; this->rom_number_ = 0; } -uint64_t HOT IRAM_ATTR ESPOneWire::search() { +uint64_t ESPOneWire::search() { if (this->last_device_flag_) { return 0u; } if (!this->reset()) { - // Reset failed + // Reset failed or no devices present this->reset_search(); return 0u; } @@ -196,7 +204,7 @@ uint64_t HOT IRAM_ATTR ESPOneWire::search() { return this->rom_number_; } -std::vector IRAM_ATTR ESPOneWire::search_vec() { +std::vector ESPOneWire::search_vec() { std::vector res; this->reset_search(); @@ -206,10 +214,9 @@ std::vector IRAM_ATTR ESPOneWire::search_vec() { return res; } -void IRAM_ATTR ESPOneWire::skip() { +void ESPOneWire::skip() { this->write8(0xCC); // skip ROM } -GPIOPin *ESPOneWire::get_pin() { return this->pin_; } uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast(&this->rom_number_); } diff --git a/esphome/components/dallas/esp_one_wire.h b/esphome/components/dallas/esp_one_wire.h index 728fa127d3..ef6f079f02 100644 --- a/esphome/components/dallas/esp_one_wire.h +++ b/esphome/components/dallas/esp_one_wire.h @@ -1,6 +1,5 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/core/hal.h" #include @@ -12,7 +11,7 @@ extern const int ONE_WIRE_ROM_SEARCH; class ESPOneWire { public: - explicit ESPOneWire(GPIOPin *pin); + explicit ESPOneWire(InternalGPIOPin *pin); /** Reset the bus, should be done before all write operations. * @@ -55,13 +54,11 @@ class ESPOneWire { /// Helper that wraps search in a std::vector. std::vector search_vec(); - GPIOPin *get_pin(); - protected: /// Helper to get the internal 64-bit unsigned rom number as a 8-bit integer pointer. inline uint8_t *rom_number8_(); - GPIOPin *pin_; + ISRInternalGPIOPin pin_; uint8_t last_discrepancy_{0}; uint8_t last_family_discrepancy_{0}; bool last_device_flag_{false}; diff --git a/esphome/components/esp32/gpio_arduino.cpp b/esphome/components/esp32/gpio_arduino.cpp index c4bb21a0aa..ba92894f97 100644 --- a/esphome/components/esp32/gpio_arduino.cpp +++ b/esphome/components/esp32/gpio_arduino.cpp @@ -9,6 +9,22 @@ namespace esp32 { static const char *const TAG = "esp32"; +static int IRAM_ATTR flags_to_mode(gpio::Flags flags) { + if (flags == gpio::FLAG_INPUT) { + return INPUT; + } else if (flags == gpio::FLAG_OUTPUT) { + return OUTPUT; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + return INPUT_PULLUP; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { + return INPUT_PULLDOWN; + } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + return OUTPUT_OPEN_DRAIN; + } else { + return 0; + } +} + struct ISRPinArg { uint8_t pin; bool inverted; @@ -43,22 +59,9 @@ void ArduinoInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, g 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 + pinMode(pin_, flags_to_mode(flags)); // NOLINT } std::string ArduinoInternalGPIOPin::dump_summary() const { @@ -101,6 +104,10 @@ void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { } #endif } +void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { + auto *arg = reinterpret_cast(arg_); + pinMode(arg->pin, flags_to_mode(flags)); // NOLINT +} } // namespace esphome diff --git a/esphome/components/esp32/gpio_idf.cpp b/esphome/components/esp32/gpio_idf.cpp index d1853e1f8b..498843ebff 100644 --- a/esphome/components/esp32/gpio_idf.cpp +++ b/esphome/components/esp32/gpio_idf.cpp @@ -10,38 +10,7 @@ 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); -} - -void IDFInternalGPIOPin::setup() { - pin_mode(flags_); - gpio_set_drive_capability(pin_, drive_strength_); -} - -void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) { - gpio_config_t conf{}; - conf.pin_bit_mask = 1ULL << 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 IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; } - -void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); } - -gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) { +static gpio_mode_t IRAM_ATTR 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; @@ -61,6 +30,18 @@ gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) { } } +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); +} + void IDFInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE; switch (type) { @@ -99,6 +80,35 @@ std::string IDFInternalGPIOPin::dump_summary() const { return buffer; } +void IDFInternalGPIOPin::setup() { + gpio_config_t conf{}; + conf.pin_bit_mask = 1ULL << 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); + gpio_set_drive_capability(pin_, drive_strength_); +} + +void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) { + // can't call gpio_config here because that logs in esp-idf which may cause issues + gpio_set_direction(pin_, flags_to_mode(flags)); + gpio_pull_mode_t pull_mode = GPIO_FLOATING; + if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) { + pull_mode = GPIO_PULLUP_PULLDOWN; + } else if (flags & gpio::FLAG_PULLUP) { + pull_mode = GPIO_PULLUP_ONLY; + } else if (flags & gpio::FLAG_PULLDOWN) { + pull_mode = GPIO_PULLDOWN_ONLY; + } + gpio_set_pull_mode(pin_, pull_mode); +} + +bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; } +void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); } +void IDFInternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); } + } // namespace esp32 using namespace esp32; @@ -114,6 +124,19 @@ void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { // not supported } +void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { + auto *arg = reinterpret_cast(arg_); + gpio_set_direction(arg->pin, flags_to_mode(flags)); + gpio_pull_mode_t pull_mode = GPIO_FLOATING; + if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) { + pull_mode = GPIO_PULLUP_PULLDOWN; + } else if (flags & gpio::FLAG_PULLUP) { + pull_mode = GPIO_PULLUP_ONLY; + } else if (flags & gpio::FLAG_PULLDOWN) { + pull_mode = GPIO_PULLDOWN_ONLY; + } + gpio_set_pull_mode(arg->pin, pull_mode); +} } // namespace esphome diff --git a/esphome/components/esp32/gpio_idf.h b/esphome/components/esp32/gpio_idf.h index a99571cc46..a07d11378a 100644 --- a/esphome/components/esp32/gpio_idf.h +++ b/esphome/components/esp32/gpio_idf.h @@ -18,13 +18,12 @@ class IDFInternalGPIOPin : public InternalGPIOPin { bool digital_read() override; void digital_write(bool value) override; std::string dump_summary() const override; - void detach_interrupt() const override { gpio_intr_disable(pin_); } + void detach_interrupt() const override; 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); void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; gpio_num_t pin_; diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index 7805889b40..2660318182 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -8,6 +8,29 @@ namespace esp8266 { static const char *const TAG = "esp8266"; +static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) { + if (flags == gpio::FLAG_INPUT) { + return INPUT; + } else if (flags == gpio::FLAG_OUTPUT) { + return OUTPUT; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + if (pin == 16) { + // GPIO16 doesn't have a pullup, so pinMode would fail. + // However, sometimes this method is called with pullup mode anyway + // for example from dallas one_wire. For those cases convert this + // to a INPUT mode. + return INPUT; + } + return INPUT_PULLUP; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { + return INPUT_PULLDOWN_16; + } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + return OUTPUT_OPEN_DRAIN; + } else { + return 0; + } +} + struct ISRPinArg { uint8_t pin; bool inverted; @@ -43,28 +66,7 @@ void ESP8266GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::Int 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; - if (pin_ == 16) { - // GPIO16 doesn't have a pullup, so pinMode would fail. - // However, sometimes this method is called with pullup mode anyway - // for example from dallas one_wire. For those cases convert this - // to a INPUT mode. - mode = INPUT; - } - } 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 + pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT } std::string ESP8266GPIOPin::dump_summary() const { @@ -97,6 +99,10 @@ void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { auto *arg = reinterpret_cast(arg_); GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin); } +void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { + auto *arg = reinterpret_cast(arg_); + pinMode(arg->pin, flags_to_mode(flags, arg->pin)); // NOLINT +} } // namespace esphome diff --git a/esphome/core/gpio.h b/esphome/core/gpio.h index 1d3fb89805..04658d567c 100644 --- a/esphome/core/gpio.h +++ b/esphome/core/gpio.h @@ -70,6 +70,7 @@ class ISRInternalGPIOPin { bool digital_read(); void digital_write(bool value); void clear_interrupt(); + void pin_mode(gpio::Flags flags); protected: void *arg_ = nullptr; From 9220d9fc5222b90187c4a4413d7def262f5c8ec3 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 10:46:44 +0200 Subject: [PATCH 240/549] Fix socket connection closed not detected (#2587) --- esphome/components/api/api_connection.cpp | 2 + esphome/components/api/api_frame_helper.cpp | 48 ++++++++++++++----- esphome/components/api/api_frame_helper.h | 1 + esphome/components/ota/ota_component.cpp | 9 ++++ .../components/socket/lwip_raw_tcp_impl.cpp | 8 +++- 5 files changed, 53 insertions(+), 15 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 47171ba50f..c87ccf4dc0 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -78,6 +78,8 @@ void APIConnection::loop() { on_fatal_error(); if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str()); + } else if (err == APIError::CONNECTION_CLOSED) { + ESP_LOGW(TAG, "%s: Connection closed", client_info_.c_str()); } else { ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); } diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 4971272f41..c0e37ec90d 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -10,7 +10,7 @@ namespace api { static const char *const TAG = "api.socket"; -/// Is the given return value (from read/write syscalls) a wouldblock error? +/// Is the given return value (from write syscalls) a wouldblock error? bool is_would_block(ssize_t ret) { if (ret == -1) { return errno == EWOULDBLOCK || errno == EAGAIN; @@ -64,6 +64,8 @@ const char *api_error_to_str(APIError err) { return "HANDSHAKESTATE_SPLIT_FAILED"; } else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) { return "BAD_HANDSHAKE_ERROR_BYTE"; + } else if (err == APIError::CONNECTION_CLOSED) { + return "CONNECTION_CLOSED"; } return "UNKNOWN"; } @@ -185,12 +187,17 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { // 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) { + if (received == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + return APIError::WOULD_BLOCK; + } state_ = State::FAILED; HELPER_LOG("Socket read failed with errno %d", errno); return APIError::SOCKET_READ_FAILED; + } else if (received == 0) { + state_ = State::FAILED; + HELPER_LOG("Connection closed"); + return APIError::CONNECTION_CLOSED; } rx_header_buf_len_ += received; if (received != to_read) { @@ -227,12 +234,17 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { // 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) { + if (received == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + return APIError::WOULD_BLOCK; + } state_ = State::FAILED; HELPER_LOG("Socket read failed with errno %d", errno); return APIError::SOCKET_READ_FAILED; + } else if (received == 0) { + state_ = State::FAILED; + HELPER_LOG("Connection closed"); + return APIError::CONNECTION_CLOSED; } rx_buf_len_ += received; if (received != to_read) { @@ -778,12 +790,17 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { 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) { + if (received == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + return APIError::WOULD_BLOCK; + } state_ = State::FAILED; HELPER_LOG("Socket read failed with errno %d", errno); return APIError::SOCKET_READ_FAILED; + } else if (received == 0) { + state_ = State::FAILED; + HELPER_LOG("Connection closed"); + return APIError::CONNECTION_CLOSED; } rx_header_buf_.push_back(data); @@ -824,12 +841,17 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { // 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) { + if (received == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + return APIError::WOULD_BLOCK; + } state_ = State::FAILED; HELPER_LOG("Socket read failed with errno %d", errno); return APIError::SOCKET_READ_FAILED; + } else if (received == 0) { + state_ = State::FAILED; + HELPER_LOG("Connection closed"); + return APIError::CONNECTION_CLOSED; } rx_buf_len_ += received; if (received != to_read) { diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 7fdb26fd40..57e3c961d5 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -53,6 +53,7 @@ enum class APIError : int { HANDSHAKESTATE_SETUP_FAILED = 1019, HANDSHAKESTATE_SPLIT_FAILED = 1020, BAD_HANDSHAKE_ERROR_BYTE = 1021, + CONNECTION_CLOSED = 1022, }; const char *api_error_to_str(APIError err); diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 89bee17452..6d51087882 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -275,6 +275,12 @@ void OTAComponent::handle_() { } ESP_LOGW(TAG, "Error receiving data for update, errno: %d", errno); goto error; + } else if (read == 0) { + // $ man recv + // "When a stream socket peer has performed an orderly shutdown, the return value will + // be 0 (the traditional "end-of-file" return)." + ESP_LOGW(TAG, "Remote end closed connection"); + goto error; } error_code = backend->write(buf, read); @@ -362,6 +368,9 @@ bool OTAComponent::readall_(uint8_t *buf, size_t len) { } ESP_LOGW(TAG, "Failed to read %d bytes of data, errno: %d", len, errno); return false; + } else if (read == 0) { + ESP_LOGW(TAG, "Remote closed connection"); + return false; } else { at += read; } diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 54dfddac3f..922d895ff4 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -320,8 +320,7 @@ class LWIPRawImpl : public Socket { return -1; } if (rx_closed_ && rx_buf_ == nullptr) { - errno = ECONNRESET; - return -1; + return 0; } if (len == 0) { return 0; @@ -366,6 +365,11 @@ class LWIPRawImpl : public Socket { read += copysize; } + if (read == 0) { + errno = EWOULDBLOCK; + return -1; + } + return read; } ssize_t readv(const struct iovec *iov, int iovcnt) override { From f7b3f52731c6842dd9c44feea5ff91aeafef0497 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Fri, 22 Oct 2021 12:09:47 +0200 Subject: [PATCH 241/549] Limit hostnames to 31 characters (#2531) --- esphome/config_validation.py | 18 +--- esphome/core/config.py | 95 ++++++++++++++-------- tests/unit_tests/test_config_validation.py | 22 ----- 3 files changed, 62 insertions(+), 73 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index fcec74b245..5d4ff64193 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -903,21 +903,9 @@ 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 -") - 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 + if re.match(r"^[a-z0-9-]{1,63}$", value, re.IGNORECASE) is not None: + return value + raise Invalid(f"Invalid hostname: {value}") def domain(value): diff --git a/esphome/core/config.py b/esphome/core/config.py index 235e0eeb84..ad132968b9 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -55,6 +55,24 @@ CONF_NAME_ADD_MAC_SUFFIX = "name_add_mac_suffix" VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} +def validate_hostname(config): + max_length = 31 + if config[CONF_NAME_ADD_MAC_SUFFIX]: + max_length -= 7 # "-AABBCC" is appended when add mac suffix option is used + if len(config[CONF_NAME]) > max_length: + raise cv.Invalid( + f"Hostnames can only be {max_length} characters long", path=[CONF_NAME] + ) + if "_" in config[CONF_NAME]: + _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", + config[CONF_NAME], + ) + return config + + def valid_include(value): try: return cv.directory(value) @@ -79,42 +97,47 @@ def valid_project_name(value: str): CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash" -CONFIG_SCHEMA = cv.Schema( - { - cv.Required(CONF_NAME): cv.hostname, - cv.Optional(CONF_COMMENT): 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.Optional(CONF_ON_BOOT): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger), - cv.Optional(CONF_PRIORITY, default=600.0): cv.float_, - } - ), - cv.Optional(CONF_ON_SHUTDOWN): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ShutdownTrigger), - } - ), - cv.Optional(CONF_ON_LOOP): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LoopTrigger), - } - ), - cv.Optional(CONF_INCLUDES, default=[]): cv.ensure_list(valid_include), - cv.Optional(CONF_LIBRARIES, default=[]): cv.ensure_list(cv.string_strict), - cv.Optional(CONF_NAME_ADD_MAC_SUFFIX, default=False): cv.boolean, - cv.Optional(CONF_PROJECT): cv.Schema( - { - cv.Required(CONF_NAME): cv.All(cv.string_strict, valid_project_name), - cv.Required(CONF_VERSION): cv.string_strict, - } - ), - } +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_NAME): cv.valid_name, + cv.Optional(CONF_COMMENT): 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.Optional(CONF_ON_BOOT): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger), + cv.Optional(CONF_PRIORITY, default=600.0): cv.float_, + } + ), + cv.Optional(CONF_ON_SHUTDOWN): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ShutdownTrigger), + } + ), + cv.Optional(CONF_ON_LOOP): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LoopTrigger), + } + ), + cv.Optional(CONF_INCLUDES, default=[]): cv.ensure_list(valid_include), + cv.Optional(CONF_LIBRARIES, default=[]): cv.ensure_list(cv.string_strict), + cv.Optional(CONF_NAME_ADD_MAC_SUFFIX, default=False): cv.boolean, + cv.Optional(CONF_PROJECT): cv.Schema( + { + cv.Required(CONF_NAME): cv.All( + cv.string_strict, valid_project_name + ), + cv.Required(CONF_VERSION): cv.string_strict, + } + ), + } + ), + validate_hostname, ) PRELOAD_CONFIG_SCHEMA = cv.Schema( diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index e34c7064fa..16cfb16e94 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -40,28 +40,6 @@ 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 be3cb9ef004991b91b68e86f96243dbb500fdac3 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Fri, 22 Oct 2021 23:10:29 +1300 Subject: [PATCH 242/549] Add EntityBase properties to ESP32 Camera (#2600) --- esphome/components/api/api.proto | 1 + esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 9 +++++++++ esphome/components/api/api_pb2.h | 1 + esphome/components/esp32_camera/__init__.py | 11 ++++------- esphome/components/esp32_camera/esp32_camera.cpp | 1 + esphome/components/esp32_camera/esp32_camera.h | 1 + 7 files changed, 18 insertions(+), 7 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 5a6eba004c..3c6a36f032 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -701,6 +701,7 @@ message ListEntitiesCameraResponse { string name = 3; string unique_id = 4; bool disabled_by_default = 5; + string icon = 6; } message CameraImageResponse { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index c87ccf4dc0..2151d6165c 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -680,6 +680,7 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { msg.name = camera->get_name(); msg.unique_id = get_default_unique_id("camera", camera); msg.disabled_by_default = camera->is_disabled_by_default(); + msg.icon = camera->get_icon(); return this->send_list_entities_camera_response(msg); } void APIConnection::camera_image(const CameraImageRequest &msg) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 6a87238186..17fc14c868 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -2910,6 +2910,10 @@ bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDel this->unique_id = value.as_string(); return true; } + case 6: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -2930,6 +2934,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(3, this->name); buffer.encode_string(4, this->unique_id); buffer.encode_bool(5, this->disabled_by_default); + buffer.encode_string(6, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCameraResponse::dump_to(std::string &out) const { @@ -2955,6 +2960,10 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 13a21c4772..eea9ab06f6 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -803,6 +803,7 @@ class ListEntitiesCameraResponse : public ProtoMessage { std::string name{}; std::string unique_id{}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 7f3aebe238..1a8b3a37ec 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -2,10 +2,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, CONF_FREQUENCY, CONF_ID, - CONF_NAME, CONF_PIN, CONF_SCL, CONF_SDA, @@ -17,6 +15,7 @@ from esphome.const import ( ) from esphome.core import CORE from esphome.components.esp32 import add_idf_sdkconfig_option +from esphome.cpp_helpers import setup_entity DEPENDENCIES = ["esp32", "api"] @@ -63,11 +62,9 @@ CONF_TEST_PATTERN = "test_pattern" camera_range_param = cv.int_range(min=-2, max=2) -CONFIG_SCHEMA = cv.Schema( +CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( { 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.internal_gpio_input_pin_number], cv.Length(min=8, max=8) ), @@ -127,8 +124,8 @@ SETTERS = { async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME]) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) + var = cg.new_Pvariable(config[CONF_ID]) + await setup_entity(var, config) await cg.register_component(var, config) for key, setter in SETTERS.items(): diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index babfda4113..ad4304d89f 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -185,6 +185,7 @@ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { global_esp32_camera = this; } +ESP32Camera::ESP32Camera() : ESP32Camera("") {} void ESP32Camera::set_data_pins(std::array pins) { this->config_.pin_d0 = pins[0]; this->config_.pin_d1 = pins[1]; diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index d0445607a4..84f8d9cbea 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -54,6 +54,7 @@ enum ESP32CameraFrameSize { class ESP32Camera : public Component, public EntityBase { public: ESP32Camera(const std::string &name); + ESP32Camera(); void set_data_pins(std::array pins); void set_vsync_pin(uint8_t pin); void set_href_pin(uint8_t pin); From c08b21b7cd5ec3101b0ade57d1b1f5c2462af308 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 12:12:07 +0200 Subject: [PATCH 243/549] Bump noise-c from 0.1.3 to 0.1.4 (#2602) --- esphome/components/api/__init__.py | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index b0608a69dd..6b2e7fd06b 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -121,7 +121,7 @@ async def to_code(config): 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.3") + cg.add_library("esphome/noise-c", "0.1.4") else: cg.add_define("USE_API_PLAINTEXT") diff --git a/platformio.ini b/platformio.ini index 6a8b342314..ac5144fc37 100644 --- a/platformio.ini +++ b/platformio.ini @@ -26,7 +26,7 @@ build_flags = [common] lib_deps = - esphome/noise-c@0.1.3 ; api + esphome/noise-c@0.1.4 ; api makuna/NeoPixelBus@2.6.7 ; neopixelbus build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE From 0d90ef94aeabe45f7229696728acec24cdb55122 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 13:02:55 +0200 Subject: [PATCH 244/549] Add OTA upload compression for ESP8266 (#2601) --- esphome/components/ota/ota_backend.h | 1 + .../ota/ota_backend_arduino_esp32.h | 1 + .../ota/ota_backend_arduino_esp8266.h | 1 + esphome/components/ota/ota_backend_esp_idf.h | 1 + esphome/components/ota/ota_component.cpp | 9 +++- esphome/components/ota/ota_component.h | 1 + esphome/espota2.py | 44 ++++++++++++------- 7 files changed, 42 insertions(+), 16 deletions(-) diff --git a/esphome/components/ota/ota_backend.h b/esphome/components/ota/ota_backend.h index c253e009c6..5c5b61a278 100644 --- a/esphome/components/ota/ota_backend.h +++ b/esphome/components/ota/ota_backend.h @@ -12,6 +12,7 @@ class OTABackend { virtual OTAResponseTypes write(uint8_t *data, size_t len) = 0; virtual OTAResponseTypes end() = 0; virtual void abort() = 0; + virtual bool supports_compression() = 0; }; } // namespace ota diff --git a/esphome/components/ota/ota_backend_arduino_esp32.h b/esphome/components/ota/ota_backend_arduino_esp32.h index 6b712502fb..f86a70d678 100644 --- a/esphome/components/ota/ota_backend_arduino_esp32.h +++ b/esphome/components/ota/ota_backend_arduino_esp32.h @@ -15,6 +15,7 @@ class ArduinoESP32OTABackend : public OTABackend { OTAResponseTypes write(uint8_t *data, size_t len) override; OTAResponseTypes end() override; void abort() override; + bool supports_compression() override { return false; } }; } // namespace ota diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.h b/esphome/components/ota/ota_backend_arduino_esp8266.h index d1195af911..cf29a90fc1 100644 --- a/esphome/components/ota/ota_backend_arduino_esp8266.h +++ b/esphome/components/ota/ota_backend_arduino_esp8266.h @@ -16,6 +16,7 @@ class ArduinoESP8266OTABackend : public OTABackend { OTAResponseTypes write(uint8_t *data, size_t len) override; OTAResponseTypes end() override; void abort() override; + bool supports_compression() override { return true; } }; } // namespace ota diff --git a/esphome/components/ota/ota_backend_esp_idf.h b/esphome/components/ota/ota_backend_esp_idf.h index 49c6e124fa..af09d0d693 100644 --- a/esphome/components/ota/ota_backend_esp_idf.h +++ b/esphome/components/ota/ota_backend_esp_idf.h @@ -17,6 +17,7 @@ class IDFOTABackend : public OTABackend { OTAResponseTypes write(uint8_t *data, size_t len) override; OTAResponseTypes end() override; void abort() override; + bool supports_compression() override { return false; } private: esp_ota_handle_t update_handle_{0}; diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 6d51087882..e49c108320 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -104,6 +104,8 @@ void OTAComponent::loop() { } } +static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01; + void OTAComponent::handle_() { OTAResponseTypes error_code = OTA_RESPONSE_ERROR_UNKNOWN; bool update_started = false; @@ -154,6 +156,8 @@ void OTAComponent::handle_() { buf[1] = OTA_VERSION_1_0; this->writeall_(buf, 2); + backend = make_ota_backend(); + // Read features - 1 byte if (!this->readall_(buf, 1)) { ESP_LOGW(TAG, "Reading features failed!"); @@ -164,6 +168,10 @@ void OTAComponent::handle_() { // Acknowledge header - 1 byte buf[0] = OTA_RESPONSE_HEADER_OK; + if ((ota_features & FEATURE_SUPPORTS_COMPRESSION) != 0 && backend->supports_compression()) { + buf[0] = OTA_RESPONSE_SUPPORTS_COMPRESSION; + } + this->writeall_(buf, 1); #ifdef USE_OTA_PASSWORD @@ -241,7 +249,6 @@ void OTAComponent::handle_() { } ESP_LOGV(TAG, "OTA size is %u bytes", ota_size); - backend = make_ota_backend(); error_code = backend->begin(ota_size); if (error_code != OTA_RESPONSE_OK) goto error; diff --git a/esphome/components/ota/ota_component.h b/esphome/components/ota/ota_component.h index e08e187df6..5647d52eeb 100644 --- a/esphome/components/ota/ota_component.h +++ b/esphome/components/ota/ota_component.h @@ -19,6 +19,7 @@ enum OTAResponseTypes { OTA_RESPONSE_BIN_MD5_OK = 67, OTA_RESPONSE_RECEIVE_OK = 68, OTA_RESPONSE_UPDATE_END_OK = 69, + OTA_RESPONSE_SUPPORTS_COMPRESSION = 70, OTA_RESPONSE_ERROR_MAGIC = 128, OTA_RESPONSE_ERROR_UPDATE_PREPARE = 129, diff --git a/esphome/espota2.py b/esphome/espota2.py index f8a2fab94c..8f299395dd 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -4,6 +4,7 @@ import random import socket import sys import time +import gzip from esphome.core import EsphomeError from esphome.helpers import is_ip_address, resolve_ip_address @@ -17,6 +18,7 @@ RESPONSE_UPDATE_PREPARE_OK = 66 RESPONSE_BIN_MD5_OK = 67 RESPONSE_RECEIVE_OK = 68 RESPONSE_UPDATE_END_OK = 69 +RESPONSE_SUPPORTS_COMPRESSION = 70 RESPONSE_ERROR_MAGIC = 128 RESPONSE_ERROR_UPDATE_PREPARE = 129 @@ -34,6 +36,8 @@ OTA_VERSION_1_0 = 1 MAGIC_BYTES = [0x6C, 0x26, 0xF7, 0x5C, 0x45] +FEATURE_SUPPORTS_COMPRESSION = 0x01 + _LOGGER = logging.getLogger(__name__) @@ -170,11 +174,9 @@ def send_check(sock, data, msg): def perform_ota(sock, password, file_handle, filename): - file_md5 = hashlib.md5(file_handle.read()).hexdigest() - file_size = file_handle.tell() + file_contents = file_handle.read() + file_size = len(file_contents) _LOGGER.info("Uploading %s (%s bytes)", filename, file_size) - file_handle.seek(0) - _LOGGER.debug("MD5 of binary is %s", file_md5) # Enable nodelay, we need it for phase 1 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) @@ -185,8 +187,16 @@ def perform_ota(sock, password, file_handle, filename): raise OTAError(f"Unsupported OTA version {version}") # Features - send_check(sock, 0x00, "features") - receive_exactly(sock, 1, "features", RESPONSE_HEADER_OK) + send_check(sock, FEATURE_SUPPORTS_COMPRESSION, "features") + features = receive_exactly( + sock, 1, "features", [RESPONSE_HEADER_OK, RESPONSE_SUPPORTS_COMPRESSION] + )[0] + + if features == RESPONSE_SUPPORTS_COMPRESSION: + upload_contents = gzip.compress(file_contents, compresslevel=9) + _LOGGER.info("Compressed to %s bytes", len(upload_contents)) + else: + upload_contents = file_contents (auth,) = receive_exactly( sock, 1, "auth", [RESPONSE_REQUEST_AUTH, RESPONSE_AUTH_OK] @@ -213,16 +223,20 @@ def perform_ota(sock, password, file_handle, filename): send_check(sock, result, "auth result") receive_exactly(sock, 1, "auth result", RESPONSE_AUTH_OK) - file_size_encoded = [ - (file_size >> 24) & 0xFF, - (file_size >> 16) & 0xFF, - (file_size >> 8) & 0xFF, - (file_size >> 0) & 0xFF, + upload_size = len(upload_contents) + upload_size_encoded = [ + (upload_size >> 24) & 0xFF, + (upload_size >> 16) & 0xFF, + (upload_size >> 8) & 0xFF, + (upload_size >> 0) & 0xFF, ] - send_check(sock, file_size_encoded, "binary size") + send_check(sock, upload_size_encoded, "binary size") receive_exactly(sock, 1, "binary size", RESPONSE_UPDATE_PREPARE_OK) - send_check(sock, file_md5, "file checksum") + upload_md5 = hashlib.md5(upload_contents).hexdigest() + _LOGGER.debug("MD5 of upload is %s", upload_md5) + + send_check(sock, upload_md5, "file checksum") receive_exactly(sock, 1, "file checksum", RESPONSE_BIN_MD5_OK) # Disable nodelay for transfer @@ -236,7 +250,7 @@ def perform_ota(sock, password, file_handle, filename): offset = 0 progress = ProgressBar() while True: - chunk = file_handle.read(1024) + chunk = upload_contents[offset : offset + 1024] if not chunk: break offset += len(chunk) @@ -247,7 +261,7 @@ def perform_ota(sock, password, file_handle, filename): sys.stderr.write("\n") raise OTAError(f"Error sending data: {err}") from err - progress.update(offset / float(file_size)) + progress.update(offset / upload_size) progress.done() # Enable nodelay for last checks From b5b3914bbfd28f7bbf2a3aa55113752e0df1a1fe Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 14:14:07 +0200 Subject: [PATCH 245/549] Re-raise keyboardinterrupt (#2603) --- esphome/util.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/util.py b/esphome/util.py index 0f168cade3..937635fa43 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -192,8 +192,8 @@ def run_external_command( sys.argv = list(cmd) sys.exit = mock_exit return func() or 0 - except KeyboardInterrupt: - return 1 + except KeyboardInterrupt: # pylint: disable=try-except-raise + raise except SystemExit as err: return err.args[0] except Exception as err: # pylint: disable=broad-except @@ -227,6 +227,8 @@ def run_external_process(*cmd, **kwargs): try: return subprocess.call(cmd, stdout=sub_stdout, stderr=sub_stderr) + except KeyboardInterrupt: # pylint: disable=try-except-raise + raise except Exception as err: # pylint: disable=broad-except _LOGGER.error("Running command failed: %s", err) _LOGGER.error("Please try running %s locally.", full_cmd) From 83bef854157e20210b7917baf54c86e645a7869c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 14:14:14 +0200 Subject: [PATCH 246/549] Add owner to all libraries used (#2604) --- esphome/components/bme680_bsec/__init__.py | 2 +- esphome/components/mqtt/__init__.py | 2 +- esphome/components/web_server_base/__init__.py | 2 +- platformio.ini | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/bme680_bsec/__init__.py b/esphome/components/bme680_bsec/__init__.py index 38da18d702..2f844fa666 100644 --- a/esphome/components/bme680_bsec/__init__.py +++ b/esphome/components/bme680_bsec/__init__.py @@ -67,4 +67,4 @@ async def to_code(config): cg.add_library("SPI", None) cg.add_define("USE_BSEC") - cg.add_library("BSEC Software Library", "1.6.1480") + cg.add_library("boschsensortec/BSEC Software Library", "1.6.1480") diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 8f02f8d437..3d52dab67f 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -215,7 +215,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("ottowinter/AsyncMqttClient-esphome", "0.8.4") + cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.6") cg.add_define("USE_MQTT") cg.add_global(mqtt_ns.using) diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 95d59a863e..019f31954f 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -28,4 +28,4 @@ async def to_code(config): cg.add_library("FS", None) cg.add_library("Update", None) # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json - cg.add_library("esphome/ESPAsyncWebServer-esphome", "1.3.0") + cg.add_library("esphome/ESPAsyncWebServer-esphome", "1.3.1") diff --git a/platformio.ini b/platformio.ini index ac5144fc37..3d720e24ac 100644 --- a/platformio.ini +++ b/platformio.ini @@ -39,9 +39,9 @@ src_filter = extends = common lib_deps = ${common.lib_deps} - ottowinter/AsyncMqttClient-esphome@0.8.4 ; mqtt + ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt ottowinter/ArduinoJson-esphomelib@5.13.3 ; json - esphome/ESPAsyncWebServer-esphome@1.3.0 ; web_server_base + esphome/ESPAsyncWebServer-esphome@1.3.1 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 From 6db9d1122f417c67054ba6e4dad25c9e95276a94 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 16:52:43 +0200 Subject: [PATCH 247/549] Fix compiler warnings and update platformio line filter (#2607) --- esphome/components/climate/climate.cpp | 4 ++++ esphome/components/mqtt/mqtt_fan.cpp | 6 ++++++ esphome/components/web_server/web_server.cpp | 6 ++++++ esphome/components/web_server_base/__init__.py | 2 +- esphome/platformio_api.py | 17 ++++++++++++----- platformio.ini | 2 +- 6 files changed, 30 insertions(+), 7 deletions(-) diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 34e6328d8a..ebea20ed1f 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -440,7 +440,11 @@ void Climate::set_visual_max_temperature_override(float visual_max_temperature_o void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) { this->visual_temperature_step_override_ = visual_temperature_step_override; } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" Climate::Climate(const std::string &name) : EntityBase(name) {} +#pragma GCC diagnostic pop + Climate::Climate() : Climate("") {} ClimateCall Climate::make_call() { return ClimateCall(this); } diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 898183cc58..1703343a77 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -88,9 +88,12 @@ 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) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" this->state_->make_call() .set_speed(payload.c_str()) // NOLINT(clang-diagnostic-deprecated-declarations) .perform(); +#pragma GCC diagnostic pop }); } @@ -145,6 +148,8 @@ bool MQTTFanComponent::publish_state() { } if (traits.supports_speed()) { const char *payload; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // 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) @@ -161,6 +166,7 @@ bool MQTTFanComponent::publish_state() { break; } } +#pragma GCC diagnostic pop bool success = this->publish(this->get_speed_state_topic(), payload); failed = failed || !success; } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index e99431be36..44ace38990 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -414,6 +414,8 @@ std::string WebServer::fan_json(fan::FanState *obj) { const auto traits = obj->get_traits(); if (traits.supports_speed()) { root["speed_level"] = obj->speed; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // 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) @@ -426,6 +428,7 @@ std::string WebServer::fan_json(fan::FanState *obj) { root["speed"] = "high"; break; } +#pragma GCC diagnostic pop } if (obj->get_traits().supports_oscillation()) root["oscillation"] = obj->oscillating; @@ -448,7 +451,10 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc auto call = obj->turn_on(); if (request->hasParam("speed")) { String speed = request->getParam("speed")->value(); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" call.set_speed(speed.c_str()); // NOLINT(clang-diagnostic-deprecated-declarations) +#pragma GCC diagnostic pop } if (request->hasParam("speed_level")) { String speed_level = request->getParam("speed_level")->value(); diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 019f31954f..4da94d990a 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -28,4 +28,4 @@ async def to_code(config): cg.add_library("FS", None) cg.add_library("Update", None) # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json - cg.add_library("esphome/ESPAsyncWebServer-esphome", "1.3.1") + cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.0.0") diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 054c0cb1b0..70e4430e71 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -46,24 +46,31 @@ 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/.*", - r"PLATFORM: .*", r"DEBUG: Current.*", - r"PACKAGES: .*", + r"LDF Modes:.*", r"LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf.*", - r"LDF Modes: Finder ~ chain, Compatibility ~ soft.*", 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", - r"esptool.py v.*", r"Found: https://platformio.org/lib/show/.*", r"Using cache: .*", r"Installing dependencies", - r".* @ .* is already installed", + r"Library Manager: Already installed, built-in library", r"Building in .* mode", r"Advanced Memory Usage is available via .*", + r"Merged .* ELF section", + r"esptool.py v.*", + r"Checking size .*", + r"Retrieving maximum program size .*", + r"PLATFORM: .*", + r"PACKAGES:.*", + r" - framework-arduinoespressif.* \(.*\)", + r" - tool-esptool.* \(.*\)", + r" - toolchain-.* \(.*\)", + r"Creating BIN file .*", ] diff --git a/platformio.ini b/platformio.ini index 3d720e24ac..ee895ed882 100644 --- a/platformio.ini +++ b/platformio.ini @@ -41,7 +41,7 @@ lib_deps = ${common.lib_deps} ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt ottowinter/ArduinoJson-esphomelib@5.13.3 ; json - esphome/ESPAsyncWebServer-esphome@1.3.1 ; web_server_base + esphome/ESPAsyncWebServer-esphome@2.0.0 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 From 77a6461c9d873a932db9db44af8d31a872f7230c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 17:23:31 +0200 Subject: [PATCH 248/549] Fix ESP8266 OTA compression only starting framework v2.7.0 (#2610) --- esphome/components/ota/ota_backend_arduino_esp8266.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.h b/esphome/components/ota/ota_backend_arduino_esp8266.h index cf29a90fc1..329f2cf0f2 100644 --- a/esphome/components/ota/ota_backend_arduino_esp8266.h +++ b/esphome/components/ota/ota_backend_arduino_esp8266.h @@ -5,6 +5,7 @@ #include "ota_component.h" #include "ota_backend.h" +#include "esphome/core/macros.h" namespace esphome { namespace ota { @@ -16,7 +17,11 @@ class ArduinoESP8266OTABackend : public OTABackend { OTAResponseTypes write(uint8_t *data, size_t len) override; OTAResponseTypes end() override; void abort() override; +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) bool supports_compression() override { return true; } +#else + bool supports_compression() override { return false; } +#endif }; } // namespace ota From 83400d0417d3df46ab8e17e76e6912823e16ce35 Mon Sep 17 00:00:00 2001 From: Andreas Hergert <36455093+andreashergert1984@users.noreply.github.com> Date: Fri, 22 Oct 2021 18:20:57 +0200 Subject: [PATCH 249/549] Bugfix tca9548a and idf refactor anh (#2612) Co-authored-by: Andreas Hergert --- esphome/components/tca9548a/tca9548a.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tca9548a/tca9548a.cpp b/esphome/components/tca9548a/tca9548a.cpp index 5117ad8969..e902eb5ed4 100644 --- a/esphome/components/tca9548a/tca9548a.cpp +++ b/esphome/components/tca9548a/tca9548a.cpp @@ -22,7 +22,7 @@ i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffer void TCA9548AComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up TCA9548A..."); uint8_t status = 0; - if (!this->read_register(0x00, &status, 1)) { + if (this->read_register(0x00, &status, 1) != i2c::ERROR_OK) { ESP_LOGI(TAG, "TCA9548A failed"); this->mark_failed(); return; From 1c4700f447df78ebc5ff1f587d2fb66caa5575de Mon Sep 17 00:00:00 2001 From: Andreas Hergert <36455093+andreashergert1984@users.noreply.github.com> Date: Fri, 22 Oct 2021 18:52:47 +0200 Subject: [PATCH 250/549] fixed dependency for pca9685 component (#2614) Co-authored-by: Otto Winter Co-authored-by: Andreas --- esphome/components/pca9685/pca9685_output.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/pca9685/pca9685_output.cpp b/esphome/components/pca9685/pca9685_output.cpp index 1ad6f4a665..957f4062fc 100644 --- a/esphome/components/pca9685/pca9685_output.cpp +++ b/esphome/components/pca9685/pca9685_output.cpp @@ -1,6 +1,7 @@ #include "pca9685_output.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace pca9685 { From d85b7a6bd079cba75f4a28380965f1d4975c0bf8 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 23 Oct 2021 12:37:50 +0200 Subject: [PATCH 251/549] Bump platform-espressif8266 from 2.6.2 to 2.6.3 (#2620) --- esphome/components/esp8266/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index ddaeee6ab7..b2706bd4fb 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -63,7 +63,7 @@ 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) +ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 3) # for arduino 3 framework versions ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 2, 0) From 1a6a063e044cd0ad335e75db778377ab78ab5a62 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 23 Oct 2021 12:38:57 +0200 Subject: [PATCH 252/549] Move default build path to .esphome directory (#2586) --- esphome/core/config.py | 2 +- esphome/platformio_api.py | 2 +- esphome/writer.py | 77 ++------------------------------------- 3 files changed, 6 insertions(+), 75 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index ad132968b9..04cac29344 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -165,7 +165,7 @@ def preload_core_config(config, result): CORE.data[KEY_CORE] = {} if CONF_BUILD_PATH not in conf: - conf[CONF_BUILD_PATH] = CORE.name + conf[CONF_BUILD_PATH] = f".esphome/build/{CORE.name}" CORE.build_path = CORE.relative_config_path(conf[CONF_BUILD_PATH]) has_oldstyle = CONF_PLATFORM in conf diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 70e4430e71..2072e25ec5 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -125,7 +125,7 @@ def _run_idedata(config): def _load_idedata(config): platformio_ini = Path(CORE.relative_build_path("platformio.ini")) - temp_idedata = Path(CORE.relative_internal_path(CORE.name, "idedata.json")) + temp_idedata = Path(CORE.relative_internal_path("idedata", f"{CORE.name}.json")) changed = False if not platformio_ini.is_file() or not temp_idedata.is_file(): diff --git a/esphome/writer.py b/esphome/writer.py index 29532d4f64..8963572752 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -38,10 +38,8 @@ CPP_BASE_FORMAT = ( """" void setup() { - // ===== DO NOT EDIT ANYTHING BELOW THIS LINE ===== """, """ - // ========= YOU CAN EDIT AFTER THIS LINE ========= App.setup(); } @@ -59,10 +57,8 @@ lib_deps = build_flags = upload_flags = -; ===== DO NOT EDIT ANYTHING BELOW THIS LINE ===== """, """ -; ========= YOU CAN EDIT AFTER THIS LINE ========= """, ) @@ -102,61 +98,6 @@ def replace_file_content(text, pattern, repl): return content_new, count -def migrate_src_version_0_to_1(): - main_cpp = CORE.relative_build_path("src", "main.cpp") - if not os.path.isfile(main_cpp): - return - - content = read_file(main_cpp) - - if CPP_INCLUDE_BEGIN in content: - return - - content, count = replace_file_content(content, r"\s*delay\((?:16|20)\);", "") - if count != 0: - _LOGGER.info( - "Migration: Removed %s occurrence of 'delay(16);' in %s", count, main_cpp - ) - - content, count = replace_file_content(content, r"using namespace esphomelib;", "") - if count != 0: - _LOGGER.info( - "Migration: Removed %s occurrence of 'using namespace esphomelib;' " - "in %s", - count, - main_cpp, - ) - - if CPP_INCLUDE_BEGIN not in content: - content, count = replace_file_content( - content, - r'#include "esphomelib/application.h"', - f"{CPP_INCLUDE_BEGIN}\n{CPP_INCLUDE_END}", - ) - if count == 0: - _LOGGER.error( - "Migration failed. ESPHome 1.10.0 needs to have a new auto-generated " - "include section in the %s file. Please remove %s and let it be " - "auto-generated again.", - main_cpp, - main_cpp, - ) - _LOGGER.info("Migration: Added include section to %s", main_cpp) - - write_file_if_changed(main_cpp, content) - - -def migrate_src_version(old, new): - if old == new: - return - if old > new: - _LOGGER.warning("The source version rolled backwards! Ignoring.") - return - - if old == 0: - migrate_src_version_0_to_1() - - def storage_should_clean(old, new): # type: (StorageJSON, StorageJSON) -> bool if old is None: return True @@ -175,9 +116,6 @@ def update_storage_json(): if old == new: return - old_src_version = old.src_version if old is not None else 0 - migrate_src_version(old_src_version, new.src_version) - if storage_should_clean(old, new): _LOGGER.info("Core config or version changed, cleaning build files...") clean_build() @@ -277,12 +215,12 @@ VERSION_H_TARGET = "esphome/core/version.h" ESPHOME_README_TXT = """ THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY -ESPHome automatically populates the esphome/ directory, and any +ESPHome automatically populates the build directory, and any changes to this directory will be removed the next time esphome is run. -For modifying esphome's core files, please use a development esphome install -or use the custom_components folder. +For modifying esphome's core files, please use a development esphome install, +the custom_components folder or the external_components feature. """ @@ -339,9 +277,7 @@ def copy_src_tree(): write_file_if_changed( CORE.relative_src_path("esphome", "core", "defines.h"), generate_defines_h() ) - write_file_if_changed( - CORE.relative_src_path("esphome", "README.txt"), ESPHOME_README_TXT - ) + write_file_if_changed(CORE.relative_build_path("README.txt"), ESPHOME_README_TXT) write_file_if_changed( CORE.relative_src_path("esphome.h"), ESPHOME_H_FORMAT.format(include_s) ) @@ -413,11 +349,6 @@ GITIGNORE_CONTENT = """# Gitignore settings for ESPHome # This is an example and may include too much for your use-case. # You can modify this file to suit your needs. /.esphome/ -**/.pioenvs/ -**/.piolibdeps/ -**/lib/ -**/src/ -**/platformio.ini /secrets.yaml """ From b9e5c7eb35678fe1147575685fc03030c3f1713d Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sat, 23 Oct 2021 13:25:46 +0200 Subject: [PATCH 253/549] Autodetect flash size (#2615) --- esphome/__main__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/__main__.py b/esphome/__main__.py index 97059154fd..c2a6dd343f 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -226,6 +226,8 @@ def upload_using_esptool(config, port): mcu, "write_flash", "-z", + "--flash_size", + "detect", ] for img in flash_images: cmd += [img.offset, img.path] From a687b083aed516cbfe93ed25dc51a31837f95fb0 Mon Sep 17 00:00:00 2001 From: 0hax <43876620+0hax@users.noreply.github.com> Date: Sat, 23 Oct 2021 19:01:23 +0200 Subject: [PATCH 254/549] Teleinfo ptec (#2599) * teleinfo: handle historical mode correctly. In historical mode, tags like PTEC leads to an issue where we detect a timestamp wheras this is not possible in historical mode. PTEC teleinfo tag looks like: PTEC HP.. Instead of the usual format IINST1 001 I This make our data parsing fails. While at here, make sure we continue parsing other tags even if parsing one of the tag fails. Signed-off-by: 0hax <0hax@protonmail.com> * teleinfo: fix compilation with loglevel set to debug. Signed-off-by: 0hax <0hax@protonmail.com> --- .../teleinfo/sensor/teleinfo_sensor.cpp | 2 +- esphome/components/teleinfo/teleinfo.cpp | 17 +++++++++-------- .../text_sensor/teleinfo_text_sensor.cpp | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp b/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp index 661c149c09..4e4cd9f9e6 100644 --- a/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp +++ b/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp @@ -9,6 +9,6 @@ void TeleInfoSensor::publish_val(const std::string &val) { auto newval = parse_float(val); publish_state(*newval); } -void TeleInfoSensor::dump_config() { LOG_SENSOR(" ", tag.c_str(), this); } +void TeleInfoSensor::dump_config() { LOG_SENSOR(" ", "Teleinfo Sensor", this); } } // namespace teleinfo } // namespace esphome diff --git a/esphome/components/teleinfo/teleinfo.cpp b/esphome/components/teleinfo/teleinfo.cpp index badd66ae83..5a1e44ac8b 100644 --- a/esphome/components/teleinfo/teleinfo.cpp +++ b/esphome/components/teleinfo/teleinfo.cpp @@ -141,21 +141,22 @@ void TeleInfo::loop() { field_len = get_field(tag_, buf_finger, grp_end, separator_, MAX_TAG_SIZE); if (!field_len || field_len >= MAX_TAG_SIZE) { ESP_LOGE(TAG, "Invalid tag."); - break; + continue; } /* Advance buf_finger to after the tag and the separator. */ buf_finger += field_len + 1; /* - * If there is two separators and the tag is not equal to "DATE", - * it means there is a timestamp to read first. + * If there is two separators and the tag is not equal to "DATE" or + * historical mode is not in use (separator_ != 0x20), it means there is a + * timestamp to read first. */ - if (std::count(buf_finger, grp_end, separator_) == 2 && strcmp(tag_, "DATE") != 0) { + if (std::count(buf_finger, grp_end, separator_) == 2 && strcmp(tag_, "DATE") != 0 && separator_ != 0x20) { field_len = get_field(timestamp_, buf_finger, grp_end, separator_, MAX_TIMESTAMP_SIZE); if (!field_len || field_len >= MAX_TIMESTAMP_SIZE) { - ESP_LOGE(TAG, "Invalid Timestamp"); - break; + ESP_LOGE(TAG, "Invalid timestamp for tag %s", timestamp_); + continue; } /* Advance buf_finger to after the first data and the separator. */ @@ -164,8 +165,8 @@ void TeleInfo::loop() { field_len = get_field(val_, buf_finger, grp_end, separator_, MAX_VAL_SIZE); if (!field_len || field_len >= MAX_VAL_SIZE) { - ESP_LOGE(TAG, "Invalid Value"); - break; + ESP_LOGE(TAG, "Invalid value for tag %s", tag_); + continue; } /* Advance buf_finger to end of group */ diff --git a/esphome/components/teleinfo/text_sensor/teleinfo_text_sensor.cpp b/esphome/components/teleinfo/text_sensor/teleinfo_text_sensor.cpp index 1adbd9ce13..87cf0dea17 100644 --- a/esphome/components/teleinfo/text_sensor/teleinfo_text_sensor.cpp +++ b/esphome/components/teleinfo/text_sensor/teleinfo_text_sensor.cpp @@ -6,6 +6,6 @@ namespace teleinfo { static const char *const TAG = "teleinfo_text_sensor"; TeleInfoTextSensor::TeleInfoTextSensor(const char *tag) { this->tag = std::string(tag); } void TeleInfoTextSensor::publish_val(const std::string &val) { publish_state(val); } -void TeleInfoTextSensor::dump_config() { LOG_TEXT_SENSOR(" ", tag.c_str(), this); } +void TeleInfoTextSensor::dump_config() { LOG_TEXT_SENSOR(" ", "Teleinfo Text Sensor", this); } } // namespace teleinfo } // namespace esphome From 8e77e3c685b4f6a37fde1b04a31fdf87e6f504a2 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 23 Oct 2021 19:25:53 +0200 Subject: [PATCH 255/549] Fix glue code missing micros() (#2623) --- esphome/core/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/core/config.py b/esphome/core/config.py index 04cac29344..68c253f7b4 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -237,6 +237,7 @@ def include_file(path, basename): ARDUINO_GLUE_CODE = """\ #define yield() esphome::yield() #define millis() esphome::millis() +#define micros() esphome::micros() #define delay(x) esphome::delay(x) #define delayMicroseconds(x) esphome::delayMicroseconds(x) """ From de06a781ff205d9b5c03ca7e0518210df1117c03 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 23 Oct 2021 19:44:55 +0200 Subject: [PATCH 256/549] ESP8266 disable PIO LDF (#2608) --- esphome/components/captive_portal/__init__.py | 2 ++ esphome/components/esp8266/__init__.py | 2 ++ esphome/components/http_request/__init__.py | 2 ++ esphome/components/spi/__init__.py | 4 +++- esphome/components/web_server_base/__init__.py | 2 +- platformio.ini | 2 +- 6 files changed, 11 insertions(+), 3 deletions(-) diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index 384a3f23a0..f024c94b01 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -36,3 +36,5 @@ async def to_code(config): if CORE.is_esp32: cg.add_library("DNSServer", None) cg.add_library("WiFi", None) + if CORE.is_esp8266: + cg.add_library("DNSServer", None) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index b2706bd4fb..7c7758b943 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -142,6 +142,8 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): cg.add(esp8266_ns.setup_preferences()) + cg.add_platformio_option("lib_ldf_mode", "off") + 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/http_request/__init__.py b/esphome/components/http_request/__init__.py index 6e249c4247..774d6a0f91 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -96,6 +96,8 @@ async def to_code(config): if CORE.is_esp32: cg.add_library("WiFiClientSecure", None) cg.add_library("HTTPClient", None) + if CORE.is_esp8266: + cg.add_library("ESP8266HTTPClient", None) await cg.register_component(var, config) diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index 3a96cce99b..c917fe1ad8 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -46,7 +46,9 @@ async def to_code(config): mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN]) cg.add(var.set_mosi(mosi)) - if CORE.is_esp32: + if CORE.is_esp32 and CORE.using_arduino: + cg.add_library("SPI", None) + if CORE.is_esp8266: cg.add_library("SPI", None) diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 4da94d990a..dc1a2bc2f0 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -28,4 +28,4 @@ async def to_code(config): cg.add_library("FS", None) cg.add_library("Update", None) # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json - cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.0.0") + cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.0.1") diff --git a/platformio.ini b/platformio.ini index ee895ed882..fa8944bf9a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -41,7 +41,7 @@ lib_deps = ${common.lib_deps} ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt ottowinter/ArduinoJson-esphomelib@5.13.3 ; json - esphome/ESPAsyncWebServer-esphome@2.0.0 ; web_server_base + esphome/ESPAsyncWebServer-esphome@2.0.1 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 From 49b17c5a2d0a2a2eab215d1cc2991298e38019f4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 23 Oct 2021 21:58:04 +0200 Subject: [PATCH 257/549] Switch issue-close-app to GH Actions and workflow cleanup (#2624) --- .github/issue-close-app.yml | 7 ------- .github/lock.yml | 36 ------------------------------------ .github/workflows/lock.yml | 14 ++++++++++---- .github/workflows/stale.yml | 20 +++++++++++++++++++- 4 files changed, 29 insertions(+), 48 deletions(-) delete mode 100644 .github/issue-close-app.yml delete mode 100644 .github/lock.yml diff --git a/.github/issue-close-app.yml b/.github/issue-close-app.yml deleted file mode 100644 index 5f5fb7572d..0000000000 --- a/.github/issue-close-app.yml +++ /dev/null @@ -1,7 +0,0 @@ -comment: >- - https://github.com/esphome/esphome/issues/430 -issueConfigs: -- content: - - "OTHERWISE THE ISSUE WILL BE CLOSED AUTOMATICALLY" - -caseInsensitive: false diff --git a/.github/lock.yml b/.github/lock.yml deleted file mode 100644 index 0680577b2e..0000000000 --- a/.github/lock.yml +++ /dev/null @@ -1,36 +0,0 @@ -# Configuration for Lock Threads - https://github.com/dessant/lock-threads - -# Number of days of inactivity before a closed issue or pull request is locked -daysUntilLock: 7 - -# Skip issues and pull requests created before a given timestamp. Timestamp must -# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable -skipCreatedBefore: false - -# Issues and pull requests with these labels will be ignored. Set to `[]` to disable -exemptLabels: - - keep-open - -# Label to add before locking, such as `outdated`. Set to `false` to disable -lockLabel: false - -# Comment to post before locking. Set to `false` to disable -lockComment: false - -# Assign `resolved` as the reason for locking. Set to `false` to disable -setLockReason: false - -# Limit to only `issues` or `pulls` -# only: issues - -# Optionally, specify configuration settings just for `issues` or `pulls` -# issues: -# exemptLabels: -# - help-wanted -# lockLabel: outdated - -# pulls: -# daysUntilLock: 30 - -# Repository to extend settings from -# _extends: repo diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 375b8f1db4..ceb45b2a91 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -9,13 +9,19 @@ permissions: issues: write pull-requests: write +concurrency: + group: lock + jobs: lock: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v2 + - uses: dessant/lock-threads@v3 with: - github-token: ${{ github.token }} - pr-lock-inactive-days: "1" + pr-inactive-days: "1" pr-lock-reason: "" - process-only: prs + exclude-any-pr-labels: keep-open + + issue-inactive-days: "7" + issue-lock-reason: "" + exclude-any-issue-labels: keep-open diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 712ae1a289..c3e450d0cf 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -9,13 +9,15 @@ permissions: issues: write pull-requests: write +concurrency: + group: lock + 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 @@ -28,3 +30,19 @@ jobs: 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. + + # Use stale to automatically close issues with a reference to the issue tracker + close-issues: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v4 + with: + days-before-pr-stale: -1 + days-before-pr-close: -1 + days-before-issue-stale: 1 + days-before-issue-close: 1 + remove-stale-when-updated: true + stale-issue-label: "stale" + exempt-issue-labels: "not-stale" + stale-issue-message: > + https://github.com/esphome/esphome/issues/430 From 81c11ba1f71249ef8b3c9bb6d72e65dcda4c6960 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 25 Oct 2021 21:53:47 +0200 Subject: [PATCH 258/549] relax max entities checking (#2629) --- esphome/components/modbus/modbus.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 45c5bfb603..9524f9daf4 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -139,7 +139,9 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address uint8_t payload_len, const uint8_t *payload) { static const size_t MAX_VALUES = 128; - if (number_of_entities > MAX_VALUES) { + // Only check max number of registers for standard function codes + // Some devices use non standard codes like 0x43 + if (number_of_entities > MAX_VALUES && function_code <= 0x10) { ESP_LOGE(TAG, "send too many values %d max=%zu", number_of_entities, MAX_VALUES); return; } From 87328686a0b0ad81f0061094e904068579d11539 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 26 Oct 2021 10:55:09 +0200 Subject: [PATCH 259/549] Allow setting URL as platform_version (#2598) --- esphome/components/esp32/__init__.py | 31 ++++++++++++++++---------- esphome/components/esp8266/__init__.py | 23 ++++++++++++------- esphome/config_validation.py | 19 ++++++++++++++++ 3 files changed, 53 insertions(+), 20 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index db24b9aa29..d84663b2d6 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -147,8 +147,9 @@ def _arduino_check_versions(value): value[CONF_VERSION] = str(version) value[CONF_SOURCE] = source or _format_framework_arduino_version(version) - platform_version = value.get(CONF_PLATFORM_VERSION, ARDUINO_PLATFORM_VERSION) - value[CONF_PLATFORM_VERSION] = str(platform_version) + value[CONF_PLATFORM_VERSION] = value.get( + CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION)) + ) if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: _LOGGER.warning( @@ -184,8 +185,9 @@ def _esp_idf_check_versions(value): value[CONF_VERSION] = str(version) value[CONF_SOURCE] = source or _format_framework_espidf_version(version) - platform_version = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION) - value[CONF_PLATFORM_VERSION] = str(platform_version) + value[CONF_PLATFORM_VERSION] = value.get( + CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION)) + ) if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION: _LOGGER.warning( @@ -196,6 +198,15 @@ def _esp_idf_check_versions(value): return value +def _parse_platform_version(value): + try: + # if platform version is a valid version constraint, prefix the default package + cv.platformio_version_constraint(value) + return f"platformio/espressif32 @ {value}" + except cv.Invalid: + return value + + def _detect_variant(value): if CONF_VARIANT not in value: board = value[CONF_BOARD] @@ -218,7 +229,7 @@ ARDUINO_FRAMEWORK_SCHEMA = cv.All( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict, - cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, + cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, } ), _arduino_check_versions, @@ -230,7 +241,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict, - cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, + cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { cv.string_strict: cv.string_strict }, @@ -280,10 +291,9 @@ async def to_code(config): cg.add_platformio_option("lib_ldf_mode", "off") conf = config[CONF_FRAMEWORK] + cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) + 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") @@ -314,9 +324,6 @@ async def to_code(config): ) 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") diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 7c7758b943..5b97d2d9d5 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -93,12 +93,12 @@ def _arduino_check_versions(value): platform_version = value.get(CONF_PLATFORM_VERSION) if platform_version is None: if version >= cv.Version(3, 0, 0): - platform_version = ARDUINO_3_PLATFORM_VERSION + platform_version = _parse_platform_version(str(ARDUINO_3_PLATFORM_VERSION)) elif version >= cv.Version(2, 5, 0): - platform_version = ARDUINO_2_PLATFORM_VERSION + platform_version = _parse_platform_version(str(ARDUINO_2_PLATFORM_VERSION)) else: - platform_version = cv.Version(1, 8, 0) - value[CONF_PLATFORM_VERSION] = str(platform_version) + platform_version = _parse_platform_version(str(cv.Version(1, 8, 0))) + value[CONF_PLATFORM_VERSION] = platform_version if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: _LOGGER.warning( @@ -109,13 +109,22 @@ def _arduino_check_versions(value): return value +def _parse_platform_version(value): + try: + # if platform version is a valid version constraint, prefix the default package + cv.platformio_version_constraint(value) + return f"platformio/espressif8266 @ {value}" + except cv.Invalid: + return value + + CONF_PLATFORM_VERSION = "platform_version" ARDUINO_FRAMEWORK_SCHEMA = cv.All( cv.Schema( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict, - cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, + cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, } ), _arduino_check_versions, @@ -152,13 +161,11 @@ async def to_code(config): 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", conf[CONF_PLATFORM_VERSION]) cg.add_platformio_option( "platform_packages", [f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"], ) - cg.add_platformio_option( - "platform", f"platformio/espressif8266 @ {conf[CONF_PLATFORM_VERSION]}" - ) # Default for platformio is LWIP2_LOW_MEMORY with: # - MSS=536 diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 5d4ff64193..fcd014f62d 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1668,6 +1668,25 @@ def version_number(value): raise Invalid("Not a version number") from e +def platformio_version_constraint(value): + # for documentation on valid version constraints: + # https://docs.platformio.org/en/latest/core/userguide/platforms/cmd_install.html#cmd-platform-install + + value = string_strict(value) + constraints = [] + for item in value.split(","): + # find and strip prefix operator + op = None + for test_op in ("^", "~", ">=", ">", "<=", "<", "!="): + if item.startswith(test_op): + op = test_op + item = item[len(test_op) :] + break + + constraints.append((op, version_number(item))) + return constraints + + def require_framework_version( *, esp_idf=None, From a01f5f5cf19213eec4501d7166e5dc64a3f34054 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 26 Oct 2021 21:55:20 +1300 Subject: [PATCH 260/549] Remove power and energy from sensors that are not true power (#2628) --- esphome/components/dsmr/sensor.py | 16 ++++++++-------- esphome/components/havells_solar/sensor.py | 4 +--- esphome/components/pipsolar/sensor/__init__.py | 4 ++-- esphome/components/sdm_meter/sensor.py | 4 ---- esphome/components/selec_meter/sensor.py | 8 -------- 5 files changed, 11 insertions(+), 25 deletions(-) diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 761009c766..d809d0d105 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -75,14 +75,14 @@ CONFIG_SCHEMA = cv.Schema( UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, ICON_EMPTY, 3, - DEVICE_CLASS_ENERGY, + DEVICE_CLASS_EMPTY, STATE_CLASS_NONE, ), cv.Optional("total_exported_energy"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, ICON_EMPTY, 3, - DEVICE_CLASS_ENERGY, + DEVICE_CLASS_EMPTY, STATE_CLASS_NONE, ), cv.Optional("power_delivered"): sensor.sensor_schema( @@ -166,42 +166,42 @@ CONFIG_SCHEMA = cv.Schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l2"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l3"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l1"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l2"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l3"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l1"): sensor.sensor_schema( diff --git a/esphome/components/havells_solar/sensor.py b/esphome/components/havells_solar/sensor.py index 3ec12d5b83..d7c8d544f9 100644 --- a/esphome/components/havells_solar/sensor.py +++ b/esphome/components/havells_solar/sensor.py @@ -93,13 +93,12 @@ PV_SENSORS = { CONF_VOLTAGE_SAMPLED_BY_SECONDARY_CPU: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=0, - device_class=DEVICE_CLASS_POWER, + device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, ), CONF_INSULATION_OF_P_TO_GROUND: sensor.sensor_schema( unit_of_measurement=UNIT_KOHM, accuracy_decimals=0, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), } @@ -135,7 +134,6 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, accuracy_decimals=2, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ENERGY_PRODUCTION_DAY): sensor.sensor_schema( diff --git a/esphome/components/pipsolar/sensor/__init__.py b/esphome/components/pipsolar/sensor/__init__.py index 5e4dd6c40c..a206e41988 100644 --- a/esphome/components/pipsolar/sensor/__init__.py +++ b/esphome/components/pipsolar/sensor/__init__.py @@ -89,7 +89,7 @@ TYPES = { 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 + UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY ), CONF_AC_OUTPUT_RATING_ACTIVE_POWER: sensor.sensor_schema( UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER @@ -159,7 +159,7 @@ TYPES = { 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 + UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY ), CONF_AC_OUTPUT_ACTIVE_POWER: sensor.sensor_schema( UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER diff --git a/esphome/components/sdm_meter/sensor.py b/esphome/components/sdm_meter/sensor.py index 8a0d9674a7..87c99c9152 100644 --- a/esphome/components/sdm_meter/sensor.py +++ b/esphome/components/sdm_meter/sensor.py @@ -64,13 +64,11 @@ PHASE_SENSORS = { CONF_APPARENT_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS, accuracy_decimals=2, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), CONF_REACTIVE_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, accuracy_decimals=2, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), CONF_POWER_FACTOR: sensor.sensor_schema( @@ -115,13 +113,11 @@ CONFIG_SCHEMA = ( 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_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_TOTAL_INCREASING, ), } diff --git a/esphome/components/selec_meter/sensor.py b/esphome/components/selec_meter/sensor.py index 168d3a3db2..e698255c25 100644 --- a/esphome/components/selec_meter/sensor.py +++ b/esphome/components/selec_meter/sensor.py @@ -71,25 +71,21 @@ SENSORS = { 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_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_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_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_TOTAL_INCREASING, ), CONF_ACTIVE_POWER: sensor.sensor_schema( @@ -101,13 +97,11 @@ SENSORS = { CONF_REACTIVE_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, accuracy_decimals=3, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), CONF_APPARENT_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS, accuracy_decimals=3, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), CONF_VOLTAGE: sensor.sensor_schema( @@ -142,13 +136,11 @@ SENSORS = { CONF_MAXIMUM_DEMAND_REACTIVE_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, accuracy_decimals=3, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), CONF_MAXIMUM_DEMAND_APPARENT_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS, accuracy_decimals=3, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), } From c612a3bf6074cf6cd2161a970d3e7d670344b0cf Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 26 Oct 2021 10:55:27 +0200 Subject: [PATCH 261/549] Constrain GH Actions workflows permissions (#2625) --- .github/workflows/ci-docker.yml | 4 ++++ .github/workflows/ci.yml | 3 +++ .github/workflows/release.yml | 9 +++++++++ 3 files changed, 16 insertions(+) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 12f5a7dfc2..1d1cc169b2 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -17,6 +17,10 @@ on: - 'requirements*.txt' - 'platformio.ini' +permissions: + contents: read + packages: read + jobs: check-docker: name: Build docker containers diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45e2f2735c..02b64d2bf5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,9 @@ on: pull_request: +permissions: + contents: read + jobs: ci: name: ${{ matrix.name }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index afd893d065..d6895becc0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,9 @@ on: schedule: - cron: "0 2 * * *" +permissions: + contents: read + jobs: init: name: Initialize build @@ -52,6 +55,9 @@ jobs: deploy-docker: name: Build and publish docker containers if: github.repository == 'esphome/esphome' + permissions: + contents: read + packages: write runs-on: ubuntu-latest needs: [init] strategy: @@ -93,6 +99,9 @@ jobs: deploy-docker-manifest: if: github.repository == 'esphome/esphome' + permissions: + contents: read + packages: write runs-on: ubuntu-latest needs: [init, deploy-docker] strategy: From 2f85c27a053a6b4553670bea90800a81b13d87f2 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Tue, 26 Oct 2021 11:30:25 +0200 Subject: [PATCH 262/549] fix modbus output (#2630) --- .../components/modbus_controller/__init__.py | 15 +++++++++++++++ .../modbus_controller/number/__init__.py | 17 +---------------- .../modbus_controller/output/__init__.py | 13 ++++++++++++- .../modbus_controller/output/modbus_output.h | 3 ++- .../modbus_controller/sensor/__init__.py | 18 +----------------- 5 files changed, 31 insertions(+), 35 deletions(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 6b452ea25c..8499cec561 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -61,6 +61,21 @@ SENSOR_VALUE_TYPE = { "FP32_R": SensorValueType.FP32_R, } +TYPE_REGISTER_MAP = { + "RAW": 1, + "U_WORD": 1, + "S_WORD": 1, + "U_DWORD": 2, + "U_DWORD_R": 2, + "S_DWORD": 2, + "S_DWORD_R": 2, + "U_QWORD": 4, + "U_QWORDU_R": 4, + "S_QWORD": 4, + "U_QWORD_R": 4, + "FP32": 2, + "FP32_R": 2, +} MULTI_CONF = True diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py index c7919bb972..afb69f8798 100644 --- a/esphome/components/modbus_controller/number/__init__.py +++ b/esphome/components/modbus_controller/number/__init__.py @@ -17,6 +17,7 @@ from .. import ( ModbusController, SENSOR_VALUE_TYPE, SensorItem, + TYPE_REGISTER_MAP, ) @@ -39,22 +40,6 @@ ModbusNumber = modbus_controller_ns.class_( "ModbusNumber", cg.Component, number.Number, SensorItem ) -TYPE_REGISTER_MAP = { - "RAW": 1, - "U_WORD": 1, - "S_WORD": 1, - "U_DWORD": 2, - "U_DWORD_R": 2, - "S_DWORD": 2, - "S_DWORD_R": 2, - "U_QWORD": 4, - "U_QWORDU_R": 4, - "S_QWORD": 4, - "U_QWORD_R": 4, - "FP32": 2, - "FP32_R": 2, -} - def validate_min_max(config): if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]: diff --git a/esphome/components/modbus_controller/output/__init__.py b/esphome/components/modbus_controller/output/__init__.py index 9c41fc011c..4aca4db64f 100644 --- a/esphome/components/modbus_controller/output/__init__.py +++ b/esphome/components/modbus_controller/output/__init__.py @@ -13,11 +13,13 @@ from .. import ( SensorItem, modbus_controller_ns, ModbusController, + TYPE_REGISTER_MAP, ) from ..const import ( CONF_BYTE_OFFSET, CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_COUNT, CONF_VALUE_TYPE, CONF_WRITE_LAMBDA, ) @@ -40,6 +42,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_OFFSET, default=0): cv.positive_int, cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), + cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, } @@ -54,8 +57,16 @@ async def to_code(config): # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET if CONF_BYTE_OFFSET in config: byte_offset = config[CONF_BYTE_OFFSET] + value_type = config[CONF_VALUE_TYPE] + reg_count = config[CONF_REGISTER_COUNT] + if reg_count == 0: + reg_count = TYPE_REGISTER_MAP[value_type] var = cg.new_Pvariable( - config[CONF_ID], config[CONF_ADDRESS], byte_offset, config[CONF_VALUE_TYPE] + config[CONF_ID], + config[CONF_ADDRESS], + byte_offset, + value_type, + reg_count, ) await output.register_output(var, config) cg.add(var.set_write_multiply(config[CONF_MULTIPLY])) diff --git a/esphome/components/modbus_controller/output/modbus_output.h b/esphome/components/modbus_controller/output/modbus_output.h index 053186a321..6e8521854b 100644 --- a/esphome/components/modbus_controller/output/modbus_output.h +++ b/esphome/components/modbus_controller/output/modbus_output.h @@ -11,12 +11,13 @@ using value_to_data_t = std::function(float); class ModbusOutput : public output::FloatOutput, public Component, public SensorItem { public: - ModbusOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type) + ModbusOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type, int register_count) : output::FloatOutput(), Component() { this->register_type = ModbusRegisterType::HOLDING; this->start_address = start_address; this->offset = offset; this->bitmask = bitmask; + this->register_count = register_count; this->sensor_value_type = value_type; this->skip_updates = 0; this->start_address += offset; diff --git a/esphome/components/modbus_controller/sensor/__init__.py b/esphome/components/modbus_controller/sensor/__init__.py index 687f3d82fb..82acfe120b 100644 --- a/esphome/components/modbus_controller/sensor/__init__.py +++ b/esphome/components/modbus_controller/sensor/__init__.py @@ -9,6 +9,7 @@ from .. import ( ModbusController, MODBUS_REGISTER_TYPE, SENSOR_VALUE_TYPE, + TYPE_REGISTER_MAP, ) from ..const import ( CONF_BITMASK, @@ -29,23 +30,6 @@ ModbusSensor = modbus_controller_ns.class_( "ModbusSensor", cg.Component, sensor.Sensor, SensorItem ) -TYPE_REGISTER_MAP = { - "RAW": 1, - "U_WORD": 1, - "S_WORD": 1, - "U_DWORD": 2, - "U_DWORD_R": 2, - "S_DWORD": 2, - "S_DWORD_R": 2, - "U_QWORD": 4, - "U_QWORDU_R": 4, - "S_QWORD": 4, - "U_QWORD_R": 4, - "FP32": 2, - "FP32_R": 2, -} - - CONFIG_SCHEMA = cv.All( sensor.SENSOR_SCHEMA.extend( { From 9f625ee7d1095b4a70605cbae45eca5441b27c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Tue, 26 Oct 2021 18:10:45 +0200 Subject: [PATCH 263/549] Fix pin number validation for sn74hc595 (#2621) --- esphome/components/sn74hc595/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sn74hc595/__init__.py b/esphome/components/sn74hc595/__init__.py index 0d1ff6ecba..630abc8bca 100644 --- a/esphome/components/sn74hc595/__init__.py +++ b/esphome/components/sn74hc595/__init__.py @@ -60,7 +60,7 @@ 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_range(min=0, max=7), + cv.Required(CONF_NUMBER): cv.int_range(min=0, max=31), cv.Optional(CONF_MODE, default={}): cv.All( { cv.Optional(CONF_OUTPUT, default=True): cv.All( From c2623a08e35e5d1560399315246c0cf110a0a20a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 27 Oct 2021 08:27:51 +1300 Subject: [PATCH 264/549] Fix select.set using lambda (#2633) --- esphome/components/select/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index c156a63a86..7e4047d3c8 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -90,6 +90,6 @@ async def to_code(config): async def select_set_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) - template_ = await cg.templatable(config[CONF_OPTION], args, str) + template_ = await cg.templatable(config[CONF_OPTION], args, cg.std_string) cg.add(var.set_option(template_)) return var From 2f4b9263c3b1c451bec78b20172b9529d059fd55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Oct 2021 22:12:37 +0200 Subject: [PATCH 265/549] Bump tzlocal from 4.0.1 to 4.0.2 (#2631) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3ed53d0c90..266f61369b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ PyYAML==6.0 paho-mqtt==1.6.1 colorama==0.4.4 tornado==6.1 -tzlocal==4.0.1 # from time +tzlocal==4.0.2 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile From 68316cbcf996bbf20b078eac6022aad7ce9c4999 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Oct 2021 22:14:44 +0200 Subject: [PATCH 266/549] Bump esptool from 3.1 to 3.2 (#2632) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 266f61369b..7f3470e467 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ tzlocal==4.0.2 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile -esptool==3.1 +esptool==3.2 click==8.0.3 esphome-dashboard==20211021.1 aioesphomeapi==10.0.3 From 980c2d4caef4c58fd3938144314958270b1c3e66 Mon Sep 17 00:00:00 2001 From: niklasweber Date: Wed, 27 Oct 2021 21:09:37 +0200 Subject: [PATCH 267/549] Add publish_initial_value option to rotary encoder (#2503) --- esphome/components/rotary_encoder/rotary_encoder.cpp | 3 ++- esphome/components/rotary_encoder/rotary_encoder.h | 2 ++ esphome/components/rotary_encoder/sensor.py | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index 7c95fac98e..e1f2584641 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -189,9 +189,10 @@ void RotaryEncoderSensor::loop() { this->store_.counter = 0; } int counter = this->store_.counter; - if (this->store_.last_read != counter) { + if (this->store_.last_read != counter || this->publish_initial_value_) { this->store_.last_read = counter; this->publish_state(counter); + this->publish_initial_value_ = false; } } diff --git a/esphome/components/rotary_encoder/rotary_encoder.h b/esphome/components/rotary_encoder/rotary_encoder.h index 4825e472a1..a134043152 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.h +++ b/esphome/components/rotary_encoder/rotary_encoder.h @@ -58,6 +58,7 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { void set_reset_pin(GPIOPin *pin_i) { this->pin_i_ = pin_i; } void set_min_value(int32_t min_value); void set_max_value(int32_t max_value); + void set_publish_initial_value(bool publish_initial_value) { publish_initial_value_ = publish_initial_value; } // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) @@ -79,6 +80,7 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { 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. + bool publish_initial_value_; RotaryEncoderSensorStore store_{}; diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py index ef1110c6d8..d868438fd3 100644 --- a/esphome/components/rotary_encoder/sensor.py +++ b/esphome/components/rotary_encoder/sensor.py @@ -27,6 +27,7 @@ RESOLUTIONS = { CONF_PIN_RESET = "pin_reset" CONF_ON_CLOCKWISE = "on_clockwise" CONF_ON_ANTICLOCKWISE = "on_anticlockwise" +CONF_PUBLISH_INITIAL_VALUE = "publish_initial_value" RotaryEncoderSensor = rotary_encoder_ns.class_( "RotaryEncoderSensor", sensor.Sensor, cg.Component @@ -70,6 +71,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_RESOLUTION, default=1): cv.enum(RESOLUTIONS, int=True), cv.Optional(CONF_MIN_VALUE): cv.int_, cv.Optional(CONF_MAX_VALUE): cv.int_, + cv.Optional(CONF_PUBLISH_INITIAL_VALUE, default=False): cv.boolean, cv.Optional(CONF_ON_CLOCKWISE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -99,6 +101,7 @@ async def to_code(config): cg.add(var.set_pin_a(pin_a)) pin_b = await cg.gpio_pin_expression(config[CONF_PIN_B]) cg.add(var.set_pin_b(pin_b)) + cg.add(var.set_publish_initial_value(config[CONF_PUBLISH_INITIAL_VALUE])) if CONF_PIN_RESET in config: pin_i = await cg.gpio_pin_expression(config[CONF_PIN_RESET]) From 2147bcbc29687decea02bbf606727dc01f00ffdf Mon Sep 17 00:00:00 2001 From: Sean Brogan Date: Wed, 27 Oct 2021 13:16:12 -0700 Subject: [PATCH 268/549] Remove autoload of xiaomi_ble and ruuvi_ble (#2617) --- esphome/components/esp32_ble_tracker/__init__.py | 1 - tests/test2.yaml | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index e3d52f345a..e647b74a8f 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -18,7 +18,6 @@ from esphome.core import CORE from esphome.components.esp32 import add_idf_sdkconfig_option DEPENDENCIES = ["esp32"] -AUTO_LOAD = ["xiaomi_ble", "ruuvi_ble"] CONF_ESP32_BLE_ID = "esp32_ble_id" CONF_SCAN_PARAMETERS = "scan_parameters" diff --git a/tests/test2.yaml b/tests/test2.yaml index 7e71d1ab4e..6869eeecb1 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -401,6 +401,11 @@ ble_client: airthings_ble: +ruuvi_ble: + +xiaomi_ble: + + #esp32_ble_beacon: # type: iBeacon # uuid: 'c29ce823-e67a-4e71-bff2-abaa32e77a98' From b3d7cc637be49e50867bf6703ede12fcee6d5c35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Oct 2021 09:17:30 +1300 Subject: [PATCH 269/549] Bump aioesphomeapi from 10.0.3 to 10.1.0 (#2638) 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 7f3470e467..99efa6798f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 esphome-dashboard==20211021.1 -aioesphomeapi==10.0.3 +aioesphomeapi==10.1.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 0d3e6b2c4c4924901e6891426b5495e2211d7daa Mon Sep 17 00:00:00 2001 From: Alex Iribarren Date: Thu, 28 Oct 2021 00:46:55 +0200 Subject: [PATCH 270/549] Expose web_server port via the API (#2467) --- esphome/core/__init__.py | 14 ++++++++++++++ esphome/dashboard/dashboard.py | 7 +++++++ esphome/storage_json.py | 9 +++++++++ 3 files changed, 30 insertions(+) diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 8bdef3a4ea..addecf1326 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_USE_ADDRESS, CONF_ETHERNET, CONF_WIFI, + CONF_PORT, KEY_CORE, KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, @@ -519,6 +520,19 @@ class EsphomeCore: return None + @property + def web_port(self) -> Optional[int]: + if self.config is None: + raise ValueError("Config has not been loaded yet") + + if "web_server" in self.config: + try: + return self.config["web_server"][CONF_PORT] + except KeyError: + return 80 + + return None + @property def comment(self) -> Optional[str]: if self.config is None: diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 084dd84a07..11571ec889 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -509,6 +509,12 @@ class DashboardEntry: return None return self.storage.address + @property + def web_port(self): + if self.storage is None: + return None + return self.storage.web_port + @property def name(self): if self.storage is None: @@ -569,6 +575,7 @@ class ListDevicesHandler(BaseHandler): "path": entry.path, "comment": entry.comment, "address": entry.address, + "web_port": entry.web_port, "target_platform": entry.target_platform, } for entry in entries diff --git a/esphome/storage_json.py b/esphome/storage_json.py index 3262559116..207a3edf57 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -41,6 +41,7 @@ class StorageJSON: esphome_version, src_version, address, + web_port, target_platform, build_path, firmware_bin_path, @@ -60,6 +61,9 @@ class StorageJSON: self.src_version = src_version # type: int # Address of the ESP, for example livingroom.local or a static IP self.address = address # type: str + # Web server port of the ESP, for example 80 + assert web_port is None or isinstance(web_port, int) + self.web_port = web_port # type: int # The type of ESP in use, either ESP32 or ESP8266 self.target_platform = target_platform # type: str # The absolute path to the platformio project @@ -78,6 +82,7 @@ class StorageJSON: "esphome_version": self.esphome_version, "src_version": self.src_version, "address": self.address, + "web_port": self.web_port, "esp_platform": self.target_platform, "build_path": self.build_path, "firmware_bin_path": self.firmware_bin_path, @@ -101,6 +106,7 @@ class StorageJSON: esphome_version=const.__version__, src_version=1, address=esph.address, + web_port=esph.web_port, target_platform=esph.target_platform, build_path=esph.build_path, firmware_bin_path=esph.firmware_bin, @@ -117,6 +123,7 @@ class StorageJSON: esphome_version=const.__version__, src_version=1, address=address, + web_port=None, target_platform=esp_platform, build_path=None, firmware_bin_path=None, @@ -135,6 +142,7 @@ class StorageJSON: ) src_version = storage.get("src_version") address = storage.get("address") + web_port = storage.get("web_port") esp_platform = storage.get("esp_platform") build_path = storage.get("build_path") firmware_bin_path = storage.get("firmware_bin_path") @@ -146,6 +154,7 @@ class StorageJSON: esphome_version, src_version, address, + web_port, esp_platform, build_path, firmware_bin_path, From 73accf747f64daf32c25a127b8410d87335b615d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 29 Oct 2021 07:12:05 +1300 Subject: [PATCH 271/549] Allow cloning/fetching Github PR branches in external_components (#2639) --- .../external_components/__init__.py | 24 ++++++++++++------- esphome/git.py | 10 +++++++- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index 110a8d95ed..e0548e8981 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -43,19 +43,27 @@ def validate_source_shorthand(value): # Regex for GitHub repo name with optional branch/tag # Note: git allows other branch/tag names as well, but never seen them used before m = re.match( - r"github://([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?", + r"github://(?:([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?|pr#([0-9]+))", value, ) if m is None: raise cv.Invalid( - "Source is not a file system path or in expected github://username/name[@branch-or-tag] format!" + "Source is not a file system path, in expected github://username/name[@branch-or-tag] or github://pr#1234 format!" ) - conf = { - CONF_TYPE: TYPE_GIT, - CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git", - } - if m.group(3): - conf[CONF_REF] = m.group(3) + if m.group(4): + conf = { + CONF_TYPE: TYPE_GIT, + CONF_URL: "https://github.com/esphome/esphome.git", + CONF_REF: f"pull/{m.group(4)}/head", + } + else: + conf = { + CONF_TYPE: TYPE_GIT, + CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git", + } + if m.group(3): + conf[CONF_REF] = m.group(3) + return SOURCE_SCHEMA(conf) diff --git a/esphome/git.py b/esphome/git.py index 12c6b41648..b64aa6a864 100644 --- a/esphome/git.py +++ b/esphome/git.py @@ -40,15 +40,23 @@ def clone_or_update( ) -> Path: key = f"{url}@{ref}" repo_dir = _compute_destination_path(key, domain) + fetch_pr_branch = ref.startswith("pull/") 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: + if ref is not None and not fetch_pr_branch: cmd += ["--branch", ref] cmd += ["--", url, str(repo_dir)] run_git_command(cmd) + if fetch_pr_branch: + # We need to fetch the PR branch first, otherwise git will complain + # about missing objects + _LOGGER.info("Fetching %s", ref) + run_git_command(["git", "fetch", "--", "origin", ref], str(repo_dir)) + run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir)) + else: # Check refresh needed file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD") From 2350c5054c691dcbb54c7c5a979e3f6f7a615de3 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Thu, 28 Oct 2021 20:57:39 +0200 Subject: [PATCH 272/549] use update_interval for sntp synchronization (#2563) * use update_interval for sntp synchronization * revert override of default interval --- esphome/components/sntp/sntp_component.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 2b6cd10e80..96be0e9709 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -3,6 +3,9 @@ #ifdef USE_ESP32 #include "lwip/apps/sntp.h" +#ifdef USE_ESP_IDF +#include "esp_sntp.h" +#endif #endif #ifdef USE_ESP8266 #include "sntp.h" @@ -37,6 +40,9 @@ void SNTPComponent::setup() { if (!this->server_3_.empty()) { sntp_setservername(2, strdup(this->server_3_.c_str())); } +#ifdef USE_ESP_IDF + sntp_set_sync_interval(this->get_update_interval()); +#endif sntp_init(); } @@ -47,7 +53,16 @@ void SNTPComponent::dump_config() { ESP_LOGCONFIG(TAG, " Server 3: '%s'", this->server_3_.c_str()); ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); } -void SNTPComponent::update() {} +void SNTPComponent::update() { +#ifndef USE_ESP_IDF + // force resync + if (sntp_enabled()) { + sntp_stop(); + this->has_time_ = false; + sntp_init(); + } +#endif +} void SNTPComponent::loop() { if (this->has_time_) return; @@ -56,7 +71,7 @@ void SNTPComponent::loop() { if (!time.is_valid()) return; - ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%d:%d", time.year, time.month, time.day_of_month, time.hour, + ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour, time.minute, time.second); this->time_sync_callback_.call(); this->has_time_ = true; From 77dbf84e55f404ceea06fde1dfde33213209f6c7 Mon Sep 17 00:00:00 2001 From: Arturo Casal Date: Thu, 28 Oct 2021 20:58:48 +0200 Subject: [PATCH 273/549] Add support for CSE7761 sensor (#2546) * Add CSE7761 sensor support * CSE7761: Added test at test3.yaml * CSE7761: changed string style * CSE7761: fixed cpp lint * CSE7761: Added codeowners * Lots of code cleanup * Revert incorrect setup_priority suggestion * Added error log in read with retries. Co-authored-by: Oxan van Leeuwen * Improved log messages Co-authored-by: Oxan van Leeuwen --- CODEOWNERS | 1 + esphome/components/cse7761/__init__.py | 0 esphome/components/cse7761/cse7761.cpp | 244 +++++++++++++++++++++++++ esphome/components/cse7761/cse7761.h | 52 ++++++ esphome/components/cse7761/sensor.py | 90 +++++++++ tests/test3.yaml | 16 ++ 6 files changed, 403 insertions(+) create mode 100644 esphome/components/cse7761/__init__.py create mode 100644 esphome/components/cse7761/cse7761.cpp create mode 100644 esphome/components/cse7761/cse7761.h create mode 100644 esphome/components/cse7761/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index a7cf3a1b68..664bf9ad6b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -39,6 +39,7 @@ esphome/components/color_temperature/* @jesserockz esphome/components/coolix/* @glmnet esphome/components/cover/* @esphome/core esphome/components/cs5460a/* @balrog-kun +esphome/components/cse7761/* @berfenger esphome/components/ct_clamp/* @jesserockz esphome/components/current_based/* @djwmarcx esphome/components/daly_bms/* @s1lvi0 diff --git a/esphome/components/cse7761/__init__.py b/esphome/components/cse7761/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/cse7761/cse7761.cpp b/esphome/components/cse7761/cse7761.cpp new file mode 100644 index 0000000000..3b8364f0bc --- /dev/null +++ b/esphome/components/cse7761/cse7761.cpp @@ -0,0 +1,244 @@ +#include "cse7761.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace cse7761 { + +static const char *const TAG = "cse7761"; + +/*********************************************************************************************\ + * CSE7761 - Energy (Sonoff Dual R3 Pow v1.x) + * + * Based on Tasmota source code + * See https://github.com/arendst/Tasmota/discussions/10793 + * https://github.com/arendst/Tasmota/blob/development/tasmota/xnrg_19_cse7761.ino +\*********************************************************************************************/ + +static const int CSE7761_UREF = 42563; // RmsUc +static const int CSE7761_IREF = 52241; // RmsIAC +static const int CSE7761_PREF = 44513; // PowerPAC + +static const uint8_t CSE7761_REG_SYSCON = 0x00; // (2) System Control Register (0x0A04) +static const uint8_t CSE7761_REG_EMUCON = 0x01; // (2) Metering control register (0x0000) +static const uint8_t CSE7761_REG_EMUCON2 = 0x13; // (2) Metering control register 2 (0x0001) +static const uint8_t CSE7761_REG_PULSE1SEL = 0x1D; // (2) Pin function output select register (0x3210) + +static const uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000) +static const uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000) +static const uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000) +static const uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000) +static const uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000) +static const uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register + +static const uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F; // (2) Coefficient checksum +static const uint8_t CSE7761_REG_RMSIAC = 0x70; // (2) Channel A effective current conversion coefficient + +static const uint8_t CSE7761_SPECIAL_COMMAND = 0xEA; // Start special command +static const uint8_t CSE7761_CMD_RESET = 0x96; // Reset command, after receiving the command, the chip resets +static const uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC; // Close write operation +static const uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation + +enum CSE7761 { RMS_IAC, RMS_IBC, RMS_UC, POWER_PAC, POWER_PBC, POWER_SC, ENERGY_AC, ENERGY_BC }; + +void CSE7761Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up CSE7761..."); + this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET); + uint16_t syscon = this->read_(0x00, 2); // Default 0x0A04 + if ((0x0A04 == syscon) && this->chip_init_()) { + this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_CLOSE_WRITE); + ESP_LOGD(TAG, "CSE7761 found"); + this->data_.ready = true; + } else { + this->mark_failed(); + } +} + +void CSE7761Component::dump_config() { + ESP_LOGCONFIG(TAG, "CSE7761:"); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with CSE7761 failed!"); + } + LOG_UPDATE_INTERVAL(this); + this->check_uart_settings(38400, 1, uart::UART_CONFIG_PARITY_EVEN, 8); +} + +float CSE7761Component::get_setup_priority() const { return setup_priority::DATA; } + +void CSE7761Component::update() { + if (this->data_.ready) { + this->get_data_(); + } +} + +void CSE7761Component::write_(uint8_t reg, uint16_t data) { + uint8_t buffer[5]; + + buffer[0] = 0xA5; + buffer[1] = reg; + uint32_t len = 2; + if (data) { + if (data < 0xFF) { + buffer[2] = data & 0xFF; + len = 3; + } else { + buffer[2] = (data >> 8) & 0xFF; + buffer[3] = data & 0xFF; + len = 4; + } + uint8_t crc = 0; + for (uint32_t i = 0; i < len; i++) { + crc += buffer[i]; + } + buffer[len] = ~crc; + len++; + } + + this->write_array(buffer, len); +} + +bool CSE7761Component::read_once_(uint8_t reg, uint8_t size, uint32_t *value) { + while (this->available()) { + this->read(); + } + + this->write_(reg, 0); + + uint8_t buffer[8] = {0}; + uint32_t rcvd = 0; + + for (uint32_t i = 0; i <= size; i++) { + int value = this->read(); + if (value > -1 && rcvd < sizeof(buffer) - 1) { + buffer[rcvd++] = value; + } + } + + if (!rcvd) { + ESP_LOGD(TAG, "Received 0 bytes for register %hhu", reg); + return false; + } + + rcvd--; + uint32_t result = 0; + // CRC check + uint8_t crc = 0xA5 + reg; + for (uint32_t i = 0; i < rcvd; i++) { + result = (result << 8) | buffer[i]; + crc += buffer[i]; + } + crc = ~crc; + if (crc != buffer[rcvd]) { + return false; + } + + *value = result; + return true; +} + +uint32_t CSE7761Component::read_(uint8_t reg, uint8_t size) { + bool result = false; // Start loop + uint8_t retry = 3; // Retry up to three times + uint32_t value = 0; // Default no value + while (!result && retry > 0) { + retry--; + if (this->read_once_(reg, size, &value)) + return value; + } + ESP_LOGE(TAG, "Reading register %hhu failed!", reg); + return value; +} + +uint32_t CSE7761Component::coefficient_by_unit_(uint32_t unit) { + switch (unit) { + case RMS_UC: + return 0x400000 * 100 / this->data_.coefficient[RMS_UC]; + case RMS_IAC: + return (0x800000 * 100 / this->data_.coefficient[RMS_IAC]) * 10; // Stay within 32 bits + case POWER_PAC: + return 0x80000000 / this->data_.coefficient[POWER_PAC]; + } + return 0; +} + +bool CSE7761Component::chip_init_() { + uint16_t calc_chksum = 0xFFFF; + for (uint32_t i = 0; i < 8; i++) { + this->data_.coefficient[i] = this->read_(CSE7761_REG_RMSIAC + i, 2); + calc_chksum += this->data_.coefficient[i]; + } + calc_chksum = ~calc_chksum; + uint16_t coeff_chksum = this->read_(CSE7761_REG_COEFFCHKSUM, 2); + if ((calc_chksum != coeff_chksum) || (!calc_chksum)) { + ESP_LOGD(TAG, "Default calibration"); + this->data_.coefficient[RMS_IAC] = CSE7761_IREF; + this->data_.coefficient[RMS_UC] = CSE7761_UREF; + this->data_.coefficient[POWER_PAC] = CSE7761_PREF; + } + + this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_ENABLE_WRITE); + + uint8_t sys_status = this->read_(CSE7761_REG_SYSSTATUS, 1); + if (sys_status & 0x10) { // Write enable to protected registers (WREN) + this->write_(CSE7761_REG_SYSCON | 0x80, 0xFF04); + this->write_(CSE7761_REG_EMUCON | 0x80, 0x1183); + this->write_(CSE7761_REG_EMUCON2 | 0x80, 0x0FC1); + this->write_(CSE7761_REG_PULSE1SEL | 0x80, 0x3290); + } else { + ESP_LOGD(TAG, "Write failed at chip_init"); + return false; + } + return true; +} + +void CSE7761Component::get_data_() { + // The effective value of current and voltage Rms is a 24-bit signed number, + // the highest bit is 0 for valid data, + // and when the highest bit is 1, the reading will be processed as zero + // The active power parameter PowerA/B is in two’s complement format, 32-bit + // data, the highest bit is Sign bit. + uint32_t value = this->read_(CSE7761_REG_RMSU, 3); + this->data_.voltage_rms = (value >= 0x800000) ? 0 : value; + + value = this->read_(CSE7761_REG_RMSIA, 3); + this->data_.current_rms[0] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA + value = this->read_(CSE7761_REG_POWERPA, 4); + this->data_.active_power[0] = (0 == this->data_.current_rms[0]) ? 0 : ((uint32_t) abs((int) value)); + + value = this->read_(CSE7761_REG_RMSIB, 3); + this->data_.current_rms[1] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA + value = this->read_(CSE7761_REG_POWERPB, 4); + this->data_.active_power[1] = (0 == this->data_.current_rms[1]) ? 0 : ((uint32_t) abs((int) value)); + + // convert values and publish to sensors + + float voltage = (float) this->data_.voltage_rms / this->coefficient_by_unit_(RMS_UC); + if (this->voltage_sensor_ != nullptr) { + this->voltage_sensor_->publish_state(voltage); + } + + for (uint32_t channel = 0; channel < 2; channel++) { + // Active power = PowerPA * PowerPAC * 1000 / 0x80000000 + float active_power = (float) this->data_.active_power[channel] / this->coefficient_by_unit_(POWER_PAC); // W + float amps = (float) this->data_.current_rms[channel] / this->coefficient_by_unit_(RMS_IAC); // A + ESP_LOGD(TAG, "Channel %d power %f W, current %f A", channel + 1, active_power, amps); + if (channel == 0) { + if (this->power_sensor_1_ != nullptr) { + this->power_sensor_1_->publish_state(active_power); + } + if (this->current_sensor_1_ != nullptr) { + this->current_sensor_1_->publish_state(amps); + } + } else if (channel == 1) { + if (this->power_sensor_2_ != nullptr) { + this->power_sensor_2_->publish_state(active_power); + } + if (this->current_sensor_2_ != nullptr) { + this->current_sensor_2_->publish_state(amps); + } + } + } +} + +} // namespace cse7761 +} // namespace esphome diff --git a/esphome/components/cse7761/cse7761.h b/esphome/components/cse7761/cse7761.h new file mode 100644 index 0000000000..71846cdcab --- /dev/null +++ b/esphome/components/cse7761/cse7761.h @@ -0,0 +1,52 @@ +#pragma once + +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace cse7761 { + +struct CSE7761DataStruct { + uint32_t frequency = 0; + uint32_t voltage_rms = 0; + uint32_t current_rms[2] = {0}; + uint32_t energy[2] = {0}; + uint32_t active_power[2] = {0}; + uint16_t coefficient[8] = {0}; + uint8_t energy_update = 0; + bool ready = false; +}; + +/// This class implements support for the CSE7761 UART power sensor. +class CSE7761Component : public PollingComponent, public uart::UARTDevice { + public: + void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } + void set_active_power_1_sensor(sensor::Sensor *power_sensor_1) { power_sensor_1_ = power_sensor_1; } + void set_current_1_sensor(sensor::Sensor *current_sensor_1) { current_sensor_1_ = current_sensor_1; } + void set_active_power_2_sensor(sensor::Sensor *power_sensor_2) { power_sensor_2_ = power_sensor_2; } + void set_current_2_sensor(sensor::Sensor *current_sensor_2) { current_sensor_2_ = current_sensor_2; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + + protected: + // Sensors + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *power_sensor_1_{nullptr}; + sensor::Sensor *current_sensor_1_{nullptr}; + sensor::Sensor *power_sensor_2_{nullptr}; + sensor::Sensor *current_sensor_2_{nullptr}; + CSE7761DataStruct data_; + + void write_(uint8_t reg, uint16_t data); + bool read_once_(uint8_t reg, uint8_t size, uint32_t *value); + uint32_t read_(uint8_t reg, uint8_t size); + uint32_t coefficient_by_unit_(uint32_t unit); + bool chip_init_(); + void get_data_(); +}; + +} // namespace cse7761 +} // namespace esphome diff --git a/esphome/components/cse7761/sensor.py b/esphome/components/cse7761/sensor.py new file mode 100644 index 0000000000..c5ec3e5b71 --- /dev/null +++ b/esphome/components/cse7761/sensor.py @@ -0,0 +1,90 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, uart +from esphome.const import ( + CONF_ID, + CONF_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, +) + +CODEOWNERS = ["@berfenger"] +DEPENDENCIES = ["uart"] + +cse7761_ns = cg.esphome_ns.namespace("cse7761") +CSE7761Component = cse7761_ns.class_( + "CSE7761Component", cg.PollingComponent, uart.UARTDevice +) + +CONF_CURRENT_1 = "current_1" +CONF_CURRENT_2 = "current_2" +CONF_ACTIVE_POWER_1 = "active_power_1" +CONF_ACTIVE_POWER_2 = "active_power_2" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(CSE7761Component), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT_1): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT_2): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ACTIVE_POWER_1): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ACTIVE_POWER_2): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(uart.UART_DEVICE_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "cse7761", baud_rate=38400, require_rx=True, require_tx=True +) + + +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) + + for key in [ + CONF_VOLTAGE, + CONF_CURRENT_1, + CONF_CURRENT_2, + CONF_ACTIVE_POWER_1, + CONF_ACTIVE_POWER_2, + ]: + if key not in config: + continue + conf = config[key] + sens = await sensor.new_sensor(conf) + cg.add(getattr(var, f"set_{key}_sensor")(sens)) diff --git a/tests/test3.yaml b/tests/test3.yaml index 836e895374..0b7f1ad71e 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -250,6 +250,10 @@ uart: tx_pin: GPIO4 rx_pin: GPIO5 baud_rate: 9600 + - id: uart7 + tx_pin: GPIO4 + rx_pin: GPIO5 + baud_rate: 38400 modbus: uart_id: uart1 @@ -549,6 +553,18 @@ sensor: name: 'PMS Humidity' formaldehyde: name: 'PMS Formaldehyde Concentration' + - platform: cse7761 + uart_id: uart7 + voltage: + name: 'CSE7761 Voltage' + current_1: + name: 'CSE7761 Current 1' + current_2: + name: 'CSE7761 Current 2' + active_power_1: + name: 'CSE7761 Active Power 1' + active_power_2: + name: 'CSE7761 Active Power 2' - platform: cse7766 uart_id: uart3 voltage: From 7e54f97003688742e0e09242eb5d0433764c410b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Oct 2021 21:24:48 +0200 Subject: [PATCH 274/549] Bump aioesphomeapi from 10.1.0 to 10.2.0 (#2642) Bumps [aioesphomeapi](https://github.com/esphome/aioesphomeapi) from 10.1.0 to 10.2.0. - [Release notes](https://github.com/esphome/aioesphomeapi/releases) - [Commits](https://github.com/esphome/aioesphomeapi/compare/v10.1.0...v10.2.0) --- updated-dependencies: - dependency-name: aioesphomeapi 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.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 99efa6798f..2c3220e825 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 esphome-dashboard==20211021.1 -aioesphomeapi==10.1.0 +aioesphomeapi==10.2.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 696643d037751cac1c85a44fb8d9a8cbb8beaaec Mon Sep 17 00:00:00 2001 From: Ed <89483561+kixtarter@users.noreply.github.com> Date: Fri, 29 Oct 2021 00:51:57 +0200 Subject: [PATCH 275/549] BH1750: Fix a too high default H-res2 mode value (#2536) --- esphome/components/bh1750/bh1750.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/bh1750/bh1750.cpp b/esphome/components/bh1750/bh1750.cpp index 951fe3670c..4e6bb3c563 100644 --- a/esphome/components/bh1750/bh1750.cpp +++ b/esphome/components/bh1750/bh1750.cpp @@ -79,6 +79,9 @@ void BH1750Sensor::read_data_() { float lx = float(raw_value) / 1.2f; lx *= 69.0f / this->measurement_duration_; + if (this->resolution_ == BH1750_RESOLUTION_0P5_LX) { + lx /= 2.0f; + } ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx); this->publish_state(lx); this->status_clear_warning(); From b12c7432e0ef751e2a0d4b4c45bbc73e447ff930 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 Oct 2021 20:12:57 +0200 Subject: [PATCH 276/549] Bump tzlocal from 4.0.2 to 4.1 (#2645) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2c3220e825..6e1fe56057 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ PyYAML==6.0 paho-mqtt==1.6.1 colorama==0.4.4 tornado==6.1 -tzlocal==4.0.2 # from time +tzlocal==4.1 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile From 7eee3cdc7fde4de32da80d5a79328895d0e9c360 Mon Sep 17 00:00:00 2001 From: Geoffrey Van Landeghem Date: Sun, 31 Oct 2021 03:29:22 +0100 Subject: [PATCH 277/549] convert SCD30 into Component, polls dataready register (#2308) Co-authored-by: Oxan van Leeuwen Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/scd30/scd30.cpp | 37 +++++++++++++++++++++++------- esphome/components/scd30/scd30.h | 7 ++++-- esphome/components/scd30/sensor.py | 14 +++++++++-- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index d1246d9766..e6d6ec1c1a 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -60,7 +60,7 @@ void SCD30Component::setup() { // 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). - // In practise it seems that clock stretching occurs during this calibration + // In practice it seems that clock stretching occurs during this calibration // calls. It also seems that delays in between calls makes them // disappear/shorter. Hence work around with delays for ESP32. // @@ -69,6 +69,16 @@ void SCD30Component::setup() { delay(30); #endif + if (!this->write_command_(SCD30_CMD_MEASUREMENT_INTERVAL, update_interval_)) { + ESP_LOGE(TAG, "Sensor SCD30 error setting update interval."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } +#ifdef USE_ESP32 + delay(30); +#endif + // The start measurement command disables the altitude compensation, if any, so we only set it if it's turned on if (this->altitude_compensation_ != 0xFFFF) { if (!this->write_command_(SCD30_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { @@ -99,6 +109,12 @@ void SCD30Component::setup() { this->mark_failed(); return; } + + // check each 500ms if data is ready, and read it in that case + this->set_interval("status-check", 500, [this]() { + if (this->is_data_ready_()) + this->update(); + }); } void SCD30Component::dump_config() { @@ -128,19 +144,13 @@ void SCD30Component::dump_config() { ESP_LOGCONFIG(TAG, " Automatic self calibration: %s", ONOFF(this->enable_asc_)); ESP_LOGCONFIG(TAG, " Ambient pressure compensation: %dmBar", this->ambient_pressure_compensation_); ESP_LOGCONFIG(TAG, " Temperature offset: %.2f °C", this->temperature_offset_); - LOG_UPDATE_INTERVAL(this); + ESP_LOGCONFIG(TAG, " Update interval: %ds", this->update_interval_); LOG_SENSOR(" ", "CO2", this->co2_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); } void SCD30Component::update() { - /// Check if measurement is ready before reading the value - if (!this->write_command_(SCD30_CMD_GET_DATA_READY_STATUS)) { - this->status_set_warning(); - return; - } - uint16_t raw_read_status[1]; if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) { this->status_set_warning(); @@ -186,6 +196,17 @@ void SCD30Component::update() { }); } +bool SCD30Component::is_data_ready_() { + if (!this->write_command_(SCD30_CMD_GET_DATA_READY_STATUS)) { + return false; + } + uint16_t is_data_ready; + if (!this->read_data_(&is_data_ready, 1)) { + return false; + } + return is_data_ready == 1; +} + bool SCD30Component::write_command_(uint16_t command) { // Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit. return this->write_byte(command >> 8, command & 0xFF); diff --git a/esphome/components/scd30/scd30.h b/esphome/components/scd30/scd30.h index f11b7cc1f4..64193d0cb6 100644 --- a/esphome/components/scd30/scd30.h +++ b/esphome/components/scd30/scd30.h @@ -8,7 +8,7 @@ namespace esphome { namespace scd30 { /// This class implements support for the Sensirion scd30 i2c GAS (VOC and CO2eq) sensors. -class SCD30Component : public PollingComponent, public i2c::I2CDevice { +class SCD30Component : public Component, public i2c::I2CDevice { public: void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; } void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } @@ -19,9 +19,10 @@ class SCD30Component : public PollingComponent, public i2c::I2CDevice { ambient_pressure_compensation_ = (uint16_t)(pressure * 1000); } void set_temperature_offset(float offset) { temperature_offset_ = offset; } + void set_update_interval(uint16_t interval) { update_interval_ = interval; } void setup() override; - void update() override; + void update(); void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } @@ -30,6 +31,7 @@ class SCD30Component : public PollingComponent, public i2c::I2CDevice { bool write_command_(uint16_t command, uint16_t data); bool read_data_(uint16_t *data, uint8_t len); uint8_t sht_crc_(uint8_t data1, uint8_t data2); + bool is_data_ready_(); enum ErrorCode { COMMUNICATION_FAILED, @@ -41,6 +43,7 @@ class SCD30Component : public PollingComponent, public i2c::I2CDevice { uint16_t altitude_compensation_{0xFFFF}; uint16_t ambient_pressure_compensation_{0x0000}; float temperature_offset_{0.0}; + uint16_t update_interval_{0xFFFF}; sensor::Sensor *co2_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; diff --git a/esphome/components/scd30/sensor.py b/esphome/components/scd30/sensor.py index c0317c96e0..cd25649f2a 100644 --- a/esphome/components/scd30/sensor.py +++ b/esphome/components/scd30/sensor.py @@ -1,3 +1,4 @@ +from esphome import core import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor @@ -6,6 +7,7 @@ from esphome.const import ( CONF_HUMIDITY, CONF_TEMPERATURE, CONF_CO2, + CONF_UPDATE_INTERVAL, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, @@ -18,7 +20,7 @@ from esphome.const import ( DEPENDENCIES = ["i2c"] scd30_ns = cg.esphome_ns.namespace("scd30") -SCD30Component = scd30_ns.class_("SCD30Component", cg.PollingComponent, i2c.I2CDevice) +SCD30Component = scd30_ns.class_("SCD30Component", cg.Component, i2c.I2CDevice) CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" CONF_ALTITUDE_COMPENSATION = "altitude_compensation" @@ -55,9 +57,15 @@ CONFIG_SCHEMA = ( ), cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION, default=0): cv.pressure, cv.Optional(CONF_TEMPERATURE_OFFSET): cv.temperature, + cv.Optional(CONF_UPDATE_INTERVAL, default="60s"): cv.All( + cv.positive_time_period_seconds, + cv.Range( + min=core.TimePeriod(seconds=1), max=core.TimePeriod(seconds=1800) + ), + ), } ) - .extend(cv.polling_component_schema("60s")) + .extend(cv.COMPONENT_SCHEMA) .extend(i2c.i2c_device_schema(0x61)) ) @@ -81,6 +89,8 @@ async def to_code(config): if CONF_TEMPERATURE_OFFSET in config: cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET])) + cg.add(var.set_update_interval(config[CONF_UPDATE_INTERVAL])) + if CONF_CO2 in config: sens = await sensor.new_sensor(config[CONF_CO2]) cg.add(var.set_co2_sensor(sens)) From 331a3ac387e881e886d0a0f651ff1728c593b895 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Sun, 31 Oct 2021 15:34:08 +1300 Subject: [PATCH 278/549] Add option to use MQTT abbreviations (#2641) --- esphome/components/mqtt/__init__.py | 5 + .../components/mqtt/mqtt_binary_sensor.cpp | 8 +- esphome/components/mqtt/mqtt_climate.cpp | 42 +- esphome/components/mqtt/mqtt_component.cpp | 40 +- esphome/components/mqtt/mqtt_const.h | 518 ++++++++++++++++++ esphome/components/mqtt/mqtt_cover.cpp | 14 +- esphome/components/mqtt/mqtt_fan.cpp | 10 +- esphome/components/mqtt/mqtt_light.cpp | 6 +- esphome/components/mqtt/mqtt_number.cpp | 8 +- esphome/components/mqtt/mqtt_select.cpp | 4 +- esphome/components/mqtt/mqtt_sensor.cpp | 12 +- esphome/components/mqtt/mqtt_switch.cpp | 4 +- esphome/const.py | 1 + tests/test1.yaml | 1 + 14 files changed, 609 insertions(+), 64 deletions(-) create mode 100644 esphome/components/mqtt/mqtt_const.h diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 3d52dab67f..0f7d246473 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -34,6 +34,7 @@ from esphome.const import ( CONF_TOPIC, CONF_TOPIC_PREFIX, CONF_TRIGGER_ID, + CONF_USE_ABBREVIATIONS, CONF_USERNAME, CONF_WILL_MESSAGE, ) @@ -152,6 +153,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_DISCOVERY_PREFIX, default="homeassistant" ): cv.publish_topic, + cv.Optional(CONF_USE_ABBREVIATIONS, default=True): cv.boolean, cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA, cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA, cv.Optional(CONF_SHUTDOWN_MESSAGE): MQTT_MESSAGE_SCHEMA, @@ -239,6 +241,9 @@ async def to_code(config): cg.add(var.set_topic_prefix(config[CONF_TOPIC_PREFIX])) + if config[CONF_USE_ABBREVIATIONS]: + cg.add_define("USE_MQTT_ABBREVIATIONS") + birth_message = config[CONF_BIRTH_MESSAGE] if not birth_message: cg.add(var.disable_birth_message()) diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 188df0f7b9..0a161f89a1 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -1,6 +1,8 @@ #include "mqtt_binary_sensor.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_BINARY_SENSOR @@ -29,11 +31,11 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor void MQTTBinarySensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (!this->binary_sensor_->get_device_class().empty()) - root["device_class"] = this->binary_sensor_->get_device_class(); + root[MQTT_DEVICE_CLASS] = this->binary_sensor_->get_device_class(); if (this->binary_sensor_->is_status_binary_sensor()) - root["payload_on"] = mqtt::global_mqtt_client->get_availability().payload_available; + root[MQTT_PAYLOAD_ON] = mqtt::global_mqtt_client->get_availability().payload_available; if (this->binary_sensor_->is_status_binary_sensor()) - root["payload_off"] = mqtt::global_mqtt_client->get_availability().payload_not_available; + root[MQTT_PAYLOAD_OFF] = mqtt::global_mqtt_client->get_availability().payload_not_available; config.command_topic = false; } bool MQTTBinarySensorComponent::send_initial_state() { diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 47b6684dec..a63eb9c4ff 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -1,6 +1,8 @@ #include "mqtt_climate.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_CLIMATE @@ -16,14 +18,14 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC // current_temperature_topic if (traits.get_supports_current_temperature()) { // current_temperature_topic - root["curr_temp_t"] = this->get_current_temperature_state_topic(); + root[MQTT_CURRENT_TEMPERATURE_TOPIC] = this->get_current_temperature_state_topic(); } // mode_command_topic - root["mode_cmd_t"] = this->get_mode_command_topic(); + root[MQTT_MODE_COMMAND_TOPIC] = this->get_mode_command_topic(); // mode_state_topic - root["mode_stat_t"] = this->get_mode_state_topic(); + root[MQTT_MODE_STATE_TOPIC] = this->get_mode_state_topic(); // modes - JsonArray &modes = root.createNestedArray("modes"); + JsonArray &modes = root.createNestedArray(MQTT_MODES); // sort array for nice UI in HA if (traits.supports_mode(CLIMATE_MODE_AUTO)) modes.add("auto"); @@ -41,45 +43,45 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC if (traits.get_supports_two_point_target_temperature()) { // temperature_low_command_topic - root["temp_lo_cmd_t"] = this->get_target_temperature_low_command_topic(); + root[MQTT_TEMPERATURE_LOW_COMMAND_TOPIC] = this->get_target_temperature_low_command_topic(); // temperature_low_state_topic - root["temp_lo_stat_t"] = this->get_target_temperature_low_state_topic(); + root[MQTT_TEMPERATURE_LOW_STATE_TOPIC] = this->get_target_temperature_low_state_topic(); // temperature_high_command_topic - root["temp_hi_cmd_t"] = this->get_target_temperature_high_command_topic(); + root[MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC] = this->get_target_temperature_high_command_topic(); // temperature_high_state_topic - root["temp_hi_stat_t"] = this->get_target_temperature_high_state_topic(); + root[MQTT_TEMPERATURE_HIGH_STATE_TOPIC] = this->get_target_temperature_high_state_topic(); } else { // temperature_command_topic - root["temp_cmd_t"] = this->get_target_temperature_command_topic(); + root[MQTT_TEMPERATURE_COMMAND_TOPIC] = this->get_target_temperature_command_topic(); // temperature_state_topic - root["temp_stat_t"] = this->get_target_temperature_state_topic(); + root[MQTT_TEMPERATURE_STATE_TOPIC] = this->get_target_temperature_state_topic(); } // min_temp - root["min_temp"] = traits.get_visual_min_temperature(); + root[MQTT_MIN_TEMP] = traits.get_visual_min_temperature(); // max_temp - root["max_temp"] = traits.get_visual_max_temperature(); + root[MQTT_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"; + root[MQTT_TEMPERATURE_UNIT] = "C"; if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { // away_mode_command_topic - root["away_mode_cmd_t"] = this->get_away_command_topic(); + root[MQTT_AWAY_MODE_COMMAND_TOPIC] = this->get_away_command_topic(); // away_mode_state_topic - root["away_mode_stat_t"] = this->get_away_state_topic(); + root[MQTT_AWAY_MODE_STATE_TOPIC] = this->get_away_state_topic(); } if (traits.get_supports_action()) { // action_topic - root["act_t"] = this->get_action_state_topic(); + root[MQTT_ACTION_TOPIC] = this->get_action_state_topic(); } 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(); + root[MQTT_FAN_MODE_COMMAND_TOPIC] = this->get_fan_mode_command_topic(); // fan_mode_state_topic - root["fan_mode_stat_t"] = this->get_fan_mode_state_topic(); + root[MQTT_FAN_MODE_STATE_TOPIC] = this->get_fan_mode_state_topic(); // fan_modes JsonArray &fan_modes = root.createNestedArray("fan_modes"); if (traits.supports_fan_mode(CLIMATE_FAN_ON)) @@ -106,9 +108,9 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC if (traits.get_supports_swing_modes()) { // swing_mode_command_topic - root["swing_mode_cmd_t"] = this->get_swing_mode_command_topic(); + root[MQTT_SWING_MODE_COMMAND_TOPIC] = this->get_swing_mode_command_topic(); // swing_mode_state_topic - root["swing_mode_stat_t"] = this->get_swing_mode_state_topic(); + root[MQTT_SWING_MODE_STATE_TOPIC] = this->get_swing_mode_state_topic(); // swing_modes JsonArray &swing_modes = root.createNestedArray("swing_modes"); if (traits.supports_swing_mode(CLIMATE_SWING_OFF)) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 0ece4b3501..be1018d97d 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -7,6 +7,8 @@ #include "esphome/core/helpers.h" #include "esphome/core/version.h" +#include "mqtt_const.h" + namespace esphome { namespace mqtt { @@ -69,49 +71,49 @@ bool MQTTComponent::send_discovery_() { this->send_discovery(root, config); // Fields from EntityBase - root["name"] = this->friendly_name(); + root[MQTT_NAME] = this->friendly_name(); if (this->is_disabled_by_default()) - root["enabled_by_default"] = false; + root[MQTT_ENABLED_BY_DEFAULT] = false; if (!this->get_icon().empty()) - root["icon"] = this->get_icon(); + root[MQTT_ICON] = this->get_icon(); if (config.state_topic) - root["state_topic"] = this->get_state_topic_(); + root[MQTT_STATE_TOPIC] = this->get_state_topic_(); if (config.command_topic) - root["command_topic"] = this->get_command_topic_(); + root[MQTT_COMMAND_TOPIC] = this->get_command_topic_(); if (this->availability_ == nullptr) { if (!global_mqtt_client->get_availability().topic.empty()) { - root["availability_topic"] = global_mqtt_client->get_availability().topic; + root[MQTT_AVAILABILITY_TOPIC] = global_mqtt_client->get_availability().topic; if (global_mqtt_client->get_availability().payload_available != "online") - root["payload_available"] = global_mqtt_client->get_availability().payload_available; + root[MQTT_PAYLOAD_AVAILABLE] = global_mqtt_client->get_availability().payload_available; if (global_mqtt_client->get_availability().payload_not_available != "offline") - root["payload_not_available"] = global_mqtt_client->get_availability().payload_not_available; + root[MQTT_PAYLOAD_NOT_AVAILABLE] = global_mqtt_client->get_availability().payload_not_available; } } else if (!this->availability_->topic.empty()) { - root["availability_topic"] = this->availability_->topic; + root[MQTT_AVAILABILITY_TOPIC] = this->availability_->topic; if (this->availability_->payload_available != "online") - root["payload_available"] = this->availability_->payload_available; + root[MQTT_PAYLOAD_AVAILABLE] = this->availability_->payload_available; if (this->availability_->payload_not_available != "offline") - root["payload_not_available"] = this->availability_->payload_not_available; + root[MQTT_PAYLOAD_NOT_AVAILABLE] = this->availability_->payload_not_available; } const std::string &node_name = App.get_name(); std::string unique_id = this->unique_id(); if (!unique_id.empty()) { - root["unique_id"] = unique_id; + root[MQTT_UNIQUE_ID] = unique_id; } else { // default to almost-unique ID. It's a hack but the only way to get that // gorgeous device registry view. - root["unique_id"] = "ESP" + this->component_type() + this->get_default_object_id_(); + root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_(); } - JsonObject &device_info = root.createNestedObject("device"); - device_info["identifiers"] = get_mac_address(); - device_info["name"] = node_name; - device_info["sw_version"] = "esphome v" ESPHOME_VERSION " " + App.get_compilation_time(); - device_info["model"] = ESPHOME_BOARD; - device_info["manufacturer"] = "espressif"; + JsonObject &device_info = root.createNestedObject(MQTT_DEVICE); + device_info[MQTT_DEVICE_IDENTIFIERS] = get_mac_address(); + device_info[MQTT_DEVICE_NAME] = node_name; + device_info[MQTT_DEVICE_SW_VERSION] = "esphome v" ESPHOME_VERSION " " + App.get_compilation_time(); + device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD; + device_info[MQTT_DEVICE_MANUFACTURER] = "espressif"; }, 0, discovery_info.retain); } diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h new file mode 100644 index 0000000000..df5465ce9a --- /dev/null +++ b/esphome/components/mqtt/mqtt_const.h @@ -0,0 +1,518 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_MQTT + +namespace esphome { +namespace mqtt { + +#ifdef USE_MQTT_ABBREVIATIONS + +constexpr const char *const MQTT_ACTION_TOPIC = "act_t"; +constexpr const char *const MQTT_ACTION_TEMPLATE = "act_tpl"; +constexpr const char *const MQTT_AUTOMATION_TYPE = "atype"; +constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_cmd_t"; +constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_stat_tpl"; +constexpr const char *const MQTT_AUX_STATE_TOPIC = "aux_stat_t"; +constexpr const char *const MQTT_AVAILABILITY = "avty"; +constexpr const char *const MQTT_AVAILABILITY_MODE = "avty_mode"; +constexpr const char *const MQTT_AVAILABILITY_TOPIC = "avty_t"; +constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_cmd_t"; +constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_stat_tpl"; +constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_stat_t"; +constexpr const char *const MQTT_BLUE_TEMPLATE = "b_tpl"; +constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "bri_cmd_t"; +constexpr const char *const MQTT_BRIGHTNESS_SCALE = "bri_scl"; +constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "bri_stat_t"; +constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "bri_tpl"; +constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "bri_val_tpl"; +constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "clr_temp_cmd_tpl"; +constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "bat_lev_t"; +constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "bat_lev_tpl"; +constexpr const char *const MQTT_CONFIGURATION_URL = "cu"; +constexpr const char *const MQTT_CHARGING_TOPIC = "chrg_t"; +constexpr const char *const MQTT_CHARGING_TEMPLATE = "chrg_tpl"; +constexpr const char *const MQTT_COLOR_MODE = "clrm"; +constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "clrm_stat_t"; +constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "clrm_val_tpl"; +constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "clr_temp_cmd_t"; +constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "clr_temp_stat_t"; +constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "clr_temp_tpl"; +constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "clr_temp_val_tpl"; +constexpr const char *const MQTT_CLEANING_TOPIC = "cln_t"; +constexpr const char *const MQTT_CLEANING_TEMPLATE = "cln_tpl"; +constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "cmd_off_tpl"; +constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "cmd_on_tpl"; +constexpr const char *const MQTT_COMMAND_TOPIC = "cmd_t"; +constexpr const char *const MQTT_COMMAND_TEMPLATE = "cmd_tpl"; +constexpr const char *const MQTT_CODE_ARM_REQUIRED = "cod_arm_req"; +constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "cod_dis_req"; +constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "curr_temp_t"; +constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "curr_temp_tpl"; +constexpr const char *const MQTT_DEVICE = "dev"; +constexpr const char *const MQTT_DEVICE_CLASS = "dev_cla"; +constexpr const char *const MQTT_DOCKED_TOPIC = "dock_t"; +constexpr const char *const MQTT_DOCKED_TEMPLATE = "dock_tpl"; +constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "en"; +constexpr const char *const MQTT_ERROR_TOPIC = "err_t"; +constexpr const char *const MQTT_ERROR_TEMPLATE = "err_tpl"; +constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fanspd_t"; +constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fanspd_tpl"; +constexpr const char *const MQTT_FAN_SPEED_LIST = "fanspd_lst"; +constexpr const char *const MQTT_FLASH_TIME_LONG = "flsh_tlng"; +constexpr const char *const MQTT_FLASH_TIME_SHORT = "flsh_tsht"; +constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "fx_cmd_t"; +constexpr const char *const MQTT_EFFECT_LIST = "fx_list"; +constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "fx_stat_t"; +constexpr const char *const MQTT_EFFECT_TEMPLATE = "fx_tpl"; +constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "fx_val_tpl"; +constexpr const char *const MQTT_EXPIRE_AFTER = "exp_aft"; +constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_cmd_tpl"; +constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_cmd_t"; +constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_stat_tpl"; +constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_stat_t"; +constexpr const char *const MQTT_FORCE_UPDATE = "frc_upd"; +constexpr const char *const MQTT_GREEN_TEMPLATE = "g_tpl"; +constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_cmd_tpl"; +constexpr const char *const MQTT_HOLD_COMMAND_TOPIC = "hold_cmd_t"; +constexpr const char *const MQTT_HOLD_STATE_TEMPLATE = "hold_stat_tpl"; +constexpr const char *const MQTT_HOLD_STATE_TOPIC = "hold_stat_t"; +constexpr const char *const MQTT_HS_COMMAND_TOPIC = "hs_cmd_t"; +constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_stat_t"; +constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_val_tpl"; +constexpr const char *const MQTT_ICON = "ic"; +constexpr const char *const MQTT_INITIAL = "init"; +constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "hum_cmd_t"; +constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "hum_cmd_tpl"; +constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "hum_stat_t"; +constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "hum_state_tpl"; +constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attr"; +constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attr_t"; +constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attr_tpl"; +constexpr const char *const MQTT_LAST_RESET_TOPIC = "lrst_t"; +constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "lrst_val_tpl"; +constexpr const char *const MQTT_MAX = "max"; +constexpr const char *const MQTT_MIN = "min"; +constexpr const char *const MQTT_MAX_HUMIDITY = "max_hum"; +constexpr const char *const MQTT_MIN_HUMIDITY = "min_hum"; +constexpr const char *const MQTT_MAX_MIREDS = "max_mirs"; +constexpr const char *const MQTT_MIN_MIREDS = "min_mirs"; +constexpr const char *const MQTT_MAX_TEMP = "max_temp"; +constexpr const char *const MQTT_MIN_TEMP = "min_temp"; +constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_cmd_tpl"; +constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_cmd_t"; +constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_stat_t"; +constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_stat_tpl"; +constexpr const char *const MQTT_MODES = "modes"; +constexpr const char *const MQTT_NAME = "name"; +constexpr const char *const MQTT_OFF_DELAY = "off_dly"; +constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_cmd_type"; +constexpr const char *const MQTT_OPTIONS = "ops"; +constexpr const char *const MQTT_OPTIMISTIC = "opt"; +constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "osc_cmd_t"; +constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "osc_cmd_tpl"; +constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "osc_stat_t"; +constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "osc_val_tpl"; +constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "pct_cmd_t"; +constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "pct_cmd_tpl"; +constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "pct_stat_t"; +constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "pct_val_tpl"; +constexpr const char *const MQTT_PAYLOAD = "pl"; +constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "pl_arm_away"; +constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "pl_arm_home"; +constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "pl_arm_nite"; +constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "pl_arm_vacation"; +constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "pl_arm_custom_b"; +constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "pl_avail"; +constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "pl_cln_sp"; +constexpr const char *const MQTT_PAYLOAD_CLOSE = "pl_cls"; +constexpr const char *const MQTT_PAYLOAD_DISARM = "pl_disarm"; +constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "pl_hi_spd"; +constexpr const char *const MQTT_PAYLOAD_HOME = "pl_home"; +constexpr const char *const MQTT_PAYLOAD_LOCK = "pl_lock"; +constexpr const char *const MQTT_PAYLOAD_LOCATE = "pl_loc"; +constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "pl_lo_spd"; +constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "pl_med_spd"; +constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "pl_not_avail"; +constexpr const char *const MQTT_PAYLOAD_NOT_HOME = "pl_not_home"; +constexpr const char *const MQTT_PAYLOAD_OFF = "pl_off"; +constexpr const char *const MQTT_PAYLOAD_OFF_SPEED = "pl_off_spd"; +constexpr const char *const MQTT_PAYLOAD_ON = "pl_on"; +constexpr const char *const MQTT_PAYLOAD_OPEN = "pl_open"; +constexpr const char *const MQTT_PAYLOAD_OSCILLATION_OFF = "pl_osc_off"; +constexpr const char *const MQTT_PAYLOAD_OSCILLATION_ON = "pl_osc_on"; +constexpr const char *const MQTT_PAYLOAD_PAUSE = "pl_paus"; +constexpr const char *const MQTT_PAYLOAD_RESET = "pl_rst"; +constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "pl_rst_hum"; +constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "pl_rst_mode"; +constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "pl_rst_pct"; +constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "pl_rst_pr_mode"; +constexpr const char *const MQTT_PAYLOAD_STOP = "pl_stop"; +constexpr const char *const MQTT_PAYLOAD_START = "pl_strt"; +constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "pl_stpa"; +constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "pl_ret"; +constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "pl_toff"; +constexpr const char *const MQTT_PAYLOAD_TURN_ON = "pl_ton"; +constexpr const char *const MQTT_PAYLOAD_UNLOCK = "pl_unlk"; +constexpr const char *const MQTT_POSITION_CLOSED = "pos_clsd"; +constexpr const char *const MQTT_POSITION_OPEN = "pos_open"; +constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "pow_cmd_t"; +constexpr const char *const MQTT_POWER_STATE_TOPIC = "pow_stat_t"; +constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "pow_stat_tpl"; +constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "pr_mode_cmd_t"; +constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "pr_mode_cmd_tpl"; +constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "pr_mode_stat_t"; +constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "pr_mode_val_tpl"; +constexpr const char *const MQTT_PRESET_MODES = "pr_modes"; +constexpr const char *const MQTT_RED_TEMPLATE = "r_tpl"; +constexpr const char *const MQTT_RETAIN = "ret"; +constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_cmd_tpl"; +constexpr const char *const MQTT_RGB_COMMAND_TOPIC = "rgb_cmd_t"; +constexpr const char *const MQTT_RGB_STATE_TOPIC = "rgb_stat_t"; +constexpr const char *const MQTT_RGB_VALUE_TEMPLATE = "rgb_val_tpl"; +constexpr const char *const MQTT_RGBW_COMMAND_TEMPLATE = "rgbw_cmd_tpl"; +constexpr const char *const MQTT_RGBW_COMMAND_TOPIC = "rgbw_cmd_t"; +constexpr const char *const MQTT_RGBW_STATE_TOPIC = "rgbw_stat_t"; +constexpr const char *const MQTT_RGBW_VALUE_TEMPLATE = "rgbw_val_tpl"; +constexpr const char *const MQTT_RGBWW_COMMAND_TEMPLATE = "rgbww_cmd_tpl"; +constexpr const char *const MQTT_RGBWW_COMMAND_TOPIC = "rgbww_cmd_t"; +constexpr const char *const MQTT_RGBWW_STATE_TOPIC = "rgbww_stat_t"; +constexpr const char *const MQTT_RGBWW_VALUE_TEMPLATE = "rgbww_val_tpl"; +constexpr const char *const MQTT_SEND_COMMAND_TOPIC = "send_cmd_t"; +constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off"; +constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_spd_t"; +constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_pos_tpl"; +constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_pos_t"; +constexpr const char *const MQTT_POSITION_TOPIC = "pos_t"; +constexpr const char *const MQTT_POSITION_TEMPLATE = "pos_tpl"; +constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "spd_cmd_t"; +constexpr const char *const MQTT_SPEED_STATE_TOPIC = "spd_stat_t"; +constexpr const char *const MQTT_SPEED_RANGE_MIN = "spd_rng_min"; +constexpr const char *const MQTT_SPEED_RANGE_MAX = "spd_rng_max"; +constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "spd_val_tpl"; +constexpr const char *const MQTT_SPEEDS = "spds"; +constexpr const char *const MQTT_SOURCE_TYPE = "src_type"; +constexpr const char *const MQTT_STATE_CLASS = "stat_cla"; +constexpr const char *const MQTT_STATE_CLOSED = "stat_clsd"; +constexpr const char *const MQTT_STATE_CLOSING = "stat_closing"; +constexpr const char *const MQTT_STATE_OFF = "stat_off"; +constexpr const char *const MQTT_STATE_ON = "stat_on"; +constexpr const char *const MQTT_STATE_OPEN = "stat_open"; +constexpr const char *const MQTT_STATE_OPENING = "stat_opening"; +constexpr const char *const MQTT_STATE_STOPPED = "stat_stopped"; +constexpr const char *const MQTT_STATE_LOCKED = "stat_locked"; +constexpr const char *const MQTT_STATE_UNLOCKED = "stat_unlocked"; +constexpr const char *const MQTT_STATE_TOPIC = "stat_t"; +constexpr const char *const MQTT_STATE_TEMPLATE = "stat_tpl"; +constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "stat_val_tpl"; +constexpr const char *const MQTT_STEP = "step"; +constexpr const char *const MQTT_SUBTYPE = "stype"; +constexpr const char *const MQTT_SUPPORTED_FEATURES = "sup_feat"; +constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "sup_clrm"; +constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_cmd_tpl"; +constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_cmd_t"; +constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_stat_tpl"; +constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_stat_t"; +constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temp_cmd_tpl"; +constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temp_cmd_t"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temp_hi_cmd_tpl"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC = "temp_hi_cmd_t"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE = "temp_hi_stat_tpl"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TOPIC = "temp_hi_stat_t"; +constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE = "temp_lo_cmd_tpl"; +constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TOPIC = "temp_lo_cmd_t"; +constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TEMPLATE = "temp_lo_stat_tpl"; +constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC = "temp_lo_stat_t"; +constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temp_stat_tpl"; +constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temp_stat_t"; +constexpr const char *const MQTT_TEMPERATURE_UNIT = "temp_unit"; +constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_clsd_val"; +constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_cmd_t"; +constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_cmd_tpl"; +constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_inv_stat"; +constexpr const char *const MQTT_TILT_MAX = "tilt_max"; +constexpr const char *const MQTT_TILT_MIN = "tilt_min"; +constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opnd_val"; +constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_opt"; +constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_t"; +constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_tpl"; +constexpr const char *const MQTT_TOPIC = "t"; +constexpr const char *const MQTT_UNIQUE_ID = "uniq_id"; +constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_meas"; +constexpr const char *const MQTT_VALUE_TEMPLATE = "val_tpl"; +constexpr const char *const MQTT_WHITE_COMMAND_TOPIC = "whit_cmd_t"; +constexpr const char *const MQTT_WHITE_SCALE = "whit_scl"; +constexpr const char *const MQTT_WHITE_VALUE_COMMAND_TOPIC = "whit_val_cmd_t"; +constexpr const char *const MQTT_WHITE_VALUE_SCALE = "whit_val_scl"; +constexpr const char *const MQTT_WHITE_VALUE_STATE_TOPIC = "whit_val_stat_t"; +constexpr const char *const MQTT_WHITE_VALUE_TEMPLATE = "whit_val_tpl"; +constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_cmd_t"; +constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_stat_t"; +constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_val_tpl"; + +constexpr const char *const MQTT_DEVICE_CONNECTIONS = "cns"; +constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "ids"; +constexpr const char *const MQTT_DEVICE_NAME = "name"; +constexpr const char *const MQTT_DEVICE_MANUFACTURER = "mf"; +constexpr const char *const MQTT_DEVICE_MODEL = "mdl"; +constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw"; +constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "sa"; + +#else + +constexpr const char *const MQTT_ACTION_TOPIC = "action_topic"; +constexpr const char *const MQTT_ACTION_TEMPLATE = "action_template"; +constexpr const char *const MQTT_AUTOMATION_TYPE = "automation_type"; +constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_command_topic"; +constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_state_template"; +constexpr const char *const MQTT_AUX_STATE_TOPIC = "aux_state_topic"; +constexpr const char *const MQTT_AVAILABILITY = "availability"; +constexpr const char *const MQTT_AVAILABILITY_MODE = "availability_mode"; +constexpr const char *const MQTT_AVAILABILITY_TOPIC = "availability_topic"; +constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_command_topic"; +constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_state_template"; +constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_state_topic"; +constexpr const char *const MQTT_BLUE_TEMPLATE = "blue_template"; +constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "brightness_command_topic"; +constexpr const char *const MQTT_BRIGHTNESS_SCALE = "brightness_scale"; +constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "brightness_state_topic"; +constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "brightness_template"; +constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "brightness_value_template"; +constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "color_temp_command_template"; +constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "battery_level_topic"; +constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "battery_level_template"; +constexpr const char *const MQTT_CONFIGURATION_URL = "configuration_url"; +constexpr const char *const MQTT_CHARGING_TOPIC = "charging_topic"; +constexpr const char *const MQTT_CHARGING_TEMPLATE = "charging_template"; +constexpr const char *const MQTT_COLOR_MODE = "color_mode"; +constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "color_mode_state_topic"; +constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "color_mode_value_template"; +constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "color_temp_command_topic"; +constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "color_temp_state_topic"; +constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "color_temp_template"; +constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "color_temp_value_template"; +constexpr const char *const MQTT_CLEANING_TOPIC = "cleaning_topic"; +constexpr const char *const MQTT_CLEANING_TEMPLATE = "cleaning_template"; +constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "command_off_template"; +constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "command_on_template"; +constexpr const char *const MQTT_COMMAND_TOPIC = "command_topic"; +constexpr const char *const MQTT_COMMAND_TEMPLATE = "command_template"; +constexpr const char *const MQTT_CODE_ARM_REQUIRED = "code_arm_required"; +constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "code_disarm_required"; +constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "current_temperature_topic"; +constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "current_temperature_template"; +constexpr const char *const MQTT_DEVICE = "device"; +constexpr const char *const MQTT_DEVICE_CLASS = "device_class"; +constexpr const char *const MQTT_DOCKED_TOPIC = "docked_topic"; +constexpr const char *const MQTT_DOCKED_TEMPLATE = "docked_template"; +constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "enabled_by_default"; +constexpr const char *const MQTT_ERROR_TOPIC = "error_topic"; +constexpr const char *const MQTT_ERROR_TEMPLATE = "error_template"; +constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fan_speed_topic"; +constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fan_speed_template"; +constexpr const char *const MQTT_FAN_SPEED_LIST = "fan_speed_list"; +constexpr const char *const MQTT_FLASH_TIME_LONG = "flash_time_long"; +constexpr const char *const MQTT_FLASH_TIME_SHORT = "flash_time_short"; +constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "effect_command_topic"; +constexpr const char *const MQTT_EFFECT_LIST = "effect_list"; +constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "effect_state_topic"; +constexpr const char *const MQTT_EFFECT_TEMPLATE = "effect_template"; +constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "effect_value_template"; +constexpr const char *const MQTT_EXPIRE_AFTER = "expire_after"; +constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_command_template"; +constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_command_topic"; +constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_state_template"; +constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_state_topic"; +constexpr const char *const MQTT_FORCE_UPDATE = "force_update"; +constexpr const char *const MQTT_GREEN_TEMPLATE = "green_template"; +constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_command_template"; +constexpr const char *const MQTT_HOLD_COMMAND_TOPIC = "hold_command_topic"; +constexpr const char *const MQTT_HOLD_STATE_TEMPLATE = "hold_state_template"; +constexpr const char *const MQTT_HOLD_STATE_TOPIC = "hold_state_topic"; +constexpr const char *const MQTT_HS_COMMAND_TOPIC = "hs_command_topic"; +constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_state_topic"; +constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_value_template"; +constexpr const char *const MQTT_ICON = "icon"; +constexpr const char *const MQTT_INITIAL = "initial"; +constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "target_humidity_command_topic"; +constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "target_humidity_command_template"; +constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "target_humidity_state_topic"; +constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "target_humidity_state_template"; +constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attributes"; +constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attributes_topic"; +constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attributes_template"; +constexpr const char *const MQTT_LAST_RESET_TOPIC = "last_reset_topic"; +constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "last_reset_value_template"; +constexpr const char *const MQTT_MAX = "max"; +constexpr const char *const MQTT_MIN = "min"; +constexpr const char *const MQTT_MAX_HUMIDITY = "max_humidity"; +constexpr const char *const MQTT_MIN_HUMIDITY = "min_humidity"; +constexpr const char *const MQTT_MAX_MIREDS = "max_mireds"; +constexpr const char *const MQTT_MIN_MIREDS = "min_mireds"; +constexpr const char *const MQTT_MAX_TEMP = "max_temp"; +constexpr const char *const MQTT_MIN_TEMP = "min_temp"; +constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_command_template"; +constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_command_topic"; +constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_state_topic"; +constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_state_template"; +constexpr const char *const MQTT_MODES = "modes"; +constexpr const char *const MQTT_NAME = "name"; +constexpr const char *const MQTT_OFF_DELAY = "off_delay"; +constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_command_type"; +constexpr const char *const MQTT_OPTIONS = "options"; +constexpr const char *const MQTT_OPTIMISTIC = "optimistic"; +constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "oscillation_command_topic"; +constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "oscillation_command_template"; +constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "oscillation_state_topic"; +constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "oscillation_value_template"; +constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "percentage_command_topic"; +constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "percentage_command_template"; +constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "percentage_state_topic"; +constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "percentage_value_template"; +constexpr const char *const MQTT_PAYLOAD = "payload"; +constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "payload_arm_away"; +constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "payload_arm_home"; +constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "payload_arm_night"; +constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "payload_arm_vacation"; +constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "payload_arm_custom_bypass"; +constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "payload_available"; +constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "payload_clean_spot"; +constexpr const char *const MQTT_PAYLOAD_CLOSE = "payload_close"; +constexpr const char *const MQTT_PAYLOAD_DISARM = "payload_disarm"; +constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "payload_high_speed"; +constexpr const char *const MQTT_PAYLOAD_HOME = "payload_home"; +constexpr const char *const MQTT_PAYLOAD_LOCK = "payload_lock"; +constexpr const char *const MQTT_PAYLOAD_LOCATE = "payload_locate"; +constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "payload_low_speed"; +constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "payload_medium_speed"; +constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "payload_not_available"; +constexpr const char *const MQTT_PAYLOAD_NOT_HOME = "payload_not_home"; +constexpr const char *const MQTT_PAYLOAD_OFF = "payload_off"; +constexpr const char *const MQTT_PAYLOAD_OFF_SPEED = "payload_off_speed"; +constexpr const char *const MQTT_PAYLOAD_ON = "payload_on"; +constexpr const char *const MQTT_PAYLOAD_OPEN = "payload_open"; +constexpr const char *const MQTT_PAYLOAD_OSCILLATION_OFF = "payload_oscillation_off"; +constexpr const char *const MQTT_PAYLOAD_OSCILLATION_ON = "payload_oscillation_on"; +constexpr const char *const MQTT_PAYLOAD_PAUSE = "payload_pause"; +constexpr const char *const MQTT_PAYLOAD_RESET = "payload_reset"; +constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "payload_reset_humidity"; +constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "payload_reset_mode"; +constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "payload_reset_percentage"; +constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "payload_reset_preset_mode"; +constexpr const char *const MQTT_PAYLOAD_STOP = "payload_stop"; +constexpr const char *const MQTT_PAYLOAD_START = "payload_start"; +constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "payload_start_pause"; +constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "payload_return_to_base"; +constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "payload_turn_off"; +constexpr const char *const MQTT_PAYLOAD_TURN_ON = "payload_turn_on"; +constexpr const char *const MQTT_PAYLOAD_UNLOCK = "payload_unlock"; +constexpr const char *const MQTT_POSITION_CLOSED = "position_closed"; +constexpr const char *const MQTT_POSITION_OPEN = "position_open"; +constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "power_command_topic"; +constexpr const char *const MQTT_POWER_STATE_TOPIC = "power_state_topic"; +constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "power_state_template"; +constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "preset_mode_command_topic"; +constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "preset_mode_command_template"; +constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "preset_mode_state_topic"; +constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "preset_mode_value_template"; +constexpr const char *const MQTT_PRESET_MODES = "preset_modes"; +constexpr const char *const MQTT_RED_TEMPLATE = "red_template"; +constexpr const char *const MQTT_RETAIN = "retain"; +constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_command_template"; +constexpr const char *const MQTT_RGB_COMMAND_TOPIC = "rgb_command_topic"; +constexpr const char *const MQTT_RGB_STATE_TOPIC = "rgb_state_topic"; +constexpr const char *const MQTT_RGB_VALUE_TEMPLATE = "rgb_value_template"; +constexpr const char *const MQTT_RGBW_COMMAND_TEMPLATE = "rgbw_command_template"; +constexpr const char *const MQTT_RGBW_COMMAND_TOPIC = "rgbw_command_topic"; +constexpr const char *const MQTT_RGBW_STATE_TOPIC = "rgbw_state_topic"; +constexpr const char *const MQTT_RGBW_VALUE_TEMPLATE = "rgbw_value_template"; +constexpr const char *const MQTT_RGBWW_COMMAND_TEMPLATE = "rgbww_command_template"; +constexpr const char *const MQTT_RGBWW_COMMAND_TOPIC = "rgbww_command_topic"; +constexpr const char *const MQTT_RGBWW_STATE_TOPIC = "rgbww_state_topic"; +constexpr const char *const MQTT_RGBWW_VALUE_TEMPLATE = "rgbww_value_template"; +constexpr const char *const MQTT_SEND_COMMAND_TOPIC = "send_command_topic"; +constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off"; +constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_speed_topic"; +constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_position_template"; +constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_position_topic"; +constexpr const char *const MQTT_POSITION_TOPIC = "position_topic"; +constexpr const char *const MQTT_POSITION_TEMPLATE = "position_template"; +constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "speed_command_topic"; +constexpr const char *const MQTT_SPEED_STATE_TOPIC = "speed_state_topic"; +constexpr const char *const MQTT_SPEED_RANGE_MIN = "speed_range_min"; +constexpr const char *const MQTT_SPEED_RANGE_MAX = "speed_range_max"; +constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "speed_value_template"; +constexpr const char *const MQTT_SPEEDS = "speeds"; +constexpr const char *const MQTT_SOURCE_TYPE = "source_type"; +constexpr const char *const MQTT_STATE_CLASS = "state_class"; +constexpr const char *const MQTT_STATE_CLOSED = "state_closed"; +constexpr const char *const MQTT_STATE_CLOSING = "state_closing"; +constexpr const char *const MQTT_STATE_OFF = "state_off"; +constexpr const char *const MQTT_STATE_ON = "state_on"; +constexpr const char *const MQTT_STATE_OPEN = "state_open"; +constexpr const char *const MQTT_STATE_OPENING = "state_opening"; +constexpr const char *const MQTT_STATE_STOPPED = "state_stopped"; +constexpr const char *const MQTT_STATE_LOCKED = "state_locked"; +constexpr const char *const MQTT_STATE_UNLOCKED = "state_unlocked"; +constexpr const char *const MQTT_STATE_TOPIC = "state_topic"; +constexpr const char *const MQTT_STATE_TEMPLATE = "state_template"; +constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "state_value_template"; +constexpr const char *const MQTT_STEP = "step"; +constexpr const char *const MQTT_SUBTYPE = "subtype"; +constexpr const char *const MQTT_SUPPORTED_FEATURES = "supported_features"; +constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "supported_color_modes"; +constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_command_template"; +constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_command_topic"; +constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_state_template"; +constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_state_topic"; +constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temperature_command_template"; +constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temperature_command_topic"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temperature_high_command_template"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC = "temperature_high_command_topic"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE = "temperature_high_state_template"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TOPIC = "temperature_high_state_topic"; +constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE = "temperature_low_command_template"; +constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TOPIC = "temperature_low_command_topic"; +constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TEMPLATE = "temperature_low_state_template"; +constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC = "temperature_low_state_topic"; +constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temperature_state_template"; +constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temperature_state_topic"; +constexpr const char *const MQTT_TEMPERATURE_UNIT = "temperature_unit"; +constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_closed_value"; +constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_command_topic"; +constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_command_template"; +constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_invert_state"; +constexpr const char *const MQTT_TILT_MAX = "tilt_max"; +constexpr const char *const MQTT_TILT_MIN = "tilt_min"; +constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opened_value"; +constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_optimistic"; +constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_topic"; +constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_template"; +constexpr const char *const MQTT_TOPIC = "topic"; +constexpr const char *const MQTT_UNIQUE_ID = "unique_id"; +constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_measurement"; +constexpr const char *const MQTT_VALUE_TEMPLATE = "value_template"; +constexpr const char *const MQTT_WHITE_COMMAND_TOPIC = "white_command_topic"; +constexpr const char *const MQTT_WHITE_SCALE = "white_scale"; +constexpr const char *const MQTT_WHITE_VALUE_COMMAND_TOPIC = "white_value_command_topic"; +constexpr const char *const MQTT_WHITE_VALUE_SCALE = "white_value_scale"; +constexpr const char *const MQTT_WHITE_VALUE_STATE_TOPIC = "white_value_state_topic"; +constexpr const char *const MQTT_WHITE_VALUE_TEMPLATE = "white_value_template"; +constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_command_topic"; +constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_state_topic"; +constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_value_template"; + +constexpr const char *const MQTT_DEVICE_CONNECTIONS = "connections"; +constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "identifiers"; +constexpr const char *const MQTT_DEVICE_NAME = "name"; +constexpr const char *const MQTT_DEVICE_MANUFACTURER = "manufacturer"; +constexpr const char *const MQTT_DEVICE_MODEL = "model"; +constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw_version"; +constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area"; +#endif + +} // namespace mqtt +} // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index e8bc7f0e30..7bf3204222 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -1,6 +1,8 @@ #include "mqtt_cover.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_COVER @@ -63,20 +65,20 @@ void MQTTCoverComponent::dump_config() { } void MQTTCoverComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (!this->cover_->get_device_class().empty()) - root["device_class"] = this->cover_->get_device_class(); + root[MQTT_DEVICE_CLASS] = this->cover_->get_device_class(); auto traits = this->cover_->get_traits(); if (traits.get_is_assumed_state()) { - root["optimistic"] = true; + root[MQTT_OPTIMISTIC] = true; } if (traits.get_supports_position()) { config.state_topic = false; - root["position_topic"] = this->get_position_state_topic(); - root["set_position_topic"] = this->get_position_command_topic(); + root[MQTT_POSITION_TOPIC] = this->get_position_state_topic(); + root[MQTT_SET_POSITION_TOPIC] = this->get_position_command_topic(); } if (traits.get_supports_tilt()) { - root["tilt_status_topic"] = this->get_tilt_state_topic(); - root["tilt_command_topic"] = this->get_tilt_command_topic(); + root[MQTT_TILT_STATUS_TOPIC] = this->get_tilt_state_topic(); + root[MQTT_TILT_COMMAND_TOPIC] = this->get_tilt_command_topic(); } if (traits.get_supports_tilt() && !traits.get_supports_position()) { config.command_topic = false; diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 1703343a77..a9d77789e1 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -1,6 +1,8 @@ #include "mqtt_fan.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_FAN #include "esphome/components/fan/fan_helpers.h" @@ -120,14 +122,14 @@ bool MQTTFanComponent::send_initial_state() { return this->publish_state(); } void MQTTFanComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (this->state_->get_traits().supports_oscillation()) { - root["oscillation_command_topic"] = this->get_oscillation_command_topic(); - root["oscillation_state_topic"] = this->get_oscillation_state_topic(); + root[MQTT_OSCILLATION_COMMAND_TOPIC] = this->get_oscillation_command_topic(); + root[MQTT_OSCILLATION_STATE_TOPIC] = this->get_oscillation_state_topic(); } if (this->state_->get_traits().supports_speed()) { root["speed_level_command_topic"] = this->get_speed_level_command_topic(); root["speed_level_state_topic"] = this->get_speed_level_state_topic(); - root["speed_command_topic"] = this->get_speed_command_topic(); - root["speed_state_topic"] = this->get_speed_state_topic(); + root[MQTT_SPEED_COMMAND_TOPIC] = this->get_speed_command_topic(); + root[MQTT_SPEED_STATE_TOPIC] = this->get_speed_state_topic(); } } bool MQTTFanComponent::publish_state() { diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index a88358a6b2..54204a9e7f 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -1,6 +1,8 @@ #include "mqtt_light.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_LIGHT @@ -38,7 +40,7 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover root["schema"] = "json"; auto traits = this->state_->get_traits(); - root["color_mode"] = true; + root[MQTT_COLOR_MODE] = true; JsonArray &color_modes = root.createNestedArray("supported_color_modes"); if (traits.supports_color_mode(ColorMode::ON_OFF)) color_modes.add("onoff"); @@ -64,7 +66,7 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover if (this->state_->supports_effects()) { root["effect"] = true; - JsonArray &effect_list = root.createNestedArray("effect_list"); + JsonArray &effect_list = root.createNestedArray(MQTT_EFFECT_LIST); for (auto *effect : this->state_->get_effects()) effect_list.add(effect->get_name()); effect_list.add("None"); diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 674fd77bdf..9b2292cd76 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -1,6 +1,8 @@ #include "mqtt_number.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_NUMBER @@ -38,9 +40,9 @@ const EntityBase *MQTTNumberComponent::get_entity() const { return this->number_ void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { const auto &traits = number_->traits; // https://www.home-assistant.io/integrations/number.mqtt/ - root["min"] = traits.get_min_value(); - root["max"] = traits.get_max_value(); - root["step"] = traits.get_step(); + root[MQTT_MIN] = traits.get_min_value(); + root[MQTT_MAX] = traits.get_max_value(); + root[MQTT_STEP] = traits.get_step(); config.command_topic = true; } diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index b499636006..b8371de00e 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -1,6 +1,8 @@ #include "mqtt_select.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_SELECT @@ -33,7 +35,7 @@ const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_ void MQTTSelectComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { const auto &traits = select_->traits; // https://www.home-assistant.io/integrations/select.mqtt/ - JsonArray &options = root.createNestedArray("options"); + JsonArray &options = root.createNestedArray(MQTT_OPTIONS); for (const auto &option : traits.get_options()) options.add(option); diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 78710ff403..dd6423e8f3 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -1,6 +1,8 @@ #include "mqtt_sensor.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_SENSOR @@ -42,19 +44,19 @@ void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (!this->sensor_->get_device_class().empty()) - root["device_class"] = this->sensor_->get_device_class(); + root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); if (!this->sensor_->get_unit_of_measurement().empty()) - root["unit_of_measurement"] = this->sensor_->get_unit_of_measurement(); + root[MQTT_UNIT_OF_MEASUREMENT] = this->sensor_->get_unit_of_measurement(); if (this->get_expire_after() > 0) - root["expire_after"] = this->get_expire_after() / 1000; + root[MQTT_EXPIRE_AFTER] = this->get_expire_after() / 1000; if (this->sensor_->get_force_update()) - root["force_update"] = true; + root[MQTT_FORCE_UPDATE] = true; if (this->sensor_->get_state_class() != STATE_CLASS_NONE) - root["state_class"] = state_class_to_string(this->sensor_->get_state_class()); + root[MQTT_STATE_CLASS] = state_class_to_string(this->sensor_->get_state_class()); config.command_topic = false; } diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index 16cf102f7e..edaa6e7859 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -1,6 +1,8 @@ #include "mqtt_switch.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_SWITCH @@ -44,7 +46,7 @@ std::string MQTTSwitchComponent::component_type() const { return "switch"; } const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; } void MQTTSwitchComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (this->switch_->assumed_state()) - root["optimistic"] = true; + root[MQTT_OPTIMISTIC] = true; } bool MQTTSwitchComponent::send_initial_state() { return this->publish_state(this->switch_->state); } diff --git a/esphome/const.py b/esphome/const.py index e00eebb1a4..bac446a7c6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -709,6 +709,7 @@ CONF_UNIT_OF_MEASUREMENT = "unit_of_measurement" CONF_UPDATE_INTERVAL = "update_interval" CONF_UPDATE_ON_BOOT = "update_on_boot" CONF_URL = "url" +CONF_USE_ABBREVIATIONS = "use_abbreviations" CONF_USE_ADDRESS = "use_address" CONF_USERNAME = "username" CONF_UUID = "uuid" diff --git a/tests/test1.yaml b/tests/test1.yaml index 157ccfc5d1..d8075e980b 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -94,6 +94,7 @@ mqtt: username: 'debug' password: 'debug' client_id: someclient + use_abbreviations: false discovery: True discovery_retain: False discovery_prefix: discovery From 2b04152482da3e9faaa4f6d0fd3370134d792fd1 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 31 Oct 2021 16:07:06 +0100 Subject: [PATCH 279/549] Fix deep sleep invert_wakeup mode (#2644) --- esphome/components/deep_sleep/deep_sleep_component.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index e4b1edfb7b..0998a57af3 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -78,8 +78,9 @@ void DeepSleepComponent::begin_sleep(bool manual) { esp_sleep_enable_timer_wakeup(*this->sleep_duration_); 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()) + 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); } if (this->ext1_wakeup_.has_value()) { From d8b3af3815dcb4bed73bebf5c1659b6dd325c78e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 1 Nov 2021 09:33:04 +1300 Subject: [PATCH 280/549] Expose webserver_port to the native API (#2640) --- esphome/components/api/api.proto | 2 ++ esphome/components/api/api_connection.cpp | 3 +++ esphome/components/api/api_pb2.cpp | 10 ++++++++++ esphome/components/api/api_pb2.h | 1 + esphome/components/web_server/__init__.py | 1 + esphome/core/defines.h | 3 +++ 6 files changed, 20 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 3c6a36f032..b1ac998608 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -182,6 +182,8 @@ message DeviceInfoResponse { // The esphome project details if set string project_name = 8; string project_version = 9; + + uint32 webserver_port = 10; } message ListEntitiesRequest { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 2151d6165c..79c53ee840 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -759,6 +759,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { #ifdef ESPHOME_PROJECT_NAME resp.project_name = ESPHOME_PROJECT_NAME; resp.project_version = ESPHOME_PROJECT_VERSION; +#endif +#ifdef USE_WEBSERVER + resp.webserver_port = WEBSERVER_PORT; #endif return resp; } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 17fc14c868..1d59d98f52 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -396,6 +396,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->has_deep_sleep = value.as_bool(); return true; } + case 10: { + this->webserver_port = value.as_uint32(); + return true; + } default: return false; } @@ -444,6 +448,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->has_deep_sleep); buffer.encode_string(8, this->project_name); buffer.encode_string(9, this->project_version); + buffer.encode_uint32(10, this->webserver_port); } #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoResponse::dump_to(std::string &out) const { @@ -484,6 +489,11 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append(" project_version: "); out.append("'").append(this->project_version).append("'"); out.append("\n"); + + out.append(" webserver_port: "); + sprintf(buffer, "%u", this->webserver_port); + out.append(buffer); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index eea9ab06f6..af85ed6856 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -224,6 +224,7 @@ class DeviceInfoResponse : public ProtoMessage { bool has_deep_sleep{false}; std::string project_name{}; std::string project_version{}; + uint32_t webserver_port{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 240ba7c8a0..ba2d866593 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -54,6 +54,7 @@ async def to_code(config): cg.add(paren.set_port(config[CONF_PORT])) cg.add_define("WEBSERVER_PORT", config[CONF_PORT]) + cg.add_define("USE_WEBSERVER") cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) if CONF_AUTH in config: diff --git a/esphome/core/defines.h b/esphome/core/defines.h index b44987a768..dc07bde196 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -36,8 +36,11 @@ #define USE_SWITCH #define USE_TEXT_SENSOR #define USE_TIME +#define USE_WEBSERVER #define USE_WIFI +#define WEBSERVER_PORT 80 // NOLINT + // Arduino-specific feature flags #ifdef USE_ARDUINO #define USE_CAPTIVE_PORTAL From d54b4e7c4466487b23c54c0e5f72892bdaed0a00 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Mon, 1 Nov 2021 20:27:57 +0100 Subject: [PATCH 281/549] Fix for noise in pulse_counter and duty_cycle components (#2646) --- .../duty_cycle/duty_cycle_sensor.cpp | 22 +++++++++---------- .../components/duty_cycle/duty_cycle_sensor.h | 2 +- .../pulse_counter/pulse_counter_sensor.cpp | 12 ++++++---- .../pulse_counter/pulse_counter_sensor.h | 3 ++- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.cpp b/esphome/components/duty_cycle/duty_cycle_sensor.cpp index 3d7f731d5d..aed22312a7 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.cpp +++ b/esphome/components/duty_cycle/duty_cycle_sensor.cpp @@ -12,7 +12,6 @@ void DutyCycleSensor::setup() { this->pin_->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_, gpio::INTERRUPT_ANY_EDGE); @@ -24,19 +23,20 @@ void DutyCycleSensor::dump_config() { } void DutyCycleSensor::update() { const uint32_t now = micros(); - const bool level = this->store_.last_level; - const uint32_t last_interrupt = this->store_.last_interrupt; - uint32_t on_time = this->store_.on_time; + if (this->last_update_ != 0) { + const bool level = this->store_.last_level; + const uint32_t last_interrupt = this->store_.last_interrupt; + uint32_t on_time = this->store_.on_time; - if (level) - on_time += now - last_interrupt; + if (level) + on_time += now - last_interrupt; - const float total_time = float(now - this->last_update_); - - const float value = (on_time / total_time) * 100.0f; - ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value); - this->publish_state(value); + const float total_time = float(now - this->last_update_); + const float value = (on_time / total_time) * 100.0f; + ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value); + this->publish_state(value); + } this->store_.on_time = 0; this->store_.last_interrupt = now; this->last_update_ = now; diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.h b/esphome/components/duty_cycle/duty_cycle_sensor.h index 22d3588fb7..ffb1802e14 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.h +++ b/esphome/components/duty_cycle/duty_cycle_sensor.h @@ -30,7 +30,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent { InternalGPIOPin *pin_; DutyCycleSensorStore store_{}; - uint32_t last_update_; + uint32_t last_update_{0}; }; } // namespace duty_cycle diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.cpp b/esphome/components/pulse_counter/pulse_counter_sensor.cpp index f538a4c905..d9f198f4fc 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.cpp +++ b/esphome/components/pulse_counter/pulse_counter_sensor.cpp @@ -155,16 +155,20 @@ void PulseCounterSensor::dump_config() { void PulseCounterSensor::update() { pulse_counter_t raw = this->storage_.read_raw_value(); - float value = (60000.0f * raw) / float(this->get_update_interval()); // per minute - - ESP_LOGD(TAG, "'%s': Retrieved counter: %0.2f pulses/min", this->get_name().c_str(), value); - this->publish_state(value); + uint32_t now = millis(); + if (this->last_time_ != 0) { + uint32_t interval = now - this->last_time_; + float value = (60000.0f * raw) / float(interval); // per minute + ESP_LOGD(TAG, "'%s': Retrieved counter: %0.2f pulses/min", this->get_name().c_str(), value); + this->publish_state(value); + } if (this->total_sensor_ != nullptr) { current_total_ += raw; ESP_LOGD(TAG, "'%s': Total : %i pulses", this->get_name().c_str(), current_total_); this->total_sensor_->publish_state(current_total_); } + this->last_time_ = now; } } // namespace pulse_counter diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.h b/esphome/components/pulse_counter/pulse_counter_sensor.h index 94e37bc232..9ed2159ae3 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.h +++ b/esphome/components/pulse_counter/pulse_counter_sensor.h @@ -65,7 +65,8 @@ class PulseCounterSensor : public sensor::Sensor, public PollingComponent { protected: InternalGPIOPin *pin_; PulseCounterStorage storage_; - uint32_t current_total_ = 0; + uint32_t last_time_{0}; + uint32_t current_total_{0}; sensor::Sensor *total_sensor_; }; From 5ea77894b72c0e36ab6fe589d00ee78a341111f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 21:03:23 +0100 Subject: [PATCH 282/549] Bump black from 21.9b0 to 21.10b0 (#2650) Bumps [black](https://github.com/psf/black) from 21.9b0 to 21.10b0. - [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 e40d51f4bf..03879c5d0e 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==2.11.1 flake8==4.0.1 -black==21.9b0 +black==21.10b0 pexpect==4.8.0 pre-commit From 379c3e98f5b03af91a941fd7dbc1d19067851880 Mon Sep 17 00:00:00 2001 From: niklasweber Date: Tue, 2 Nov 2021 19:32:24 +0100 Subject: [PATCH 283/549] Add restore_mode to rotary_encoder (#2643) --- .../rotary_encoder/rotary_encoder.cpp | 34 +++++++++++++++++++ .../rotary_encoder/rotary_encoder.h | 17 ++++++++++ esphome/components/rotary_encoder/sensor.py | 12 +++++++ 3 files changed, 63 insertions(+) diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index e1f2584641..aff8fc381c 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -125,6 +125,22 @@ void IRAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensorStore void RotaryEncoderSensor::setup() { ESP_LOGCONFIG(TAG, "Setting up Rotary Encoder '%s'...", this->name_.c_str()); + + int32_t initial_value = 0; + switch (this->restore_mode_) { + case ROTARY_ENCODER_RESTORE_DEFAULT_ZERO: + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); + if (!this->rtc_.load(&initial_value)) { + initial_value = 0; + } + break; + case ROTARY_ENCODER_ALWAYS_ZERO: + initial_value = 0; + break; + } + this->store_.counter = initial_value; + this->store_.last_read = initial_value; + this->pin_a_->setup(); this->store_.pin_a = this->pin_a_->to_isr(); this->pin_b_->setup(); @@ -142,6 +158,18 @@ void RotaryEncoderSensor::dump_config() { LOG_PIN(" Pin A: ", this->pin_a_); LOG_PIN(" Pin B: ", this->pin_b_); LOG_PIN(" Pin I: ", this->pin_i_); + + const LogString *restore_mode = LOG_STR(""); + switch (this->restore_mode_) { + case ROTARY_ENCODER_RESTORE_DEFAULT_ZERO: + restore_mode = LOG_STR("Restore (Defaults to zero)"); + break; + case ROTARY_ENCODER_ALWAYS_ZERO: + restore_mode = LOG_STR("Always zero"); + break; + } + ESP_LOGCONFIG(TAG, " Restore Mode: %s", LOG_STR_ARG(restore_mode)); + switch (this->store_.resolution) { case ROTARY_ENCODER_1_PULSE_PER_CYCLE: ESP_LOGCONFIG(TAG, " Resolution: 1 Pulse Per Cycle"); @@ -190,6 +218,9 @@ void RotaryEncoderSensor::loop() { } int counter = this->store_.counter; if (this->store_.last_read != counter || this->publish_initial_value_) { + if (this->restore_mode_ == ROTARY_ENCODER_RESTORE_DEFAULT_ZERO) { + this->rtc_.save(&counter); + } this->store_.last_read = counter; this->publish_state(counter); this->publish_initial_value_ = false; @@ -197,6 +228,9 @@ void RotaryEncoderSensor::loop() { } float RotaryEncoderSensor::get_setup_priority() const { return setup_priority::DATA; } +void RotaryEncoderSensor::set_restore_mode(RotaryEncoderRestoreMode restore_mode) { + this->restore_mode_ = restore_mode; +} void RotaryEncoderSensor::set_resolution(RotaryEncoderResolution mode) { this->store_.resolution = mode; } void RotaryEncoderSensor::set_min_value(int32_t min_value) { this->store_.min_value = min_value; } void RotaryEncoderSensor::set_max_value(int32_t max_value) { this->store_.max_value = max_value; } diff --git a/esphome/components/rotary_encoder/rotary_encoder.h b/esphome/components/rotary_encoder/rotary_encoder.h index a134043152..a69d738fa8 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.h +++ b/esphome/components/rotary_encoder/rotary_encoder.h @@ -10,6 +10,12 @@ namespace esphome { namespace rotary_encoder { +/// All possible restore modes for the rotary encoder +enum RotaryEncoderRestoreMode { + ROTARY_ENCODER_RESTORE_DEFAULT_ZERO, /// try to restore counter, otherwise set to zero + ROTARY_ENCODER_ALWAYS_ZERO, /// do not restore counter, always set to zero +}; + /// All possible resolutions for the rotary encoder enum RotaryEncoderResolution { ROTARY_ENCODER_1_PULSE_PER_CYCLE = @@ -40,6 +46,15 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { void set_pin_a(InternalGPIOPin *pin_a) { pin_a_ = pin_a; } void set_pin_b(InternalGPIOPin *pin_b) { pin_b_ = pin_b; } + /** Set the restore mode of the rotary encoder. + * + * By default (if possible) the last known counter state is restored. Otherwise the value 0 is used. + * Restoring the state can also be turned off. + * + * @param restore_mode The restore mode to use. + */ + void set_restore_mode(RotaryEncoderRestoreMode restore_mode); + /** Set the resolution of the rotary encoder. * * By default, this component will increment the counter by 1 with every A-B input cycle. @@ -81,6 +96,8 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { 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. bool publish_initial_value_; + ESPPreferenceObject rtc_; + RotaryEncoderRestoreMode restore_mode_{ROTARY_ENCODER_RESTORE_DEFAULT_ZERO}; RotaryEncoderSensorStore store_{}; diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py index d868438fd3..cd747264b3 100644 --- a/esphome/components/rotary_encoder/sensor.py +++ b/esphome/components/rotary_encoder/sensor.py @@ -14,9 +14,17 @@ from esphome.const import ( CONF_PIN_A, CONF_PIN_B, CONF_TRIGGER_ID, + CONF_RESTORE_MODE, ) rotary_encoder_ns = cg.esphome_ns.namespace("rotary_encoder") + +RotaryEncoderRestoreMode = rotary_encoder_ns.enum("RotaryEncoderRestoreMode") +RESTORE_MODES = { + "RESTORE_DEFAULT_ZERO": RotaryEncoderRestoreMode.ROTARY_ENCODER_RESTORE_DEFAULT_ZERO, + "ALWAYS_ZERO": RotaryEncoderRestoreMode.ROTARY_ENCODER_ALWAYS_ZERO, +} + RotaryEncoderResolution = rotary_encoder_ns.enum("RotaryEncoderResolution") RESOLUTIONS = { 1: RotaryEncoderResolution.ROTARY_ENCODER_1_PULSE_PER_CYCLE, @@ -72,6 +80,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_MIN_VALUE): cv.int_, cv.Optional(CONF_MAX_VALUE): cv.int_, cv.Optional(CONF_PUBLISH_INITIAL_VALUE, default=False): cv.boolean, + cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_ZERO"): cv.enum( + RESTORE_MODES, upper=True, space="_" + ), cv.Optional(CONF_ON_CLOCKWISE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -102,6 +113,7 @@ async def to_code(config): pin_b = await cg.gpio_pin_expression(config[CONF_PIN_B]) cg.add(var.set_pin_b(pin_b)) cg.add(var.set_publish_initial_value(config[CONF_PUBLISH_INITIAL_VALUE])) + cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) if CONF_PIN_RESET in config: pin_i = await cg.gpio_pin_expression(config[CONF_PIN_RESET]) From 11f1e28139cf07aafbb4ff1f8257ad5caa369780 Mon Sep 17 00:00:00 2001 From: Tim Niemueller Date: Wed, 3 Nov 2021 17:56:09 +0100 Subject: [PATCH 284/549] Make per-loop display clearing optional (#2626) Currently, in each loop during DisplayBuffer::update_() the display is cleared by calling DisplayBuffer::clear(). This prevents more efficient display usages that do not render the screen in each loop, but only if necessary. This can be helpful, for example, if images are rendered. This would cause the loop time to be exceeded frequently. This change adds a new optional flag "auto_clear" that can be used to control the clearing behavior. If unset, the DisplayBuffer defaults to enabled auto clearing, the current behavior and thus backward compatible. This flag applies to displays that use DisplayBuffer. Example excerpt: globals: - id: state type: bool restore_value: no initial_value: "false" - id: state_processed type: bool restore_value: no initial_value: "false" switch: - platform: template name: "State" id: state_switch lambda: |- return id(state); turn_on_action: - globals.set: id: state value: "true" - globals.set: id: state_processed value: "false" turn_off_action: - globals.set: id: state value: "false" - globals.set: id: state_processed value: "false" display: - platform: ili9341 # ... auto_clear_enabled: false lambda: |- if (!id(state_processed)) { it.fill(COLOR_WHITE); if (id(state)) { it.image(80, 20, id(image1)); } else { it.image(80, 20, id(image2)); } id(state_processed) = true; } Co-authored-by: Tim Niemueller --- esphome/components/display/__init__.py | 6 ++++ esphome/components/display/display_buffer.cpp | 4 ++- esphome/components/display/display_buffer.h | 4 +++ esphome/const.py | 1 + tests/test1.yaml | 29 +++++++++++++++++++ 5 files changed, 43 insertions(+), 1 deletion(-) diff --git a/esphome/components/display/__init__.py b/esphome/components/display/__init__.py index 947b09a258..0d403f99f0 100644 --- a/esphome/components/display/__init__.py +++ b/esphome/components/display/__init__.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome import core, automation from esphome.automation import maybe_simple_id from esphome.const import ( + CONF_AUTO_CLEAR_ENABLED, CONF_ID, CONF_LAMBDA, CONF_PAGES, @@ -79,6 +80,7 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend( cv.Optional(CONF_TO): cv.use_id(DisplayPage), } ), + cv.Optional(CONF_AUTO_CLEAR_ENABLED, default=True): cv.boolean, } ) @@ -86,6 +88,10 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend( async def setup_display_core_(var, config): if CONF_ROTATION in config: cg.add(var.set_rotation(DISPLAY_ROTATIONS[config[CONF_ROTATION]])) + + if CONF_AUTO_CLEAR_ENABLED in config: + cg.add(var.set_auto_clear(config[CONF_AUTO_CLEAR_ENABLED])) + if CONF_PAGES in config: pages = [] for conf in config[CONF_PAGES]: diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 2ee06e379f..ac806611b5 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -336,7 +336,9 @@ void DisplayBuffer::show_page(DisplayPage *page) { void DisplayBuffer::show_next_page() { this->page_->show_next(); } void DisplayBuffer::show_prev_page() { this->page_->show_prev(); } void DisplayBuffer::do_update_() { - this->clear(); + if (this->auto_clear_enabled_) { + this->clear(); + } if (this->page_ != nullptr) { this->page_->get_writer()(*this); } else if (this->writer_.has_value()) { diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index 54488f18f7..c803180a2d 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -333,6 +333,9 @@ class DisplayBuffer { /// Internal method to set the display rotation with. void set_rotation(DisplayRotation rotation); + // Internal method to set display auto clearing. + void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; } + protected: void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg); @@ -352,6 +355,7 @@ class DisplayBuffer { DisplayPage *page_{nullptr}; DisplayPage *previous_page_{nullptr}; std::vector on_page_change_triggers_; + bool auto_clear_enabled_{true}; }; class DisplayPage { diff --git a/esphome/const.py b/esphome/const.py index bac446a7c6..eb7d56d7e1 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -59,6 +59,7 @@ CONF_AT = "at" CONF_ATTENUATION = "attenuation" CONF_ATTRIBUTE = "attribute" CONF_AUTH = "auth" +CONF_AUTO_CLEAR_ENABLED = "auto_clear_enabled" CONF_AUTO_MODE = "auto_mode" CONF_AUTOCONF = "autoconf" CONF_AUTOMATION_ID = "automation_id" diff --git a/tests/test1.yaml b/tests/test1.yaml index d8075e980b..585e01635f 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2214,6 +2214,31 @@ display: row_start: 0 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: ili9341 + model: "TFT 2.4" + cs_pin: GPIO5 + dc_pin: GPIO4 + reset_pin: GPIO22 + led_pin: + number: GPIO15 + inverted: true + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: ili9341 + model: "TFT 2.4" + cs_pin: GPIO5 + dc_pin: GPIO4 + reset_pin: GPIO22 + led_pin: + number: GPIO15 + inverted: true + auto_clear_enabled: false + rotation: 90 + lambda: |- + if (!id(glob_bool_processed)) { + it.fill(Color::WHITE); + id(glob_bool_processed) = true; + } tm1651: id: tm1651_battery @@ -2393,6 +2418,10 @@ globals: type: std::string restore_value: no # initial_value: "" + - id: glob_bool_processed + type: bool + restore_value: no + initial_value: 'false' text_sensor: - platform: mqtt_subscribe From d536509a63189490f314e5923471828ca28ba26d Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Thu, 4 Nov 2021 18:52:38 -0300 Subject: [PATCH 285/549] Allow esp8266 to compile with no wifi (#2664) --- esphome/core/helpers.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index bc97259a71..ada9a48c3b 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -6,7 +6,9 @@ #include #if defined(USE_ESP8266) +#ifdef USE_WIFI #include +#endif #include #elif defined(USE_ESP32_FRAMEWORK_ARDUINO) #include @@ -39,7 +41,7 @@ void get_mac_address_raw(uint8_t *mac) { esp_efuse_mac_get_default(mac); #endif #endif -#ifdef USE_ESP8266 +#if (defined USE_ESP8266 && defined USE_WIFI) WiFi.macAddress(mac); #endif } @@ -48,7 +50,11 @@ std::string get_mac_address() { char tmp[20]; uint8_t mac[6]; get_mac_address_raw(mac); +#ifdef USE_WIFI sprintf(tmp, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +#else + return ""; +#endif return std::string(tmp); } @@ -475,8 +481,13 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green } #ifdef USE_ESP8266 +#ifdef USE_WIFI IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } +#else +IRAM_ATTR InterruptLock::InterruptLock() {} +IRAM_ATTR InterruptLock::~InterruptLock() {} +#endif #endif #ifdef USE_ESP32 IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } From b450d4c734e18c1cfe04c07da09431b57ffdc8e3 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sat, 6 Nov 2021 22:52:04 +0100 Subject: [PATCH 286/549] Fix CRC error during DSMR chunked message reading (#2622) * DSMR chunk size from 50 to 500 * Still a few CRC errors with 500, upping to 1024. * Adding timers to measure how long processing DSMR takes * Handle chunked output from smart meter. * Cleaning up and commenting the new chunk handling code * Remove debug code. * Fixing clang-tidy issues. * Implementing chunked reading support for encrypted telegrams. * Remove redundant extra delay for encrypted reader * Beware not to flush crypted telegram headers * Use insane data timeout for testing * Improve logging * Make clang-tidy happy Co-authored-by: Maurice Makaay Co-authored-by: Maurice Makaay --- esphome/components/dsmr/dsmr.cpp | 104 ++++++++++++++++++------------- esphome/components/dsmr/dsmr.h | 14 ++++- 2 files changed, 71 insertions(+), 47 deletions(-) diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index b798fe5d44..031fb275f5 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -19,14 +19,30 @@ void Dsmr::loop() { this->receive_encrypted_(); } +bool Dsmr::available_within_timeout_() { + uint8_t tries = READ_TIMEOUT_MS / 5; + while (tries--) { + delay(5); + if (available()) { + return true; + } + } + return false; +} + void Dsmr::receive_telegram_() { - int count = MAX_BYTES_PER_LOOP; - while (available() && count-- > 0) { + while (true) { + if (!available()) { + if (!header_found_ || !available_within_timeout_()) { + return; + } + } + const char c = read(); // Find a new telegram header, i.e. forward slash. if (c == '/') { - ESP_LOGV(TAG, "Header found"); + ESP_LOGV(TAG, "Header of telegram found"); header_found_ = true; footer_found_ = false; telegram_len_ = 0; @@ -38,7 +54,7 @@ void Dsmr::receive_telegram_() { if (telegram_len_ >= MAX_TELEGRAM_LENGTH) { header_found_ = false; footer_found_ = false; - ESP_LOGE(TAG, "Error: Message larger than buffer"); + ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH); return; } @@ -54,7 +70,7 @@ void Dsmr::receive_telegram_() { // Check for a footer, i.e. exlamation mark, followed by a hex checksum. if (c == '!') { - ESP_LOGV(TAG, "Footer found"); + ESP_LOGV(TAG, "Footer of telegram found"); footer_found_ = true; continue; } @@ -62,8 +78,8 @@ void Dsmr::receive_telegram_() { if (footer_found_ && c == '\n') { header_found_ = false; // Parse the telegram and publish sensor values. - if (parse_telegram()) - return; + parse_telegram(); + return; } } } @@ -72,41 +88,46 @@ void Dsmr::receive_encrypted_() { // Encrypted buffer uint8_t buffer[MAX_TELEGRAM_LENGTH]; size_t buffer_length = 0; - size_t packet_size = 0; - while (available()) { - const char c = read(); - if (!header_found_) { - if ((uint8_t) c == 0xdb) { - ESP_LOGV(TAG, "Start byte 0xDB found"); - header_found_ = true; + while (true) { + if (!available()) { + if (!header_found_) { + return; + } + if (!available_within_timeout_()) { + ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram"); + return; } } - // Sanity check - if (!header_found_ || buffer_length >= MAX_TELEGRAM_LENGTH) { - if (buffer_length == 0) { - ESP_LOGE(TAG, "First byte of encrypted telegram should be 0xDB, aborting."); - } else { - ESP_LOGW(TAG, "Unexpected data"); + const char c = read(); + + // Find a new telegram start byte. + if (!header_found_) { + if ((uint8_t) c == 0xDB) { + ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); + header_found_ = true; } - this->status_momentary_warning("unexpected_data"); - this->flush(); - while (available()) - read(); + continue; + } + + // Check for buffer overflow. + if (buffer_length >= MAX_TELEGRAM_LENGTH) { + header_found_ = false; + ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH); return; } buffer[buffer_length++] = c; if (packet_size == 0 && buffer_length > 20) { - // Complete header + a few bytes of data - packet_size = buffer[11] << 8 | buffer[12]; + // Complete header + data bytes + packet_size = 13 + (buffer[11] << 8 | buffer[12]); + ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size); } - if (buffer_length == packet_size + 13 && packet_size > 0) { - ESP_LOGV(TAG, "Encrypted data: %d bytes", buffer_length); - + if (buffer_length == packet_size && packet_size > 0) { + ESP_LOGV(TAG, "End of encrypted telegram found"); GCM *gcmaes128{new GCM()}; gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize()); // the iv is 8 bytes of the system title + 4 bytes frame counter @@ -123,28 +144,21 @@ void Dsmr::receive_encrypted_() { delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory) telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_)); - ESP_LOGV(TAG, "Decrypted data length: %d", telegram_len_); - ESP_LOGVV(TAG, "Decrypted data %s", this->telegram_); + ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_); + ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); + + header_found_ = false; + telegram_len_ = 0; parse_telegram(); - telegram_len_ = 0; return; } - - if (!available()) { - // baud rate is 115200 for encrypted data, this means a few byte should arrive every time - // program runs faster than buffer loading then available() might return false in the middle - delay(4); // Wait for data - } - } - if (buffer_length > 0) { - ESP_LOGW(TAG, "Timeout while waiting for encrypted data or invalid data received."); } } bool Dsmr::parse_telegram() { MyData data; - ESP_LOGV(TAG, "Trying to parse"); + ESP_LOGV(TAG, "Trying to parse telegram"); ::dsmr::ParseResult res = ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false, this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. @@ -161,7 +175,7 @@ bool Dsmr::parse_telegram() { } void Dsmr::dump_config() { - ESP_LOGCONFIG(TAG, "dsmr:"); + ESP_LOGCONFIG(TAG, "DSMR:"); #define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_); DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, ) @@ -178,12 +192,12 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { } if (decryption_key.length() != 32) { - ESP_LOGE(TAG, "Error, decryption key must be 32 character long."); + ESP_LOGE(TAG, "Error, decryption key must be 32 character long"); return; } this->decryption_key_.clear(); - ESP_LOGI(TAG, "Decryption key is set."); + ESP_LOGI(TAG, "Decryption key is set"); // Verbose level prints decryption key ESP_LOGV(TAG, "Using decryption key: %s", decryption_key.c_str()); diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index 4f9a66b3d0..ca2c0f0877 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -17,8 +17,7 @@ namespace esphome { namespace dsmr { static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500; -static constexpr uint32_t MAX_BYTES_PER_LOOP = 50; -static constexpr uint32_t POLL_TIMEOUT = 1000; +static constexpr uint32_t READ_TIMEOUT_MS = 200; using namespace ::dsmr::fields; @@ -86,6 +85,17 @@ class Dsmr : public Component, public uart::UARTDevice { void receive_telegram_(); void receive_encrypted_(); + /// Wait for UART data to become available within the read timeout. + /// + /// The smart meter might provide data in chunks, causing available() to + /// return 0. When we're already reading a telegram, then we don't return + /// right away (to handle further data in an upcoming loop) but wait a + /// little while using this method to see if more data are incoming. + /// By not returning, we prevent other components from taking so much + /// time that the UART RX buffer overflows and bytes of the telegram get + /// lost in the process. + bool available_within_timeout_(); + // Telegram buffer char telegram_[MAX_TELEGRAM_LENGTH]; int telegram_len_{0}; From 3c0414c42027d8cc3cab8e59c878116f62d8fac7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 8 Nov 2021 07:24:52 +1300 Subject: [PATCH 287/549] Add Entity categories for Home Assistant (#2636) --- esphome/codegen.py | 1 + esphome/components/am43/sensor.py | 12 +- esphome/components/api/api.proto | 19 +++ esphome/components/api/api_connection.cpp | 12 +- esphome/components/api/api_pb2.cpp | 111 ++++++++++++++++++ esphome/components/api/api_pb2.h | 16 +++ .../components/atc_mithermometer/sensor.py | 3 + esphome/components/atm90e32/sensor.py | 2 + esphome/components/b_parasite/sensor.py | 2 + esphome/components/binary_sensor/__init__.py | 1 + esphome/components/fingerprint_grow/sensor.py | 7 ++ .../components/inkbird_ibsth1_mini/sensor.py | 2 + esphome/components/number/__init__.py | 1 - .../components/pvvx_mithermometer/sensor.py | 3 + esphome/components/restart/switch.py | 12 +- esphome/components/ruuvitag/sensor.py | 5 + .../components/safe_mode/switch/__init__.py | 5 + esphome/components/select/__init__.py | 1 - esphome/components/sensor/__init__.py | 11 +- esphome/components/shutdown/switch.py | 12 +- esphome/components/status/binary_sensor.py | 11 +- esphome/components/switch/__init__.py | 1 + esphome/components/text_sensor/__init__.py | 1 + esphome/components/uptime/sensor.py | 2 + esphome/components/version/text_sensor.py | 12 +- esphome/components/wifi_info/text_sensor.py | 17 +++ esphome/components/wifi_signal/sensor.py | 2 + esphome/components/xiaomi_cgd1/sensor.py | 2 + esphome/components/xiaomi_cgdk2/sensor.py | 2 + esphome/components/xiaomi_cgg1/sensor.py | 2 + .../components/xiaomi_cgpr1/binary_sensor.py | 16 ++- esphome/components/xiaomi_hhccjcy01/sensor.py | 2 + esphome/components/xiaomi_jqjcy01ym/sensor.py | 2 + esphome/components/xiaomi_lywsd02/sensor.py | 2 + .../components/xiaomi_lywsd03mmc/sensor.py | 2 + esphome/components/xiaomi_lywsdcgq/sensor.py | 2 + esphome/components/xiaomi_mhoc401/sensor.py | 2 + .../xiaomi_mjyd02yla/binary_sensor.py | 2 + .../components/xiaomi_wx08zm/binary_sensor.py | 2 + esphome/config_validation.py | 17 +++ esphome/const.py | 10 ++ esphome/core/entity_base.cpp | 4 + esphome/core/entity_base.h | 11 ++ esphome/cpp_helpers.py | 3 + esphome/cpp_types.py | 1 + 45 files changed, 354 insertions(+), 14 deletions(-) diff --git a/esphome/codegen.py b/esphome/codegen.py index 4f9f67245d..5e1e934e58 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -81,4 +81,5 @@ from esphome.cpp_types import ( # noqa GPIOPin, InternalGPIOPin, gpio_Flags, + EntityCategory, ) diff --git a/esphome/components/am43/sensor.py b/esphome/components/am43/sensor.py index c88e529a0c..68c85d0e9c 100644 --- a/esphome/components/am43/sensor.py +++ b/esphome/components/am43/sensor.py @@ -4,7 +4,8 @@ from esphome.components import sensor, ble_client from esphome.const import ( CONF_ID, CONF_BATTERY_LEVEL, - ICON_BATTERY, + DEVICE_CLASS_BATTERY, + ENTITY_CATEGORY_DIAGNOSTIC, CONF_ILLUMINANCE, ICON_BRIGHTNESS_5, UNIT_PERCENT, @@ -20,10 +21,15 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(Am43), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, ICON_BATTERY, 0 + unit_of_measurement=UNIT_PERCENT, + device_class=DEVICE_CLASS_BATTERY, + accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( - UNIT_PERCENT, ICON_BRIGHTNESS_5, 0 + unit_of_measurement=UNIT_PERCENT, + icon=ICON_BRIGHTNESS_5, + accuracy_decimals=0, ), } ) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index b1ac998608..0eb7ead735 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -203,6 +203,14 @@ message SubscribeStatesRequest { // Empty } +// ==================== COMMON ===================== + +enum EntityCategory { + ENTITY_CATEGORY_NONE = 0; + ENTITY_CATEGORY_CONFIG = 1; + ENTITY_CATEGORY_DIAGNOSTIC = 2; +} + // ==================== BINARY SENSOR ==================== message ListEntitiesBinarySensorResponse { option (id) = 12; @@ -218,6 +226,7 @@ message ListEntitiesBinarySensorResponse { bool is_status_binary_sensor = 6; bool disabled_by_default = 7; string icon = 8; + EntityCategory entity_category = 9; } message BinarySensorStateResponse { option (id) = 21; @@ -249,6 +258,7 @@ message ListEntitiesCoverResponse { string device_class = 8; bool disabled_by_default = 9; string icon = 10; + EntityCategory entity_category = 11; } enum LegacyCoverState { @@ -318,6 +328,7 @@ message ListEntitiesFanResponse { int32 supported_speed_count = 8; bool disabled_by_default = 9; string icon = 10; + EntityCategory entity_category = 11; } enum FanSpeed { FAN_SPEED_LOW = 0; @@ -394,6 +405,7 @@ message ListEntitiesLightResponse { repeated string effects = 11; bool disabled_by_default = 13; string icon = 14; + EntityCategory entity_category = 15; } message LightStateResponse { option (id) = 24; @@ -482,6 +494,7 @@ message ListEntitiesSensorResponse { // Last reset type removed in 2021.9.0 SensorLastResetType legacy_last_reset_type = 11; bool disabled_by_default = 12; + EntityCategory entity_category = 13; } message SensorStateResponse { option (id) = 25; @@ -510,6 +523,7 @@ message ListEntitiesSwitchResponse { string icon = 5; bool assumed_state = 6; bool disabled_by_default = 7; + EntityCategory entity_category = 8; } message SwitchStateResponse { option (id) = 26; @@ -543,6 +557,7 @@ message ListEntitiesTextSensorResponse { string icon = 5; bool disabled_by_default = 6; + EntityCategory entity_category = 7; } message TextSensorStateResponse { option (id) = 27; @@ -704,6 +719,7 @@ message ListEntitiesCameraResponse { string unique_id = 4; bool disabled_by_default = 5; string icon = 6; + EntityCategory entity_category = 7; } message CameraImageResponse { @@ -798,6 +814,7 @@ message ListEntitiesClimateResponse { repeated string supported_custom_presets = 17; bool disabled_by_default = 18; string icon = 19; + EntityCategory entity_category = 20; } message ClimateStateResponse { option (id) = 47; @@ -866,6 +883,7 @@ message ListEntitiesNumberResponse { float max_value = 7; float step = 8; bool disabled_by_default = 9; + EntityCategory entity_category = 10; } message NumberStateResponse { option (id) = 50; @@ -903,6 +921,7 @@ message ListEntitiesSelectResponse { string icon = 5; repeated string options = 6; bool disabled_by_default = 7; + EntityCategory entity_category = 8; } message SelectStateResponse { option (id) = 53; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 79c53ee840..715a4f48c1 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -184,6 +184,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_ msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); msg.disabled_by_default = binary_sensor->is_disabled_by_default(); msg.icon = binary_sensor->get_icon(); + msg.entity_category = static_cast(binary_sensor->get_entity_category()); return this->send_list_entities_binary_sensor_response(msg); } #endif @@ -217,6 +218,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) { msg.device_class = cover->get_device_class(); msg.disabled_by_default = cover->is_disabled_by_default(); msg.icon = cover->get_icon(); + msg.entity_category = static_cast(cover->get_entity_category()); return this->send_list_entities_cover_response(msg); } void APIConnection::cover_command(const CoverCommandRequest &msg) { @@ -283,6 +285,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) { msg.supported_speed_count = traits.supported_speed_count(); msg.disabled_by_default = fan->is_disabled_by_default(); msg.icon = fan->get_icon(); + msg.entity_category = static_cast(fan->get_entity_category()); return this->send_list_entities_fan_response(msg); } void APIConnection::fan_command(const FanCommandRequest &msg) { @@ -346,6 +349,7 @@ bool APIConnection::send_light_info(light::LightState *light) { msg.disabled_by_default = light->is_disabled_by_default(); msg.icon = light->get_icon(); + msg.entity_category = static_cast(light->get_entity_category()); for (auto mode : traits.get_supported_color_modes()) msg.supported_color_modes.push_back(static_cast(mode)); @@ -432,7 +436,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { msg.device_class = sensor->get_device_class(); msg.state_class = static_cast(sensor->get_state_class()); msg.disabled_by_default = sensor->is_disabled_by_default(); - + msg.entity_category = static_cast(sensor->get_entity_category()); return this->send_list_entities_sensor_response(msg); } #endif @@ -456,6 +460,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) { msg.icon = a_switch->get_icon(); msg.assumed_state = a_switch->assumed_state(); msg.disabled_by_default = a_switch->is_disabled_by_default(); + msg.entity_category = static_cast(a_switch->get_entity_category()); return this->send_list_entities_switch_response(msg); } void APIConnection::switch_command(const SwitchCommandRequest &msg) { @@ -491,6 +496,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) msg.unique_id = get_default_unique_id("text_sensor", text_sensor); msg.icon = text_sensor->get_icon(); msg.disabled_by_default = text_sensor->is_disabled_by_default(); + msg.entity_category = static_cast(text_sensor->get_entity_category()); return this->send_list_entities_text_sensor_response(msg); } #endif @@ -537,6 +543,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { msg.disabled_by_default = climate->is_disabled_by_default(); msg.icon = climate->get_icon(); + msg.entity_category = static_cast(climate->get_entity_category()); msg.supports_current_temperature = traits.get_supports_current_temperature(); msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); @@ -611,6 +618,7 @@ bool APIConnection::send_number_info(number::Number *number) { msg.unique_id = get_default_unique_id("number", number); msg.icon = number->get_icon(); msg.disabled_by_default = number->is_disabled_by_default(); + msg.entity_category = static_cast(number->get_entity_category()); msg.min_value = number->traits.get_min_value(); msg.max_value = number->traits.get_max_value(); @@ -648,6 +656,7 @@ bool APIConnection::send_select_info(select::Select *select) { msg.unique_id = get_default_unique_id("select", select); msg.icon = select->get_icon(); msg.disabled_by_default = select->is_disabled_by_default(); + msg.entity_category = static_cast(select->get_entity_category()); for (const auto &option : select->traits.get_options()) msg.options.push_back(option); @@ -681,6 +690,7 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { msg.unique_id = get_default_unique_id("camera", camera); msg.disabled_by_default = camera->is_disabled_by_default(); msg.icon = camera->get_icon(); + msg.entity_category = static_cast(camera->get_entity_category()); return this->send_list_entities_camera_response(msg); } void APIConnection::camera_image(const CameraImageRequest &msg) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 1d59d98f52..7bfa1e9edb 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -6,6 +6,18 @@ namespace esphome { namespace api { +template<> const char *proto_enum_to_string(enums::EntityCategory value) { + switch (value) { + case enums::ENTITY_CATEGORY_NONE: + return "ENTITY_CATEGORY_NONE"; + case enums::ENTITY_CATEGORY_CONFIG: + return "ENTITY_CATEGORY_CONFIG"; + case enums::ENTITY_CATEGORY_DIAGNOSTIC: + return "ENTITY_CATEGORY_DIAGNOSTIC"; + default: + return "UNKNOWN"; + } +} template<> const char *proto_enum_to_string(enums::LegacyCoverState value) { switch (value) { case enums::LEGACY_COVER_STATE_OPEN: @@ -519,6 +531,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar this->disabled_by_default = value.as_bool(); return true; } + case 9: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -568,6 +584,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->is_status_binary_sensor); buffer.encode_bool(7, this->disabled_by_default); buffer.encode_string(8, this->icon); + buffer.encode_enum(9, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { @@ -605,6 +622,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -674,6 +695,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val this->disabled_by_default = value.as_bool(); return true; } + case 11: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -725,6 +750,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(8, this->device_class); buffer.encode_bool(9, this->disabled_by_default); buffer.encode_string(10, this->icon); + buffer.encode_enum(11, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { @@ -770,6 +796,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -958,6 +988,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value this->disabled_by_default = value.as_bool(); return true; } + case 11: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -1005,6 +1039,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_int32(8, this->supported_speed_count); buffer.encode_bool(9, this->disabled_by_default); buffer.encode_string(10, this->icon); + buffer.encode_enum(11, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesFanResponse::dump_to(std::string &out) const { @@ -1051,6 +1086,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -1277,6 +1316,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val this->disabled_by_default = value.as_bool(); return true; } + case 15: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -1344,6 +1387,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(13, this->disabled_by_default); buffer.encode_string(14, this->icon); + buffer.encode_enum(15, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLightResponse::dump_to(std::string &out) const { @@ -1411,6 +1455,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -1870,6 +1918,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->disabled_by_default = value.as_bool(); return true; } + case 13: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -1927,6 +1979,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(10, this->state_class); buffer.encode_enum(11, this->legacy_last_reset_type); buffer.encode_bool(12, this->disabled_by_default); + buffer.encode_enum(13, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSensorResponse::dump_to(std::string &out) const { @@ -1981,6 +2034,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -2043,6 +2100,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->disabled_by_default = value.as_bool(); return true; } + case 8: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -2087,6 +2148,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->assumed_state); buffer.encode_bool(7, this->disabled_by_default); + buffer.encode_enum(8, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSwitchResponse::dump_to(std::string &out) const { @@ -2120,6 +2182,10 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -2207,6 +2273,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn this->disabled_by_default = value.as_bool(); return true; } + case 7: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -2250,6 +2320,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(4, this->unique_id); buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); + buffer.encode_enum(7, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { @@ -2279,6 +2350,10 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -2902,6 +2977,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->disabled_by_default = value.as_bool(); return true; } + case 7: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -2945,6 +3024,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(4, this->unique_id); buffer.encode_bool(5, this->disabled_by_default); buffer.encode_string(6, this->icon); + buffer.encode_enum(7, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCameraResponse::dump_to(std::string &out) const { @@ -2974,6 +3054,10 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -3101,6 +3185,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v this->disabled_by_default = value.as_bool(); return true; } + case 20: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -3189,6 +3277,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(18, this->disabled_by_default); buffer.encode_string(19, this->icon); + buffer.encode_enum(20, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesClimateResponse::dump_to(std::string &out) const { @@ -3285,6 +3374,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -3661,6 +3754,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->disabled_by_default = value.as_bool(); return true; } + case 10: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -3719,6 +3816,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(7, this->max_value); buffer.encode_float(8, this->step); buffer.encode_bool(9, this->disabled_by_default); + buffer.encode_enum(10, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { @@ -3763,6 +3861,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -3855,6 +3957,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->disabled_by_default = value.as_bool(); return true; } + case 8: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -3905,6 +4011,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(6, it, true); } buffer.encode_bool(7, this->disabled_by_default); + buffer.encode_enum(8, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSelectResponse::dump_to(std::string &out) const { @@ -3940,6 +4047,10 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index af85ed6856..ec11732c7d 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -9,6 +9,11 @@ namespace api { namespace enums { +enum EntityCategory : uint32_t { + ENTITY_CATEGORY_NONE = 0, + ENTITY_CATEGORY_CONFIG = 1, + ENTITY_CATEGORY_DIAGNOSTIC = 2, +}; enum LegacyCoverState : uint32_t { LEGACY_COVER_STATE_OPEN = 0, LEGACY_COVER_STATE_CLOSED = 1, @@ -271,6 +276,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage { bool is_status_binary_sensor{false}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -307,6 +313,7 @@ class ListEntitiesCoverResponse : public ProtoMessage { std::string device_class{}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -364,6 +371,7 @@ class ListEntitiesFanResponse : public ProtoMessage { int32_t supported_speed_count{0}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -429,6 +437,7 @@ class ListEntitiesLightResponse : public ProtoMessage { std::vector effects{}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -517,6 +526,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { enums::SensorStateClass state_class{}; enums::SensorLastResetType legacy_last_reset_type{}; bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -550,6 +560,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage { std::string icon{}; bool assumed_state{false}; bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -594,6 +605,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage { std::string unique_id{}; std::string icon{}; bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -805,6 +817,7 @@ class ListEntitiesCameraResponse : public ProtoMessage { std::string unique_id{}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -863,6 +876,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { std::vector supported_custom_presets{}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -942,6 +956,7 @@ class ListEntitiesNumberResponse : public ProtoMessage { float max_value{0.0f}; float step{0.0f}; bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -987,6 +1002,7 @@ class ListEntitiesSelectResponse : public ProtoMessage { std::string icon{}; std::vector options{}; bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/atc_mithermometer/sensor.py b/esphome/components/atc_mithermometer/sensor.py index 0f6cc1abcb..bde83c28b6 100644 --- a/esphome/components/atc_mithermometer/sensor.py +++ b/esphome/components/atc_mithermometer/sensor.py @@ -12,6 +12,7 @@ from esphome.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -49,12 +50,14 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/atm90e32/sensor.py b/esphome/components/atm90e32/sensor.py index 05e5250d89..9c876bb62c 100644 --- a/esphome/components/atm90e32/sensor.py +++ b/esphome/components/atm90e32/sensor.py @@ -17,6 +17,7 @@ from esphome.const import ( DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, ICON_LIGHTBULB, ICON_CURRENT_AC, STATE_CLASS_MEASUREMENT, @@ -125,6 +126,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True), cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum( diff --git a/esphome/components/b_parasite/sensor.py b/esphome/components/b_parasite/sensor.py index d51c48c602..201685adc4 100644 --- a/esphome/components/b_parasite/sensor.py +++ b/esphome/components/b_parasite/sensor.py @@ -13,6 +13,7 @@ from esphome.const import ( DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_LUX, @@ -51,6 +52,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_MOISTURE): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index ec199cc5fa..faafcddd06 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -313,6 +313,7 @@ def validate_multi_click_timing(value): device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") + BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(BinarySensor), diff --git a/esphome/components/fingerprint_grow/sensor.py b/esphome/components/fingerprint_grow/sensor.py index f359a10348..4ae670743d 100644 --- a/esphome/components/fingerprint_grow/sensor.py +++ b/esphome/components/fingerprint_grow/sensor.py @@ -8,6 +8,7 @@ from esphome.const import ( CONF_LAST_FINGER_ID, CONF_SECURITY_LEVEL, CONF_STATUS, + ENTITY_CATEGORY_DIAGNOSTIC, ICON_ACCOUNT, ICON_ACCOUNT_CHECK, ICON_DATABASE, @@ -26,30 +27,36 @@ CONFIG_SCHEMA = cv.Schema( icon=ICON_FINGERPRINT, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_STATUS): sensor.sensor_schema( accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_CAPACITY): sensor.sensor_schema( icon=ICON_DATABASE, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_SECURITY_LEVEL): sensor.sensor_schema( icon=ICON_SECURITY, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_LAST_FINGER_ID): sensor.sensor_schema( icon=ICON_ACCOUNT, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_LAST_CONFIDENCE): sensor.sensor_schema( icon=ICON_ACCOUNT_CHECK, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/inkbird_ibsth1_mini/sensor.py b/esphome/components/inkbird_ibsth1_mini/sensor.py index 0ab9f8b3e0..aa11fb3172 100644 --- a/esphome/components/inkbird_ibsth1_mini/sensor.py +++ b/esphome/components/inkbird_ibsth1_mini/sensor.py @@ -9,6 +9,7 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -53,6 +54,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 2856a25ee7..bb3427e4bd 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -41,7 +41,6 @@ NumberInRangeCondition = number_ns.class_( icon = cv.icon - NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent), diff --git a/esphome/components/pvvx_mithermometer/sensor.py b/esphome/components/pvvx_mithermometer/sensor.py index b17878f01b..12090bddba 100644 --- a/esphome/components/pvvx_mithermometer/sensor.py +++ b/esphome/components/pvvx_mithermometer/sensor.py @@ -12,6 +12,7 @@ from esphome.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -49,12 +50,14 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/restart/switch.py b/esphome/components/restart/switch.py index 4f1904e273..de30392b45 100644 --- a/esphome/components/restart/switch.py +++ b/esphome/components/restart/switch.py @@ -1,7 +1,14 @@ 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_RESTART +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ID, + CONF_INVERTED, + CONF_ICON, + ENTITY_CATEGORY_CONFIG, + ICON_RESTART, +) restart_ns = cg.esphome_ns.namespace("restart") RestartSwitch = restart_ns.class_("RestartSwitch", switch.Switch, cg.Component) @@ -13,6 +20,9 @@ CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( "Restart switches do not support inverted mode!" ), cv.Optional(CONF_ICON, default=ICON_RESTART): switch.icon, + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG + ): cv.entity_category, } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/ruuvitag/sensor.py b/esphome/components/ruuvitag/sensor.py index 342a5eff24..2bb9549195 100644 --- a/esphome/components/ruuvitag/sensor.py +++ b/esphome/components/ruuvitag/sensor.py @@ -19,6 +19,7 @@ from esphome.const import ( DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, UNIT_CELSIUS, @@ -95,22 +96,26 @@ CONFIG_SCHEMA = ( accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_TX_POWER): sensor.sensor_schema( unit_of_measurement=UNIT_DECIBEL_MILLIWATT, accuracy_decimals=0, device_class=DEVICE_CLASS_SIGNAL_STRENGTH, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_MOVEMENT_COUNTER): sensor.sensor_schema( icon=ICON_GAUGE, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_MEASUREMENT_SEQUENCE_NUMBER): sensor.sensor_schema( icon=ICON_GAUGE, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/safe_mode/switch/__init__.py b/esphome/components/safe_mode/switch/__init__.py index 0ad814ff4f..b6c3e852f6 100644 --- a/esphome/components/safe_mode/switch/__init__.py +++ b/esphome/components/safe_mode/switch/__init__.py @@ -3,10 +3,12 @@ import esphome.config_validation as cv from esphome.components import switch from esphome.components.ota import OTAComponent from esphome.const import ( + CONF_ENTITY_CATEGORY, CONF_ID, CONF_INVERTED, CONF_ICON, CONF_OTA, + ENTITY_CATEGORY_CONFIG, ICON_RESTART_ALERT, ) from .. import safe_mode_ns @@ -23,6 +25,9 @@ CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( "Safe Mode Restart switches do not support inverted mode!" ), cv.Optional(CONF_ICON, default=ICON_RESTART_ALERT): switch.icon, + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG + ): cv.entity_category, } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index 7e4047d3c8..8ea159d657 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -30,7 +30,6 @@ SelectSetAction = select_ns.class_("SelectSetAction", automation.Action) icon = cv.icon - SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent), diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 4b2e9dc019..d9d226aab6 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_ACCURACY_DECIMALS, CONF_ALPHA, CONF_BELOW, + CONF_ENTITY_CATEGORY, CONF_EXPIRE_AFTER, CONF_FILTERS, CONF_FROM, @@ -133,7 +134,6 @@ def validate_datapoint(value): # Base -sensor_ns = cg.esphome_ns.namespace("sensor") Sensor = sensor_ns.class_("Sensor", cg.EntityBase) SensorPtr = Sensor.operator("ptr") @@ -226,6 +226,7 @@ def sensor_schema( accuracy_decimals: int = _UNDEF, device_class: str = _UNDEF, state_class: str = _UNDEF, + entity_category: str = _UNDEF, ) -> cv.Schema: schema = SENSOR_SCHEMA if unit_of_measurement is not _UNDEF: @@ -258,6 +259,14 @@ def sensor_schema( schema = schema.extend( {cv.Optional(CONF_STATE_CLASS, default=state_class): validate_state_class} ) + if entity_category is not _UNDEF: + schema = schema.extend( + { + cv.Optional( + CONF_ENTITY_CATEGORY, default=entity_category + ): cv.entity_category + } + ) return schema diff --git a/esphome/components/shutdown/switch.py b/esphome/components/shutdown/switch.py index 30c2bc2b74..49970b4c2f 100644 --- a/esphome/components/shutdown/switch.py +++ b/esphome/components/shutdown/switch.py @@ -1,7 +1,14 @@ 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 esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ID, + CONF_INVERTED, + CONF_ICON, + ENTITY_CATEGORY_CONFIG, + ICON_POWER, +) shutdown_ns = cg.esphome_ns.namespace("shutdown") ShutdownSwitch = shutdown_ns.class_("ShutdownSwitch", switch.Switch, cg.Component) @@ -13,6 +20,9 @@ CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( "Shutdown switches do not support inverted mode!" ), cv.Optional(CONF_ICON, default=ICON_POWER): switch.icon, + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG + ): cv.entity_category, } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/status/binary_sensor.py b/esphome/components/status/binary_sensor.py index e462bc5385..9367706388 100644 --- a/esphome/components/status/binary_sensor.py +++ b/esphome/components/status/binary_sensor.py @@ -1,7 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_ID, CONF_DEVICE_CLASS, DEVICE_CLASS_CONNECTIVITY +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ID, + CONF_DEVICE_CLASS, + DEVICE_CLASS_CONNECTIVITY, + ENTITY_CATEGORY_DIAGNOSTIC, +) status_ns = cg.esphome_ns.namespace("status") StatusBinarySensor = status_ns.class_( @@ -14,6 +20,9 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( cv.Optional( CONF_DEVICE_CLASS, default=DEVICE_CLASS_CONNECTIVITY ): binary_sensor.device_class, + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index 88341e0add..08cbccbe35 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -36,6 +36,7 @@ SwitchTurnOffTrigger = switch_ns.class_( icon = cv.icon + SWITCH_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSwitchComponent), diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index 5c739e1d0a..a070e6f86d 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -108,6 +108,7 @@ async def substitute_filter_to_code(config, filter_id): icon = cv.icon + TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor), diff --git a/esphome/components/uptime/sensor.py b/esphome/components/uptime/sensor.py index 7989f3befc..16a1e4c125 100644 --- a/esphome/components/uptime/sensor.py +++ b/esphome/components/uptime/sensor.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( CONF_ID, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_TOTAL_INCREASING, UNIT_SECOND, ICON_TIMER, @@ -17,6 +18,7 @@ CONFIG_SCHEMA = ( icon=ICON_TIMER, accuracy_decimals=0, state_class=STATE_CLASS_TOTAL_INCREASING, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ) .extend( { diff --git a/esphome/components/version/text_sensor.py b/esphome/components/version/text_sensor.py index e67f881d32..4835caf35b 100644 --- a/esphome/components/version/text_sensor.py +++ b/esphome/components/version/text_sensor.py @@ -1,7 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor -from esphome.const import CONF_ID, CONF_ICON, ICON_NEW_BOX, CONF_HIDE_TIMESTAMP +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ID, + CONF_ICON, + ENTITY_CATEGORY_DIAGNOSTIC, + ICON_NEW_BOX, + CONF_HIDE_TIMESTAMP, +) version_ns = cg.esphome_ns.namespace("version") VersionTextSensor = version_ns.class_( @@ -13,6 +20,9 @@ CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( cv.GenerateID(): cv.declare_id(VersionTextSensor), cv.Optional(CONF_ICON, default=ICON_NEW_BOX): text_sensor.icon, cv.Optional(CONF_HIDE_TIMESTAMP, default=False): cv.boolean, + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index 1922502204..706a8967be 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -3,11 +3,13 @@ import esphome.config_validation as cv from esphome.components import text_sensor from esphome.const import ( CONF_BSSID, + CONF_ENTITY_CATEGORY, CONF_ID, CONF_IP_ADDRESS, CONF_SCAN_RESULTS, CONF_SSID, CONF_MAC_ADDRESS, + ENTITY_CATEGORY_DIAGNOSTIC, ) DEPENDENCIES = ["wifi"] @@ -32,26 +34,41 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_IP_ADDRESS): text_sensor.TEXT_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(IPAddressWiFiInfo), + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ), cv.Optional(CONF_SCAN_RESULTS): text_sensor.TEXT_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(ScanResultsWiFiInfo), + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ).extend(cv.polling_component_schema("60s")), cv.Optional(CONF_SSID): text_sensor.TEXT_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(SSIDWiFiInfo), + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ), cv.Optional(CONF_BSSID): text_sensor.TEXT_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(BSSIDWiFiInfo), + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ), cv.Optional(CONF_MAC_ADDRESS): text_sensor.TEXT_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(MacAddressWifiInfo), + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ), } diff --git a/esphome/components/wifi_signal/sensor.py b/esphome/components/wifi_signal/sensor.py index 37bee75928..2097c21bd7 100644 --- a/esphome/components/wifi_signal/sensor.py +++ b/esphome/components/wifi_signal/sensor.py @@ -4,6 +4,7 @@ from esphome.components import sensor from esphome.const import ( CONF_ID, DEVICE_CLASS_SIGNAL_STRENGTH, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_DECIBEL_MILLIWATT, ) @@ -20,6 +21,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_SIGNAL_STRENGTH, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ) .extend( { diff --git a/esphome/components/xiaomi_cgd1/sensor.py b/esphome/components/xiaomi_cgd1/sensor.py index 774c87fee9..5b88121d7c 100644 --- a/esphome/components/xiaomi_cgd1/sensor.py +++ b/esphome/components/xiaomi_cgd1/sensor.py @@ -10,6 +10,7 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -47,6 +48,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_cgdk2/sensor.py b/esphome/components/xiaomi_cgdk2/sensor.py index d4e7230fd0..ac487d87fc 100644 --- a/esphome/components/xiaomi_cgdk2/sensor.py +++ b/esphome/components/xiaomi_cgdk2/sensor.py @@ -9,6 +9,7 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -47,6 +48,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_cgg1/sensor.py b/esphome/components/xiaomi_cgg1/sensor.py index 4e606d95f8..a4f9a39aff 100644 --- a/esphome/components/xiaomi_cgg1/sensor.py +++ b/esphome/components/xiaomi_cgg1/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -47,6 +48,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_cgpr1/binary_sensor.py b/esphome/components/xiaomi_cgpr1/binary_sensor.py index a7f6c41225..7f0aac873d 100644 --- a/esphome/components/xiaomi_cgpr1/binary_sensor.py +++ b/esphome/components/xiaomi_cgpr1/binary_sensor.py @@ -10,6 +10,8 @@ from esphome.const import ( DEVICE_CLASS_EMPTY, DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_MOTION, + ENTITY_CATEGORY_DIAGNOSTIC, ICON_EMPTY, UNIT_PERCENT, CONF_IDLE_TIME, @@ -37,13 +39,21 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_BINDKEY): cv.bind_key, cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional( - CONF_DEVICE_CLASS, default="motion" + CONF_DEVICE_CLASS, + default=DEVICE_CLASS_MOTION, ): binary_sensor.device_class, cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_IDLE_TIME): sensor.sensor_schema( - UNIT_MINUTE, ICON_TIMELAPSE, 0, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_MINUTE, + icon=ICON_TIMELAPSE, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( UNIT_LUX, ICON_EMPTY, 0, DEVICE_CLASS_ILLUMINANCE diff --git a/esphome/components/xiaomi_hhccjcy01/sensor.py b/esphome/components/xiaomi_hhccjcy01/sensor.py index 1818731a0f..535316e246 100644 --- a/esphome/components/xiaomi_hhccjcy01/sensor.py +++ b/esphome/components/xiaomi_hhccjcy01/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_TEMPERATURE, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, ICON_WATER_PERCENT, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, @@ -63,6 +64,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_jqjcy01ym/sensor.py b/esphome/components/xiaomi_jqjcy01ym/sensor.py index 40991c3d0f..f4d2b342fd 100644 --- a/esphome/components/xiaomi_jqjcy01ym/sensor.py +++ b/esphome/components/xiaomi_jqjcy01ym/sensor.py @@ -9,6 +9,7 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -54,6 +55,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_lywsd02/sensor.py b/esphome/components/xiaomi_lywsd02/sensor.py index 339c5e673a..20629a0a9c 100644 --- a/esphome/components/xiaomi_lywsd02/sensor.py +++ b/esphome/components/xiaomi_lywsd02/sensor.py @@ -7,6 +7,7 @@ from esphome.const import ( CONF_MAC_ADDRESS, CONF_TEMPERATURE, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -45,6 +46,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_lywsd03mmc/sensor.py b/esphome/components/xiaomi_lywsd03mmc/sensor.py index f27cee3800..b2784e58fc 100644 --- a/esphome/components/xiaomi_lywsd03mmc/sensor.py +++ b/esphome/components/xiaomi_lywsd03mmc/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -49,6 +50,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_lywsdcgq/sensor.py b/esphome/components/xiaomi_lywsdcgq/sensor.py index 39a207327e..80f24ac0ef 100644 --- a/esphome/components/xiaomi_lywsdcgq/sensor.py +++ b/esphome/components/xiaomi_lywsdcgq/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -45,6 +46,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_mhoc401/sensor.py b/esphome/components/xiaomi_mhoc401/sensor.py index 57b2190150..9e92e34230 100644 --- a/esphome/components/xiaomi_mhoc401/sensor.py +++ b/esphome/components/xiaomi_mhoc401/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -48,6 +49,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py index fd4bae60c1..1bedae26cf 100644 --- a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py +++ b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_BATTERY_LEVEL, DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, UNIT_PERCENT, @@ -51,6 +52,7 @@ CONFIG_SCHEMA = cv.All( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( unit_of_measurement=UNIT_LUX, diff --git a/esphome/components/xiaomi_wx08zm/binary_sensor.py b/esphome/components/xiaomi_wx08zm/binary_sensor.py index d2b353beff..8667794923 100644 --- a/esphome/components/xiaomi_wx08zm/binary_sensor.py +++ b/esphome/components/xiaomi_wx08zm/binary_sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_MAC_ADDRESS, CONF_TABLET, DEVICE_CLASS_BATTERY, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_PERCENT, ICON_BUG, @@ -40,6 +41,7 @@ CONFIG_SCHEMA = cv.All( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index fcd014f62d..3e5c7940a4 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -12,12 +12,14 @@ from string import ascii_letters, digits import voluptuous as vol from esphome import core +import esphome.codegen as cg from esphome.const import ( ALLOWED_NAME_CHARS, CONF_AVAILABILITY, CONF_COMMAND_TOPIC, CONF_DISABLED_BY_DEFAULT, CONF_DISCOVERY, + CONF_ENTITY_CATEGORY, CONF_ICON, CONF_ID, CONF_INTERNAL, @@ -35,6 +37,9 @@ from esphome.const import ( CONF_UPDATE_INTERVAL, CONF_TYPE_ID, CONF_TYPE, + ENTITY_CATEGORY_CONFIG, + ENTITY_CATEGORY_DIAGNOSTIC, + ENTITY_CATEGORY_NONE, KEY_CORE, KEY_FRAMEWORK_VERSION, KEY_TARGET_FRAMEWORK, @@ -1551,6 +1556,17 @@ def maybe_simple_value(*validators, **kwargs): return validate +_ENTITY_CATEGORIES = { + ENTITY_CATEGORY_NONE: cg.EntityCategory.ENTITY_CATEGORY_NONE, + ENTITY_CATEGORY_CONFIG: cg.EntityCategory.ENTITY_CATEGORY_CONFIG, + ENTITY_CATEGORY_DIAGNOSTIC: cg.EntityCategory.ENTITY_CATEGORY_DIAGNOSTIC, +} + + +def entity_category(value): + return enum(_ENTITY_CATEGORIES, lower=True)(value) + + MQTT_COMPONENT_AVAILABILITY_SCHEMA = Schema( { Required(CONF_TOPIC): subscribe_topic, @@ -1582,6 +1598,7 @@ ENTITY_BASE_SCHEMA = Schema( Optional(CONF_INTERNAL): boolean, Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean, Optional(CONF_ICON): icon, + Optional(CONF_ENTITY_CATEGORY): entity_category, } ) diff --git a/esphome/const.py b/esphome/const.py index eb7d56d7e1..ff8510b40e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -203,6 +203,7 @@ CONF_ELSE = "else" CONF_ENABLE_PIN = "enable_pin" CONF_ENABLE_TIME = "enable_time" CONF_ENERGY = "energy" +CONF_ENTITY_CATEGORY = "entity_category" CONF_ENTITY_ID = "entity_id" CONF_ESP8266_DISABLE_SSL_SUPPORT = "esp8266_disable_ssl_support" CONF_ESPHOME = "esphome" @@ -919,3 +920,12 @@ KEY_CORE = "core" KEY_TARGET_PLATFORM = "target_platform" KEY_TARGET_FRAMEWORK = "target_framework" KEY_FRAMEWORK_VERSION = "framework_version" + +# Entity categories +ENTITY_CATEGORY_NONE = "" + +# The entity category for configuration values/controls +ENTITY_CATEGORY_CONFIG = "config" + +# The entity category for read only diagnostic values, for example RSSI, uptime or MAC Address +ENTITY_CATEGORY_DIAGNOSTIC = "diagnostic" diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index bc94da85fe..41f08b28a6 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -26,6 +26,10 @@ void EntityBase::set_disabled_by_default(bool disabled_by_default) { this->disab const std::string &EntityBase::get_icon() const { return this->icon_; } void EntityBase::set_icon(const std::string &name) { this->icon_ = name; } +// Entity Category +EntityCategory EntityBase::get_entity_category() const { return this->entity_category_; } +void EntityBase::set_entity_category(EntityCategory entity_category) { this->entity_category_ = entity_category; } + // Entity Object ID const std::string &EntityBase::get_object_id() { return this->object_id_; } diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 263747b721..c489d71910 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -5,6 +5,12 @@ namespace esphome { +enum EntityCategory : uint8_t { + ENTITY_CATEGORY_NONE = 0, + ENTITY_CATEGORY_CONFIG = 1, + ENTITY_CATEGORY_DIAGNOSTIC = 2, +}; + // The generic Entity base class that provides an interface common to all Entities. class EntityBase { public: @@ -31,6 +37,10 @@ class EntityBase { bool is_disabled_by_default() const; void set_disabled_by_default(bool disabled_by_default); + // Get/set the entity category. + EntityCategory get_entity_category() const; + void set_entity_category(EntityCategory entity_category); + // Get/set this entity's icon const std::string &get_icon() const; void set_icon(const std::string &name); @@ -45,6 +55,7 @@ class EntityBase { uint32_t object_id_hash_; bool internal_{false}; bool disabled_by_default_{false}; + EntityCategory entity_category_{ENTITY_CATEGORY_NONE}; }; } // namespace esphome diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 5b081698ad..9127f88e39 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -2,6 +2,7 @@ import logging from esphome.const import ( CONF_DISABLED_BY_DEFAULT, + CONF_ENTITY_CATEGORY, CONF_ICON, CONF_INTERNAL, CONF_NAME, @@ -102,6 +103,8 @@ async def setup_entity(var, config): add(var.set_internal(config[CONF_INTERNAL])) if CONF_ICON in config: add(var.set_icon(config[CONF_ICON])) + if CONF_ENTITY_CATEGORY in config: + add(var.set_entity_category(config[CONF_ENTITY_CATEGORY])) def extract_registry_entry_config(registry, full_config): diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 888c319024..13d088e1cb 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -34,3 +34,4 @@ 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) +EntityCategory = esphome_ns.enum("EntityCategory") From 96a50f5c6b3113ee774875929a2552c2bc35baeb Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sun, 7 Nov 2021 19:31:41 +0100 Subject: [PATCH 288/549] Add SPI lib for ESP8266 and only add lib for ESP32 when using Arduino (#2677) * Add SPI lib for ESP8266 and only add lib for ESP32 when using Arduino * Make inclusion of the SPI library unconditional As suggested by @Oxan. Because the component requires Arduino anyway, there is no need to make the inclusion conditional. Co-authored-by: Oxan van Leeuwen * Fix Python lint issue Co-authored-by: Maurice Makaay Co-authored-by: Oxan van Leeuwen --- esphome/components/bme680_bsec/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/esphome/components/bme680_bsec/__init__.py b/esphome/components/bme680_bsec/__init__.py index 2f844fa666..83e519f8aa 100644 --- a/esphome/components/bme680_bsec/__init__.py +++ b/esphome/components/bme680_bsec/__init__.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c from esphome.const import CONF_ID -from esphome.core import CORE CODEOWNERS = ["@trvrnrth"] DEPENDENCIES = ["i2c"] @@ -62,9 +61,8 @@ async def to_code(config): var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds) ) - if CORE.is_esp32: - # Although this component does not use SPI, the BSEC library requires the SPI library - cg.add_library("SPI", None) + # Although this component does not use SPI, the BSEC library requires the SPI library + cg.add_library("SPI", None) cg.add_define("USE_BSEC") cg.add_library("boschsensortec/BSEC Software Library", "1.6.1480") From be9439f10d5468c0c864722a4e66047aea5dee20 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Mon, 8 Nov 2021 00:39:16 +0100 Subject: [PATCH 289/549] Fix for encrypted DSMR regression (#2679) Co-authored-by: Maurice Makaay --- esphome/components/dsmr/dsmr.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 031fb275f5..ea852e626e 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -76,9 +76,10 @@ void Dsmr::receive_telegram_() { } // Check for the end of the hex checksum, i.e. a newline. if (footer_found_ && c == '\n') { - header_found_ = false; // Parse the telegram and publish sensor values. parse_telegram(); + + header_found_ = false; return; } } @@ -105,11 +106,11 @@ void Dsmr::receive_encrypted_() { // Find a new telegram start byte. if (!header_found_) { - if ((uint8_t) c == 0xDB) { - ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); - header_found_ = true; + if ((uint8_t) c != 0xDB) { + continue; } - continue; + ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); + header_found_ = true; } // Check for buffer overflow. @@ -147,10 +148,10 @@ void Dsmr::receive_encrypted_() { ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_); ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); + parse_telegram(); + header_found_ = false; telegram_len_ = 0; - - parse_telegram(); return; } } From a17a6d53465b12b7e86e3262cc92c10d25f80b2d Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Mon, 8 Nov 2021 22:03:30 +1300 Subject: [PATCH 290/549] Add HA Entity Category support to MQTT (#2678) --- esphome/components/mqtt/mqtt_component.cpp | 11 +++++++++++ esphome/components/mqtt/mqtt_const.h | 3 +++ 2 files changed, 14 insertions(+) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index be1018d97d..cebb8dd086 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -77,6 +77,17 @@ bool MQTTComponent::send_discovery_() { if (!this->get_icon().empty()) root[MQTT_ICON] = this->get_icon(); + switch (this->get_entity()->get_entity_category()) { + case ENTITY_CATEGORY_NONE: + break; + case ENTITY_CATEGORY_CONFIG: + root[MQTT_ENTITY_CATEGORY] = "config"; + break; + case ENTITY_CATEGORY_DIAGNOSTIC: + root[MQTT_ENTITY_CATEGORY] = "diagnostic"; + break; + } + if (config.state_topic) root[MQTT_STATE_TOPIC] = this->get_state_topic_(); if (config.command_topic) diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h index df5465ce9a..1d5e22efde 100644 --- a/esphome/components/mqtt/mqtt_const.h +++ b/esphome/components/mqtt/mqtt_const.h @@ -512,6 +512,9 @@ constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw_version"; constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area"; #endif +// Additional MQTT fields where no abbreviation is defined in HA source +constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category"; + } // namespace mqtt } // namespace esphome From add484a2ea06ba2bf2091306d3a53bd8967214bc Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 8 Nov 2021 19:29:28 +0100 Subject: [PATCH 291/549] Fix gpio validation for esp32 variants (#2609) Co-authored-by: Otto Winter Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/esp32/gpio.py | 96 +++++++++++++---------- esphome/components/esp32/gpio_esp32.py | 77 ++++++++++++++++++ esphome/components/esp32/gpio_esp32_c3.py | 53 +++++++++++++ esphome/components/esp32/gpio_esp32_h2.py | 11 +++ esphome/components/esp32/gpio_esp32_s2.py | 80 +++++++++++++++++++ esphome/components/esp32/gpio_esp32_s3.py | 74 +++++++++++++++++ 6 files changed, 348 insertions(+), 43 deletions(-) create mode 100644 esphome/components/esp32/gpio_esp32.py create mode 100644 esphome/components/esp32/gpio_esp32_c3.py create mode 100644 esphome/components/esp32/gpio_esp32_h2.py create mode 100644 esphome/components/esp32/gpio_esp32_s2.py create mode 100644 esphome/components/esp32/gpio_esp32_s3.py diff --git a/esphome/components/esp32/gpio.py b/esphome/components/esp32/gpio.py index 93ab17db22..5819943f37 100644 --- a/esphome/components/esp32/gpio.py +++ b/esphome/components/esp32/gpio.py @@ -1,4 +1,5 @@ -import logging +from dataclasses import dataclass +from typing import Any from esphome.const import ( CONF_ID, @@ -17,10 +18,24 @@ import esphome.config_validation as cv import esphome.codegen as cg from . import boards -from .const import KEY_BOARD, KEY_ESP32, esp32_ns +from .const import ( + KEY_BOARD, + KEY_ESP32, + KEY_VARIANT, + VARIANT_ESP32, + VARIANT_ESP32C3, + VARIANT_ESP32S2, + VARIANT_ESP32S3, + VARIANT_ESP32H2, + esp32_ns, +) -_LOGGER = logging.getLogger(__name__) +from .gpio_esp32 import esp32_validate_gpio_pin, esp32_validate_supports +from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports +from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports +from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_supports +from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports IDFInternalGPIOPin = esp32_ns.class_("IDFInternalGPIOPin", cg.InternalGPIOPin) @@ -59,65 +74,61 @@ def _translate_pin(value): return _lookup_pin(value) -_ESP_SDIO_PINS = { - 6: "Flash Clock", - 7: "Flash Data 0", - 8: "Flash Data 1", - 11: "Flash Command", +@dataclass +class ESP32ValidationFunctions: + pin_validation: Any + usage_validation: Any + + +_esp32_validations = { + VARIANT_ESP32: ESP32ValidationFunctions( + pin_validation=esp32_validate_gpio_pin, usage_validation=esp32_validate_supports + ), + VARIANT_ESP32S2: ESP32ValidationFunctions( + pin_validation=esp32_s2_validate_gpio_pin, + usage_validation=esp32_s2_validate_supports, + ), + VARIANT_ESP32C3: ESP32ValidationFunctions( + pin_validation=esp32_c3_validate_gpio_pin, + usage_validation=esp32_c3_validate_supports, + ), + VARIANT_ESP32S3: ESP32ValidationFunctions( + pin_validation=esp32_s3_validate_gpio_pin, + usage_validation=esp32_s3_validate_supports, + ), + VARIANT_ESP32H2: ESP32ValidationFunctions( + pin_validation=esp32_h2_validate_gpio_pin, + usage_validation=esp32_h2_validate_supports, + ), } 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 + variant = CORE.data[KEY_ESP32][KEY_VARIANT] + if variant not in _esp32_validations: + raise cv.Invalid("Unsupported ESP32 variant {variant}") + + return _esp32_validations[variant].pin_validation(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] + variant = CORE.data[KEY_ESP32][KEY_VARIANT] + if variant not in _esp32_validations: + raise cv.Invalid("Unsupported ESP32 variant {variant}") - 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] - ) + value = _esp32_validations[variant].usage_validation(value) if CORE.using_arduino: # (input, output, open_drain, pullup, pulldown) supported_modes = { @@ -138,7 +149,6 @@ def validate_supports(value): "This pin mode is not supported on ESP32 for arduino frameworks", [CONF_MODE], ) - return value diff --git a/esphome/components/esp32/gpio_esp32.py b/esphome/components/esp32/gpio_esp32.py new file mode 100644 index 0000000000..425d77b343 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32.py @@ -0,0 +1,77 @@ +import logging + +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, + CONF_OUTPUT, + CONF_PULLDOWN, + CONF_PULLUP, +) +import esphome.config_validation as cv + + +_ESP_SDIO_PINS = { + 6: "Flash Clock", + 7: "Flash Data 0", + 8: "Flash Data 1", + 11: "Flash Command", +} + +_ESP32_STRAPPING_PINS = {0, 2, 4, 15} +_LOGGER = logging.getLogger(__name__) + + +def esp32_validate_gpio_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 _ESP32_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + 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 esp32_validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + is_output = mode[CONF_OUTPUT] + 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_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] + ) + + return value diff --git a/esphome/components/esp32/gpio_esp32_c3.py b/esphome/components/esp32/gpio_esp32_c3.py new file mode 100644 index 0000000000..fc1cef29e5 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_c3.py @@ -0,0 +1,53 @@ +import logging + +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, +) +import esphome.config_validation as cv + +_ESP32C3_SPI_PSRAM_PINS = { + 12: "SPIHD", + 13: "SPIWP", + 14: "SPICS0", + 15: "SPICLK", + 16: "SPID", + 17: "SPIQ", +} + +_ESP32C3_STRAPPING_PINS = {2, 8, 9} + +_LOGGER = logging.getLogger(__name__) + + +def esp32_c3_validate_gpio_pin(value): + if value < 0 or value > 21: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)") + if value in _ESP32C3_SPI_PSRAM_PINS: + raise cv.Invalid( + f"This pin cannot be used on ESP32-C3s and is already used by the SPI/PSRAM interface (function: {_ESP32C3_SPI_PSRAM_PINS[value]})" + ) + if value in _ESP32C3_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + value, + ) + + return value + + +def esp32_c3_validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + + if num < 0 or num > 21: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)") + + if is_input: + # All ESP32 pins support input mode + pass + return value diff --git a/esphome/components/esp32/gpio_esp32_h2.py b/esphome/components/esp32/gpio_esp32_h2.py new file mode 100644 index 0000000000..5196ef0c09 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_h2.py @@ -0,0 +1,11 @@ +import esphome.config_validation as cv + + +def esp32_h2_validate_gpio_pin(value): + # ESP32-H2 not yet supported + raise cv.Invalid("ESP32-H2 isn't supported yet") + + +def esp32_h2_validate_supports(value): + # ESP32-H2 not yet supported + raise cv.Invalid("ESP32-H2 isn't supported yet") diff --git a/esphome/components/esp32/gpio_esp32_s2.py b/esphome/components/esp32/gpio_esp32_s2.py new file mode 100644 index 0000000000..db244b6259 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_s2.py @@ -0,0 +1,80 @@ +import logging + +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, + CONF_OUTPUT, + CONF_PULLDOWN, + CONF_PULLUP, +) + +import esphome.config_validation as cv + +_ESP32S2_SPI_PSRAM_PINS = { + 26: "SPICS1", + 27: "SPIHD", + 28: "SPIWP", + 29: "SPICS0", + 30: "SPICLK", + 31: "SPIQ", + 32: "SPID", +} + +_ESP32S2_STRAPPING_PINS = {0, 45, 46} + +_LOGGER = logging.getLogger(__name__) + + +def esp32_s2_validate_gpio_pin(value): + if value < 0 or value > 46: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)") + + if value in _ESP32S2_SPI_PSRAM_PINS: + raise cv.Invalid( + f"This pin cannot be used on ESP32-S2s and is already used by the SPI/PSRAM interface (function: {_ESP32S2_SPI_PSRAM_PINS[value]})" + ) + if value in _ESP32S2_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + value, + ) + + if value in (22, 23, 24, 25): + # 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 ESP32-S2s.") + + return value + + +def esp32_s2_validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + is_output = mode[CONF_OUTPUT] + is_pullup = mode[CONF_PULLUP] + is_pulldown = mode[CONF_PULLDOWN] + + if num < 0 or num > 46: + raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)") + if is_input: + # All ESP32 pins support input mode + pass + if is_output and num == 46: + raise cv.Invalid( + f"GPIO{num} does not support output pin mode.", + [CONF_MODE, CONF_OUTPUT], + ) + if is_pullup and num == 46: + raise cv.Invalid( + f"GPIO{num} does not support pullups.", [CONF_MODE, CONF_PULLUP] + ) + if is_pulldown and num == 46: + raise cv.Invalid( + f"GPIO{num} does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN] + ) + + return value diff --git a/esphome/components/esp32/gpio_esp32_s3.py b/esphome/components/esp32/gpio_esp32_s3.py new file mode 100644 index 0000000000..f729a757c2 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_s3.py @@ -0,0 +1,74 @@ +import logging + +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, +) + +import esphome.config_validation as cv + +_ESP_32S3_SPI_PSRAM_PINS = { + 26: "SPICS1", + 27: "SPIHD", + 28: "SPIWP", + 29: "SPICS0", + 30: "SPICLK", + 31: "SPIQ", + 32: "SPID", +} + +_ESP_32_ESP32_S3R8_PSRAM_PINS = { + 33: "SPIIO4", + 34: "SPIIO5", + 35: "SPIIO6", + 36: "SPIIO7", + 37: "SPIDQS", +} + +_ESP_32S3_STRAPPING_PINS = {0, 3, 45, 46} + +_LOGGER = logging.getLogger(__name__) + + +def esp32_s3_validate_gpio_pin(value): + if value < 0 or value > 48: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)") + + if value in _ESP_32S3_SPI_PSRAM_PINS: + raise cv.Invalid( + f"This pin cannot be used on ESP32-S3s and is already used by the SPI/PSRAM interface(function: {_ESP_32S3_SPI_PSRAM_PINS[value]})" + ) + if value in _ESP_32_ESP32_S3R8_PSRAM_PINS: + _LOGGER.warning( + "GPIO%d is used by the PSRAM interface on ESP32-S3R8 / ESP32-S3R8V and should be avoided on these models", + value, + ) + + if value in _ESP_32S3_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + value, + ) + + if value in (22, 23, 24, 25): + # 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 ESP32-S3s.") + + return value + + +def esp32_s3_validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + + if num < 0 or num > 48: + raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)") + if is_input: + # All ESP32 pins support input mode + pass + return value From a509f6ccd29b84c2fb894c9d72f00553fbbc7ce5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 9 Nov 2021 17:14:08 +1300 Subject: [PATCH 292/549] Fix when package url has no branch/ref (#2683) --- esphome/git.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/git.py b/esphome/git.py index b64aa6a864..25d893b2f5 100644 --- a/esphome/git.py +++ b/esphome/git.py @@ -40,7 +40,7 @@ def clone_or_update( ) -> Path: key = f"{url}@{ref}" repo_dir = _compute_destination_path(key, domain) - fetch_pr_branch = ref.startswith("pull/") + fetch_pr_branch = ref is not None and ref.startswith("pull/") if not repo_dir.is_dir(): _LOGGER.info("Cloning %s", key) _LOGGER.debug("Location: %s", repo_dir) From f72389147d79f282f577b7f0884905c6e1d06296 Mon Sep 17 00:00:00 2001 From: ychieux Date: Tue, 9 Nov 2021 20:47:19 +0300 Subject: [PATCH 293/549] SSD1306_base: Add support for 64x32 size and fix flip functions (#2682) * Add support for SSD1306 OLED display 0.42inch 64x32 and fix a typo in __init__.py preventing flip functions to operate as intended * convert tab to spaces * fix typo on filename for __init__.py --- esphome/components/ssd1306_base/__init__.py | 3 ++- esphome/components/ssd1306_base/ssd1306_base.cpp | 6 ++++++ esphome/components/ssd1306_base/ssd1306_base.h | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/components/ssd1306_base/__init__.py b/esphome/components/ssd1306_base/__init__.py index bc2e558f1b..e4f62e5ff9 100644 --- a/esphome/components/ssd1306_base/__init__.py +++ b/esphome/components/ssd1306_base/__init__.py @@ -26,6 +26,7 @@ MODELS = { "SSD1306_128X64": SSD1306Model.SSD1306_MODEL_128_64, "SSD1306_96X16": SSD1306Model.SSD1306_MODEL_96_16, "SSD1306_64X48": SSD1306Model.SSD1306_MODEL_64_48, + "SSD1306_64X32": SSD1306Model.SSD1306_MODEL_64_32, "SH1106_128X32": SSD1306Model.SH1106_MODEL_128_32, "SH1106_128X64": SSD1306Model.SH1106_MODEL_128_64, "SH1106_96X16": SSD1306Model.SH1106_MODEL_96_16, @@ -84,7 +85,7 @@ async def setup_ssd1306(var, config): if CONF_FLIP_X in config: cg.add(var.init_flip_x(config[CONF_FLIP_X])) if CONF_FLIP_Y in config: - cg.add(var.init_flip_y(config[CONF_FLIP_X])) + cg.add(var.init_flip_y(config[CONF_FLIP_Y])) if CONF_OFFSET_X in config: cg.add(var.init_offset_x(config[CONF_OFFSET_X])) if CONF_OFFSET_Y in config: diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index b1a2538ebd..2537133605 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -94,6 +94,7 @@ void SSD1306::setup() { case SSD1306_MODEL_128_64: case SH1106_MODEL_128_64: case SSD1306_MODEL_64_48: + case SSD1306_MODEL_64_32: case SH1106_MODEL_64_48: case SSD1305_MODEL_128_32: case SSD1305_MODEL_128_64: @@ -141,6 +142,7 @@ void SSD1306::display() { this->command(SSD1306_COMMAND_COLUMN_ADDRESS); switch (this->model_) { case SSD1306_MODEL_64_48: + case SSD1306_MODEL_64_32: this->command(0x20 + this->offset_x_); this->command(0x20 + this->offset_x_ + this->get_width_internal() - 1); break; @@ -197,6 +199,7 @@ void SSD1306::turn_off() { int SSD1306::get_height_internal() { switch (this->model_) { case SSD1306_MODEL_128_32: + case SSD1306_MODEL_64_32: case SH1106_MODEL_128_32: case SSD1305_MODEL_128_32: return 32; @@ -227,6 +230,7 @@ int SSD1306::get_width_internal() { case SH1106_MODEL_96_16: return 96; case SSD1306_MODEL_64_48: + case SSD1306_MODEL_64_32: case SH1106_MODEL_64_48: return 64; default: @@ -271,6 +275,8 @@ const char *SSD1306::model_str_() { return "SSD1306 128x32"; case SSD1306_MODEL_128_64: return "SSD1306 128x64"; + case SSD1306_MODEL_64_32: + return "SSD1306 64x32"; case SSD1306_MODEL_96_16: return "SSD1306 96x16"; case SSD1306_MODEL_64_48: diff --git a/esphome/components/ssd1306_base/ssd1306_base.h b/esphome/components/ssd1306_base/ssd1306_base.h index 09417a2c10..c77b1985e4 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.h +++ b/esphome/components/ssd1306_base/ssd1306_base.h @@ -12,6 +12,7 @@ enum SSD1306Model { SSD1306_MODEL_128_64, SSD1306_MODEL_96_16, SSD1306_MODEL_64_48, + SSD1306_MODEL_64_32, SH1106_MODEL_128_32, SH1106_MODEL_128_64, SH1106_MODEL_96_16, From d6717c0032a2370364141bca66aae1011714f39a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 10 Nov 2021 08:38:20 +1300 Subject: [PATCH 294/549] Fix dashboard imports for adoption (#2684) --- .../components/dashboard_import/__init__.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/esphome/components/dashboard_import/__init__.py b/esphome/components/dashboard_import/__init__.py index 2b884d3b9a..d483c77c61 100644 --- a/esphome/components/dashboard_import/__init__.py +++ b/esphome/components/dashboard_import/__init__.py @@ -29,6 +29,14 @@ CONFIG_SCHEMA = cv.Schema( } ) +WIFI_MESSAGE = """ + +# Do not forget to add your own wifi configuration before installing this configuration +# wifi: +# ssid: !secret wifi_ssid +# password: !secret wifi_password +""" + async def to_code(config): cg.add_define("USE_DASHBOARD_IMPORT") @@ -41,5 +49,12 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N if p.exists(): raise FileExistsError - config = {"substitutions": {"name": name}, "packages": {project_name: import_url}} - p.write_text(dump(config), encoding="utf8") + config = { + "substitutions": {"name": name}, + "packages": {project_name: import_url}, + "esphome": {"name_add_mac_suffix": False}, + } + p.write_text( + dump(config) + WIFI_MESSAGE, + encoding="utf8", + ) From fb57ab0adde56b24a056c925bec3205278472b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Wed, 10 Nov 2021 01:10:07 +0100 Subject: [PATCH 295/549] Add `esp32_camera_web_server:` to expose mjpg/jpg images (#2237) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/api/api_server.cpp | 2 +- esphome/components/esp32_camera/__init__.py | 2 +- .../components/esp32_camera/esp32_camera.cpp | 1 + .../esp32_camera_web_server/__init__.py | 28 ++ .../camera_web_server.cpp | 239 ++++++++++++++++++ .../camera_web_server.h | 51 ++++ tests/test4.yaml | 6 + 8 files changed, 328 insertions(+), 2 deletions(-) create mode 100644 esphome/components/esp32_camera_web_server/__init__.py create mode 100644 esphome/components/esp32_camera_web_server/camera_web_server.cpp create mode 100644 esphome/components/esp32_camera_web_server/camera_web_server.h diff --git a/CODEOWNERS b/CODEOWNERS index 664bf9ad6b..e535608db3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -52,6 +52,7 @@ esphome/components/dsmr/* @glmnet @zuidwijk esphome/components/esp32/* @esphome/core esphome/components/esp32_ble/* @jesserockz esphome/components/esp32_ble_server/* @jesserockz +esphome/components/esp32_camera_web_server/* @ayufan esphome/components/esp32_improv/* @jesserockz esphome/components/esp8266/* @esphome/core esphome/components/exposure_notifications/* @OttoWinter diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 4e2899d94f..25081a809a 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -77,7 +77,7 @@ void APIServer::setup() { this->last_connected_ = millis(); #ifdef USE_ESP32_CAMERA - if (esp32_camera::global_esp32_camera != nullptr) { + if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) { esp32_camera::global_esp32_camera->add_image_callback( [this](const std::shared_ptr &image) { for (auto &c : this->clients_) diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 1a8b3a37ec..2b1890267f 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -17,7 +17,7 @@ from esphome.core import CORE from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.cpp_helpers import setup_entity -DEPENDENCIES = ["esp32", "api"] +DEPENDENCIES = ["esp32"] esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase) diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index ad4304d89f..6f93532f47 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -45,6 +45,7 @@ void ESP32Camera::dump_config() { auto conf = this->config_; ESP_LOGCONFIG(TAG, "ESP32 Camera:"); ESP_LOGCONFIG(TAG, " Name: %s", this->name_.c_str()); + ESP_LOGCONFIG(TAG, " Internal: %s", YESNO(this->internal_)); #ifdef USE_ARDUINO ESP_LOGCONFIG(TAG, " Board Has PSRAM: %s", YESNO(psramFound())); #endif // USE_ARDUINO diff --git a/esphome/components/esp32_camera_web_server/__init__.py b/esphome/components/esp32_camera_web_server/__init__.py new file mode 100644 index 0000000000..d8afea27b4 --- /dev/null +++ b/esphome/components/esp32_camera_web_server/__init__.py @@ -0,0 +1,28 @@ +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_ID, CONF_PORT, CONF_MODE + +CODEOWNERS = ["@ayufan"] +DEPENDENCIES = ["esp32_camera"] +MULTI_CONF = True + +esp32_camera_web_server_ns = cg.esphome_ns.namespace("esp32_camera_web_server") +CameraWebServer = esp32_camera_web_server_ns.class_("CameraWebServer", cg.Component) +Mode = esp32_camera_web_server_ns.enum("Mode") + +MODES = {"STREAM": Mode.STREAM, "SNAPSHOT": Mode.SNAPSHOT} + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(CameraWebServer), + cv.Required(CONF_PORT): cv.port, + cv.Required(CONF_MODE): cv.enum(MODES, upper=True), + }, +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + server = cg.new_Pvariable(config[CONF_ID]) + cg.add(server.set_port(config[CONF_PORT])) + cg.add(server.set_mode(config[CONF_MODE])) + await cg.register_component(server, config) diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.cpp b/esphome/components/esp32_camera_web_server/camera_web_server.cpp new file mode 100644 index 0000000000..ecaef78b77 --- /dev/null +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp @@ -0,0 +1,239 @@ +#ifdef USE_ESP32 + +#include "camera_web_server.h" +#include "esphome/core/application.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/util.h" + +#include +#include +#include + +namespace esphome { +namespace esp32_camera_web_server { + +static const int IMAGE_REQUEST_TIMEOUT = 2000; +static const char *const TAG = "esp32_camera_web_server"; + +#define PART_BOUNDARY "123456789000000000000987654321" +#define CONTENT_TYPE "image/jpeg" +#define CONTENT_LENGTH "Content-Length" + +static const char *const STREAM_HEADER = + "HTTP/1.1 200\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY + "\r\n"; +static const char *const STREAM_500 = "HTTP/1.1 500\r\nContent-Type: text/plain\r\n\r\nNo frames send.\r\n"; +static const char *const STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; +static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n"; + +CameraWebServer::CameraWebServer() {} + +CameraWebServer::~CameraWebServer() {} + +void CameraWebServer::setup() { + if (!esp32_camera::global_esp32_camera || esp32_camera::global_esp32_camera->is_failed()) { + this->mark_failed(); + return; + } + + this->semaphore_ = xSemaphoreCreateBinary(); + + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.server_port = this->port_; + config.ctrl_port = this->port_; + config.max_open_sockets = 1; + config.backlog_conn = 2; + + if (httpd_start(&this->httpd_, &config) != ESP_OK) { + mark_failed(); + return; + } + + httpd_uri_t uri = { + .uri = "/", + .method = HTTP_GET, + .handler = [](struct httpd_req *req) { return ((CameraWebServer *) req->user_ctx)->handler_(req); }, + .user_ctx = this}; + + httpd_register_uri_handler(this->httpd_, &uri); + + esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr image) { + if (this->running_) { + this->image_ = std::move(image); + xSemaphoreGive(this->semaphore_); + } + }); +} + +void CameraWebServer::on_shutdown() { + this->running_ = false; + this->image_ = nullptr; + httpd_stop(this->httpd_); + this->httpd_ = nullptr; + vSemaphoreDelete(this->semaphore_); + this->semaphore_ = nullptr; +} + +void CameraWebServer::dump_config() { + ESP_LOGCONFIG(TAG, "ESP32 Camera Web Server:"); + ESP_LOGCONFIG(TAG, " Port: %d", this->port_); + if (this->mode_ == STREAM) + ESP_LOGCONFIG(TAG, " Mode: stream"); + else + ESP_LOGCONFIG(TAG, " Mode: snapshot"); + + if (this->is_failed()) { + ESP_LOGE(TAG, " Setup Failed"); + } +} + +float CameraWebServer::get_setup_priority() const { return setup_priority::LATE; } + +void CameraWebServer::loop() { + if (!this->running_) { + this->image_ = nullptr; + } +} + +std::shared_ptr CameraWebServer::wait_for_image_() { + std::shared_ptr image; + image.swap(this->image_); + + if (!image) { + // retry as we might still be fetching image + xSemaphoreTake(this->semaphore_, IMAGE_REQUEST_TIMEOUT / portTICK_PERIOD_MS); + image.swap(this->image_); + } + + return image; +} + +esp_err_t CameraWebServer::handler_(struct httpd_req *req) { + esp_err_t res = ESP_FAIL; + + this->image_ = nullptr; + this->running_ = true; + + switch (this->mode_) { + case STREAM: + res = this->streaming_handler_(req); + break; + + case SNAPSHOT: + res = this->snapshot_handler_(req); + break; + } + + this->running_ = false; + this->image_ = nullptr; + return res; +} + +static esp_err_t httpd_send_all(httpd_req_t *r, const char *buf, size_t buf_len) { + int ret; + + while (buf_len > 0) { + ret = httpd_send(r, buf, buf_len); + if (ret < 0) { + return ESP_FAIL; + } + buf += ret; + buf_len -= ret; + } + return ESP_OK; +} + +esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) { + esp_err_t res = ESP_OK; + char part_buf[64]; + + // This manually constructs HTTP response to avoid chunked encoding + // which is not supported by some clients + + res = httpd_send_all(req, STREAM_HEADER, strlen(STREAM_HEADER)); + if (res != ESP_OK) { + ESP_LOGW(TAG, "STREAM: failed to set HTTP header"); + return res; + } + + uint32_t last_frame = millis(); + uint32_t frames = 0; + + while (res == ESP_OK && this->running_) { + if (esp32_camera::global_esp32_camera != nullptr) { + esp32_camera::global_esp32_camera->request_stream(); + } + + auto image = this->wait_for_image_(); + + if (!image) { + ESP_LOGW(TAG, "STREAM: failed to acquire frame"); + res = ESP_FAIL; + } + if (res == ESP_OK) { + res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY)); + } + if (res == ESP_OK) { + size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length()); + res = httpd_send_all(req, part_buf, hlen); + } + if (res == ESP_OK) { + res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length()); + } + if (res == ESP_OK) { + frames++; + int64_t frame_time = millis() - last_frame; + last_frame = millis(); + + ESP_LOGD(TAG, "MJPG: %uB %ums (%.1ffps)", (uint32_t) image->get_data_length(), (uint32_t) frame_time, + 1000.0 / (uint32_t) frame_time); + } + } + + if (!frames) { + res = httpd_send_all(req, STREAM_500, strlen(STREAM_500)); + } + + ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames); + + return res; +} + +esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) { + esp_err_t res = ESP_OK; + + if (esp32_camera::global_esp32_camera != nullptr) { + esp32_camera::global_esp32_camera->request_image(); + } + + auto image = this->wait_for_image_(); + + if (!image) { + ESP_LOGW(TAG, "SNAPSHOT: failed to acquire frame"); + httpd_resp_send_500(req); + res = ESP_FAIL; + return res; + } + + res = httpd_resp_set_type(req, CONTENT_TYPE); + if (res != ESP_OK) { + ESP_LOGW(TAG, "SNAPSHOT: failed to set HTTP response type"); + return res; + } + + httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg"); + + if (res == ESP_OK) { + res = httpd_resp_set_hdr(req, CONTENT_LENGTH, esphome::to_string(image->get_data_length()).c_str()); + } + if (res == ESP_OK) { + res = httpd_resp_send(req, (const char *) image->get_data_buffer(), image->get_data_length()); + } + return res; +} + +} // namespace esp32_camera_web_server +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.h b/esphome/components/esp32_camera_web_server/camera_web_server.h new file mode 100644 index 0000000000..df30a43ed2 --- /dev/null +++ b/esphome/components/esp32_camera_web_server/camera_web_server.h @@ -0,0 +1,51 @@ +#pragma once + +#ifdef USE_ESP32 + +#include +#include + +#include "esphome/components/esp32_camera/esp32_camera.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "esphome/core/preferences.h" + +struct httpd_req; + +namespace esphome { +namespace esp32_camera_web_server { + +enum Mode { STREAM, SNAPSHOT }; + +class CameraWebServer : public Component { + public: + CameraWebServer(); + ~CameraWebServer(); + + void setup() override; + void on_shutdown() override; + void dump_config() override; + float get_setup_priority() const override; + void set_port(uint16_t port) { this->port_ = port; } + void set_mode(Mode mode) { this->mode_ = mode; } + void loop() override; + + protected: + std::shared_ptr wait_for_image_(); + esp_err_t handler_(struct httpd_req *req); + esp_err_t streaming_handler_(struct httpd_req *req); + esp_err_t snapshot_handler_(struct httpd_req *req); + + protected: + uint16_t port_{0}; + void *httpd_{nullptr}; + SemaphoreHandle_t semaphore_; + std::shared_ptr image_; + bool running_{false}; + Mode mode_{STREAM}; +}; + +} // namespace esp32_camera_web_server +} // namespace esphome + +#endif // USE_ESP32 diff --git a/tests/test4.yaml b/tests/test4.yaml index bc249c5ecb..4228c7494c 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -481,6 +481,12 @@ esp32_camera: resolution: 640x480 jpeg_quality: 10 +esp32_camera_web_server: + - port: 8080 + mode: stream + - port: 8081 + mode: snapshot + external_components: - source: github://esphome/esphome@dev refresh: 1d From 57b07441a1fed3fbab934cdae35ff3098471c537 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Tue, 9 Nov 2021 21:15:02 -0300 Subject: [PATCH 296/549] fix esp32 rmt receiver item array length (#2671) --- .../remote_receiver/remote_receiver_esp32.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_esp32.cpp index dde9b843c9..5a7fb3c985 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp32.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp32.cpp @@ -78,6 +78,7 @@ void RemoteReceiverComponent::loop() { if (this->temp_.empty()) return; + this->temp_.push_back(-this->idle_us_); this->call_listeners_dumpers_(); } } @@ -86,9 +87,10 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { uint32_t prev_length = 0; this->temp_.clear(); int32_t multiplier = this->pin_->is_inverted() ? -1 : 1; + size_t item_count = len / sizeof(rmt_item32_t); ESP_LOGVV(TAG, "START:"); - for (size_t i = 0; i < len; i++) { + for (size_t i = 0; i < item_count; i++) { if (item[i].level0) { ESP_LOGVV(TAG, "%u A: ON %uus (%u ticks)", i, this->to_microseconds_(item[i].duration0), item[i].duration0); } else { @@ -102,8 +104,8 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { } ESP_LOGVV(TAG, "\n"); - this->temp_.reserve(len / 4); - for (size_t i = 0; i < len; i++) { + this->temp_.reserve(item_count * 2); // each RMT item has 2 pulses + for (size_t i = 0; i < item_count; i++) { if (item[i].duration0 == 0u) { // Do nothing } else if (bool(item[i].level0) == prev_level) { @@ -120,10 +122,6 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { prev_length = item[i].duration0; } - if (this->to_microseconds_(prev_length) > this->idle_us_) { - break; - } - if (item[i].duration1 == 0u) { // Do nothing } else if (bool(item[i].level1) == prev_level) { @@ -139,10 +137,6 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { prev_level = bool(item[i].level1); prev_length = item[i].duration1; } - - if (this->to_microseconds_(prev_length) > this->idle_us_) { - break; - } } if (prev_length > 0) { if (prev_level) { From 366552a9692ef5e80ffc986d6b38bec900633646 Mon Sep 17 00:00:00 2001 From: cvwillegen Date: Wed, 10 Nov 2021 04:11:35 +0100 Subject: [PATCH 297/549] Remote base add pronto protocol (#2619) --- esphome/components/remote_base/__init__.py | 43 ++++++ .../remote_base/pronto_protocol.cpp | 135 ++++++++++++++++++ .../components/remote_base/pronto_protocol.h | 40 ++++++ 3 files changed, 218 insertions(+) create mode 100644 esphome/components/remote_base/pronto_protocol.cpp create mode 100644 esphome/components/remote_base/pronto_protocol.h diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index d2b848600d..914ce42efe 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -439,6 +439,49 @@ async def pioneer_action(var, config, args): cg.add(var.set_rc_code_2(template_)) +# Pronto +( + ProntoData, + ProntoBinarySensor, + ProntoTrigger, + ProntoAction, + ProntoDumper, +) = declare_protocol("Pronto") +PRONTO_SCHEMA = cv.Schema( + { + cv.Required(CONF_DATA): cv.string, + } +) + + +@register_binary_sensor("pronto", ProntoBinarySensor, PRONTO_SCHEMA) +def pronto_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + ProntoData, + ("data", config[CONF_DATA]), + ) + ) + ) + + +@register_trigger("pronto", ProntoTrigger, ProntoData) +def pronto_trigger(var, config): + pass + + +@register_dumper("pronto", ProntoDumper) +def pronto_dumper(var, config): + pass + + +@register_action("pronto", ProntoAction, PRONTO_SCHEMA) +async def pronto_action(var, config, args): + template_ = await cg.templatable(config[CONF_DATA], args, cg.std_string) + cg.add(var.set_data(template_)) + + # Sony SonyData, SonyBinarySensor, SonyTrigger, SonyAction, SonyDumper = declare_protocol( "Sony" diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp new file mode 100644 index 0000000000..11aebb6c5d --- /dev/null +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -0,0 +1,135 @@ +/* + * @file irPronto.cpp + * @brief In this file, the functions IRrecv::compensateAndPrintPronto and IRsend::sendPronto are defined. + * + * See http://www.harctoolbox.org/Glossary.html#ProntoSemantics + * Pronto database http://www.remotecentral.com/search.htm + * + * This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote. + * + ************************************************************************************ + * MIT License + * + * Copyright (c) 2020 Bengt Martensson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************************ + */ + +#include "pronto_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.pronto"; + +// DO NOT EXPORT from this file +static const uint16_t MICROSECONDS_T_MAX = 0xFFFFU; +static const uint16_t LEARNED_TOKEN = 0x0000U; +static const uint16_t LEARNED_NON_MODULATED_TOKEN = 0x0100U; +static const uint16_t BITS_IN_HEXADECIMAL = 4U; +static const uint16_t DIGITS_IN_PRONTO_NUMBER = 4U; +static const uint16_t NUMBERS_IN_PREAMBLE = 4U; +static const uint16_t HEX_MASK = 0xFU; +static const uint32_t REFERENCE_FREQUENCY = 4145146UL; +static const uint16_t FALLBACK_FREQUENCY = 64767U; // To use with frequency = 0; +static const uint32_t MICROSECONDS_IN_SECONDS = 1000000UL; +static const uint16_t PRONTO_DEFAULT_GAP = 45000; + +static uint16_t to_frequency_k_hz(uint16_t code) { + if (code == 0) + return 0; + + return ((REFERENCE_FREQUENCY / code) + 500) / 1000; +} + +/* + * Parse the string given as Pronto Hex, and send it a number of times given as argument. + */ +void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::vector &data) { + if (data.size() < 4) + return; + + uint16_t timebase = (MICROSECONDS_IN_SECONDS * data[1] + REFERENCE_FREQUENCY / 2) / REFERENCE_FREQUENCY; + uint16_t khz; + switch (data[0]) { + case LEARNED_TOKEN: // normal, "learned" + khz = to_frequency_k_hz(data[1]); + break; + case LEARNED_NON_MODULATED_TOKEN: // non-demodulated, "learned" + khz = 0U; + break; + default: + return; // There are other types, but they are not handled yet. + } + ESP_LOGD(TAG, "Send Pronto: frequency=%dkHz", khz); + dst->set_carrier_frequency(khz * 1000); + + uint16_t intros = 2 * data[2]; + uint16_t repeats = 2 * data[3]; + ESP_LOGD(TAG, "Send Pronto: intros=%d", intros); + ESP_LOGD(TAG, "Send Pronto: repeats=%d", repeats); + if (NUMBERS_IN_PREAMBLE + intros + repeats != data.size()) { // inconsistent sizes + return; + } + + /* + * Generate a new microseconds timing array for sendRaw. + * If recorded by IRremote, intro contains the whole IR data and repeat is empty + */ + dst->reserve(intros + repeats); + + for (uint16_t i = 0; i < intros + repeats; i += 2) { + uint32_t duration0 = ((uint32_t) data[i + 0 + NUMBERS_IN_PREAMBLE]) * timebase; + duration0 = duration0 < MICROSECONDS_T_MAX ? duration0 : MICROSECONDS_T_MAX; + + uint32_t duration1 = ((uint32_t) data[i + 1 + NUMBERS_IN_PREAMBLE]) * timebase; + duration1 = duration1 < MICROSECONDS_T_MAX ? duration1 : MICROSECONDS_T_MAX; + + dst->item(duration0, duration1); + } +} + +void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &str) { + size_t len = str.length() / (DIGITS_IN_PRONTO_NUMBER + 1) + 1; + std::vector data; + const char *p = str.c_str(); + char *endptr[1]; + + for (uint16_t i = 0; i < len; i++) { + uint16_t x = strtol(p, endptr, 16); + if (x == 0 && i >= NUMBERS_IN_PREAMBLE) { + // Alignment error?, bail immediately (often right result). + break; + } + data.push_back(x); // If input is conforming, there can be no overflow! + p = *endptr; + } + send_pronto_(dst, data); +} + +void ProntoProtocol::encode(RemoteTransmitData *dst, const ProntoData &data) { send_pronto_(dst, data.data); } + +optional ProntoProtocol::decode(RemoteReceiveData src) { return {}; } + +void ProntoProtocol::dump(const ProntoData &data) { ESP_LOGD(TAG, "Received Pronto: data=%s", data.data.c_str()); } + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/pronto_protocol.h b/esphome/components/remote_base/pronto_protocol.h new file mode 100644 index 0000000000..e96511383f --- /dev/null +++ b/esphome/components/remote_base/pronto_protocol.h @@ -0,0 +1,40 @@ +#pragma once + +#include "esphome/core/component.h" +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +struct ProntoData { + std::string data; + + bool operator==(const ProntoData &rhs) const { return data == rhs.data; } +}; + +class ProntoProtocol : public RemoteProtocol { + private: + void send_pronto_(RemoteTransmitData *dst, const std::vector &data); + void send_pronto_(RemoteTransmitData *dst, const std::string &str); + + public: + void encode(RemoteTransmitData *dst, const ProntoData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const ProntoData &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(Pronto) + +template class ProntoAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(std::string, data) + + void encode(RemoteTransmitData *dst, Ts... x) override { + ProntoData data{}; + data.data = this->data_.value(x...); + ProntoProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome From 97eaf3d4a1f009a33d05262ee32449fa423fdbb6 Mon Sep 17 00:00:00 2001 From: Duncan Findlay Date: Tue, 9 Nov 2021 19:12:20 -0800 Subject: [PATCH 298/549] Set up output_switch at priority DATA instead of HARDWARE. (#2648) --- esphome/components/output/switch/output_switch.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/output/switch/output_switch.h b/esphome/components/output/switch/output_switch.h index fc9540fede..a184a342fe 100644 --- a/esphome/components/output/switch/output_switch.h +++ b/esphome/components/output/switch/output_switch.h @@ -12,7 +12,7 @@ class OutputSwitch : public switch_::Switch, public Component { void set_output(BinaryOutput *output) { output_ = output; } void setup() override; - float get_setup_priority() const override { return setup_priority::HARDWARE; } + float get_setup_priority() const override { return setup_priority::HARDWARE - 1.0f; } void dump_config() override; protected: From 6e5cfac927c8d222d3557b959924c33ee4870833 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Wed, 10 Nov 2021 00:15:15 -0300 Subject: [PATCH 299/549] fix rc switch protocol 6 (#2672) --- esphome/components/remote_base/rc_switch_protocol.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/components/remote_base/rc_switch_protocol.cpp b/esphome/components/remote_base/rc_switch_protocol.cpp index 6b7d1b725a..1dc094d552 100644 --- a/esphome/components/remote_base/rc_switch_protocol.cpp +++ b/esphome/components/remote_base/rc_switch_protocol.cpp @@ -101,10 +101,13 @@ bool RCSwitchBase::expect_sync(RemoteReceiveData &src) const { if (!src.peek_space(this->sync_low_, 1)) return false; } else { - if (!src.peek_space(this->sync_high_)) - return false; - if (!src.peek_mark(this->sync_low_, 1)) + // We cant peek a space at the beginning because signals starts with a low to high transition. + // this long space at the beginning is the separation between the transmissions itself, so it is actually + // added at the end kind of artificially (by the value given to "idle:" option by the user in the yaml) + if (!src.peek_mark(this->sync_low_)) return false; + src.advance(1); + return true; } src.advance(2); return true; From 875b803483a064f08457a821dc090807149bd81b Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Wed, 10 Nov 2021 04:22:00 +0100 Subject: [PATCH 300/549] Remove "delay_microseconds_accurate()" and improve systemwide delayMicroseconds() (#2497) --- esphome/components/aht10/aht10.cpp | 2 +- esphome/components/esp32/core.cpp | 6 +----- esphome/components/esp8266/core.cpp | 2 +- .../remote_transmitter_esp32.cpp | 6 ++---- .../remote_transmitter_esp8266.cpp | 13 ++++++------ esphome/components/sdp3x/sdp3x.cpp | 3 ++- esphome/core/helpers.cpp | 21 ++++++++++--------- esphome/core/helpers.h | 2 +- 8 files changed, 25 insertions(+), 30 deletions(-) diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp index 713199212c..e5e04ac181 100644 --- a/esphome/components/aht10/aht10.cpp +++ b/esphome/components/aht10/aht10.cpp @@ -73,7 +73,7 @@ void AHT10Component::update() { bool success = false; for (int i = 0; i < AHT10_ATTEMPTS; ++i) { ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis()); - delay_microseconds_accurate(4); + delayMicroseconds(4); uint8_t reg = 0; if (this->write(®, 1) != i2c::ERROR_OK) { diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 96047df535..359999120f 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -21,11 +21,7 @@ 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 IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); } void arch_restart() { esp_restart(); // restart() doesn't always end execution diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp index b78600e7a3..51f3ca50ec 100644 --- a/esphome/components/esp8266/core.cpp +++ b/esphome/components/esp8266/core.cpp @@ -12,7 +12,7 @@ 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 IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); } void arch_restart() { ESP.restart(); // NOLINT(readability-static-accessed-through-instance) // restart() doesn't always end execution diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 500d7193f3..c3b61b72c2 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -121,10 +121,8 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen } else { this->status_clear_warning(); } - if (i + 1 < send_times) { - delay(send_wait / 1000UL); - delayMicroseconds(send_wait % 1000UL); - } + if (i + 1 < send_times) + delayMicroseconds(send_wait); } } diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp index 33c01985d7..74e62d4e3b 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp @@ -36,7 +36,7 @@ void RemoteTransmitterComponent::calculate_on_off_time_(uint32_t carrier_frequen void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint32_t usec) { if (this->carrier_duty_percent_ == 100 || (on_time == 0 && off_time == 0)) { this->pin_->digital_write(true); - delay_microseconds_accurate(usec); + delayMicroseconds(usec); this->pin_->digital_write(false); return; } @@ -48,19 +48,19 @@ void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint const uint32_t elapsed = current_time - start_time; this->pin_->digital_write(true); - delay_microseconds_accurate(std::min(on_time, usec - elapsed)); + delayMicroseconds(std::min(on_time, usec - elapsed)); this->pin_->digital_write(false); if (elapsed + on_time >= usec) return; - delay_microseconds_accurate(std::min(usec - elapsed - on_time, off_time)); + delayMicroseconds(std::min(usec - elapsed - on_time, off_time)); current_time = micros(); } } void RemoteTransmitterComponent::space_(uint32_t usec) { this->pin_->digital_write(false); - delay_microseconds_accurate(usec); + delayMicroseconds(usec); } void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { ESP_LOGD(TAG, "Sending remote code..."); @@ -81,9 +81,8 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen } } - if (i + 1 < send_times) { - delay_microseconds_accurate(send_wait); - } + if (i + 1 < send_times) + delayMicroseconds(send_wait); } } diff --git a/esphome/components/sdp3x/sdp3x.cpp b/esphome/components/sdp3x/sdp3x.cpp index ba7a028f8e..b0d8bcc6c4 100644 --- a/esphome/components/sdp3x/sdp3x.cpp +++ b/esphome/components/sdp3x/sdp3x.cpp @@ -1,5 +1,6 @@ #include "sdp3x.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" namespace esphome { @@ -25,7 +26,7 @@ void SDP3XComponent::setup() { ESP_LOGW(TAG, "Soft Reset SDP3X failed!"); // This sometimes fails for no good reason } - delay_microseconds_accurate(20000); + delayMicroseconds(20000); if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Read ID1 SDP3X failed!"); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index ada9a48c3b..8cf972e4db 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -209,17 +209,18 @@ uint8_t crc8(uint8_t *data, uint8_t len) { return crc; } -void delay_microseconds_accurate(uint32_t usec) { - if (usec == 0) - return; - if (usec < 5000UL) { - delayMicroseconds(usec); - return; - } - uint32_t start = micros(); - while (micros() - start < usec) { - delay(0); +void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trigger WDT or affect WiFi/BT stability + auto start = micros(); + const uint32_t lag = 5000; // microseconds, specifies the maximum time for a CPU busy-loop. + // it must be larger than the worst-case duration of a delay(1) call (hardware tasks) + // 5ms is conservative, it could be reduced when exact BT/WiFi stack delays are known + if (us > lag) { + delay((us - lag) / 1000UL); // note: in disabled-interrupt contexts delay() won't actually sleep + while (micros() - start < us - lag) + delay(1); // in those cases, this loop allows to yield for BT/WiFi stack tasks } + while (micros() - start < us) // fine delay the remaining usecs + ; } uint8_t reverse_bits_8(uint8_t x) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 905cb76170..040780e072 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -255,7 +255,7 @@ struct is_callable // NOLINT static constexpr auto value = decltype(test(nullptr))::value; // NOLINT }; -void delay_microseconds_accurate(uint32_t usec); +void delay_microseconds_safe(uint32_t us); template class Deduplicator { public: From 662773b075331d3efdbb59c02458c733e7005105 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Wed, 10 Nov 2021 04:24:44 +0100 Subject: [PATCH 301/549] modbus_controller: remove hard coded register size (#2654) --- .../modbus_controller/modbus_controller.h | 32 +++---------------- .../text_sensor/modbus_textsensor.h | 7 ---- 2 files changed, 4 insertions(+), 35 deletions(-) diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 4b5f4337db..222ebbd020 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -260,35 +260,11 @@ struct SensorItem { virtual void parse_and_publish(const std::vector &data) = 0; uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); } - size_t virtual get_register_size() const { - size_t size = 0; - switch (sensor_value_type) { - case SensorValueType::BIT: - size = 1; - break; - case SensorValueType::U_WORD: - case SensorValueType::S_WORD: - size = 2; - break; - case SensorValueType::U_DWORD: - case SensorValueType::S_DWORD: - case SensorValueType::U_DWORD_R: - case SensorValueType::S_DWORD_R: - case SensorValueType::FP32: - case SensorValueType::FP32_R: - size = 4; - break; - case SensorValueType::U_QWORD: - case SensorValueType::U_QWORD_R: - case SensorValueType::S_QWORD: - case SensorValueType::S_QWORD_R: - size = 8; - break; - case SensorValueType::RAW: - size = this->register_count * 2; - } - return size; + if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) + return 1; + else + return register_count * 2; } }; diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h index 28d0f0b241..77b5b9363a 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h @@ -25,13 +25,6 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi this->sensor_value_type = SensorValueType::RAW; this->force_new_range = force_new_range; } - size_t get_register_size() const override { - if (sensor_value_type == SensorValueType::RAW) { - return this->response_bytes_; - } else { - return SensorItem::get_register_size(); - } - } void dump_config() override; From 710866ff4e0525949fdb1d5ae6334e68a707923b Mon Sep 17 00:00:00 2001 From: Sam Hughes Date: Wed, 10 Nov 2021 17:52:49 +0000 Subject: [PATCH 302/549] CAP1188 Capacitive Touch Sensor Support (#2653) --- CODEOWNERS | 1 + esphome/components/cap1188/__init__.py | 45 +++++++++++ esphome/components/cap1188/binary_sensor.py | 25 ++++++ esphome/components/cap1188/cap1188.cpp | 88 +++++++++++++++++++++ esphome/components/cap1188/cap1188.h | 68 ++++++++++++++++ tests/test2.yaml | 6 ++ 6 files changed, 233 insertions(+) create mode 100644 esphome/components/cap1188/__init__.py create mode 100644 esphome/components/cap1188/binary_sensor.py create mode 100644 esphome/components/cap1188/cap1188.cpp create mode 100644 esphome/components/cap1188/cap1188.h diff --git a/CODEOWNERS b/CODEOWNERS index e535608db3..8f98fe1f7f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -31,6 +31,7 @@ esphome/components/binary_sensor/* @esphome/core esphome/components/ble_client/* @buxtronix esphome/components/bme680_bsec/* @trvrnrth esphome/components/canbus/* @danielschramm @mvturnho +esphome/components/cap1188/* @MrEditor97 esphome/components/captive_portal/* @OttoWinter esphome/components/ccs811/* @habbie esphome/components/climate/* @esphome/core diff --git a/esphome/components/cap1188/__init__.py b/esphome/components/cap1188/__init__.py new file mode 100644 index 0000000000..80794c5146 --- /dev/null +++ b/esphome/components/cap1188/__init__.py @@ -0,0 +1,45 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c +from esphome.const import CONF_ID, CONF_RESET_PIN +from esphome import pins + +CONF_TOUCH_THRESHOLD = "touch_threshold" +CONF_ALLOW_MULTIPLE_TOUCHES = "allow_multiple_touches" + +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["binary_sensor", "output"] +CODEOWNERS = ["@MrEditor97"] + +cap1188_ns = cg.esphome_ns.namespace("cap1188") +CONF_CAP1188_ID = "cap1188_id" +CAP1188Component = cap1188_ns.class_("CAP1188Component", cg.Component, i2c.I2CDevice) + +MULTI_CONF = True +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(CAP1188Component), + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_TOUCH_THRESHOLD, default=0x20): cv.int_range( + min=0x01, max=0x80 + ), + cv.Optional(CONF_ALLOW_MULTIPLE_TOUCHES, default=False): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x29)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD])) + cg.add(var.set_allow_multiple_touches(config[CONF_ALLOW_MULTIPLE_TOUCHES])) + + if CONF_RESET_PIN in config: + pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) + cg.add(var.set_reset_pin(pin)) + + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/cap1188/binary_sensor.py b/esphome/components/cap1188/binary_sensor.py new file mode 100644 index 0000000000..c249eb7330 --- /dev/null +++ b/esphome/components/cap1188/binary_sensor.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import CONF_CHANNEL, CONF_ID +from . import cap1188_ns, CAP1188Component, CONF_CAP1188_ID + +DEPENDENCIES = ["cap1188"] +CAP1188Channel = cap1188_ns.class_("CAP1188Channel", binary_sensor.BinarySensor) + +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CAP1188Channel), + cv.GenerateID(CONF_CAP1188_ID): cv.use_id(CAP1188Component), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7), + } +) + + +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_CAP1188_ID]) + cg.add(var.set_channel(config[CONF_CHANNEL])) + + cg.add(hub.register_channel(var)) diff --git a/esphome/components/cap1188/cap1188.cpp b/esphome/components/cap1188/cap1188.cpp new file mode 100644 index 0000000000..10d8325537 --- /dev/null +++ b/esphome/components/cap1188/cap1188.cpp @@ -0,0 +1,88 @@ +#include "cap1188.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace cap1188 { + +static const char *const TAG = "cap1188"; + +void CAP1188Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up CAP1188..."); + + // Reset device using the reset pin + if (this->reset_pin_ != nullptr) { + this->reset_pin_->setup(); + this->reset_pin_->digital_write(false); + delay(100); // NOLINT + this->reset_pin_->digital_write(true); + delay(100); // NOLINT + this->reset_pin_->digital_write(false); + delay(100); // NOLINT + } + + // Check if CAP1188 is actually connected + this->read_byte(CAP1188_PRODUCT_ID, &this->cap1188_product_id_); + this->read_byte(CAP1188_MANUFACTURE_ID, &this->cap1188_manufacture_id_); + this->read_byte(CAP1188_REVISION, &this->cap1188_revision_); + + if ((this->cap1188_product_id_ != 0x50) || (this->cap1188_manufacture_id_ != 0x5D)) { + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + + // Set sensitivity + uint8_t sensitivity = 0; + this->read_byte(CAP1188_SENSITVITY, &sensitivity); + sensitivity = sensitivity & 0x0f; + this->write_byte(CAP1188_SENSITVITY, sensitivity | this->touch_threshold_); + + // Allow multiple touches + this->write_byte(CAP1188_MULTI_TOUCH, this->allow_multiple_touches_); + + // Have LEDs follow touches + this->write_byte(CAP1188_LED_LINK, 0xFF); + + // Speed up a bit + this->write_byte(CAP1188_STAND_BY_CONFIGURATION, 0x30); +} + +void CAP1188Component::dump_config() { + ESP_LOGCONFIG(TAG, "CAP1188:"); + LOG_I2C_DEVICE(this); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + ESP_LOGCONFIG(TAG, " Product ID: 0x%x", this->cap1188_product_id_); + ESP_LOGCONFIG(TAG, " Manufacture ID: 0x%x", this->cap1188_manufacture_id_); + ESP_LOGCONFIG(TAG, " Revision ID: 0x%x", this->cap1188_revision_); + + switch (this->error_code_) { + case COMMUNICATION_FAILED: + ESP_LOGE(TAG, "Product ID or Manufacture ID of the connected device does not match a known CAP1188."); + break; + case NONE: + default: + break; + } +} + +void CAP1188Component::loop() { + uint8_t touched = 0; + + this->read_register(CAP1188_SENSOR_INPUT_STATUS, &touched, 1); + + if (touched) { + uint8_t data = 0; + this->read_register(CAP1188_MAIN, &data, 1); + data = data & ~CAP1188_MAIN_INT; + + this->write_register(CAP1188_MAIN, &data, 2); + } + + for (auto *channel : this->channels_) { + channel->process(touched); + } +} + +} // namespace cap1188 +} // namespace esphome diff --git a/esphome/components/cap1188/cap1188.h b/esphome/components/cap1188/cap1188.h new file mode 100644 index 0000000000..a1433deb0f --- /dev/null +++ b/esphome/components/cap1188/cap1188.h @@ -0,0 +1,68 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/output/binary_output.h" +#include "esphome/components/binary_sensor/binary_sensor.h" + +namespace esphome { +namespace cap1188 { + +enum { + CAP1188_I2CADDR = 0x29, + CAP1188_SENSOR_INPUT_STATUS = 0x3, + CAP1188_MULTI_TOUCH = 0x2A, + CAP1188_LED_LINK = 0x72, + CAP1188_PRODUCT_ID = 0xFD, + CAP1188_MANUFACTURE_ID = 0xFE, + CAP1188_STAND_BY_CONFIGURATION = 0x41, + CAP1188_REVISION = 0xFF, + CAP1188_MAIN = 0x00, + CAP1188_MAIN_INT = 0x01, + CAP1188_LEDPOL = 0x73, + CAP1188_INTERUPT_REPEAT = 0x28, + CAP1188_SENSITVITY = 0x1f, +}; + +class CAP1188Channel : public binary_sensor::BinarySensor { + public: + void set_channel(uint8_t channel) { channel_ = channel; } + void process(uint8_t data) { this->publish_state(static_cast(data & (1 << this->channel_))); } + + protected: + uint8_t channel_{0}; +}; + +class CAP1188Component : public Component, public i2c::I2CDevice { + public: + void register_channel(CAP1188Channel *channel) { this->channels_.push_back(channel); } + void set_touch_threshold(uint8_t touch_threshold) { this->touch_threshold_ = touch_threshold; }; + void set_allow_multiple_touches(bool allow_multiple_touches) { + this->allow_multiple_touches_ = allow_multiple_touches ? 0x41 : 0x80; + }; + void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void loop() override; + + protected: + std::vector channels_{}; + uint8_t touch_threshold_{0x20}; + uint8_t allow_multiple_touches_{0x80}; + + GPIOPin *reset_pin_{nullptr}; + + uint8_t cap1188_product_id_{0}; + uint8_t cap1188_manufacture_id_{0}; + uint8_t cap1188_revision_{0}; + + enum ErrorCode { + NONE = 0, + COMMUNICATION_FAILED, + } error_code_{NONE}; +}; + +} // namespace cap1188 +} // namespace esphome diff --git a/tests/test2.yaml b/tests/test2.yaml index 6869eeecb1..f90e522b1e 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -504,3 +504,9 @@ interval: display: +cap1188: + id: cap1188_component + address: 0x29 + touch_threshold: 0x20 + allow_multiple_touches: true + reset_pin: 14 From 2ac232e6349e573f892e3183bb44cf87ff627c4b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:09:10 +0100 Subject: [PATCH 303/549] Add missing hal.h include in esp32_camera_web_server (#2689) --- esphome/components/esp32_camera_web_server/camera_web_server.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.cpp b/esphome/components/esp32_camera_web_server/camera_web_server.cpp index ecaef78b77..c9a684c7e5 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.cpp +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp @@ -2,6 +2,7 @@ #include "camera_web_server.h" #include "esphome/core/application.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/util.h" From 219b225ac08cdfb32e94a92e700cbf3d5c729fa9 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Wed, 10 Nov 2021 19:12:57 +0100 Subject: [PATCH 304/549] [ESP32 ADC] Add option for raw uncalibrated output (#2663) --- esphome/components/adc/adc_sensor.cpp | 17 ++++++++++------- esphome/components/adc/adc_sensor.h | 4 ++-- esphome/components/adc/sensor.py | 16 ++++++++++++++-- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 9b7d0437e1..c8242ce008 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -91,17 +91,21 @@ void ADCSensor::dump_config() { float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } void ADCSensor::update() { float value_v = this->sample(); - ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v); + ESP_LOGD(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); this->publish_state(value_v); } #ifdef USE_ESP8266 float ADCSensor::sample() { #ifdef USE_ADC_SENSOR_VCC - return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance) + int raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) #else - return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT + int raw = analogRead(this->pin_->get_pin()); // NOLINT #endif + if (output_raw_) { + return raw; + } + return raw / 1024.0f; } #endif @@ -112,6 +116,9 @@ float ADCSensor::sample() { if (raw == -1) { return NAN; } + if (output_raw_) { + return raw; + } uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]); return mv / 1000.0f; } @@ -135,10 +142,6 @@ float ADCSensor::sample() { if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) { return NAN; } - // prevent divide by zero - if (raw0 == 0 && raw2 == 0 && raw6 == 0 && raw11 == 0) { - return 0; - } uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]); uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]); diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index 9984c72819..12272a1577 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -31,6 +31,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage /// `HARDWARE_LATE` setup priority. float get_setup_priority() const override; void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } + void set_output_raw(bool output_raw) { output_raw_ = output_raw; } float sample() override; #ifdef USE_ESP8266 @@ -39,8 +40,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage protected: InternalGPIOPin *pin_; - uint16_t read_raw_(); - uint32_t raw_to_microvolts_(uint16_t raw); + bool output_raw_{false}; #ifdef USE_ESP32 adc_atten_t attenuation_{ADC_ATTEN_DB_0}; diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 9fdddaa0a6..c812e67a68 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -4,6 +4,7 @@ from esphome import pins from esphome.components import sensor, voltage_sampler from esphome.const import ( CONF_ATTENUATION, + CONF_RAW, CONF_ID, CONF_INPUT, CONF_NUMBER, @@ -119,12 +120,18 @@ def validate_adc_pin(value): raise NotImplementedError +def validate_config(config): + if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto": + raise cv.Invalid("Automatic attenuation cannot be used when raw output is set.") + return config + + adc_ns = cg.esphome_ns.namespace("adc") ADCSensor = adc_ns.class_( "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler ) -CONFIG_SCHEMA = ( +CONFIG_SCHEMA = cv.All( sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=2, @@ -135,12 +142,14 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(ADCSensor), cv.Required(CONF_PIN): validate_adc_pin, + cv.Optional(CONF_RAW, default=False): cv.boolean, cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All( cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True) ), } ) - .extend(cv.polling_component_schema("60s")) + .extend(cv.polling_component_schema("60s")), + validate_config, ) @@ -155,6 +164,9 @@ async def to_code(config): pin = await cg.gpio_pin_expression(config[CONF_PIN]) cg.add(var.set_pin(pin)) + if CONF_RAW in config: + cg.add(var.set_output_raw(config[CONF_RAW])) + if CONF_ATTENUATION in config: if config[CONF_ATTENUATION] == "auto": cg.add(var.set_autorange(cg.global_ns.true)) From 15f9677d33c1bb39bbbf04795d4dc1c64089f98c Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:15:06 +0100 Subject: [PATCH 305/549] Introduce parse_number() helper function (#2659) --- esphome/components/anova/anova_base.cpp | 6 +-- esphome/components/ezo/ezo.cpp | 2 +- .../sensor/homeassistant_sensor.cpp | 2 +- .../hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp | 2 +- esphome/components/mqtt/mqtt_climate.cpp | 6 +-- esphome/components/mqtt/mqtt_cover.cpp | 4 +- esphome/components/mqtt/mqtt_fan.cpp | 2 +- esphome/components/mqtt/mqtt_number.cpp | 2 +- .../sensor/mqtt_subscribe_sensor.cpp | 2 +- esphome/components/pipsolar/pipsolar.cpp | 2 +- esphome/components/sim800l/sim800l.cpp | 4 +- .../teleinfo/sensor/teleinfo_sensor.cpp | 4 +- esphome/components/web_server/web_server.cpp | 2 +- esphome/core/helpers.cpp | 14 ----- esphome/core/helpers.h | 52 ++++++++++++++++++- 15 files changed, 70 insertions(+), 36 deletions(-) diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp index 811a34a27a..d55404089e 100644 --- a/esphome/components/anova/anova_base.cpp +++ b/esphome/components/anova/anova_base.cpp @@ -103,21 +103,21 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) { break; } case READ_TARGET_TEMPERATURE: { - this->target_temp_ = strtof(this->buf_, nullptr); + this->target_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f); if (this->fahrenheit_) this->target_temp_ = ftoc(this->target_temp_); this->has_target_temp_ = true; break; } case SET_TARGET_TEMPERATURE: { - this->target_temp_ = strtof(this->buf_, nullptr); + this->target_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f); if (this->fahrenheit_) this->target_temp_ = ftoc(this->target_temp_); this->has_target_temp_ = true; break; } case READ_CURRENT_TEMPERATURE: { - this->current_temp_ = strtof(this->buf_, nullptr); + this->current_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f); if (this->fahrenheit_) this->current_temp_ = ftoc(this->current_temp_); this->has_current_temp_ = true; diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index 81597f3466..7f7a41fb41 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -74,7 +74,7 @@ void EZOSensor::loop() { if (buf[0] != 1) return; - float val = strtof((char *) &buf[1], nullptr); + float val = parse_number((char *) &buf[1], sizeof(buf) - 1).value_or(0); this->publish_state(val); } diff --git a/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp b/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp index b6f2acdbe4..f5e73c8854 100644 --- a/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp +++ b/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "homeassistant.sensor"; void HomeassistantSensor::setup() { api::global_api_server->subscribe_home_assistant_state( this->entity_id_, this->attribute_, [this](const std::string &state) { - auto val = parse_float(state); + auto val = parse_number(state); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str()); this->publish_state(NAN); diff --git a/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp index cf6c9eea65..bd1c82c96b 100644 --- a/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp +++ b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp @@ -44,7 +44,7 @@ void HrxlMaxsonarWrComponent::check_buffer_() { if (this->buffer_.length() == MAX_DATA_LENGTH_BYTES && this->buffer_[0] == 'R' && this->buffer_.back() == static_cast(ASCII_CR)) { - int millimeters = strtol(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2).c_str(), nullptr, 10); + int millimeters = parse_number(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2)).value_or(0); float meters = float(millimeters) / 1000.0; ESP_LOGV(TAG, "Distance from sensor: %d mm, %f m", millimeters, meters); this->publish_state(meters); diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index a63eb9c4ff..ebc708f444 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -137,7 +137,7 @@ void MQTTClimateComponent::setup() { if (traits.get_supports_two_point_target_temperature()) { this->subscribe(this->get_target_temperature_low_command_topic(), [this](const std::string &topic, const std::string &payload) { - auto val = parse_float(payload); + auto val = parse_number(payload); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str()); return; @@ -148,7 +148,7 @@ void MQTTClimateComponent::setup() { }); this->subscribe(this->get_target_temperature_high_command_topic(), [this](const std::string &topic, const std::string &payload) { - auto val = parse_float(payload); + auto val = parse_number(payload); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str()); return; @@ -160,7 +160,7 @@ void MQTTClimateComponent::setup() { } else { this->subscribe(this->get_target_temperature_command_topic(), [this](const std::string &topic, const std::string &payload) { - auto val = parse_float(payload); + auto val = parse_number(payload); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str()); return; diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index 7bf3204222..7e42abcd05 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -24,7 +24,7 @@ void MQTTCoverComponent::setup() { }); if (traits.get_supports_position()) { this->subscribe(this->get_position_command_topic(), [this](const std::string &topic, const std::string &payload) { - auto value = parse_float(payload); + auto value = parse_number(payload); if (!value.has_value()) { ESP_LOGW(TAG, "Invalid position value: '%s'", payload.c_str()); return; @@ -36,7 +36,7 @@ void MQTTCoverComponent::setup() { } if (traits.get_supports_tilt()) { this->subscribe(this->get_tilt_command_topic(), [this](const std::string &topic, const std::string &payload) { - auto value = parse_float(payload); + auto value = parse_number(payload); if (!value.has_value()) { ESP_LOGW(TAG, "Invalid tilt value: '%s'", payload.c_str()); return; diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index a9d77789e1..d58e3abc88 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -71,7 +71,7 @@ void MQTTFanComponent::setup() { if (this->state_->get_traits().supports_speed()) { this->subscribe(this->get_speed_level_command_topic(), [this](const std::string &topic, const std::string &payload) { - optional speed_level_opt = parse_int(payload); + optional speed_level_opt = parse_number(payload); if (speed_level_opt.has_value()) { const int speed_level = speed_level_opt.value(); if (speed_level >= 0 && speed_level <= this->state_->get_traits().supported_speed_count()) { diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 9b2292cd76..337013055a 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -17,7 +17,7 @@ MQTTNumberComponent::MQTTNumberComponent(Number *number) : MQTTComponent(), numb void MQTTNumberComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) { - auto val = parse_float(state); + auto val = parse_number(state); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str()); return; diff --git a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp index e1accf3c70..273de10376 100644 --- a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp +++ b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp @@ -13,7 +13,7 @@ void MQTTSubscribeSensor::setup() { mqtt::global_mqtt_client->subscribe( this->topic_, [this](const std::string &topic, const std::string &payload) { - auto val = parse_float(payload); + auto val = parse_number(payload); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str()); this->publish_state(NAN); diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index 7dbbd798ad..9f8b57003a 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -656,7 +656,7 @@ void Pipsolar::loop() { case 32: fc = tmp[i]; fc += tmp[i + 1]; - this->value_fault_code_ = strtol(fc.c_str(), nullptr, 10); + this->value_fault_code_ = parse_number(fc).value_or(0); break; case 34: this->value_warnung_low_pv_energy_ = enabled; diff --git a/esphome/components/sim800l/sim800l.cpp b/esphome/components/sim800l/sim800l.cpp index e48b1ac9bd..eb6d62ca33 100644 --- a/esphome/components/sim800l/sim800l.cpp +++ b/esphome/components/sim800l/sim800l.cpp @@ -128,7 +128,7 @@ void Sim800LComponent::parse_cmd_(std::string message) { if (message.compare(0, 5, "+CSQ:") == 0) { size_t comma = message.find(',', 6); if (comma != 6) { - this->rssi_ = strtol(message.substr(6, comma - 6).c_str(), nullptr, 10); + this->rssi_ = parse_number(message.substr(6, comma - 6)).value_or(0); ESP_LOGD(TAG, "RSSI: %d", this->rssi_); } } @@ -146,7 +146,7 @@ void Sim800LComponent::parse_cmd_(std::string message) { while (end != start) { item++; if (item == 1) { // Slot Index - this->parse_index_ = strtol(message.substr(start, end - start).c_str(), nullptr, 10); + this->parse_index_ = parse_number(message.substr(start, end - start)).value_or(0); } // item 2 = STATUS, usually "REC UNERAD" if (item == 3) { // recipient diff --git a/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp b/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp index 4e4cd9f9e6..ad9c6dae00 100644 --- a/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp +++ b/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp @@ -6,8 +6,8 @@ namespace teleinfo { static const char *const TAG = "teleinfo_sensor"; TeleInfoSensor::TeleInfoSensor(const char *tag) { this->tag = std::string(tag); } void TeleInfoSensor::publish_val(const std::string &val) { - auto newval = parse_float(val); - publish_state(*newval); + auto newval = parse_number(val).value_or(0.0f); + publish_state(newval); } void TeleInfoSensor::dump_config() { LOG_SENSOR(" ", "Teleinfo Sensor", this); } } // namespace teleinfo diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 44ace38990..17b17fcc3c 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -458,7 +458,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc } if (request->hasParam("speed_level")) { String speed_level = request->getParam("speed_level")->value(); - auto val = parse_int(speed_level.c_str()); + auto val = parse_number(speed_level.c_str()); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", speed_level.c_str()); return; diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 8cf972e4db..3047facf45 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -279,20 +279,6 @@ std::string to_string(long double val) { sprintf(buf, "%Lf", val); return buf; } -optional parse_float(const std::string &str) { - char *end; - float value = ::strtof(str.c_str(), &end); - if (end == nullptr || end != str.end().base()) - return {}; - return value; -} -optional parse_int(const std::string &str) { - char *end; - int value = ::strtol(str.c_str(), &end, 10); - if (end == nullptr || end != str.end().base()) - return {}; - return value; -} optional parse_hex(const char chr) { int out = chr; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 040780e072..fde631514b 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -51,8 +53,6 @@ std::string to_string(unsigned long long val); // NOLINT std::string to_string(float val); std::string to_string(double val); std::string to_string(long double val); -optional parse_float(const std::string &str); -optional parse_int(const std::string &str); optional parse_hex(const std::string &str, size_t start, size_t length); optional parse_hex(char chr); /// Sanitize the hostname by removing characters that are not in the allowlist and truncating it to 63 chars. @@ -304,4 +304,52 @@ template T *new_buffer(size_t length) { return buffer; } +// --------------------------------------------------------------------------------------------------------------------- + +/// @name Parsing & formatting +///@{ + +/// Parse a unsigned decimal number. +template::value && std::is_unsigned::value), int> = 0> +optional parse_number(const char *str, size_t len) { + char *end = nullptr; + unsigned long value = ::strtoul(str, &end, 10); // NOLINT(google-runtime-int) + if (end == nullptr || end != str + len || value > std::numeric_limits::max()) + return {}; + return value; +} +template::value && std::is_unsigned::value), int> = 0> +optional parse_number(const std::string &str) { + return parse_number(str.c_str(), str.length()); +} +/// Parse a signed decimal number. +template::value && std::is_signed::value), int> = 0> +optional parse_number(const char *str, size_t len) { + char *end = nullptr; + signed long value = ::strtol(str, &end, 10); // NOLINT(google-runtime-int) + if (end == nullptr || end != str + len || value < std::numeric_limits::min() || + value > std::numeric_limits::max()) + return {}; + return value; +} +template::value && std::is_signed::value), int> = 0> +optional parse_number(const std::string &str) { + return parse_number(str.c_str(), str.length()); +} +/// Parse a decimal floating-point number. +template::value), int> = 0> +optional parse_number(const char *str, size_t len) { + char *end = nullptr; + float value = ::strtof(str, &end); + if (end == nullptr || end != str + len || value == HUGE_VALF) + return {}; + return value; +} +template::value), int> = 0> +optional parse_number(const std::string &str) { + return parse_number(str.c_str(), str.length()); +} + +///@} + } // namespace esphome From d8e33c5a69aa0053ae01352341e57265df7df802 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:30:07 +0100 Subject: [PATCH 306/549] Add repeat action for automations (#2538) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/automation.py | 21 +++++++++++++++++++++ esphome/core/base_automation.h | 33 +++++++++++++++++++++++++++++++++ tests/test5.yaml | 8 ++++++++ 3 files changed, 62 insertions(+) diff --git a/esphome/automation.py b/esphome/automation.py index 0768bf8869..fab998527f 100644 --- a/esphome/automation.py +++ b/esphome/automation.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome.const import ( CONF_AUTOMATION_ID, CONF_CONDITION, + CONF_COUNT, CONF_ELSE, CONF_ID, CONF_THEN, @@ -66,6 +67,7 @@ DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component) LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) IfAction = cg.esphome_ns.class_("IfAction", Action) WhileAction = cg.esphome_ns.class_("WhileAction", Action) +RepeatAction = cg.esphome_ns.class_("RepeatAction", Action) WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component) UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action) Automation = cg.esphome_ns.class_("Automation") @@ -241,6 +243,25 @@ async def while_action_to_code(config, action_id, template_arg, args): return var +@register_action( + "repeat", + RepeatAction, + cv.Schema( + { + cv.Required(CONF_COUNT): cv.templatable(cv.positive_not_null_int), + cv.Required(CONF_THEN): validate_action_list, + } + ), +) +async def repeat_action_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32) + cg.add(var.set_count(count_template)) + actions = await build_action_list(config[CONF_THEN], template_arg, args) + cg.add(var.add_then(actions)) + return var + + def validate_wait_until(value): schema = cv.Schema( { diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index d97d369d33..e87a4a2765 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -224,6 +224,39 @@ template class WhileAction : public Action { std::tuple var_{}; }; +template class RepeatAction : public Action { + public: + TEMPLATABLE_VALUE(uint32_t, count) + + void add_then(const std::vector *> &actions) { + this->then_.add_actions(actions); + this->then_.add_action(new LambdaAction([this](Ts... x) { + this->iteration_++; + if (this->iteration_ == this->count_.value(x...)) + this->play_next_tuple_(this->var_); + else + this->then_.play_tuple(this->var_); + })); + } + + void play_complex(Ts... x) override { + this->num_running_++; + this->var_ = std::make_tuple(x...); + this->iteration_ = 0; + this->then_.play_tuple(this->var_); + } + + void play(Ts... x) override { /* ignore - see play_complex */ + } + + void stop() override { this->then_.stop(); } + + protected: + uint32_t iteration_; + ActionList then_; + std::tuple var_; +}; + template class WaitUntilAction : public Action, public Component { public: WaitUntilAction(Condition *condition) : condition_(condition) {} diff --git a/tests/test5.yaml b/tests/test5.yaml index 72df3ed212..f1fb786fe5 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -173,3 +173,11 @@ sensor: uart_id: uart2 co2: name: CO2 Sensor + +script: + - id: automation_test + then: + - repeat: + count: 5 + then: + - logger.log: "looping!" From 8aa72f4c1efeb46aaa3728bfe553713d644db8d0 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 10 Nov 2021 19:35:31 +0100 Subject: [PATCH 307/549] Neopixelbus redo method definitions (#2616) --- esphome/components/neopixelbus/_methods.py | 418 +++++++++++++++++++++ esphome/components/neopixelbus/const.py | 42 +++ esphome/components/neopixelbus/light.py | 232 ++++++------ esphome/config_validation.py | 2 +- platformio.ini | 2 +- script/clang-tidy | 1 + 6 files changed, 585 insertions(+), 112 deletions(-) create mode 100644 esphome/components/neopixelbus/_methods.py create mode 100644 esphome/components/neopixelbus/const.py diff --git a/esphome/components/neopixelbus/_methods.py b/esphome/components/neopixelbus/_methods.py new file mode 100644 index 0000000000..b03544f246 --- /dev/null +++ b/esphome/components/neopixelbus/_methods.py @@ -0,0 +1,418 @@ +from dataclasses import dataclass +from typing import Any, List +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import ( + CONF_CHANNEL, + CONF_CLOCK_PIN, + CONF_DATA_PIN, + CONF_METHOD, + CONF_PIN, + CONF_SPEED, +) +from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32.const import ( + VARIANT_ESP32, + VARIANT_ESP32S2, + VARIANT_ESP32C3, +) +from esphome.core import CORE +from .const import ( + CONF_ASYNC, + CONF_BUS, + CHIP_400KBPS, + CHIP_800KBPS, + CHIP_APA106, + CHIP_DOTSTAR, + CHIP_LC8812, + CHIP_LPD6803, + CHIP_LPD8806, + CHIP_P9813, + CHIP_SK6812, + CHIP_TM1814, + CHIP_TM1829, + CHIP_TM1914, + CHIP_WS2801, + CHIP_WS2811, + CHIP_WS2812, + CHIP_WS2812X, + CHIP_WS2813, + ONE_WIRE_CHIPS, + TWO_WIRE_CHIPS, +) + +METHOD_BIT_BANG = "bit_bang" +METHOD_ESP8266_UART = "esp8266_uart" +METHOD_ESP8266_DMA = "esp8266_dma" +METHOD_ESP32_RMT = "esp32_rmt" +METHOD_ESP32_I2S = "esp32_i2s" +METHOD_SPI = "spi" + +CHANNEL_DYNAMIC = "dynamic" +BUS_DYNAMIC = "dynamic" +SPI_BUS_VSPI = "vspi" +SPI_BUS_HSPI = "hspi" +SPI_SPEEDS = [40e6, 20e6, 10e6, 5e6, 2e6, 1e6, 500e3] + + +def _esp32_rmt_default_channel(): + return { + VARIANT_ESP32S2: 1, + VARIANT_ESP32C3: 1, + }.get(get_esp32_variant(), 6) + + +def _validate_esp32_rmt_channel(value): + if isinstance(value, str) and value.lower() == CHANNEL_DYNAMIC: + value = CHANNEL_DYNAMIC + else: + value = cv.int_(value) + variant_channels = { + VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7, CHANNEL_DYNAMIC], + VARIANT_ESP32S2: [0, 1, 2, 3, CHANNEL_DYNAMIC], + VARIANT_ESP32C3: [0, 1, CHANNEL_DYNAMIC], + } + variant = get_esp32_variant() + if variant not in variant_channels: + raise cv.Invalid(f"{variant} does not support the rmt method") + if value not in variant_channels[variant]: + raise cv.Invalid(f"{variant} does not support rmt channel {value}") + return value + + +def _esp32_i2s_default_bus(): + return { + VARIANT_ESP32: 1, + VARIANT_ESP32S2: 0, + }.get(get_esp32_variant(), 0) + + +def _validate_esp32_i2s_bus(value): + if isinstance(value, str) and value.lower() == CHANNEL_DYNAMIC: + value = CHANNEL_DYNAMIC + else: + value = cv.int_(value) + variant_buses = { + VARIANT_ESP32: [0, 1, BUS_DYNAMIC], + VARIANT_ESP32S2: [0, BUS_DYNAMIC], + } + variant = get_esp32_variant() + if variant not in variant_buses: + raise cv.Invalid(f"{variant} does not support the i2s method") + if value not in variant_buses[variant]: + raise cv.Invalid(f"{variant} does not support i2s bus {value}") + return value + + +neo_ns = cg.global_ns + + +def _bit_bang_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEspBitBangMethod.h + # Some chips are only aliases + chip = { + CHIP_WS2813: CHIP_WS2812X, + CHIP_LC8812: CHIP_SK6812, + CHIP_TM1914: CHIP_TM1814, + CHIP_WS2812: CHIP_800KBPS, + }.get(chip, chip) + + lookup = { + CHIP_WS2811: (neo_ns.NeoEspBitBangSpeedWs2811, False), + CHIP_WS2812X: (neo_ns.NeoEspBitBangSpeedWs2812x, False), + CHIP_SK6812: (neo_ns.NeoEspBitBangSpeedSk6812, False), + CHIP_TM1814: (neo_ns.NeoEspBitBangSpeedTm1814, True), + CHIP_TM1829: (neo_ns.NeoEspBitBangSpeedTm1829, True), + CHIP_800KBPS: (neo_ns.NeoEspBitBangSpeed800Kbps, False), + CHIP_400KBPS: (neo_ns.NeoEspBitBangSpeed400Kbps, False), + CHIP_APA106: (neo_ns.NeoEspBitBangSpeedApa106, False), + } + # For tm variants opposite of inverted is needed + speed, pinset_inverted = lookup[chip] + pinset = { + False: neo_ns.NeoEspPinset, + True: neo_ns.NeoEspPinsetInverted, + }[inverted != pinset_inverted] + return neo_ns.NeoEspBitBangMethodBase.template(speed, pinset) + + +def _bit_bang_extra_validate(config): + pin = config[CONF_PIN] + if CORE.is_esp8266 and not (0 <= pin <= 15): + # Due to use of w1ts + raise cv.Invalid("Bit bang only supports pins GPIO0-GPIO15 on ESP8266") + if CORE.is_esp32 and not (0 <= pin <= 31): + raise cv.Invalid("Bit bang only supports pins GPIO0-GPIO31 on ESP32") + + +def _esp8266_uart_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp8266UartMethod.h + uart_context, uart_base = { + False: (neo_ns.NeoEsp8266UartContext, neo_ns.NeoEsp8266Uart), + True: (neo_ns.NeoEsp8266UartInterruptContext, neo_ns.NeoEsp8266AsyncUart), + }[config[CONF_ASYNC]] + uart_feature = { + 0: neo_ns.UartFeature0, + 1: neo_ns.UartFeature1, + }[config[CONF_BUS]] + # Some chips are only aliases + chip = { + CHIP_WS2811: CHIP_WS2812X, + CHIP_WS2813: CHIP_WS2812X, + CHIP_LC8812: CHIP_SK6812, + CHIP_TM1914: CHIP_TM1814, + CHIP_WS2812: CHIP_800KBPS, + }.get(chip, chip) + + lookup = { + CHIP_WS2812X: (neo_ns.NeoEsp8266UartSpeedWs2812x, False), + CHIP_SK6812: (neo_ns.NeoEsp8266UartSpeedSk6812, False), + CHIP_TM1814: (neo_ns.NeoEsp8266UartSpeedTm1814, True), + CHIP_TM1829: (neo_ns.NeoEsp8266UartSpeedTm1829, True), + CHIP_800KBPS: (neo_ns.NeoEsp8266UartSpeed800Kbps, False), + CHIP_400KBPS: (neo_ns.NeoEsp8266UartSpeed400Kbps, False), + CHIP_APA106: (neo_ns.NeoEsp8266UartSpeedApa106, False), + } + speed, uart_inverted = lookup[chip] + # For tm variants opposite of inverted is needed + inv = { + False: neo_ns.NeoEsp8266UartNotInverted, + True: neo_ns.NeoEsp8266UartInverted, + }[inverted != uart_inverted] + return neo_ns.NeoEsp8266UartMethodBase.template( + speed, uart_base.template(uart_feature, uart_context), inv + ) + + +def _esp8266_uart_extra_validate(config): + pin = config[CONF_PIN] + bus = config[CONF_METHOD][CONF_BUS] + right_pin = { + 0: 1, # U0TXD + 1: 2, # U1TXD + }[bus] + if pin != right_pin: + raise cv.Invalid(f"ESP8266 uart bus {bus} only supports pin GPIO{right_pin}") + + +def _esp8266_dma_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp8266DmaMethod.h + # Some chips are only aliases + chip = { + CHIP_WS2811: CHIP_WS2812X, + CHIP_WS2813: CHIP_WS2812X, + CHIP_LC8812: CHIP_SK6812, + CHIP_TM1914: CHIP_TM1814, + CHIP_WS2812: CHIP_800KBPS, + }.get(chip, chip) + + lookup = { + (CHIP_WS2812X, False): neo_ns.NeoEsp8266DmaSpeedWs2812x, + (CHIP_SK6812, False): neo_ns.NeoEsp8266DmaSpeedSk6812, + (CHIP_TM1814, True): neo_ns.NeoEsp8266DmaInvertedSpeedTm1814, + (CHIP_TM1829, True): neo_ns.NeoEsp8266DmaInvertedSpeedTm1829, + (CHIP_800KBPS, False): neo_ns.NeoEsp8266DmaSpeed800Kbps, + (CHIP_400KBPS, False): neo_ns.NeoEsp8266DmaSpeed400Kbps, + (CHIP_APA106, False): neo_ns.NeoEsp8266DmaSpeedApa106, + (CHIP_WS2812X, True): neo_ns.NeoEsp8266DmaInvertedSpeedWs2812x, + (CHIP_SK6812, True): neo_ns.NeoEsp8266DmaInvertedSpeedSk6812, + (CHIP_TM1814, False): neo_ns.NeoEsp8266DmaSpeedTm1814, + (CHIP_TM1829, False): neo_ns.NeoEsp8266DmaSpeedTm1829, + (CHIP_800KBPS, True): neo_ns.NeoEsp8266DmaInvertedSpeed800Kbps, + (CHIP_400KBPS, True): neo_ns.NeoEsp8266DmaInvertedSpeed400Kbps, + (CHIP_APA106, True): neo_ns.NeoEsp8266DmaInvertedSpeedApa106, + } + speed = lookup[(chip, inverted)] + return neo_ns.NeoEsp8266DmaMethodBase.template(speed) + + +def _esp8266_dma_extra_validate(config): + if config[CONF_PIN] != 3: + raise cv.Invalid("ESP8266 dma method only supports pin GPIO3") + + +def _esp32_rmt_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp32RmtMethod.h + channel = { + 0: neo_ns.NeoEsp32RmtChannel0, + 1: neo_ns.NeoEsp32RmtChannel1, + 2: neo_ns.NeoEsp32RmtChannel2, + 3: neo_ns.NeoEsp32RmtChannel3, + 4: neo_ns.NeoEsp32RmtChannel4, + 5: neo_ns.NeoEsp32RmtChannel5, + 6: neo_ns.NeoEsp32RmtChannel6, + 7: neo_ns.NeoEsp32RmtChannel7, + CHANNEL_DYNAMIC: neo_ns.NeoEsp32RmtChannelN, + }[config[CONF_CHANNEL]] + # Some chips are only aliases + chip = { + CHIP_WS2813: CHIP_WS2812X, + CHIP_LC8812: CHIP_SK6812, + CHIP_WS2812: CHIP_800KBPS, + }.get(chip, chip) + + lookup = { + (CHIP_WS2811, False): neo_ns.NeoEsp32RmtSpeedWs2811, + (CHIP_WS2812X, False): neo_ns.NeoEsp32RmtSpeedWs2812x, + (CHIP_SK6812, False): neo_ns.NeoEsp32RmtSpeedSk6812, + (CHIP_TM1814, False): neo_ns.NeoEsp32RmtSpeedTm1814, + (CHIP_TM1829, False): neo_ns.NeoEsp32RmtSpeedTm1829, + (CHIP_TM1914, False): neo_ns.NeoEsp32RmtSpeedTm1914, + (CHIP_800KBPS, False): neo_ns.NeoEsp32RmtSpeed800Kbps, + (CHIP_400KBPS, False): neo_ns.NeoEsp32RmtSpeed400Kbps, + (CHIP_APA106, False): neo_ns.NeoEsp32RmtSpeedApa106, + (CHIP_WS2811, True): neo_ns.NeoEsp32RmtInvertedSpeedWs2811, + (CHIP_WS2812X, True): neo_ns.NeoEsp32RmtInvertedSpeedWs2812x, + (CHIP_SK6812, True): neo_ns.NeoEsp32RmtInvertedSpeedSk6812, + (CHIP_TM1814, True): neo_ns.NeoEsp32RmtInvertedSpeedTm1814, + (CHIP_TM1829, True): neo_ns.NeoEsp32RmtInvertedSpeedTm1829, + (CHIP_TM1914, True): neo_ns.NeoEsp32RmtInvertedSpeedTm1914, + (CHIP_800KBPS, True): neo_ns.NeoEsp32RmtInvertedSpeed800Kbps, + (CHIP_400KBPS, True): neo_ns.NeoEsp32RmtInvertedSpeed400Kbps, + (CHIP_APA106, True): neo_ns.NeoEsp32RmtInvertedSpeedApa106, + } + speed = lookup[(chip, inverted)] + return neo_ns.NeoEsp32RmtMethodBase.template(speed, channel) + + +def _esp32_i2s_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp32I2sMethod.h + bus = { + 0: neo_ns.NeoEsp32I2sBusZero, + 1: neo_ns.NeoEsp32I2sBusOne, + BUS_DYNAMIC: neo_ns.NeoEsp32I2sBusN, + }[config[CONF_BUS]] + # Some chips are only aliases + chip = { + CHIP_WS2811: CHIP_WS2812X, + CHIP_WS2813: CHIP_WS2812X, + CHIP_LC8812: CHIP_SK6812, + CHIP_WS2812: CHIP_800KBPS, + }.get(chip, chip) + + lookup = { + CHIP_WS2812X: (neo_ns.NeoEsp32I2sSpeedWs2812x, False), + CHIP_SK6812: (neo_ns.NeoEsp32I2sSpeedSk6812, False), + CHIP_TM1814: (neo_ns.NeoEsp32I2sSpeedTm1814, True), + CHIP_TM1914: (neo_ns.NeoEsp32I2sSpeedTm1914, True), + CHIP_TM1829: (neo_ns.NeoEsp32I2sSpeedTm1829, True), + CHIP_800KBPS: (neo_ns.NeoEsp32I2sSpeed800Kbps, False), + CHIP_400KBPS: (neo_ns.NeoEsp32I2sSpeed400Kbps, False), + CHIP_APA106: (neo_ns.NeoEsp32I2sSpeedApa106, False), + } + speed, inv_inverted = lookup[chip] + # For tm variants opposite of inverted is needed + inv = { + False: neo_ns.NeoEsp32I2sNotInverted, + True: neo_ns.NeoEsp32I2sInverted, + }[inverted != inv_inverted] + return neo_ns.NeoEsp32I2sMethodBase.template(speed, bus, inv) + + +def _spi_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/TwoWireSpiImple.h + spi_imple = { + None: neo_ns.TwoWireSpiImple, + SPI_BUS_VSPI: neo_ns.TwoWireSpiImple, + SPI_BUS_HSPI: neo_ns.TwoWireHspiImple, + }[config.get(CONF_BUS)] + spi_speed = { + 40e6: neo_ns.SpiSpeed40Mhz, + 20e6: neo_ns.SpiSpeed20Mhz, + 10e6: neo_ns.SpiSpeed10Mhz, + 5e6: neo_ns.SpiSpeed5Mhz, + 2e6: neo_ns.SpiSpeed2Mhz, + 1e6: neo_ns.SpiSpeed1Mhz, + 500e3: neo_ns.SpiSpeed500Khz, + }[config[CONF_SPEED]] + chip_method_base = { + CHIP_DOTSTAR: neo_ns.DotStarMethodBase, + CHIP_LPD6803: neo_ns.Lpd6803MethodBase, + CHIP_LPD8806: neo_ns.Lpd8806MethodBase, + CHIP_WS2801: neo_ns.Ws2801MethodBase, + CHIP_P9813: neo_ns.P9813MethodBase, + }[chip] + return chip_method_base.template(spi_imple.template(spi_speed)) + + +def _spi_extra_validate(config): + if CORE.is_esp32: + return + + if config[CONF_DATA_PIN] != 13 and config[CONF_CLOCK_PIN] != 14: + raise cv.Invalid( + "SPI only supports pins GPIO13 for data and GPIO14 for clock on ESP8266" + ) + + +@dataclass +class MethodDescriptor: + method_schema: Any + to_code: Any + supported_chips: List[str] + extra_validate: Any = None + + +METHODS = { + METHOD_BIT_BANG: MethodDescriptor( + method_schema={}, + to_code=_bit_bang_to_code, + extra_validate=_bit_bang_extra_validate, + supported_chips=ONE_WIRE_CHIPS, + ), + METHOD_ESP8266_UART: MethodDescriptor( + method_schema=cv.All( + cv.only_on_esp8266, + { + cv.Optional(CONF_ASYNC, default=False): cv.boolean, + cv.Optional(CONF_BUS, default=1): cv.int_range(min=0, max=1), + }, + ), + extra_validate=_esp8266_uart_extra_validate, + to_code=_esp8266_uart_to_code, + supported_chips=ONE_WIRE_CHIPS, + ), + METHOD_ESP8266_DMA: MethodDescriptor( + method_schema=cv.All(cv.only_on_esp8266, {}), + extra_validate=_esp8266_dma_extra_validate, + to_code=_esp8266_dma_to_code, + supported_chips=ONE_WIRE_CHIPS, + ), + METHOD_ESP32_RMT: MethodDescriptor( + method_schema=cv.All( + cv.only_on_esp32, + { + cv.Optional( + CONF_CHANNEL, default=_esp32_rmt_default_channel + ): _validate_esp32_rmt_channel, + }, + ), + to_code=_esp32_rmt_to_code, + supported_chips=ONE_WIRE_CHIPS, + ), + METHOD_ESP32_I2S: MethodDescriptor( + method_schema=cv.All( + cv.only_on_esp32, + { + cv.Optional( + CONF_BUS, default=_esp32_i2s_default_bus + ): _validate_esp32_i2s_bus, + }, + ), + to_code=_esp32_i2s_to_code, + supported_chips=ONE_WIRE_CHIPS, + ), + METHOD_SPI: MethodDescriptor( + method_schema={ + cv.Optional(CONF_BUS): cv.All( + cv.only_on_esp32, cv.one_of(SPI_BUS_VSPI, SPI_BUS_HSPI, lower=True) + ), + cv.Optional(CONF_SPEED, default="10MHz"): cv.All( + cv.frequency, cv.one_of(*SPI_SPEEDS) + ), + }, + to_code=_spi_to_code, + extra_validate=_spi_extra_validate, + supported_chips=TWO_WIRE_CHIPS, + ), +} diff --git a/esphome/components/neopixelbus/const.py b/esphome/components/neopixelbus/const.py new file mode 100644 index 0000000000..ec1bd74c29 --- /dev/null +++ b/esphome/components/neopixelbus/const.py @@ -0,0 +1,42 @@ +CHIP_DOTSTAR = "dotstar" +CHIP_WS2801 = "ws2801" +CHIP_WS2811 = "ws2811" +CHIP_WS2812 = "ws2812" +CHIP_WS2812X = "ws2812x" +CHIP_WS2813 = "ws2813" +CHIP_SK6812 = "sk6812" +CHIP_TM1814 = "tm1814" +CHIP_TM1829 = "tm1829" +CHIP_TM1914 = "tm1914" +CHIP_800KBPS = "800kbps" +CHIP_400KBPS = "400kbps" +CHIP_APA106 = "apa106" +CHIP_LC8812 = "lc8812" +CHIP_LPD8806 = "lpd8806" +CHIP_LPD6803 = "lpd6803" +CHIP_P9813 = "p9813" + +ONE_WIRE_CHIPS = [ + CHIP_WS2811, + CHIP_WS2812, + CHIP_WS2812X, + CHIP_WS2813, + CHIP_SK6812, + CHIP_TM1814, + CHIP_TM1829, + CHIP_TM1914, + CHIP_800KBPS, + CHIP_400KBPS, + CHIP_APA106, + CHIP_LC8812, +] +TWO_WIRE_CHIPS = [ + CHIP_DOTSTAR, + CHIP_WS2801, + CHIP_LPD6803, + CHIP_LPD8806, + CHIP_P9813, +] +CHIP_TYPES = [*ONE_WIRE_CHIPS, *TWO_WIRE_CHIPS] +CONF_ASYNC = "async" +CONF_BUS = "bus" diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index 0117f1b063..6bb1bc8f99 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import light from esphome.const import ( + CONF_CHANNEL, CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_METHOD, @@ -13,7 +14,26 @@ from esphome.const import ( CONF_OUTPUT_ID, CONF_INVERT, ) +from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32.const import ( + VARIANT_ESP32C3, +) from esphome.core import CORE +from ._methods import ( + METHODS, + METHOD_SPI, + METHOD_ESP8266_UART, + METHOD_BIT_BANG, + METHOD_ESP32_I2S, + METHOD_ESP32_RMT, + METHOD_ESP8266_DMA, +) +from .const import ( + CHIP_TYPES, + CONF_ASYNC, + CONF_BUS, + ONE_WIRE_CHIPS, +) neopixelbus_ns = cg.esphome_ns.namespace("neopixelbus") NeoPixelBusLightOutputBase = neopixelbus_ns.class_( @@ -46,127 +66,115 @@ def validate_type(value): return value -def validate_variant(value): - value = cv.string(value).upper() - if value == "WS2813": - value = "WS2812X" - if value == "WS2812": - value = "800KBPS" - if value == "LC8812": - value = "SK6812" - return cv.one_of(*VARIANTS)(value) +def _choose_default_method(config): + if CONF_METHOD in config: + return config + config = config.copy() + if CONF_PIN not in config: + config[CONF_METHOD] = _validate_method(METHOD_SPI) + return config - -def validate_method(value): - if value is None: - if CORE.is_esp32: - return "ESP32_I2S_1" - if CORE.is_esp8266: - return "ESP8266_DMA" - raise NotImplementedError - - if CORE.is_esp32: - return cv.one_of(*ESP32_METHODS, upper=True, space="_")(value) + pin = config[CONF_PIN] if CORE.is_esp8266: - return cv.one_of(*ESP8266_METHODS, upper=True, space="_")(value) - raise NotImplementedError - - -def validate_method_pin(value): - method = value[CONF_METHOD] - method_pins = { - "ESP8266_DMA": [3], - "ESP8266_UART0": [1], - "ESP8266_ASYNC_UART0": [1], - "ESP8266_UART1": [2], - "ESP8266_ASYNC_UART1": [2], - "ESP32_I2S_0": list(range(0, 32)), - "ESP32_I2S_1": list(range(0, 32)), - } - if CORE.is_esp8266: - method_pins["BIT_BANG"] = list(range(0, 16)) - elif CORE.is_esp32: - method_pins["BIT_BANG"] = list(range(0, 32)) - pins_ = method_pins.get(method) - if pins_ is None: - # all pins allowed for this method - return 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( - f"Method {method} only supports pin(s) {', '.join(f'GPIO{x}' for x in pins_)}", - path=[CONF_METHOD], + if pin == 3: + config[CONF_METHOD] = _validate_method(METHOD_ESP8266_DMA) + elif pin == 1: + config[CONF_METHOD] = _validate_method( + { + CONF_TYPE: METHOD_ESP8266_UART, + CONF_BUS: 0, + } + ) + elif pin == 2: + config[CONF_METHOD] = _validate_method( + { + CONF_TYPE: METHOD_ESP8266_UART, + CONF_BUS: 1, + } ) - return value - - -VARIANTS = { - "WS2812X": "Ws2812x", - "SK6812": "Sk6812", - "800KBPS": "800Kbps", - "400KBPS": "400Kbps", -} - -ESP8266_METHODS = { - "ESP8266_DMA": "NeoEsp8266Dma{}Method", - "ESP8266_UART0": "NeoEsp8266Uart0{}Method", - "ESP8266_UART1": "NeoEsp8266Uart1{}Method", - "ESP8266_ASYNC_UART0": "NeoEsp8266AsyncUart0{}Method", - "ESP8266_ASYNC_UART1": "NeoEsp8266AsyncUart1{}Method", - "BIT_BANG": "NeoEsp8266BitBang{}Method", -} -ESP32_METHODS = { - "ESP32_I2S_0": "NeoEsp32I2s0{}Method", - "ESP32_I2S_1": "NeoEsp32I2s1{}Method", - "ESP32_RMT_0": "NeoEsp32Rmt0{}Method", - "ESP32_RMT_1": "NeoEsp32Rmt1{}Method", - "ESP32_RMT_2": "NeoEsp32Rmt2{}Method", - "ESP32_RMT_3": "NeoEsp32Rmt3{}Method", - "ESP32_RMT_4": "NeoEsp32Rmt4{}Method", - "ESP32_RMT_5": "NeoEsp32Rmt5{}Method", - "ESP32_RMT_6": "NeoEsp32Rmt6{}Method", - "ESP32_RMT_7": "NeoEsp32Rmt7{}Method", - "BIT_BANG": "NeoEsp32BitBang{}Method", -} - - -def format_method(config): - variant = VARIANTS[config[CONF_VARIANT]] - method = config[CONF_METHOD] - - if config[CONF_INVERT]: - if method == "ESP8266_DMA": - variant = f"Inverted{variant}" else: - variant += "Inverted" + config[CONF_METHOD] = _validate_method(METHOD_BIT_BANG) - if CORE.is_esp8266: - return ESP8266_METHODS[method].format(variant) if CORE.is_esp32: - return ESP32_METHODS[method].format(variant) - raise NotImplementedError + if get_esp32_variant() == VARIANT_ESP32C3: + config[CONF_METHOD] = _validate_method(METHOD_ESP32_RMT) + else: + config[CONF_METHOD] = _validate_method(METHOD_ESP32_I2S) + + return config def _validate(config): - if CONF_PIN in config: + variant = config[CONF_VARIANT] + if variant in ONE_WIRE_CHIPS: + if CONF_PIN not in config: + raise cv.Invalid( + f"Chip {variant} is a 1-wire chip and needs the [pin] option." + ) if CONF_CLOCK_PIN in config or CONF_DATA_PIN in config: - raise cv.Invalid("Cannot specify both 'pin' and 'clock_pin'+'data_pin'") - return config - if CONF_CLOCK_PIN in config: - if CONF_DATA_PIN not in config: - raise cv.Invalid("If you give clock_pin, you must also specify data_pin") - return config - raise cv.Invalid("Must specify at least one of 'pin' or 'clock_pin'+'data_pin'") + raise cv.Invalid( + f"Chip {variant} is a 1-wire chip, you need to set [pin] instead of ." + ) + else: + if CONF_PIN in config: + raise cv.Invalid( + f"Chip {variant} is a 2-wire chip and needs the [data_pin]+[clock_pin] option instead of [pin]." + ) + if CONF_CLOCK_PIN not in config or CONF_DATA_PIN not in config: + raise cv.Invalid( + f"Chip {variant} is a 2-wire chip, you need to set [data_pin]+[clock_pin]." + ) + + method_type = config[CONF_METHOD][CONF_TYPE] + method_desc = METHODS[method_type] + if variant not in method_desc.supported_chips: + raise cv.Invalid(f"Method {method_type} does not support {variant}") + if method_desc.extra_validate is not None: + method_desc.extra_validate(config) + + return config + + +def _validate_method(value): + if value is None: + # default method is determined afterwards because it depends on the chip type chosen + return None + + compat_methods = {} + for bus in [0, 1]: + for is_async in [False, True]: + compat_methods[f"ESP8266{'_ASYNC' if is_async else ''}_UART{bus}"] = { + CONF_TYPE: METHOD_ESP8266_UART, + CONF_BUS: bus, + CONF_ASYNC: is_async, + } + compat_methods[f"ESP32_I2S_{bus}"] = { + CONF_TYPE: METHOD_ESP32_I2S, + CONF_BUS: bus, + } + for channel in range(8): + compat_methods[f"ESP32_RMT_{channel}"] = { + CONF_TYPE: METHOD_ESP32_RMT, + CONF_CHANNEL: channel, + } + + if isinstance(value, str): + if value.upper() in compat_methods: + return _validate_method(compat_methods[value.upper()]) + return _validate_method({CONF_TYPE: value}) + return cv.typed_schema( + {k: v.method_schema for k, v in METHODS.items()}, lower=True + )(value) CONFIG_SCHEMA = cv.All( + cv.only_with_arduino, light.ADDRESSABLE_LIGHT_SCHEMA.extend( { cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(NeoPixelBusLightOutputBase), cv.Optional(CONF_TYPE, default="GRB"): validate_type, - cv.Optional(CONF_VARIANT, default="800KBPS"): validate_variant, - cv.Optional(CONF_METHOD, default=None): validate_method, + cv.Required(CONF_VARIANT): cv.one_of(*CHIP_TYPES, lower=True), + cv.Optional(CONF_METHOD): _validate_method, cv.Optional(CONF_INVERT, default="no"): cv.boolean, cv.Optional(CONF_PIN): pins.internal_gpio_output_pin_number, cv.Optional(CONF_CLOCK_PIN): pins.internal_gpio_output_pin_number, @@ -174,19 +182,23 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, } ).extend(cv.COMPONENT_SCHEMA), + _choose_default_method, _validate, - validate_method_pin, - cv.only_with_arduino, ) async def to_code(config): has_white = "W" in config[CONF_TYPE] - template = cg.TemplateArguments(getattr(cg.global_ns, format_method(config))) + method = config[CONF_METHOD] + + method_template = METHODS[method[CONF_TYPE]].to_code( + method, config[CONF_VARIANT], config[CONF_INVERT] + ) + if has_white: - out_type = NeoPixelRGBWLightOutput.template(template) + out_type = NeoPixelRGBWLightOutput.template(method_template) else: - out_type = NeoPixelRGBLightOutput.template(template) + out_type = NeoPixelRGBLightOutput.template(method_template) rhs = out_type.new() var = cg.Pvariable(config[CONF_OUTPUT_ID], rhs, out_type) await light.register_light(var, config) @@ -204,4 +216,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("makuna/NeoPixelBus", "2.6.7") + cg.add_library("makuna/NeoPixelBus", "2.6.9") diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 3e5c7940a4..2bb45487fa 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1399,7 +1399,7 @@ def typed_schema(schemas, **kwargs): if schema_option is None: raise Invalid(f"{key} not specified!") key_v = key_validator(schema_option) - value = schemas[key_v](value) + value = Schema(schemas[key_v])(value) value[key] = key_v return value diff --git a/platformio.ini b/platformio.ini index fa8944bf9a..0dd32268e0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -27,7 +27,7 @@ build_flags = [common] lib_deps = esphome/noise-c@0.1.4 ; api - makuna/NeoPixelBus@2.6.7 ; neopixelbus + makuna/NeoPixelBus@2.6.9 ; neopixelbus build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = diff --git a/script/clang-tidy b/script/clang-tidy index 87ba1c84b5..ad5fdfeb04 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -32,6 +32,7 @@ def clang_options(idedata): '-D_PGMSPACE_H_', '-Dpgm_read_byte(s)=(*(const uint8_t *)(s))', '-Dpgm_read_byte_near(s)=(*(const uint8_t *)(s))', + '-Dpgm_read_word(s)=(*(const uint16_t *)(s))', '-Dpgm_read_dword(s)=(*(const uint32_t *)(s))', '-DPROGMEM=', '-DPGM_P=const char *', From c422b2fb0b6fb98641dbc6c444c17b284ef8986a Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:40:18 +0100 Subject: [PATCH 308/549] Introduce byteswap helpers (#2661) * Backport std::byteswap() in helpers.h * Introduce convert_big_endian() function * Use convert_big_endian() in i2c byte swap functions --- esphome/components/i2c/i2c.h | 13 +++---------- esphome/core/helpers.h | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/esphome/components/i2c/i2c.h b/esphome/components/i2c/i2c.h index 7ee4cdd811..50a0b3ae50 100644 --- a/esphome/components/i2c/i2c.h +++ b/esphome/components/i2c/i2c.h @@ -1,6 +1,7 @@ #pragma once #include "i2c_bus.h" +#include "esphome/core/helpers.h" #include "esphome/core/optional.h" #include #include @@ -32,16 +33,8 @@ class I2CRegister { // 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); } +inline uint16_t i2ctohs(uint16_t i2cshort) { return convert_big_endian(i2cshort); } +inline uint16_t htoi2cs(uint16_t hostshort) { return convert_big_endian(hostshort); } class I2CDevice { public: diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index fde631514b..f29af06d89 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -306,6 +306,31 @@ template T *new_buffer(size_t length) { // --------------------------------------------------------------------------------------------------------------------- +/// @name STL backports +///@{ + +// std::byteswap is from C++23 and technically should be a template, but this will do for now. +constexpr uint8_t byteswap(uint8_t n) { return n; } +constexpr uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); } +constexpr uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); } +constexpr uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); } + +///@} + +/// @name Bit manipulation +///@{ + +/// Convert a value between host byte order and big endian (most significant byte first) order. +template::value, int> = 0> constexpr T convert_big_endian(T val) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + return byteswap(val); +#else + return val; +#endif +} + +///@} + /// @name Parsing & formatting ///@{ From 92321e219ab298045c0948dad3ef50f28993e426 Mon Sep 17 00:00:00 2001 From: TVDLoewe Date: Wed, 10 Nov 2021 19:41:04 +0100 Subject: [PATCH 309/549] Max7219digit multiline (#1622) Co-authored-by: Otto winter Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/max7219digit/display.py | 20 +++- .../components/max7219digit/max7219digit.cpp | 112 +++++++++++------- .../components/max7219digit/max7219digit.h | 25 +++- 3 files changed, 110 insertions(+), 47 deletions(-) diff --git a/esphome/components/max7219digit/display.py b/esphome/components/max7219digit/display.py index e1ca128699..2753f70eef 100644 --- a/esphome/components/max7219digit/display.py +++ b/esphome/components/max7219digit/display.py @@ -13,10 +13,20 @@ CONF_SCROLL_DELAY = "scroll_delay" CONF_SCROLL_ENABLE = "scroll_enable" CONF_SCROLL_MODE = "scroll_mode" CONF_REVERSE_ENABLE = "reverse_enable" +CONF_NUM_CHIP_LINES = "num_chip_lines" +CONF_CHIP_LINES_STYLE = "chip_lines_style" +integration_ns = cg.esphome_ns.namespace("max7219digit") +ChipLinesStyle = integration_ns.enum("ChipLinesStyle") +CHIP_LINES_STYLE = { + "ZIGZAG": ChipLinesStyle.ZIGZAG, + "SNAKE": ChipLinesStyle.SNAKE, +} + +ScrollMode = integration_ns.enum("ScrollMode") SCROLL_MODES = { - "CONTINUOUS": 0, - "STOP": 1, + "CONTINUOUS": ScrollMode.CONTINUOUS, + "STOP": ScrollMode.STOP, } CHIP_MODES = { @@ -37,6 +47,10 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(MAX7219Component), cv.Optional(CONF_NUM_CHIPS, default=4): cv.int_range(min=1, max=255), + cv.Optional(CONF_NUM_CHIP_LINES, default=1): cv.int_range(min=1, max=255), + cv.Optional(CONF_CHIP_LINES_STYLE, default="SNAKE"): cv.enum( + CHIP_LINES_STYLE, upper=True + ), cv.Optional(CONF_INTENSITY, default=15): cv.int_range(min=0, max=15), cv.Optional(CONF_ROTATE_CHIP, default="0"): cv.enum(CHIP_MODES, upper=True), cv.Optional(CONF_SCROLL_MODE, default="CONTINUOUS"): cv.enum( @@ -67,6 +81,8 @@ async def to_code(config): await display.register_display(var, config) cg.add(var.set_num_chips(config[CONF_NUM_CHIPS])) + cg.add(var.set_num_chip_lines(config[CONF_NUM_CHIP_LINES])) + cg.add(var.set_chip_lines_style(config[CONF_CHIP_LINES_STYLE])) cg.add(var.set_intensity(config[CONF_INTENSITY])) cg.add(var.set_chip_orientation(config[CONF_ROTATE_CHIP])) cg.add(var.set_scroll_speed(config[CONF_SCROLL_SPEED])) diff --git a/esphome/components/max7219digit/max7219digit.cpp b/esphome/components/max7219digit/max7219digit.cpp index 4fedd3d312..0f86ac635c 100644 --- a/esphome/components/max7219digit/max7219digit.cpp +++ b/esphome/components/max7219digit/max7219digit.cpp @@ -26,9 +26,12 @@ void MAX7219Component::setup() { ESP_LOGCONFIG(TAG, "Setting up MAX7219_DIGITS..."); this->spi_setup(); this->stepsleft_ = 0; - this->max_displaybuffer_.reserve(500); // Create base space to write buffer - // Initialize buffer with 0 for display so all non written pixels are blank - this->max_displaybuffer_.resize(this->num_chips_ * 8, 0); + for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { + std::vector vec(1); + this->max_displaybuffer_.push_back(vec); + // Initialize buffer with 0 for display so all non written pixels are blank + this->max_displaybuffer_[chip_line].resize(get_width_internal(), 0); + } // let's assume the user has all 8 digits connected, only important in daisy chained setups anyway this->send_to_all_(MAX7219_REGISTER_SCAN_LIMIT, 7); // let's use our own ASCII -> led pattern encoding @@ -46,6 +49,8 @@ void MAX7219Component::setup() { void MAX7219Component::dump_config() { ESP_LOGCONFIG(TAG, "MAX7219DIGIT:"); ESP_LOGCONFIG(TAG, " Number of Chips: %u", this->num_chips_); + ESP_LOGCONFIG(TAG, " Number of Chips Lines: %u", this->num_chip_lines_); + ESP_LOGCONFIG(TAG, " Chips Lines Style : %u", this->chip_lines_style_); ESP_LOGCONFIG(TAG, " Intensity: %u", this->intensity_); ESP_LOGCONFIG(TAG, " Scroll Mode: %u", this->scroll_mode_); ESP_LOGCONFIG(TAG, " Scroll Speed: %u", this->scroll_speed_); @@ -59,19 +64,19 @@ void MAX7219Component::loop() { uint32_t now = millis(); // check if the buffer has shrunk past the current position since last update - if ((this->max_displaybuffer_.size() >= this->old_buffer_size_ + 3) || - (this->max_displaybuffer_.size() <= this->old_buffer_size_ - 3)) { + if ((this->max_displaybuffer_[0].size() >= this->old_buffer_size_ + 3) || + (this->max_displaybuffer_[0].size() <= this->old_buffer_size_ - 3)) { this->stepsleft_ = 0; this->display(); - this->old_buffer_size_ = this->max_displaybuffer_.size(); + this->old_buffer_size_ = this->max_displaybuffer_[0].size(); } // Reset the counter back to 0 when full string has been displayed. - if (this->stepsleft_ > this->max_displaybuffer_.size()) + if (this->stepsleft_ > this->max_displaybuffer_[0].size()) this->stepsleft_ = 0; // Return if there is no need to scroll or scroll is off - if (!this->scroll_ || (this->max_displaybuffer_.size() <= this->num_chips_ * 8)) { + if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= get_width_internal())) { this->display(); return; } @@ -82,8 +87,8 @@ void MAX7219Component::loop() { } // Dwell time at end of string in case of stop at end - if (this->scroll_mode_ == 1) { - if (this->stepsleft_ >= this->max_displaybuffer_.size() - this->num_chips_ * 8 + 1) { + if (this->scroll_mode_ == ScrollMode::STOP) { + if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - get_width_internal() + 1) { if (now - this->last_scroll_ >= this->scroll_dwell_) { this->stepsleft_ = 0; this->last_scroll_ = now; @@ -107,30 +112,53 @@ void MAX7219Component::display() { // Run this routine for the rows of every chip 8x row 0 top to 7 bottom // Fill the pixel parameter with display data // Send the data to the chip - for (uint8_t i = 0; i < this->num_chips_; i++) { - for (uint8_t j = 0; j < 8; j++) { - if (this->reverse_) { - pixels[j] = this->max_displaybuffer_[(this->num_chips_ - i - 1) * 8 + j]; - } else { - pixels[j] = this->max_displaybuffer_[i * 8 + j]; + for (uint8_t chip = 0; chip < this->num_chips_ / this->num_chip_lines_; chip++) { + for (uint8_t chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { + for (uint8_t j = 0; j < 8; j++) { + bool reverse = + chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE ? !this->reverse_ : this->reverse_; + if (reverse) { + pixels[j] = + this->max_displaybuffer_[chip_line][(this->num_chips_ / this->num_chip_lines_ - chip - 1) * 8 + j]; + } else { + pixels[j] = this->max_displaybuffer_[chip_line][chip * 8 + j]; + } } + if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE) + this->orientation_ = orientation_180_(); + this->send64pixels(chip_line * this->num_chips_ / this->num_chip_lines_ + chip, pixels); + if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE) + this->orientation_ = orientation_180_(); } - this->send64pixels(i, pixels); + } +} + +uint8_t MAX7219Component::orientation_180_() { + switch (this->orientation_) { + case 0: + return 2; + case 1: + return 3; + case 2: + return 0; + case 3: + return 1; + default: + return 0; } } int MAX7219Component::get_height_internal() { - return 8; // TO BE DONE -> STACK TWO DISPLAYS ON TOP OF EACH OTHER - // TO BE DONE -> CREATE Virtual size of screen and scroll + return 8 * this->num_chip_lines_; // TO BE DONE -> CREATE Virtual size of screen and scroll } -int MAX7219Component::get_width_internal() { return this->num_chips_ * 8; } - -size_t MAX7219Component::get_buffer_length_() { return this->num_chips_ * 8; } +int MAX7219Component::get_width_internal() { return this->num_chips_ / this->num_chip_lines_ * 8; } void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) { - if (x + 1 > this->max_displaybuffer_.size()) { // Extend the display buffer in case required - this->max_displaybuffer_.resize(x + 1, this->bckgrnd_); + if (x + 1 > this->max_displaybuffer_[0].size()) { // Extend the display buffer in case required + for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { + this->max_displaybuffer_[chip_line].resize(x + 1, this->bckgrnd_); + } } if ((y >= this->get_height_internal()) || (y < 0) || (x < 0)) // If pixel is outside display then dont draw @@ -140,9 +168,9 @@ void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color colo uint8_t subpos = y; // Y is starting at 0 top left if (color.is_on()) { - this->max_displaybuffer_[pos] |= (1 << subpos); + this->max_displaybuffer_[subpos / 8][pos] |= (1 << subpos % 8); } else { - this->max_displaybuffer_[pos] &= ~(1 << subpos); + this->max_displaybuffer_[subpos / 8][pos] &= ~(1 << subpos % 8); } } @@ -158,8 +186,10 @@ void MAX7219Component::send_to_all_(uint8_t a_register, uint8_t data) { } void MAX7219Component::update() { this->update_ = true; - this->max_displaybuffer_.clear(); - this->max_displaybuffer_.resize(this->num_chips_ * 8, this->bckgrnd_); + for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { + this->max_displaybuffer_[chip_line].clear(); + this->max_displaybuffer_[chip_line].resize(get_width_internal(), this->bckgrnd_); + } if (this->writer_local_.has_value()) // insert Labda function if available (*this->writer_local_)(*this); } @@ -175,7 +205,7 @@ void MAX7219Component::turn_on_off(bool on_off) { } } -void MAX7219Component::scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_t delay, uint16_t dwell) { +void MAX7219Component::scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell) { this->set_scroll(on_off); this->set_scroll_mode(mode); this->set_scroll_speed(speed); @@ -183,7 +213,7 @@ void MAX7219Component::scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_ this->set_scroll_delay(delay); } -void MAX7219Component::scroll(bool on_off, uint8_t mode) { +void MAX7219Component::scroll(bool on_off, ScrollMode mode) { this->set_scroll(on_off); this->set_scroll_mode(mode); } @@ -196,24 +226,26 @@ void MAX7219Component::intensity(uint8_t intensity) { void MAX7219Component::scroll(bool on_off) { this->set_scroll(on_off); } void MAX7219Component::scroll_left() { - if (this->update_) { - this->max_displaybuffer_.push_back(this->bckgrnd_); - for (uint16_t i = 0; i < this->stepsleft_; i++) { - this->max_displaybuffer_.push_back(this->max_displaybuffer_.front()); - this->max_displaybuffer_.erase(this->max_displaybuffer_.begin()); - this->update_ = false; + for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { + if (this->update_) { + this->max_displaybuffer_[chip_line].push_back(this->bckgrnd_); + for (uint16_t i = 0; i < this->stepsleft_; i++) { + this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front()); + this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin()); + } + } else { + this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front()); + this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin()); } - } else { - this->max_displaybuffer_.push_back(this->max_displaybuffer_.front()); - this->max_displaybuffer_.erase(this->max_displaybuffer_.begin()); } + this->update_ = false; this->stepsleft_++; } 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] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]); + this->max_displaybuffer_[0][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 02fe8b6f42..3bf934632f 100644 --- a/esphome/components/max7219digit/max7219digit.h +++ b/esphome/components/max7219digit/max7219digit.h @@ -12,6 +12,16 @@ namespace esphome { namespace max7219digit { +enum ChipLinesStyle { + ZIGZAG = 0, + SNAKE, +}; + +enum ScrollMode { + CONTINUOUS = 0, + STOP, +}; + class MAX7219Component; using max7219_writer_t = std::function; @@ -46,20 +56,22 @@ class MAX7219Component : public PollingComponent, void set_intensity(uint8_t intensity) { this->intensity_ = intensity; }; void set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; }; + void set_num_chip_lines(uint8_t num_chip_lines) { this->num_chip_lines_ = num_chip_lines; }; + void set_chip_lines_style(ChipLinesStyle chip_lines_style) { this->chip_lines_style_ = chip_lines_style; }; void set_chip_orientation(uint8_t rotate) { this->orientation_ = rotate; }; void set_scroll_speed(uint16_t speed) { this->scroll_speed_ = speed; }; void set_scroll_dwell(uint16_t dwell) { this->scroll_dwell_ = dwell; }; void set_scroll_delay(uint16_t delay) { this->scroll_delay_ = delay; }; void set_scroll(bool on_off) { this->scroll_ = on_off; }; - void set_scroll_mode(uint8_t mode) { this->scroll_mode_ = mode; }; + void set_scroll_mode(ScrollMode mode) { this->scroll_mode_ = mode; }; void set_reverse(bool on_off) { this->reverse_ = on_off; }; 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); - void scroll(bool on_off, uint8_t mode); + void scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell); + void scroll(bool on_off, ScrollMode mode); void scroll(bool on_off); void intensity(uint8_t intensity); @@ -84,9 +96,12 @@ class MAX7219Component : public PollingComponent, protected: void send_byte_(uint8_t a_register, uint8_t data); void send_to_all_(uint8_t a_register, uint8_t data); + uint8_t orientation_180_(); uint8_t intensity_; /// Intensity of the display from 0 to 15 (most) uint8_t num_chips_; + uint8_t num_chip_lines_; + ChipLinesStyle chip_lines_style_; bool scroll_; bool reverse_; bool update_{false}; @@ -94,11 +109,11 @@ class MAX7219Component : public PollingComponent, uint16_t scroll_delay_; uint16_t scroll_dwell_; uint16_t old_buffer_size_ = 0; - uint8_t scroll_mode_; + ScrollMode scroll_mode_; bool invert_ = false; uint8_t orientation_; uint8_t bckgrnd_ = 0x0; - std::vector max_displaybuffer_; + std::vector> max_displaybuffer_; uint32_t last_scroll_ = 0; uint16_t stepsleft_; size_t get_buffer_length_(); From 4d433968359d54b6d48c3e1cbb6a024aeaffe08f Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:42:41 +0100 Subject: [PATCH 310/549] Clean-up string sanitation helpers (#2660) --- esphome/components/mqtt/mqtt_component.cpp | 4 +- esphome/core/entity_base.cpp | 2 +- esphome/core/helpers.cpp | 47 +++++++++------------- esphome/core/helpers.h | 28 ++++++------- 4 files changed, 37 insertions(+), 44 deletions(-) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index cebb8dd086..e3ae4dea50 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -17,7 +17,7 @@ static const char *const TAG = "mqtt.component"; void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; } std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const { - std::string sanitized_name = sanitize_string_allowlist(App.get_name(), HOSTNAME_CHARACTER_ALLOWLIST); + std::string sanitized_name = str_sanitize(App.get_name()); return discovery_info.prefix + "/" + this->component_type() + "/" + sanitized_name + "/" + this->get_default_object_id_() + "/config"; } @@ -136,7 +136,7 @@ bool MQTTComponent::is_discovery_enabled() const { } std::string MQTTComponent::get_default_object_id_() const { - return sanitize_string_allowlist(to_lowercase_underscore(this->friendly_name()), HOSTNAME_CHARACTER_ALLOWLIST); + return str_sanitize(str_snake_case(this->friendly_name())); } void MQTTComponent::subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos) { diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 41f08b28a6..a9e1414018 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -35,7 +35,7 @@ const std::string &EntityBase::get_object_id() { return this->object_id_; } // Calculate Object ID Hash from Entity Name void EntityBase::calc_object_id_() { - this->object_id_ = sanitize_string_allowlist(to_lowercase_underscore(this->name_), HOSTNAME_CHARACTER_ALLOWLIST); + this->object_id_ = str_sanitize(str_snake_case(this->name_)); // FNV-1 hash this->object_id_hash_ = fnv1_hash(this->object_id_); } diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 3047facf45..edd2f74c12 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -128,31 +128,6 @@ float gamma_uncorrect(float value, float gamma) { return powf(value, 1 / gamma); } -std::string to_lowercase_underscore(std::string s) { - std::transform(s.begin(), s.end(), s.begin(), ::tolower); - std::replace(s.begin(), s.end(), ' ', '_'); - return s; -} - -std::string sanitize_string_allowlist(const std::string &s, const std::string &allowlist) { - std::string out(s); - out.erase(std::remove_if(out.begin(), out.end(), - [&allowlist](const char &c) { return allowlist.find(c) == std::string::npos; }), - out.end()); - return out; -} - -std::string sanitize_hostname(const std::string &hostname) { - std::string s = sanitize_string_allowlist(hostname, HOSTNAME_CHARACTER_ALLOWLIST); - return truncate_string(s, 63); -} - -std::string truncate_string(const std::string &s, size_t length) { - if (s.length() > length) - return s.substr(0, length); - return s; -} - std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { if (accuracy_decimals < 0) { auto multiplier = powf(10.0f, accuracy_decimals); @@ -191,8 +166,6 @@ ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { return PARSE_NONE; } -const char *const HOSTNAME_CHARACTER_ALLOWLIST = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"; - uint8_t crc8(uint8_t *data, uint8_t len) { uint8_t crc = 0; @@ -481,4 +454,24 @@ IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } #endif +// --------------------------------------------------------------------------------------------------------------------- + +std::string str_truncate(const std::string &str, size_t length) { + return str.length() > length ? str.substr(0, length) : str; +} +std::string str_snake_case(const std::string &str) { + std::string result; + result.resize(str.length()); + std::transform(str.begin(), str.end(), result.begin(), ::tolower); + std::replace(result.begin(), result.end(), ' ', '_'); + return result; +} +std::string str_sanitize(const std::string &str) { + std::string out; + std::copy_if(str.begin(), str.end(), std::back_inserter(out), [](const char &c) { + return c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + }); + return out; +} + } // namespace esphome diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index f29af06d89..9a60d036e4 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -25,9 +25,6 @@ namespace esphome { -/// The characters that are allowed in a hostname. -extern const char *const HOSTNAME_CHARACTER_ALLOWLIST; - /// Read the raw MAC address into the provided byte array (6 bytes). void get_mac_address_raw(uint8_t *mac); @@ -55,14 +52,6 @@ std::string to_string(double val); std::string to_string(long double val); optional parse_hex(const std::string &str, size_t start, size_t length); optional parse_hex(char chr); -/// Sanitize the hostname by removing characters that are not in the allowlist and truncating it to 63 chars. -std::string sanitize_hostname(const std::string &hostname); - -/// Truncate a string to a specific length -std::string truncate_string(const std::string &s, size_t length); - -/// Convert the string to lowercase_underscore. -std::string to_lowercase_underscore(std::string s); /// Compare string a to string b (ignoring case) and return whether they are equal. bool str_equals_case_insensitive(const std::string &a, const std::string &b); @@ -145,9 +134,6 @@ std::string uint64_to_string(uint64_t num); /// Convert a uint32_t to a hex string std::string uint32_to_string(uint32_t num); -/// Sanitizes the input string with the allowlist. -std::string sanitize_string_allowlist(const std::string &s, const std::string &allowlist); - uint8_t reverse_bits_8(uint8_t x); uint16_t reverse_bits_16(uint16_t x); uint32_t reverse_bits_32(uint32_t x); @@ -331,6 +317,20 @@ template::value, int> = 0> constexpr ///@} +/// @name Strings +///@{ + +/// Truncate a string to a specific length. +std::string str_truncate(const std::string &str, size_t length); + +/// Convert the string to snake case (lowercase with underscores). +std::string str_snake_case(const std::string &str); + +/// Sanitizes the input string by removing all characters but alphanumerics, dashes and underscores. +std::string str_sanitize(const std::string &str); + +///@} + /// @name Parsing & formatting ///@{ From 99c775d8cbad9afe3be4ae6c9a446dde34c1f3cd Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:44:01 +0100 Subject: [PATCH 311/549] Introduce encode_value/decode_value() template functions (#2662) --- esphome/components/ccs811/ccs811.cpp | 2 +- esphome/core/helpers.cpp | 11 -------- esphome/core/helpers.h | 42 +++++++++++++++++++++++----- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/esphome/components/ccs811/ccs811.cpp b/esphome/components/ccs811/ccs811.cpp index 11a66f5100..f8cee79c55 100644 --- a/esphome/components/ccs811/ccs811.cpp +++ b/esphome/components/ccs811/ccs811.cpp @@ -52,7 +52,7 @@ void CCS811Component::setup() { if (this->baseline_.has_value()) { // baseline available, write to sensor - this->write_bytes(0x11, decode_uint16(*this->baseline_)); + this->write_bytes(0x11, decode_value(*this->baseline_)); } auto hardware_version_data = this->read_bytes<1>(0x21); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index edd2f74c12..daca3ffd32 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -350,17 +350,6 @@ std::string str_sprintf(const char *fmt, ...) { return str; } -uint16_t encode_uint16(uint8_t msb, uint8_t lsb) { return (uint16_t(msb) << 8) | uint16_t(lsb); } -std::array decode_uint16(uint16_t value) { - uint8_t msb = (value >> 8) & 0xFF; - uint8_t lsb = (value >> 0) & 0xFF; - return {msb, lsb}; -} - -uint32_t encode_uint32(uint8_t msb, uint8_t byte2, uint8_t byte3, uint8_t lsb) { - return (uint32_t(msb) << 24) | (uint32_t(byte2) << 16) | (uint32_t(byte3) << 8) | uint32_t(lsb); -} - std::string hexencode(const uint8_t *data, uint32_t len) { char buf[20]; std::string res; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 9a60d036e4..c67ad8eea3 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -138,13 +138,6 @@ uint8_t reverse_bits_8(uint8_t x); uint16_t reverse_bits_16(uint16_t x); uint32_t reverse_bits_32(uint32_t x); -/// Encode a 16-bit unsigned integer given a most and least-significant byte. -uint16_t encode_uint16(uint8_t msb, uint8_t lsb); -/// Decode a 16-bit unsigned integer into an array of two values: most significant byte, least significant byte. -std::array decode_uint16(uint16_t value); -/// Encode a 32-bit unsigned integer given four bytes in MSB -> LSB order -uint32_t encode_uint32(uint8_t msb, uint8_t byte2, uint8_t byte3, uint8_t lsb); - /// Convert RGB floats (0-1) to hue (0-360) & saturation/value percentage (0-1) void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value); /// Convert hue (0-360) & saturation/value percentage (0-1) to RGB floats (0-1) @@ -306,6 +299,41 @@ constexpr uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); } /// @name Bit manipulation ///@{ +/// Encode a 16-bit value given the most and least significant byte. +constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb) { + return (static_cast(msb) << 8) | (static_cast(lsb)); +} +/// Encode a 32-bit value given four bytes in most to least significant byte order. +constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4) { + return (static_cast(byte1) << 24) | (static_cast(byte2) << 16) | + (static_cast(byte3) << 8) | (static_cast(byte4)); +} + +/// Encode a value from its constituent bytes (from most to least significant) in an array with length sizeof(T). +template::value, int> = 0> inline T encode_value(const uint8_t *bytes) { + T val = 0; + for (size_t i = 0; i < sizeof(T); i++) { + val <<= 8; + val |= bytes[i]; + } + return val; +} +/// Encode a value from its constituent bytes (from most to least significant) in an std::array with length sizeof(T). +template::value, int> = 0> +inline T encode_value(const std::array bytes) { + return encode_value(bytes.data()); +} +/// Decode a value into its constituent bytes (from most to least significant). +template::value, int> = 0> +inline std::array decode_value(T val) { + std::array ret{}; + for (size_t i = sizeof(T); i > 0; i--) { + ret[i - 1] = val & 0xFF; + val >>= 8; + } + return ret; +} + /// Convert a value between host byte order and big endian (most significant byte first) order. template::value, int> = 0> constexpr T convert_big_endian(T val) { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ From 0bdb48bcac9244df6700410b9cf4ec581dbbda38 Mon Sep 17 00:00:00 2001 From: Laszlo Gazdag Date: Wed, 10 Nov 2021 20:31:22 +0100 Subject: [PATCH 312/549] Make OTA function switchable in web_server component (#2685) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/web_server/__init__.py | 3 +++ esphome/components/web_server/web_server.cpp | 16 +++++++++++----- esphome/components/web_server/web_server.h | 7 +++++++ tests/test1.yaml | 1 + tests/test4.yaml | 1 + 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index ba2d866593..61b1fa5ad6 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -12,6 +12,7 @@ from esphome.const import ( CONF_AUTH, CONF_USERNAME, CONF_PASSWORD, + CONF_OTA, ) from esphome.core import CORE, coroutine_with_priority @@ -41,6 +42,7 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( web_server_base.WebServerBase ), + cv.Optional(CONF_OTA, default=True): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA) @@ -57,6 +59,7 @@ async def to_code(config): cg.add_define("USE_WEBSERVER") cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) + cg.add(var.set_allow_ota(config[CONF_OTA])) if CONF_AUTH in config: cg.add(paren.set_auth_username(config[CONF_AUTH][CONF_USERNAME])) cg.add(paren.set_auth_password(config[CONF_AUTH][CONF_PASSWORD])) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 17b17fcc3c..6f47f460af 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -152,7 +152,9 @@ void WebServer::setup() { #endif this->base_->add_handler(&this->events_); this->base_->add_handler(this); - this->base_->add_ota_handler(); + + if (this->allow_ota_) + this->base_->add_ota_handler(); this->set_interval(10000, [this]() { this->events_.send("", "ping", millis(), 30000); }); } @@ -240,10 +242,14 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #endif stream->print(F("

See ESPHome Web API for " - "REST API documentation.

" - "

OTA Update

" - "

Debug Log

"));
+                  "REST API documentation.

")); + if (this->allow_ota_) { + stream->print( + F("

OTA Update

")); + } + stream->print(F("

Debug Log

"));
+
 #ifdef WEBSERVER_JS_INCLUDE
   if (this->js_include_ != nullptr) {
     stream->print(F(""));
diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h
index 021d5a0646..cdfec51cf1 100644
--- a/esphome/components/web_server/web_server.h
+++ b/esphome/components/web_server/web_server.h
@@ -58,6 +58,12 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
    */
   void set_js_include(const char *js_include);
 
+  /** Set whether or not the webserver should expose the OTA form and handler.
+   *
+   * @param allow_ota.
+   */
+  void set_allow_ota(bool allow_ota) { this->allow_ota_ = allow_ota; }
+
   // ========== INTERNAL METHODS ==========
   // (In most use cases you won't need these)
   /// Setup the internal web server and register handlers.
@@ -182,6 +188,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
   const char *css_include_{nullptr};
   const char *js_url_{nullptr};
   const char *js_include_{nullptr};
+  bool allow_ota_{true};
 };
 
 }  // namespace web_server
diff --git a/tests/test1.yaml b/tests/test1.yaml
index 585e01635f..dee7493bf2 100644
--- a/tests/test1.yaml
+++ b/tests/test1.yaml
@@ -234,6 +234,7 @@ logger:
 
 web_server:
   port: 8080
+  ota: true
   css_url: https://esphome.io/_static/webserver-v1.min.css
   js_url: https://esphome.io/_static/webserver-v1.min.js
 
diff --git a/tests/test4.yaml b/tests/test4.yaml
index 4228c7494c..938145235a 100644
--- a/tests/test4.yaml
+++ b/tests/test4.yaml
@@ -45,6 +45,7 @@ logger:
   level: DEBUG
 
 web_server:
+  ota: false
   auth:
     username: admin
     password: admin

From 5ff7c8418c584fc10f49caaaf9736ffffb321c00 Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Thu, 11 Nov 2021 08:55:45 +1300
Subject: [PATCH 313/549] Implement Improv via Serial component (#2423)

Co-authored-by: Paulus Schoutsen 
---
 CODEOWNERS                                    |   1 +
 .../captive_portal/captive_portal.cpp         |   1 +
 esphome/components/esp32/__init__.py          |   2 +
 esphome/components/esp32/const.py             |   8 +
 esphome/components/esp8266/__init__.py        |   1 +
 esphome/components/improv/improv.cpp          |  12 +-
 esphome/components/improv/improv.h            |  10 +-
 esphome/components/improv_serial/__init__.py  |  33 +++
 .../improv_serial/improv_serial_component.cpp | 250 ++++++++++++++++++
 .../improv_serial/improv_serial_component.h   |  69 +++++
 esphome/components/logger/logger.cpp          |   2 +-
 esphome/components/web_server/__init__.py     |   2 +
 esphome/components/wifi/__init__.py           |   3 +-
 esphome/components/wifi/wifi_component.cpp    |   2 -
 esphome/core/defines.h                        |   1 +
 script/ci-custom.py                           |   6 +-
 tests/test3.yaml                              |   2 +
 17 files changed, 391 insertions(+), 14 deletions(-)
 create mode 100644 esphome/components/improv_serial/__init__.py
 create mode 100644 esphome/components/improv_serial/improv_serial_component.cpp
 create mode 100644 esphome/components/improv_serial/improv_serial_component.h

diff --git a/CODEOWNERS b/CODEOWNERS
index 8f98fe1f7f..18b4564280 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -73,6 +73,7 @@ esphome/components/homeassistant/* @OttoWinter
 esphome/components/hrxl_maxsonar_wr/* @netmikey
 esphome/components/i2c/* @esphome/core
 esphome/components/improv/* @jesserockz
+esphome/components/improv_serial/* @esphome/core
 esphome/components/inkbird_ibsth1_mini/* @fkirill
 esphome/components/inkplate6/* @jesserockz
 esphome/components/integration/* @OttoWinter
diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp
index 9e00adae3d..ad4c32bb1f 100644
--- a/esphome/components/captive_portal/captive_portal.cpp
+++ b/esphome/components/captive_portal/captive_portal.cpp
@@ -67,6 +67,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
   ESP_LOGI(TAG, "  SSID='%s'", ssid.c_str());
   ESP_LOGI(TAG, "  Password=" LOG_SECRET("'%s'"), psk.c_str());
   wifi::global_wifi_component->save_wifi_sta(ssid, psk);
+  wifi::global_wifi_component->start_scanning();
   request->redirect("/?save=true");
 }
 
diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py
index d84663b2d6..1c249476e7 100644
--- a/esphome/components/esp32/__init__.py
+++ b/esphome/components/esp32/__init__.py
@@ -28,6 +28,7 @@ from .const import (  # noqa
     KEY_SDKCONFIG_OPTIONS,
     KEY_VARIANT,
     VARIANT_ESP32C3,
+    VARIANT_FRIENDLY,
     VARIANTS,
 )
 from .boards import BOARD_TO_VARIANT
@@ -287,6 +288,7 @@ async def to_code(config):
     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]}")
+    cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]])
 
     cg.add_platformio_option("lib_ldf_mode", "off")
 
diff --git a/esphome/components/esp32/const.py b/esphome/components/esp32/const.py
index b82f03bf68..d92b449ee9 100644
--- a/esphome/components/esp32/const.py
+++ b/esphome/components/esp32/const.py
@@ -18,4 +18,12 @@ VARIANTS = [
     VARIANT_ESP32H2,
 ]
 
+VARIANT_FRIENDLY = {
+    VARIANT_ESP32: "ESP32",
+    VARIANT_ESP32S2: "ESP32-S2",
+    VARIANT_ESP32S3: "ESP32-S3",
+    VARIANT_ESP32C3: "ESP32-C3",
+    VARIANT_ESP32H2: "ESP32-H2",
+}
+
 esp32_ns = cg.esphome_ns.namespace("esp32")
diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py
index 5b97d2d9d5..34c792499d 100644
--- a/esphome/components/esp8266/__init__.py
+++ b/esphome/components/esp8266/__init__.py
@@ -156,6 +156,7 @@ 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])
+    cg.add_define("ESPHOME_VARIANT", "ESP8266")
 
     conf = config[CONF_FRAMEWORK]
     cg.add_platformio_option("framework", "arduino")
diff --git a/esphome/components/improv/improv.cpp b/esphome/components/improv/improv.cpp
index 4f6ed7702d..94068bc626 100644
--- a/esphome/components/improv/improv.cpp
+++ b/esphome/components/improv/improv.cpp
@@ -7,11 +7,13 @@ ImprovCommand parse_improv_data(const std::vector &data) {
 }
 
 ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
+  ImprovCommand improv_command;
   Command command = (Command) data[0];
   uint8_t data_length = data[1];
 
   if (data_length != length - 3) {
-    return {.command = UNKNOWN};
+    improv_command.command = UNKNOWN;
+    return improv_command;
   }
 
   uint8_t checksum = data[length - 1];
@@ -22,7 +24,8 @@ ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
   }
 
   if ((uint8_t) calculated_checksum != checksum) {
-    return {.command = BAD_CHECKSUM};
+    improv_command.command = BAD_CHECKSUM;
+    return improv_command;
   }
 
   if (command == WIFI_SETTINGS) {
@@ -39,9 +42,8 @@ ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
     return {.command = command, .ssid = ssid, .password = password};
   }
 
-  return {
-      .command = command,
-  };
+  improv_command.command = command;
+  return improv_command;
 }
 
 std::vector build_rpc_response(Command command, const std::vector &datum) {
diff --git a/esphome/components/improv/improv.h b/esphome/components/improv/improv.h
index 0ead80e2cf..542eb82bd3 100644
--- a/esphome/components/improv/improv.h
+++ b/esphome/components/improv/improv.h
@@ -1,8 +1,8 @@
 #pragma once
 
-#ifdef USE_ARDUINO
+#ifdef ARDUINO
 #include "WString.h"
-#endif  // USE_ARDUINO
+#endif  // ARDUINO
 
 #include 
 #include 
@@ -38,6 +38,8 @@ enum Command : uint8_t {
   UNKNOWN = 0x00,
   WIFI_SETTINGS = 0x01,
   IDENTIFY = 0x02,
+  GET_CURRENT_STATE = 0x02,
+  GET_DEVICE_INFO = 0x03,
   BAD_CHECKSUM = 0xFF,
 };
 
@@ -53,8 +55,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
+#ifdef ARDUINO
 std::vector build_rpc_response(Command command, const std::vector &datum);
-#endif  // USE_ARDUINO
+#endif  // ARDUINO
 
 }  // namespace improv
diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py
new file mode 100644
index 0000000000..b1cdc2d93e
--- /dev/null
+++ b/esphome/components/improv_serial/__init__.py
@@ -0,0 +1,33 @@
+from esphome.const import CONF_BAUD_RATE, CONF_ID, CONF_LOGGER
+import esphome.codegen as cg
+import esphome.config_validation as cv
+import esphome.final_validate as fv
+
+CODEOWNERS = ["@esphome/core"]
+DEPENDENCIES = ["logger", "wifi"]
+AUTO_LOAD = ["improv"]
+
+improv_serial_ns = cg.esphome_ns.namespace("improv_serial")
+
+ImprovSerialComponent = improv_serial_ns.class_("ImprovSerialComponent", cg.Component)
+
+CONFIG_SCHEMA = cv.Schema(
+    {
+        cv.GenerateID(): cv.declare_id(ImprovSerialComponent),
+    }
+).extend(cv.COMPONENT_SCHEMA)
+
+
+def validate_logger_baud_rate(config):
+    logger_conf = fv.full_config.get()[CONF_LOGGER]
+    if logger_conf[CONF_BAUD_RATE] == 0:
+        raise cv.Invalid("improv_serial requires the logger baud_rate to be not 0")
+    return config
+
+
+FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate
+
+
+async def to_code(config):
+    var = cg.new_Pvariable(config[CONF_ID])
+    await cg.register_component(var, config)
diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp
new file mode 100644
index 0000000000..a12f1bd83b
--- /dev/null
+++ b/esphome/components/improv_serial/improv_serial_component.cpp
@@ -0,0 +1,250 @@
+#include "improv_serial_component.h"
+
+#include "esphome/core/application.h"
+#include "esphome/core/defines.h"
+#include "esphome/core/hal.h"
+#include "esphome/core/log.h"
+#include "esphome/core/version.h"
+
+#include "esphome/components/logger/logger.h"
+
+namespace esphome {
+namespace improv_serial {
+
+static const char *const TAG = "improv_serial";
+
+void ImprovSerialComponent::setup() {
+  global_improv_serial_component = this;
+#ifdef USE_ARDUINO
+  this->hw_serial_ = logger::global_logger->get_hw_serial();
+#endif
+#ifdef USE_ESP_IDF
+  this->uart_num_ = logger::global_logger->get_uart_num();
+#endif
+
+  if (wifi::global_wifi_component->has_sta()) {
+    this->state_ = improv::STATE_PROVISIONED;
+  }
+}
+
+void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:"); }
+
+int ImprovSerialComponent::available_() {
+#ifdef USE_ARDUINO
+  return this->hw_serial_->available();
+#endif
+#ifdef USE_ESP_IDF
+  size_t available;
+  uart_get_buffered_data_len(this->uart_num_, &available);
+  return available;
+#endif
+}
+
+uint8_t ImprovSerialComponent::read_byte_() {
+  uint8_t data;
+#ifdef USE_ARDUINO
+  this->hw_serial_->readBytes(&data, 1);
+#endif
+#ifdef USE_ESP_IDF
+  uart_read_bytes(this->uart_num_, &data, 1, 20 / portTICK_RATE_MS);
+#endif
+  return data;
+}
+
+void ImprovSerialComponent::write_data_(std::vector &data) {
+  data.push_back('\n');
+#ifdef USE_ARDUINO
+  this->hw_serial_->write(data.data(), data.size());
+#endif
+#ifdef USE_ESP_IDF
+  uart_write_bytes(this->uart_num_, data.data(), data.size());
+#endif
+}
+
+void ImprovSerialComponent::loop() {
+  const uint32_t now = millis();
+  if (now - this->last_read_byte_ > 50) {
+    this->rx_buffer_.clear();
+    this->last_read_byte_ = now;
+  }
+
+  while (this->available_()) {
+    uint8_t byte = this->read_byte_();
+    if (this->parse_improv_serial_byte_(byte)) {
+      this->last_read_byte_ = now;
+    } else {
+      this->rx_buffer_.clear();
+    }
+  }
+
+  if (this->state_ == improv::STATE_PROVISIONING) {
+    if (wifi::global_wifi_component->is_connected()) {
+      wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
+                                                 this->connecting_sta_.get_password());
+      this->connecting_sta_ = {};
+      this->cancel_timeout("wifi-connect-timeout");
+      this->set_state_(improv::STATE_PROVISIONED);
+
+      std::vector url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
+      this->send_response_(url);
+    }
+  }
+}
+
+std::vector ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
+  std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
+  std::vector urls = {url};
+#ifdef USE_WEBSERVER
+  auto ip = wifi::global_wifi_component->wifi_sta_ip();
+  std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
+  urls.push_back(webserver_url);
+#endif
+  std::vector data = improv::build_rpc_response(command, urls);
+  return data;
+}
+
+std::vector ImprovSerialComponent::build_version_info_() {
+  std::vector infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
+  std::vector data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos);
+  return data;
+};
+
+bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
+  size_t at = this->rx_buffer_.size();
+  this->rx_buffer_.push_back(byte);
+  ESP_LOGD(TAG, "Improv Serial byte: 0x%02X", byte);
+  const uint8_t *raw = &this->rx_buffer_[0];
+  if (at == 0)
+    return byte == 'I';
+  if (at == 1)
+    return byte == 'M';
+  if (at == 2)
+    return byte == 'P';
+  if (at == 3)
+    return byte == 'R';
+  if (at == 4)
+    return byte == 'O';
+  if (at == 5)
+    return byte == 'V';
+
+  if (at == 6)
+    return byte == IMPROV_SERIAL_VERSION;
+
+  if (at == 7)
+    return true;
+  uint8_t type = raw[7];
+
+  if (at == 8)
+    return true;
+  uint8_t data_len = raw[8];
+
+  if (at < 8 + data_len)
+    return true;
+
+  if (at == 8 + data_len) {
+    if (type == TYPE_RPC) {
+      this->set_error_(improv::ERROR_NONE);
+      auto command = improv::parse_improv_data(&raw[9], data_len);
+      return this->parse_improv_payload_(command);
+    }
+  }
+  return true;
+}
+
+bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
+  switch (command.command) {
+    case improv::BAD_CHECKSUM:
+      ESP_LOGW(TAG, "Error decoding Improv payload");
+      this->set_error_(improv::ERROR_INVALID_RPC);
+      return false;
+    case improv::WIFI_SETTINGS: {
+      wifi::WiFiAP sta{};
+      sta.set_ssid(command.ssid);
+      sta.set_password(command.password);
+      this->connecting_sta_ = sta;
+
+      wifi::global_wifi_component->set_sta(sta);
+      wifi::global_wifi_component->start_scanning();
+      this->set_state_(improv::STATE_PROVISIONING);
+      ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
+               command.password.c_str());
+
+      auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
+      this->set_timeout("wifi-connect-timeout", 30000, f);
+      return true;
+    }
+    case improv::GET_CURRENT_STATE:
+      this->set_state_(this->state_);
+      if (this->state_ == improv::STATE_PROVISIONED) {
+        std::vector url = this->build_rpc_settings_response_(improv::GET_CURRENT_STATE);
+        this->send_response_(url);
+      }
+      return true;
+    case improv::GET_DEVICE_INFO: {
+      std::vector info = this->build_version_info_();
+      this->send_response_(info);
+      return true;
+    }
+    default: {
+      ESP_LOGW(TAG, "Unknown Improv payload");
+      this->set_error_(improv::ERROR_UNKNOWN_RPC);
+      return false;
+    }
+  }
+}
+
+void ImprovSerialComponent::set_state_(improv::State state) {
+  this->state_ = state;
+
+  std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'};
+  data.resize(11);
+  data[6] = IMPROV_SERIAL_VERSION;
+  data[7] = TYPE_CURRENT_STATE;
+  data[8] = 1;
+  data[9] = state;
+
+  uint8_t checksum = 0x00;
+  for (uint8_t d : data)
+    checksum += d;
+  data[10] = checksum;
+
+  this->write_data_(data);
+}
+
+void ImprovSerialComponent::set_error_(improv::Error error) {
+  std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'};
+  data.resize(11);
+  data[6] = IMPROV_SERIAL_VERSION;
+  data[7] = TYPE_ERROR_STATE;
+  data[8] = 1;
+  data[9] = error;
+
+  uint8_t checksum = 0x00;
+  for (uint8_t d : data)
+    checksum += d;
+  data[10] = checksum;
+  this->write_data_(data);
+}
+
+void ImprovSerialComponent::send_response_(std::vector &response) {
+  std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'};
+  data.resize(9);
+  data[6] = IMPROV_SERIAL_VERSION;
+  data[7] = TYPE_RPC_RESPONSE;
+  data[8] = response.size();
+  data.insert(data.end(), response.begin(), response.end());
+  this->write_data_(data);
+}
+
+void ImprovSerialComponent::on_wifi_connect_timeout_() {
+  this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
+  this->set_state_(improv::STATE_AUTHORIZED);
+  ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
+  wifi::global_wifi_component->clear_sta();
+}
+
+ImprovSerialComponent *global_improv_serial_component =  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
+    nullptr;                                             // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
+
+}  // namespace improv_serial
+}  // namespace esphome
diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h
new file mode 100644
index 0000000000..539674e2d3
--- /dev/null
+++ b/esphome/components/improv_serial/improv_serial_component.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include "esphome/components/improv/improv.h"
+#include "esphome/components/wifi/wifi_component.h"
+#include "esphome/core/component.h"
+#include "esphome/core/defines.h"
+#include "esphome/core/helpers.h"
+
+#ifdef USE_ARDUINO
+#include 
+#endif
+#ifdef USE_ESP_IDF
+#include 
+#endif
+
+namespace esphome {
+namespace improv_serial {
+
+enum ImprovSerialType : uint8_t {
+  TYPE_CURRENT_STATE = 0x01,
+  TYPE_ERROR_STATE = 0x02,
+  TYPE_RPC = 0x03,
+  TYPE_RPC_RESPONSE = 0x04
+};
+
+static const uint8_t IMPROV_SERIAL_VERSION = 1;
+
+class ImprovSerialComponent : public Component {
+ public:
+  void setup() override;
+  void loop() override;
+  void dump_config() override;
+
+  float get_setup_priority() const override { return setup_priority::HARDWARE; }
+
+ protected:
+  bool parse_improv_serial_byte_(uint8_t byte);
+  bool parse_improv_payload_(improv::ImprovCommand &command);
+
+  void set_state_(improv::State state);
+  void set_error_(improv::Error error);
+  void send_response_(std::vector &response);
+  void on_wifi_connect_timeout_();
+
+  std::vector build_rpc_settings_response_(improv::Command command);
+  std::vector build_version_info_();
+
+  int available_();
+  uint8_t read_byte_();
+  void write_data_(std::vector &data);
+
+#ifdef USE_ARDUINO
+  HardwareSerial *hw_serial_{nullptr};
+#endif
+#ifdef USE_ESP_IDF
+  uart_port_t uart_num_;
+#endif
+
+  std::vector rx_buffer_;
+  uint32_t last_read_byte_{0};
+  wifi::WiFiAP connecting_sta_;
+  improv::State state_{improv::STATE_AUTHORIZED};
+};
+
+extern ImprovSerialComponent
+    *global_improv_serial_component;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
+
+}  // namespace improv_serial
+}  // namespace esphome
diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp
index 97ad4c2cb9..11c0733701 100644
--- a/esphome/components/logger/logger.cpp
+++ b/esphome/components/logger/logger.cpp
@@ -221,7 +221,7 @@ UARTSelection Logger::get_uart() const { return this->uart_; }
 void Logger::add_on_log_callback(std::function &&callback) {
   this->log_callback_.add(std::move(callback));
 }
-float Logger::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
+float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
 const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
 #ifdef USE_ESP32
 const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART2"};
diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py
index 61b1fa5ad6..dc652e0312 100644
--- a/esphome/components/web_server/__init__.py
+++ b/esphome/components/web_server/__init__.py
@@ -54,6 +54,8 @@ async def to_code(config):
     var = cg.new_Pvariable(config[CONF_ID], paren)
     await cg.register_component(var, config)
 
+    cg.add_define("USE_WEBSERVER")
+
     cg.add(paren.set_port(config[CONF_PORT]))
     cg.add_define("WEBSERVER_PORT", config[CONF_PORT])
     cg.add_define("USE_WEBSERVER")
diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py
index faf3cca280..7a9319f5e0 100644
--- a/esphome/components/wifi/__init__.py
+++ b/esphome/components/wifi/__init__.py
@@ -140,7 +140,8 @@ 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):
+    has_improv_serial = "improv_serial" in fv.full_config.get()
+    if not (has_sta or has_ap or has_improv or has_improv_serial):
         raise cv.Invalid(
             "Please specify at least an SSID or an Access Point to create."
         )
diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp
index 703afa99bc..36944e3633 100644
--- a/esphome/components/wifi/wifi_component.cpp
+++ b/esphome/components/wifi/wifi_component.cpp
@@ -239,8 +239,6 @@ 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) {
diff --git a/esphome/core/defines.h b/esphome/core/defines.h
index dc07bde196..94fac73906 100644
--- a/esphome/core/defines.h
+++ b/esphome/core/defines.h
@@ -9,6 +9,7 @@
 #define ESPHOME_BOARD "dummy_board"
 #define ESPHOME_PROJECT_NAME "dummy project"
 #define ESPHOME_PROJECT_VERSION "v2"
+#define ESPHOME_VARIANT "ESP32"
 
 // Feature flags
 #define USE_API
diff --git a/script/ci-custom.py b/script/ci-custom.py
index 8e9ca487a6..89550afd3d 100755
--- a/script/ci-custom.py
+++ b/script/ci-custom.py
@@ -263,7 +263,11 @@ 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", "esphome/components/socket/headers.h"],
+    exclude=[
+        "esphome/core/log.h",
+        "esphome/components/socket/headers.h",
+        "esphome/core/defines.h",
+    ],
 )
 def lint_no_defines(fname, match):
     s = highlight(
diff --git a/tests/test3.yaml b/tests/test3.yaml
index 0b7f1ad71e..cf80c06aa8 100644
--- a/tests/test3.yaml
+++ b/tests/test3.yaml
@@ -268,6 +268,8 @@ logger:
   level: DEBUG
   esp8266_store_log_strings_in_flash: true
 
+improv_serial:
+
 deep_sleep:
   run_duration: 20s
   sleep_duration: 50s

From f310cacd411c743b0850b6bc26fcc3ada7078a1d Mon Sep 17 00:00:00 2001
From: anatoly-savchenkov
 <48646998+anatoly-savchenkov@users.noreply.github.com>
Date: Thu, 11 Nov 2021 00:01:47 +0300
Subject: [PATCH 314/549] [ms5611] Re-implement conversion from ADC readings to
 sensor values (#2665)

---
 esphome/components/ms5611/ms5611.cpp | 52 +++++++++++++++++++---------
 1 file changed, 35 insertions(+), 17 deletions(-)

diff --git a/esphome/components/ms5611/ms5611.cpp b/esphome/components/ms5611/ms5611.cpp
index 1d7516dbe8..4b34e1d71a 100644
--- a/esphome/components/ms5611/ms5611.cpp
+++ b/esphome/components/ms5611/ms5611.cpp
@@ -75,30 +75,48 @@ void MS5611Component::read_pressure_(uint32_t raw_temperature) {
   const uint32_t raw_pressure = (uint32_t(bytes[0]) << 16) | (uint32_t(bytes[1]) << 8) | (uint32_t(bytes[2]));
   this->calculate_values_(raw_temperature, raw_pressure);
 }
+
+// Calculations are taken from the datasheet which can be found here:
+// https://www.te.com/commerce/DocumentDelivery/DDEController?Action=showdoc&DocId=Data+Sheet%7FMS5611-01BA03%7FB3%7Fpdf%7FEnglish%7FENG_DS_MS5611-01BA03_B3.pdf%7FCAT-BLPS0036
+// Sections PRESSURE AND TEMPERATURE CALCULATION and SECOND ORDER TEMPERATURE COMPENSATION
+// Variable names below match variable names from the datasheet but lowercased
 void MS5611Component::calculate_values_(uint32_t raw_temperature, uint32_t raw_pressure) {
-  const int32_t d_t = int32_t(raw_temperature) - (uint32_t(this->prom_[4]) << 8);
-  float temperature = (2000 + (int64_t(d_t) * this->prom_[5]) / 8388608.0f) / 100.0f;
+  const uint32_t c1 = uint32_t(this->prom_[0]);
+  const uint32_t c2 = uint32_t(this->prom_[1]);
+  const uint16_t c3 = uint16_t(this->prom_[2]);
+  const uint16_t c4 = uint16_t(this->prom_[3]);
+  const int32_t c5 = int32_t(this->prom_[4]);
+  const uint16_t c6 = uint16_t(this->prom_[5]);
+  const uint32_t d1 = raw_pressure;
+  const int32_t d2 = raw_temperature;
 
-  float pressure_offset = (uint32_t(this->prom_[1]) << 16) + ((this->prom_[3] * d_t) >> 7);
-  float pressure_sensitivity = (uint32_t(this->prom_[0]) << 15) + ((this->prom_[2] * d_t) >> 8);
+  // Promote dt to 64 bit here to make the math below cleaner
+  const int64_t dt = d2 - (c5 << 8);
+  int32_t temp = (2000 + ((dt * c6) >> 23));
 
-  if (temperature < 20.0f) {
-    const float t2 = (d_t * d_t) / 2147483648.0f;
-    const float temp20 = (temperature - 20.0f) * 100.0f;
-    float pressure_offset_2 = 2.5f * temp20 * temp20;
-    float pressure_sensitivity_2 = 1.25f * temp20 * temp20;
-    if (temp20 < -15.0f) {
-      const float temp15 = (temperature + 15.0f) * 100.0f;
-      pressure_offset_2 += 7.0f * temp15;
-      pressure_sensitivity_2 += 5.5f * temp15;
+  int64_t off = (c2 << 16) + ((dt * c4) >> 7);
+  int64_t sens = (c1 << 15) + ((dt * c3) >> 8);
+
+  if (temp < 2000) {
+    const int32_t t2 = (dt * dt) >> 31;
+    int32_t off2 = ((5 * (temp - 2000) * (temp - 2000)) >> 1);
+    int32_t sens2 = ((5 * (temp - 2000) * (temp - 2000)) >> 2);
+    if (temp < -1500) {
+      off2 = (off2 + 7 * (temp + 1500) * (temp + 1500));
+      sens2 = sens2 + ((11 * (temp + 1500) * (temp + 1500)) >> 1);
     }
-    temperature -= t2;
-    pressure_offset -= pressure_offset_2;
-    pressure_sensitivity -= pressure_sensitivity_2;
+    temp = temp - t2;
+    off = off - off2;
+    sens = sens - sens2;
   }
 
-  const float pressure = ((raw_pressure * pressure_sensitivity) / 2097152.0f - pressure_offset) / 3276800.0f;
+  // Here we multiply unsigned 32-bit by signed 64-bit using signed 64-bit math.
+  // Possible ranges of D1 and SENS from the datasheet guarantee
+  // that this multiplication does not overflow
+  const int32_t p = ((((d1 * sens) >> 21) - off) >> 15);
 
+  const float temperature = temp / 100.0f;
+  const float pressure = p / 100.0f;
   ESP_LOGD(TAG, "Got temperature=%0.02f°C pressure=%0.01fhPa", temperature, pressure);
 
   if (this->temperature_sensor_ != nullptr)

From 04ba53c870344ef23d2eb1a19b7573310485770b Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Thu, 11 Nov 2021 10:10:05 +1300
Subject: [PATCH 315/549] Bump version to 2021.12.0-dev

---
 esphome/const.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/esphome/const.py b/esphome/const.py
index ff8510b40e..99cb53fe4b 100644
--- a/esphome/const.py
+++ b/esphome/const.py
@@ -1,6 +1,6 @@
 """Constants used by esphome."""
 
-__version__ = "2021.11.0-dev"
+__version__ = "2021.12.0-dev"
 
 ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
 

From 4395d6156daa3c283a55a4f16950455598c32120 Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Thu, 11 Nov 2021 11:24:48 +1300
Subject: [PATCH 316/549] Fix template number initial value being NaN (#2692)

---
 esphome/components/template/number/__init__.py | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/esphome/components/template/number/__init__.py b/esphome/components/template/number/__init__.py
index 887f6b15ad..3dec7066d3 100644
--- a/esphome/components/template/number/__init__.py
+++ b/esphome/components/template/number/__init__.py
@@ -35,6 +35,9 @@ 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_VALUE not in config:
+        config[CONF_INITIAL_VALUE] = config[CONF_MIN_VALUE]
+
     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."
@@ -80,8 +83,7 @@ async def to_code(config):
 
     else:
         cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
-        if CONF_INITIAL_VALUE in config:
-            cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE]))
+        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]))
 

From abf3708cc2b80f69d061800e7aa6c85293440499 Mon Sep 17 00:00:00 2001
From: Carlos Garcia Saura 
Date: Wed, 10 Nov 2021 23:28:45 +0100
Subject: [PATCH 317/549] [remote_transmitter] accurate pulse timing for
 ESP8266 (#2476)

---
 .../remote_transmitter/remote_transmitter.h   |  3 +
 .../remote_transmitter_esp8266.cpp            | 78 ++++++++++---------
 2 files changed, 46 insertions(+), 35 deletions(-)

diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h
index 733ac5e50d..a4235e875f 100644
--- a/esphome/components/remote_transmitter/remote_transmitter.h
+++ b/esphome/components/remote_transmitter/remote_transmitter.h
@@ -32,6 +32,9 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
   void mark_(uint32_t on_time, uint32_t off_time, uint32_t usec);
 
   void space_(uint32_t usec);
+
+  void await_target_time_();
+  uint32_t target_time_;
 #endif
 
 #ifdef USE_ESP32
diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp
index 74e62d4e3b..39752cac5b 100644
--- a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp
+++ b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp
@@ -33,56 +33,64 @@ void RemoteTransmitterComponent::calculate_on_off_time_(uint32_t carrier_frequen
   *off_time_period = period - *on_time_period;
 }
 
+void RemoteTransmitterComponent::await_target_time_() {
+  const uint32_t current_time = micros();
+  if (this->target_time_ == 0)
+    this->target_time_ = current_time;
+  else if (this->target_time_ > current_time)
+    delayMicroseconds(this->target_time_ - current_time);
+}
+
 void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint32_t usec) {
-  if (this->carrier_duty_percent_ == 100 || (on_time == 0 && off_time == 0)) {
-    this->pin_->digital_write(true);
-    delayMicroseconds(usec);
-    this->pin_->digital_write(false);
-    return;
-  }
-
-  const uint32_t start_time = micros();
-  uint32_t current_time = start_time;
-
-  while (current_time - start_time < usec) {
-    const uint32_t elapsed = current_time - start_time;
-    this->pin_->digital_write(true);
-
-    delayMicroseconds(std::min(on_time, usec - elapsed));
-    this->pin_->digital_write(false);
-    if (elapsed + on_time >= usec)
-      return;
-
-    delayMicroseconds(std::min(usec - elapsed - on_time, off_time));
-
-    current_time = micros();
+  this->await_target_time_();
+  this->pin_->digital_write(true);
+
+  const uint32_t target = this->target_time_ + usec;
+  if (this->carrier_duty_percent_ < 100 && (on_time > 0 || off_time > 0)) {
+    while (true) {  // Modulate with carrier frequency
+      this->target_time_ += on_time;
+      if (this->target_time_ >= target)
+        break;
+      this->await_target_time_();
+      this->pin_->digital_write(false);
+
+      this->target_time_ += off_time;
+      if (this->target_time_ >= target)
+        break;
+      this->await_target_time_();
+      this->pin_->digital_write(true);
+    }
   }
+  this->target_time_ = target;
 }
+
 void RemoteTransmitterComponent::space_(uint32_t usec) {
+  this->await_target_time_();
   this->pin_->digital_write(false);
-  delayMicroseconds(usec);
+  this->target_time_ += usec;
 }
+
 void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
   ESP_LOGD(TAG, "Sending remote code...");
   uint32_t on_time, off_time;
   this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time);
+  this->target_time_ = 0;
   for (uint32_t i = 0; i < send_times; i++) {
-    {
-      InterruptLock lock;
-      for (int32_t item : this->temp_.get_data()) {
-        if (item > 0) {
-          const auto length = uint32_t(item);
-          this->mark_(on_time, off_time, length);
-        } else {
-          const auto length = uint32_t(-item);
-          this->space_(length);
-        }
-        App.feed_wdt();
+    for (int32_t item : this->temp_.get_data()) {
+      if (item > 0) {
+        const auto length = uint32_t(item);
+        this->mark_(on_time, off_time, length);
+      } else {
+        const auto length = uint32_t(-item);
+        this->space_(length);
       }
+      App.feed_wdt();
     }
+    this->await_target_time_();  // wait for duration of last pulse
+    this->pin_->digital_write(false);
 
     if (i + 1 < send_times)
-      delayMicroseconds(send_wait);
+      this->target_time_ += send_wait;
   }
 }
 

From e99af991ecc63c942b64dfba17ad1d70261f415e Mon Sep 17 00:00:00 2001
From: Maurice Makaay 
Date: Wed, 10 Nov 2021 23:34:17 +0100
Subject: [PATCH 318/549] Uart debugging support (#2478)

Co-authored-by: Maurice Makaay 
Co-authored-by: Maurice Makaay 
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
---
 esphome/components/uart/__init__.py           |  69 +++++++
 esphome/components/uart/uart.h                |   6 +-
 esphome/components/uart/uart_component.h      |  21 ++
 .../uart/uart_component_esp32_arduino.cpp     |  12 +-
 .../uart/uart_component_esp8266.cpp           |   9 +-
 .../uart/uart_component_esp_idf.cpp           |  12 +-
 esphome/components/uart/uart_debugger.cpp     | 193 ++++++++++++++++++
 esphome/components/uart/uart_debugger.h       | 101 +++++++++
 esphome/const.py                              |   7 +
 esphome/core/defines.h                        |   1 +
 tests/test1.yaml                              |  12 ++
 11 files changed, 430 insertions(+), 13 deletions(-)
 create mode 100644 esphome/components/uart/uart_debugger.cpp
 create mode 100644 esphome/components/uart/uart_debugger.h

diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py
index 35af3eedf7..53209dfc7b 100644
--- a/esphome/components/uart/__init__.py
+++ b/esphome/components/uart/__init__.py
@@ -14,6 +14,16 @@ from esphome.const import (
     CONF_DATA,
     CONF_RX_BUFFER_SIZE,
     CONF_INVERT,
+    CONF_TRIGGER_ID,
+    CONF_SEQUENCE,
+    CONF_TIMEOUT,
+    CONF_DEBUG,
+    CONF_DIRECTION,
+    CONF_AFTER,
+    CONF_BYTES,
+    CONF_DELIMITER,
+    CONF_DUMMY_RECEIVER,
+    CONF_DUMMY_RECEIVER_ID,
 )
 from esphome.core import CORE
 
@@ -31,6 +41,8 @@ ESP8266UartComponent = uart_ns.class_(
 
 UARTDevice = uart_ns.class_("UARTDevice")
 UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action)
+UARTDebugger = uart_ns.class_("UARTDebugger", cg.Component, automation.Action)
+UARTDummyReceiver = uart_ns.class_("UARTDummyReceiver", cg.Component)
 MULTI_CONF = True
 
 
@@ -75,6 +87,34 @@ CONF_STOP_BITS = "stop_bits"
 CONF_DATA_BITS = "data_bits"
 CONF_PARITY = "parity"
 
+UARTDirection = uart_ns.enum("UARTDirection")
+UART_DIRECTIONS = {
+    "RX": UARTDirection.UART_DIRECTION_RX,
+    "TX": UARTDirection.UART_DIRECTION_TX,
+    "BOTH": UARTDirection.UART_DIRECTION_BOTH,
+}
+
+DEBUG_SCHEMA = cv.Schema(
+    {
+        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UARTDebugger),
+        cv.Optional(CONF_DIRECTION, default="BOTH"): cv.enum(
+            UART_DIRECTIONS, upper=True
+        ),
+        cv.Optional(CONF_AFTER): cv.Schema(
+            {
+                cv.Optional(CONF_BYTES, default=256): cv.validate_bytes,
+                cv.Optional(
+                    CONF_TIMEOUT, default="100ms"
+                ): cv.positive_time_period_milliseconds,
+                cv.Optional(CONF_DELIMITER): cv.templatable(validate_raw_data),
+            }
+        ),
+        cv.Required(CONF_SEQUENCE): automation.validate_automation(),
+        cv.Optional(CONF_DUMMY_RECEIVER, default=False): cv.boolean,
+        cv.GenerateID(CONF_DUMMY_RECEIVER_ID): cv.declare_id(UARTDummyReceiver),
+    }
+)
+
 CONFIG_SCHEMA = cv.All(
     cv.Schema(
         {
@@ -91,12 +131,38 @@ CONFIG_SCHEMA = cv.All(
             cv.Optional(CONF_INVERT): cv.invalid(
                 "This option has been removed. Please instead use invert in the tx/rx pin schemas."
             ),
+            cv.Optional(CONF_DEBUG): DEBUG_SCHEMA,
         }
     ).extend(cv.COMPONENT_SCHEMA),
     cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN),
 )
 
 
+async def debug_to_code(config, parent):
+    trigger = cg.new_Pvariable(config[CONF_TRIGGER_ID], parent)
+    await cg.register_component(trigger, config)
+    for action in config[CONF_SEQUENCE]:
+        await automation.build_automation(
+            trigger,
+            [(UARTDirection, "direction"), (cg.std_vector.template(cg.uint8), "bytes")],
+            action,
+        )
+    cg.add(trigger.set_direction(config[CONF_DIRECTION]))
+    after = config[CONF_AFTER]
+    cg.add(trigger.set_after_bytes(after[CONF_BYTES]))
+    cg.add(trigger.set_after_timeout(after[CONF_TIMEOUT]))
+    if CONF_DELIMITER in after:
+        data = after[CONF_DELIMITER]
+        if isinstance(data, bytes):
+            data = list(data)
+        for byte in after[CONF_DELIMITER]:
+            cg.add(trigger.add_delimiter_byte(byte))
+    if config[CONF_DUMMY_RECEIVER]:
+        dummy = cg.new_Pvariable(config[CONF_DUMMY_RECEIVER_ID], parent)
+        await cg.register_component(dummy, {})
+    cg.add_define("USE_UART_DEBUGGER")
+
+
 async def to_code(config):
     cg.add_global(uart_ns.using)
     var = cg.new_Pvariable(config[CONF_ID])
@@ -115,6 +181,9 @@ async def to_code(config):
     cg.add(var.set_data_bits(config[CONF_DATA_BITS]))
     cg.add(var.set_parity(config[CONF_PARITY]))
 
+    if CONF_DEBUG in config:
+        await debug_to_code(config[CONF_DEBUG], var)
+
 
 # A schema to use for all UART devices, all UART integrations must extend this!
 UART_DEVICE_SCHEMA = cv.Schema(
diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h
index c368f9ed6b..d41dbe26e6 100644
--- a/esphome/components/uart/uart.h
+++ b/esphome/components/uart/uart.h
@@ -45,17 +45,17 @@ class UARTDevice {
   // Compat APIs
   int read() {
     uint8_t data;
-    if (!read_byte(&data))
+    if (!this->read_byte(&data))
       return -1;
     return data;
   }
   size_t write(uint8_t data) {
-    write_byte(data);
+    this->write_byte(data);
     return 1;
   }
   int peek() {
     uint8_t data;
-    if (!peek_byte(&data))
+    if (!this->peek_byte(&data))
       return -1;
     return data;
   }
diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h
index de85cd2ca3..73694d3db7 100644
--- a/esphome/components/uart/uart_component.h
+++ b/esphome/components/uart/uart_component.h
@@ -2,9 +2,13 @@
 
 #include 
 #include 
+#include "esphome/core/defines.h"
 #include "esphome/core/component.h"
 #include "esphome/core/hal.h"
 #include "esphome/core/log.h"
+#ifdef USE_UART_DEBUGGER
+#include "esphome/core/automation.h"
+#endif
 
 namespace esphome {
 namespace uart {
@@ -15,6 +19,14 @@ enum UARTParityOptions {
   UART_CONFIG_PARITY_ODD,
 };
 
+#ifdef USE_UART_DEBUGGER
+enum UARTDirection {
+  UART_DIRECTION_RX,
+  UART_DIRECTION_TX,
+  UART_DIRECTION_BOTH,
+};
+#endif
+
 const LogString *parity_to_str(UARTParityOptions parity);
 
 class UARTComponent {
@@ -50,6 +62,12 @@ class UARTComponent {
   void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; }
   uint32_t get_baud_rate() const { return baud_rate_; }
 
+#ifdef USE_UART_DEBUGGER
+  void add_debug_callback(std::function &&callback) {
+    this->debug_callback_.add(std::move(callback));
+  }
+#endif
+
  protected:
   virtual void check_logger_conflict() = 0;
   bool check_read_timeout_(size_t len = 1);
@@ -61,6 +79,9 @@ class UARTComponent {
   uint8_t stop_bits_;
   uint8_t data_bits_;
   UARTParityOptions parity_;
+#ifdef USE_UART_DEBUGGER
+  CallbackManager debug_callback_{};
+#endif
 };
 
 }  // namespace uart
diff --git a/esphome/components/uart/uart_component_esp32_arduino.cpp b/esphome/components/uart/uart_component_esp32_arduino.cpp
index 1b1ce382f2..95cdde4a43 100644
--- a/esphome/components/uart/uart_component_esp32_arduino.cpp
+++ b/esphome/components/uart/uart_component_esp32_arduino.cpp
@@ -117,26 +117,32 @@ void ESP32ArduinoUARTComponent::dump_config() {
 
 void ESP32ArduinoUARTComponent::write_array(const uint8_t *data, size_t len) {
   this->hw_serial_->write(data, len);
+#ifdef USE_UART_DEBUGGER
   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]);
+    this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
   }
+#endif
 }
+
 bool ESP32ArduinoUARTComponent::peek_byte(uint8_t *data) {
   if (!this->check_read_timeout_())
     return false;
   *data = this->hw_serial_->peek();
   return true;
 }
+
 bool ESP32ArduinoUARTComponent::read_array(uint8_t *data, size_t len) {
   if (!this->check_read_timeout_(len))
     return false;
   this->hw_serial_->readBytes(data, len);
+#ifdef USE_UART_DEBUGGER
   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]);
+    this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
   }
-
+#endif
   return true;
 }
+
 int ESP32ArduinoUARTComponent::available() { return this->hw_serial_->available(); }
 void ESP32ArduinoUARTComponent::flush() {
   ESP_LOGVV(TAG, "    Flushing...");
diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp
index 973306cde2..c367de05bb 100644
--- a/esphome/components/uart/uart_component_esp8266.cpp
+++ b/esphome/components/uart/uart_component_esp8266.cpp
@@ -130,9 +130,11 @@ void ESP8266UartComponent::write_array(const uint8_t *data, size_t len) {
     for (size_t i = 0; i < len; i++)
       this->sw_serial_->write_byte(data[i]);
   }
+#ifdef USE_UART_DEBUGGER
   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]);
+    this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
   }
+#endif
 }
 bool ESP8266UartComponent::peek_byte(uint8_t *data) {
   if (!this->check_read_timeout_())
@@ -153,10 +155,11 @@ bool ESP8266UartComponent::read_array(uint8_t *data, size_t len) {
     for (size_t i = 0; i < len; i++)
       data[i] = this->sw_serial_->read_byte();
   }
+#ifdef USE_UART_DEBUGGER
   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]);
+    this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
   }
-
+#endif
   return true;
 }
 int ESP8266UartComponent::available() {
diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp
index 1cccd5821e..4d6a6af0fc 100644
--- a/esphome/components/uart/uart_component_esp_idf.cpp
+++ b/esphome/components/uart/uart_component_esp_idf.cpp
@@ -130,10 +130,13 @@ 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_);
+#ifdef USE_UART_DEBUGGER
   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]);
+    this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
   }
+#endif
 }
+
 bool IDFUARTComponent::peek_byte(uint8_t *data) {
   if (!this->check_read_timeout_())
     return false;
@@ -152,6 +155,7 @@ bool IDFUARTComponent::peek_byte(uint8_t *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))
@@ -165,12 +169,12 @@ bool IDFUARTComponent::read_array(uint8_t *data, size_t len) {
   }
   if (length_to_read > 0)
     uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_RATE_MS);
-
   xSemaphoreGive(this->lock_);
+#ifdef USE_UART_DEBUGGER
   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]);
+    this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
   }
-
+#endif
   return true;
 }
 
diff --git a/esphome/components/uart/uart_debugger.cpp b/esphome/components/uart/uart_debugger.cpp
new file mode 100644
index 0000000000..9a535656a2
--- /dev/null
+++ b/esphome/components/uart/uart_debugger.cpp
@@ -0,0 +1,193 @@
+#include "esphome/core/defines.h"
+#ifdef USE_UART_DEBUGGER
+
+#include 
+#include "uart_debugger.h"
+#include "esphome/core/helpers.h"
+#include "esphome/core/log.h"
+
+namespace esphome {
+namespace uart {
+
+static const char *const TAG = "uart_debug";
+
+UARTDebugger::UARTDebugger(UARTComponent *parent) {
+  parent->add_debug_callback([this](UARTDirection direction, uint8_t byte) {
+    if (!this->is_my_direction_(direction) || this->is_recursive_()) {
+      return;
+    }
+    this->trigger_after_direction_change_(direction);
+    this->store_byte_(direction, byte);
+    this->trigger_after_delimiter_(byte);
+    this->trigger_after_bytes_();
+  });
+}
+
+void UARTDebugger::loop() { this->trigger_after_timeout_(); }
+
+bool UARTDebugger::is_my_direction_(UARTDirection direction) {
+  return this->for_direction_ == UART_DIRECTION_BOTH || this->for_direction_ == direction;
+}
+
+bool UARTDebugger::is_recursive_() { return this->is_triggering_; }
+
+void UARTDebugger::trigger_after_direction_change_(UARTDirection direction) {
+  if (this->has_buffered_bytes_() && this->for_direction_ == UART_DIRECTION_BOTH &&
+      this->last_direction_ != direction) {
+    this->fire_trigger_();
+  }
+}
+
+void UARTDebugger::store_byte_(UARTDirection direction, uint8_t byte) {
+  this->bytes_.push_back(byte);
+  this->last_direction_ = direction;
+  this->last_time_ = millis();
+}
+
+void UARTDebugger::trigger_after_delimiter_(uint8_t byte) {
+  if (this->after_delimiter_.empty() || !this->has_buffered_bytes_()) {
+    return;
+  }
+  if (this->after_delimiter_[this->after_delimiter_pos_] != byte) {
+    this->after_delimiter_pos_ = 0;
+    return;
+  }
+  this->after_delimiter_pos_++;
+  if (this->after_delimiter_pos_ == this->after_delimiter_.size()) {
+    this->fire_trigger_();
+    this->after_delimiter_pos_ = 0;
+  }
+}
+
+void UARTDebugger::trigger_after_bytes_() {
+  if (this->has_buffered_bytes_() && this->after_bytes_ > 0 && this->bytes_.size() >= this->after_bytes_) {
+    this->fire_trigger_();
+  }
+}
+
+void UARTDebugger::trigger_after_timeout_() {
+  if (this->has_buffered_bytes_() && this->after_timeout_ > 0 && millis() - this->last_time_ >= this->after_timeout_) {
+    this->fire_trigger_();
+  }
+}
+
+bool UARTDebugger::has_buffered_bytes_() { return !this->bytes_.empty(); }
+
+void UARTDebugger::fire_trigger_() {
+  this->is_triggering_ = true;
+  trigger(this->last_direction_, this->bytes_);
+  this->bytes_.clear();
+  this->is_triggering_ = false;
+}
+
+void UARTDummyReceiver::loop() {
+  // Reading up to a limited number of bytes, to make sure that this loop()
+  // won't lock up the system on a continuous incoming stream of bytes.
+  uint8_t data;
+  int count = 50;
+  while (this->available() && count--) {
+    this->read_byte(&data);
+  }
+}
+
+void UARTDebug::log_hex(UARTDirection direction, std::vector bytes, uint8_t separator) {
+  std::string res;
+  if (direction == UART_DIRECTION_RX) {
+    res += "<<< ";
+  } else {
+    res += ">>> ";
+  }
+  size_t len = bytes.size();
+  char buf[5];
+  for (size_t i = 0; i < len; i++) {
+    if (i > 0) {
+      res += separator;
+    }
+    sprintf(buf, "%02X", bytes[i]);
+    res += buf;
+  }
+  ESP_LOGD(TAG, "%s", res.c_str());
+}
+
+void UARTDebug::log_string(UARTDirection direction, std::vector bytes) {
+  std::string res;
+  if (direction == UART_DIRECTION_RX) {
+    res += "<<< \"";
+  } else {
+    res += ">>> \"";
+  }
+  size_t len = bytes.size();
+  char buf[5];
+  for (size_t i = 0; i < len; i++) {
+    if (bytes[i] == 7) {
+      res += "\\a";
+    } else if (bytes[i] == 8) {
+      res += "\\b";
+    } else if (bytes[i] == 9) {
+      res += "\\t";
+    } else if (bytes[i] == 10) {
+      res += "\\n";
+    } else if (bytes[i] == 11) {
+      res += "\\v";
+    } else if (bytes[i] == 12) {
+      res += "\\f";
+    } else if (bytes[i] == 13) {
+      res += "\\r";
+    } else if (bytes[i] == 27) {
+      res += "\\e";
+    } else if (bytes[i] == 34) {
+      res += "\\\"";
+    } else if (bytes[i] == 39) {
+      res += "\\'";
+    } else if (bytes[i] == 92) {
+      res += "\\\\";
+    } else if (bytes[i] < 32 || bytes[i] > 127) {
+      sprintf(buf, "\\x%02X", bytes[i]);
+      res += buf;
+    } else {
+      res += bytes[i];
+    }
+  }
+  res += '"';
+  ESP_LOGD(TAG, "%s", res.c_str());
+}
+
+void UARTDebug::log_int(UARTDirection direction, std::vector bytes, uint8_t separator) {
+  std::string res;
+  size_t len = bytes.size();
+  if (direction == UART_DIRECTION_RX) {
+    res += "<<< ";
+  } else {
+    res += ">>> ";
+  }
+  for (size_t i = 0; i < len; i++) {
+    if (i > 0) {
+      res += separator;
+    }
+    res += to_string(bytes[i]);
+  }
+  ESP_LOGD(TAG, "%s", res.c_str());
+}
+
+void UARTDebug::log_binary(UARTDirection direction, std::vector bytes, uint8_t separator) {
+  std::string res;
+  size_t len = bytes.size();
+  if (direction == UART_DIRECTION_RX) {
+    res += "<<< ";
+  } else {
+    res += ">>> ";
+  }
+  char buf[20];
+  for (size_t i = 0; i < len; i++) {
+    if (i > 0) {
+      res += separator;
+    }
+    sprintf(buf, "0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(bytes[i]), bytes[i]);
+    res += buf;
+  }
+  ESP_LOGD(TAG, "%s", res.c_str());
+}
+
+}  // namespace uart
+}  // namespace esphome
+#endif
diff --git a/esphome/components/uart/uart_debugger.h b/esphome/components/uart/uart_debugger.h
new file mode 100644
index 0000000000..6e84bbe450
--- /dev/null
+++ b/esphome/components/uart/uart_debugger.h
@@ -0,0 +1,101 @@
+#pragma once
+#include "esphome/core/defines.h"
+#ifdef USE_UART_DEBUGGER
+
+#include 
+#include "esphome/core/component.h"
+#include "esphome/core/automation.h"
+#include "uart.h"
+#include "uart_component.h"
+
+namespace esphome {
+namespace uart {
+
+/// The UARTDebugger class adds debugging support to a UART bus.
+///
+/// It accumulates bytes that travel over the UART bus and triggers one or
+/// more actions that can log the data at an appropriate time. What
+/// 'appropriate time' means exactly, is determined by a number of
+/// configurable constraints. E.g. when a given number of bytes is gathered
+/// and/or when no more data has been seen for a given time interval.
+class UARTDebugger : public Component, public Trigger> {
+ public:
+  explicit UARTDebugger(UARTComponent *parent);
+  void loop() override;
+
+  /// Set the direction in which to inspect the bytes: incoming, outgoing
+  /// or both. When debugging in both directions, logging will be triggered
+  /// when the direction of the data stream changes.
+  void set_direction(UARTDirection direction) { this->for_direction_ = direction; }
+
+  /// Set the maximum number of bytes to accumulate. When the number of bytes
+  /// is reached, logging will be triggered.
+  void set_after_bytes(size_t size) { this->after_bytes_ = size; }
+
+  /// Set a timeout for the data stream. When no new bytes are seen during
+  /// this timeout, logging will be triggered.
+  void set_after_timeout(uint32_t timeout) { this->after_timeout_ = timeout; }
+
+  /// Add a delimiter byte. This can be called multiple times to setup a
+  /// multi-byte delimiter (a typical example would be '\r\n').
+  /// When the constructued byte sequence is found in the data stream,
+  /// logging will be triggered.
+  void add_delimiter_byte(uint8_t byte) { this->after_delimiter_.push_back(byte); }
+
+ protected:
+  UARTDirection for_direction_;
+  UARTDirection last_direction_{};
+  std::vector bytes_{};
+  size_t after_bytes_;
+  uint32_t after_timeout_;
+  uint32_t last_time_{};
+  std::vector after_delimiter_{};
+  size_t after_delimiter_pos_{};
+  bool is_triggering_{false};
+
+  bool is_my_direction_(UARTDirection direction);
+  bool is_recursive_();
+  void store_byte_(UARTDirection direction, uint8_t byte);
+  void trigger_after_direction_change_(UARTDirection direction);
+  void trigger_after_delimiter_(uint8_t byte);
+  void trigger_after_bytes_();
+  void trigger_after_timeout_();
+  bool has_buffered_bytes_();
+  void fire_trigger_();
+};
+
+/// This UARTDevice is used by the serial debugger to read data from a
+/// serial interface when the 'dummy_receiver' option is enabled.
+/// The data are not stored, nor processed. This is most useful when the
+/// debugger is used to reverse engineer a serial protocol, for which no
+/// specific UARTDevice implementation exists (yet), but for which the
+/// incoming bytes must be read to drive the debugger.
+class UARTDummyReceiver : public Component, public UARTDevice {
+ public:
+  UARTDummyReceiver(UARTComponent *parent) : UARTDevice(parent) {}
+  void loop() override;
+};
+
+/// This class contains some static methods, that can be used to easily
+/// create a logging action for the debugger.
+class UARTDebug {
+ public:
+  /// Log the bytes as hex values, separated by the provided separator
+  /// character.
+  static void log_hex(UARTDirection direction, std::vector bytes, uint8_t separator);
+
+  /// Log the bytes as string values, escaping unprintable characters.
+  static void log_string(UARTDirection direction, std::vector bytes);
+
+  /// Log the bytes as integer values, separated by the provided separator
+  /// character.
+  static void log_int(UARTDirection direction, std::vector bytes, uint8_t separator);
+
+  /// Log the bytes as ' ()' values, separated by the provided
+  /// separator.
+  static void log_binary(UARTDirection direction, std::vector bytes, uint8_t separator);
+};
+
+}  // namespace uart
+}  // namespace esphome
+#endif
diff --git a/esphome/const.py b/esphome/const.py
index 99cb53fe4b..9951747ee2 100644
--- a/esphome/const.py
+++ b/esphome/const.py
@@ -34,6 +34,7 @@ ARDUINO_VERSION_ESP8266 = {
 SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}
 HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}
 
+
 CONF_ABOVE = "above"
 CONF_ACCELERATION = "acceleration"
 CONF_ACCELERATION_X = "acceleration_x"
@@ -47,6 +48,7 @@ CONF_ACTIVE_POWER = "active_power"
 CONF_ADDRESS = "address"
 CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id"
 CONF_ADVANCED = "advanced"
+CONF_AFTER = "after"
 CONF_ALPHA = "alpha"
 CONF_ALTITUDE = "altitude"
 CONF_AND = "and"
@@ -93,6 +95,7 @@ CONF_BUFFER_SIZE = "buffer_size"
 CONF_BUILD_PATH = "build_path"
 CONF_BUS_VOLTAGE = "bus_voltage"
 CONF_BUSY_PIN = "busy_pin"
+CONF_BYTES = "bytes"
 CONF_CALCULATED_LUX = "calculated_lux"
 CONF_CALIBRATE_LINEAR = "calibrate_linear"
 CONF_CALIBRATION = "calibration"
@@ -164,6 +167,7 @@ CONF_DAYS_OF_WEEK = "days_of_week"
 CONF_DC_PIN = "dc_pin"
 CONF_DEASSERT_RTS_DTR = "deassert_rts_dtr"
 CONF_DEBOUNCE = "debounce"
+CONF_DEBUG = "debug"
 CONF_DECAY_MODE = "decay_mode"
 CONF_DECELERATION = "deceleration"
 CONF_DEFAULT_MODE = "default_mode"
@@ -171,6 +175,7 @@ CONF_DEFAULT_TARGET_TEMPERATURE_HIGH = "default_target_temperature_high"
 CONF_DEFAULT_TARGET_TEMPERATURE_LOW = "default_target_temperature_low"
 CONF_DEFAULT_TRANSITION_LENGTH = "default_transition_length"
 CONF_DELAY = "delay"
+CONF_DELIMITER = "delimiter"
 CONF_DELTA = "delta"
 CONF_DEVICE = "device"
 CONF_DEVICE_CLASS = "device_class"
@@ -192,6 +197,8 @@ CONF_DNS2 = "dns2"
 CONF_DOMAIN = "domain"
 CONF_DRY_ACTION = "dry_action"
 CONF_DRY_MODE = "dry_mode"
+CONF_DUMMY_RECEIVER = "dummy_receiver"
+CONF_DUMMY_RECEIVER_ID = "dummy_receiver_id"
 CONF_DUMP = "dump"
 CONF_DURATION = "duration"
 CONF_EAP = "eap"
diff --git a/esphome/core/defines.h b/esphome/core/defines.h
index 94fac73906..e679fe1cef 100644
--- a/esphome/core/defines.h
+++ b/esphome/core/defines.h
@@ -37,6 +37,7 @@
 #define USE_SWITCH
 #define USE_TEXT_SENSOR
 #define USE_TIME
+#define USE_UART_DEBUGGER
 #define USE_WEBSERVER
 #define USE_WIFI
 
diff --git a/tests/test1.yaml b/tests/test1.yaml
index dee7493bf2..04d928e1b8 100644
--- a/tests/test1.yaml
+++ b/tests/test1.yaml
@@ -193,6 +193,18 @@ uart:
     data_bits: 8
     stop_bits: 1
     rx_buffer_size: 512
+    debug:
+      dummy_receiver: true
+      direction: both
+      after:
+        bytes: 50
+        timeout: 500ms
+        delimiter: "\r\n"
+      sequence:
+        - lambda: UARTDebug::log_hex(direction, bytes, ':');
+        - lambda: UARTDebug::log_string(direction, bytes);
+        - lambda: UARTDebug::log_int(direction, bytes, ',');
+        - lambda: UARTDebug::log_binary(direction, bytes, ';');
 
   - id: adalight_uart
     tx_pin: GPIO25

From bb9793d5b7b7238074ce2fdc91a4f9ecee728d7d Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Wed, 10 Nov 2021 23:53:25 +0100
Subject: [PATCH 319/549] Enable addressable light power supply based on raw
 values (#2690)

---
 esphome/components/light/addressable_light.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h
index 2b2c0ca7e4..8302239d6a 100644
--- a/esphome/components/light/addressable_light.h
+++ b/esphome/components/light/addressable_light.h
@@ -87,7 +87,7 @@ class AddressableLight : public LightOutput, public Component {
   void mark_shown_() {
 #ifdef USE_POWER_SUPPLY
     for (const auto &c : *this) {
-      if (c.get().is_on()) {
+      if (c.get_red_raw() > 0 || c.get_green_raw() > 0 || c.get_blue_raw() > 0 || c.get_white_raw() > 0) {
         this->power_.request();
         return;
       }

From f11220da3a24d2ff1ca5d83d5f26674d8033049f Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Thu, 11 Nov 2021 15:15:37 +1300
Subject: [PATCH 320/549] Remove my.ha links from improv (#2695)

---
 .../components/esp32_improv/esp32_improv_component.cpp | 10 ++++++++--
 .../improv_serial/improv_serial_component.cpp          |  3 +--
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp
index faa9ab7df6..22bebdfe98 100644
--- a/esphome/components/esp32_improv/esp32_improv_component.cpp
+++ b/esphome/components/esp32_improv/esp32_improv_component.cpp
@@ -11,6 +11,7 @@ namespace esphome {
 namespace esp32_improv {
 
 static const char *const TAG = "esp32_improv.component";
+static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
 
 ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; }
 
@@ -124,8 +125,13 @@ void ESP32ImprovComponent::loop() {
         this->cancel_timeout("wifi-connect-timeout");
         this->set_state_(improv::STATE_PROVISIONED);
 
-        std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
-        std::vector data = improv::build_rpc_response(improv::WIFI_SETTINGS, {url});
+        std::vector urls = {ESPHOME_MY_LINK};
+#ifdef USE_WEBSERVER
+        auto ip = wifi::global_wifi_component->wifi_sta_ip();
+        std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
+        urls.push_back(webserver_url);
+#endif
+        std::vector data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
         this->send_response_(data);
         this->set_timeout("end-service", 1000, [this] {
           this->service_->stop();
diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp
index a12f1bd83b..abbb76ab11 100644
--- a/esphome/components/improv_serial/improv_serial_component.cpp
+++ b/esphome/components/improv_serial/improv_serial_component.cpp
@@ -92,8 +92,7 @@ void ImprovSerialComponent::loop() {
 }
 
 std::vector ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
-  std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
-  std::vector urls = {url};
+  std::vector urls;
 #ifdef USE_WEBSERVER
   auto ip = wifi::global_wifi_component->wifi_sta_ip();
   std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);

From a6873c1520eaf21e85ad455fb15049c5761b32eb Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Thu, 11 Nov 2021 22:56:35 +1300
Subject: [PATCH 321/549] Only allow prometheus when using arduino (#2697)

---
 esphome/components/prometheus/__init__.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/esphome/components/prometheus/__init__.py b/esphome/components/prometheus/__init__.py
index f5f166d085..45345f06e8 100644
--- a/esphome/components/prometheus/__init__.py
+++ b/esphome/components/prometheus/__init__.py
@@ -15,7 +15,8 @@ CONFIG_SCHEMA = cv.Schema(
         cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
             web_server_base.WebServerBase
         ),
-    }
+    },
+    cv.only_with_arduino,
 ).extend(cv.COMPONENT_SCHEMA)
 
 

From 0372e12b81db6a78d7397a1d6ab274dbce7ee3e1 Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Thu, 11 Nov 2021 22:56:54 +1300
Subject: [PATCH 322/549] Defines tidy (#2696)

* Move webserver defines inside arduino block

* Move esp8266 flash define

* Move prometheus define
---
 esphome/core/defines.h | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/esphome/core/defines.h b/esphome/core/defines.h
index e679fe1cef..8dcae9fa31 100644
--- a/esphome/core/defines.h
+++ b/esphome/core/defines.h
@@ -19,7 +19,6 @@
 #define USE_CLIMATE
 #define USE_COVER
 #define USE_DEEP_SLEEP
-#define USE_ESP8266_PREFERENCES_FLASH
 #define USE_FAN
 #define USE_GRAPH
 #define USE_HOMEASSISTANT_TIME
@@ -30,7 +29,6 @@
 #define USE_OTA_PASSWORD
 #define USE_OTA_STATE_CALLBACK
 #define USE_POWER_SUPPLY
-#define USE_PROMETHEUS
 #define USE_SELECT
 #define USE_SENSOR
 #define USE_STATUS_LED
@@ -38,18 +36,18 @@
 #define USE_TEXT_SENSOR
 #define USE_TIME
 #define USE_UART_DEBUGGER
-#define USE_WEBSERVER
 #define USE_WIFI
 
-#define WEBSERVER_PORT 80  // NOLINT
-
 // Arduino-specific feature flags
 #ifdef USE_ARDUINO
 #define USE_CAPTIVE_PORTAL
 #define USE_JSON
 #define USE_NEXTION_TFT_UPLOAD
 #define USE_MQTT
+#define USE_PROMETHEUS
+#define USE_WEBSERVER
 #define USE_WIFI_WPA2_EAP
+#define WEBSERVER_PORT 80  // NOLINT
 #endif
 
 // ESP32-specific feature flags
@@ -68,6 +66,7 @@
 // ESP8266-specific feature flags
 #ifdef USE_ESP8266
 #define USE_ADC_SENSOR_VCC
+#define USE_ESP8266_PREFERENCES_FLASH
 #define USE_HTTP_REQUEST_ESP8266_HTTPS
 #define USE_SOCKET_IMPL_LWIP_TCP
 #endif

From 7bb7456a8b034ce0f04aef4994db341b646af7b7 Mon Sep 17 00:00:00 2001
From: lcavalli 
Date: Fri, 12 Nov 2021 01:17:10 +0100
Subject: [PATCH 323/549] Update device classes for binary sensors (#2703)

---
 esphome/components/binary_sensor/__init__.py | 6 ++++--
 esphome/const.py                             | 3 ++-
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py
index faafcddd06..3f11e18e45 100644
--- a/esphome/components/binary_sensor/__init__.py
+++ b/esphome/components/binary_sensor/__init__.py
@@ -44,10 +44,11 @@ from esphome.const import (
     DEVICE_CLASS_POWER,
     DEVICE_CLASS_PRESENCE,
     DEVICE_CLASS_PROBLEM,
+    DEVICE_CLASS_RUNNING,
     DEVICE_CLASS_SAFETY,
     DEVICE_CLASS_SMOKE,
     DEVICE_CLASS_SOUND,
-    DEVICE_CLASS_UPDATE,
+    DEVICE_CLASS_TAMPER,
     DEVICE_CLASS_VIBRATION,
     DEVICE_CLASS_WINDOW,
 )
@@ -76,10 +77,11 @@ DEVICE_CLASSES = [
     DEVICE_CLASS_POWER,
     DEVICE_CLASS_PRESENCE,
     DEVICE_CLASS_PROBLEM,
+    DEVICE_CLASS_RUNNING,
     DEVICE_CLASS_SAFETY,
     DEVICE_CLASS_SMOKE,
     DEVICE_CLASS_SOUND,
-    DEVICE_CLASS_UPDATE,
+    DEVICE_CLASS_TAMPER,
     DEVICE_CLASS_VIBRATION,
     DEVICE_CLASS_WINDOW,
 ]
diff --git a/esphome/const.py b/esphome/const.py
index 9951747ee2..017feb0268 100644
--- a/esphome/const.py
+++ b/esphome/const.py
@@ -878,10 +878,11 @@ DEVICE_CLASS_OPENING = "opening"
 DEVICE_CLASS_PLUG = "plug"
 DEVICE_CLASS_PRESENCE = "presence"
 DEVICE_CLASS_PROBLEM = "problem"
+DEVICE_CLASS_RUNNING = "running"
 DEVICE_CLASS_SAFETY = "safety"
 DEVICE_CLASS_SMOKE = "smoke"
 DEVICE_CLASS_SOUND = "sound"
-DEVICE_CLASS_UPDATE = "update"
+DEVICE_CLASS_TAMPER = "tamper"
 DEVICE_CLASS_VIBRATION = "vibration"
 DEVICE_CLASS_WINDOW = "window"
 # device classes of both binary_sensor and sensor component

From 2e0c89409d487eb4e382d307e6e2fe836aec8ee1 Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Sat, 13 Nov 2021 21:22:32 +1300
Subject: [PATCH 324/549] Bump ESPAsyncWebServer to 2.1.0 (#2686)

---
 esphome/components/web_server_base/__init__.py | 2 +-
 platformio.ini                                 | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py
index dc1a2bc2f0..14fb033a56 100644
--- a/esphome/components/web_server_base/__init__.py
+++ b/esphome/components/web_server_base/__init__.py
@@ -28,4 +28,4 @@ async def to_code(config):
         cg.add_library("FS", None)
         cg.add_library("Update", None)
     # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json
-    cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.0.1")
+    cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.1.0")
diff --git a/platformio.ini b/platformio.ini
index 0dd32268e0..5e89afe8e6 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -41,7 +41,7 @@ lib_deps =
     ${common.lib_deps}
     ottowinter/AsyncMqttClient-esphome@0.8.6              ; mqtt
     ottowinter/ArduinoJson-esphomelib@5.13.3              ; json
-    esphome/ESPAsyncWebServer-esphome@2.0.1               ; web_server_base
+    esphome/ESPAsyncWebServer-esphome@2.1.0               ; web_server_base
     fastled/FastLED@3.3.2                                 ; fastled_base
     mikalhart/TinyGPSPlus@1.0.2                           ; gps
     freekode/TM1651@1.0.1                                 ; tm1651

From 582567696ebb5e4711a062325238a643c5aa5176 Mon Sep 17 00:00:00 2001
From: NeoAcheron <12508986+NeoAcheron@users.noreply.github.com>
Date: Sat, 13 Nov 2021 15:14:23 +0100
Subject: [PATCH 325/549] pmsx003: add support for PMS5003S device (#2710)

---
 esphome/components/pmsx003/pmsx003.cpp | 14 ++++++++++----
 esphome/components/pmsx003/pmsx003.h   |  1 +
 esphome/components/pmsx003/sensor.py   | 10 ++++++----
 3 files changed, 17 insertions(+), 8 deletions(-)

diff --git a/esphome/components/pmsx003/pmsx003.cpp b/esphome/components/pmsx003/pmsx003.cpp
index 0474d6ffd0..5de94699f0 100644
--- a/esphome/components/pmsx003/pmsx003.cpp
+++ b/esphome/components/pmsx003/pmsx003.cpp
@@ -96,6 +96,7 @@ optional PMSX003Component::check_byte_() {
         length_matches = payload_length == 28 || payload_length == 20;
         break;
       case PMSX003_TYPE_5003T:
+      case PMSX003_TYPE_5003S:
         length_matches = payload_length == 28;
         break;
       case PMSX003_TYPE_5003ST:
@@ -133,20 +134,25 @@ optional PMSX003Component::check_byte_() {
 void PMSX003Component::parse_data_() {
   switch (this->type_) {
     case PMSX003_TYPE_5003ST: {
-      uint16_t formaldehyde = this->get_16_bit_uint_(28);
       float temperature = this->get_16_bit_uint_(30) / 10.0f;
       float humidity = this->get_16_bit_uint_(32) / 10.0f;
 
-      ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%% Formaldehyde: %u µg/m^3", temperature, humidity,
-               formaldehyde);
+      ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%%", temperature, humidity);
 
       if (this->temperature_sensor_ != nullptr)
         this->temperature_sensor_->publish_state(temperature);
       if (this->humidity_sensor_ != nullptr)
         this->humidity_sensor_->publish_state(humidity);
+      // The rest of the PMS5003ST matches the PMS5003S, continue on
+    }
+    case PMSX003_TYPE_5003S: {
+      uint16_t formaldehyde = this->get_16_bit_uint_(28);
+
+      ESP_LOGD(TAG, "Got Formaldehyde: %u µg/m^3", formaldehyde);
+
       if (this->formaldehyde_sensor_ != nullptr)
         this->formaldehyde_sensor_->publish_state(formaldehyde);
-      // The rest of the PMS5003ST matches the PMS5003, continue on
+      // The rest of the PMS5003S matches the PMS5003, continue on
     }
     case PMSX003_TYPE_X003: {
       uint16_t pm_1_0_std_concentration = this->get_16_bit_uint_(4);
diff --git a/esphome/components/pmsx003/pmsx003.h b/esphome/components/pmsx003/pmsx003.h
index a5adecb534..fd6364c70c 100644
--- a/esphome/components/pmsx003/pmsx003.h
+++ b/esphome/components/pmsx003/pmsx003.h
@@ -11,6 +11,7 @@ enum PMSX003Type {
   PMSX003_TYPE_X003 = 0,
   PMSX003_TYPE_5003T,
   PMSX003_TYPE_5003ST,
+  PMSX003_TYPE_5003S,
 };
 
 class PMSX003Component : public uart::UARTDevice, public Component {
diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py
index 350117a235..56a91d22fc 100644
--- a/esphome/components/pmsx003/sensor.py
+++ b/esphome/components/pmsx003/sensor.py
@@ -42,21 +42,23 @@ PMSX003Sensor = pmsx003_ns.class_("PMSX003Sensor", sensor.Sensor)
 TYPE_PMSX003 = "PMSX003"
 TYPE_PMS5003T = "PMS5003T"
 TYPE_PMS5003ST = "PMS5003ST"
+TYPE_PMS5003S = "PMS5003S"
 
 PMSX003Type = pmsx003_ns.enum("PMSX003Type")
 PMSX003_TYPES = {
     TYPE_PMSX003: PMSX003Type.PMSX003_TYPE_X003,
     TYPE_PMS5003T: PMSX003Type.PMSX003_TYPE_5003T,
     TYPE_PMS5003ST: PMSX003Type.PMSX003_TYPE_5003ST,
+    TYPE_PMS5003S: PMSX003Type.PMSX003_TYPE_5003S,
 }
 
 SENSORS_TO_TYPE = {
-    CONF_PM_1_0: [TYPE_PMSX003, TYPE_PMS5003ST],
-    CONF_PM_2_5: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST],
-    CONF_PM_10_0: [TYPE_PMSX003, TYPE_PMS5003ST],
+    CONF_PM_1_0: [TYPE_PMSX003, TYPE_PMS5003ST, TYPE_PMS5003S],
+    CONF_PM_2_5: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S],
+    CONF_PM_10_0: [TYPE_PMSX003, TYPE_PMS5003ST, TYPE_PMS5003S],
     CONF_TEMPERATURE: [TYPE_PMS5003T, TYPE_PMS5003ST],
     CONF_HUMIDITY: [TYPE_PMS5003T, TYPE_PMS5003ST],
-    CONF_FORMALDEHYDE: [TYPE_PMS5003ST],
+    CONF_FORMALDEHYDE: [TYPE_PMS5003ST, TYPE_PMS5003S],
 }
 
 

From aae63a7ff3b64054860384a461314dea31a22e80 Mon Sep 17 00:00:00 2001
From: "Sergey V. DUDANOV" 
Date: Sat, 13 Nov 2021 18:42:15 +0400
Subject: [PATCH 326/549] Add climate on_state trigger (#2707)

---
 esphome/components/climate/__init__.py  | 12 ++++++++++++
 esphome/components/climate/automation.h |  7 +++++++
 tests/test1.yaml                        |  2 ++
 3 files changed, 21 insertions(+)

diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py
index 7ff769e5cb..b0f9146012 100644
--- a/esphome/components/climate/__init__.py
+++ b/esphome/components/climate/__init__.py
@@ -20,6 +20,7 @@ from esphome.const import (
     CONF_MODE,
     CONF_MODE_COMMAND_TOPIC,
     CONF_MODE_STATE_TOPIC,
+    CONF_ON_STATE,
     CONF_PRESET,
     CONF_SWING_MODE,
     CONF_SWING_MODE_COMMAND_TOPIC,
@@ -34,6 +35,7 @@ from esphome.const import (
     CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC,
     CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC,
     CONF_TEMPERATURE_STEP,
+    CONF_TRIGGER_ID,
     CONF_VISUAL,
     CONF_MQTT_ID,
 )
@@ -101,6 +103,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
 
 # Actions
 ControlAction = climate_ns.class_("ControlAction", automation.Action)
+StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template())
 
 CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
     {
@@ -161,6 +164,11 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
         cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
             cv.requires_component("mqtt"), cv.publish_topic
         ),
+        cv.Optional(CONF_ON_STATE): automation.validate_automation(
+            {
+                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
+            }
+        ),
     }
 )
 
@@ -256,6 +264,10 @@ async def setup_climate_core_(var, config):
                 )
             )
 
+    for conf in config.get(CONF_ON_STATE, []):
+        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
+        await automation.build_automation(trigger, [], conf)
+
 
 async def register_climate(var, config):
     if not CORE.has_id(config[CONF_ID]):
diff --git a/esphome/components/climate/automation.h b/esphome/components/climate/automation.h
index 49a87027f2..3145358dab 100644
--- a/esphome/components/climate/automation.h
+++ b/esphome/components/climate/automation.h
@@ -42,5 +42,12 @@ template class ControlAction : public Action {
   Climate *climate_;
 };
 
+class StateTrigger : public Trigger<> {
+ public:
+  StateTrigger(Climate *climate) {
+    climate->add_on_state_callback([this]() { this->trigger(); });
+  }
+};
+
 }  // namespace climate
 }  // namespace esphome
diff --git a/tests/test1.yaml b/tests/test1.yaml
index 04d928e1b8..f10922a7b9 100644
--- a/tests/test1.yaml
+++ b/tests/test1.yaml
@@ -1707,6 +1707,8 @@ climate:
     min_temperature: 18
     max_temperature: 30
   - platform: midea
+    on_state:
+      logger.log: "State changed!"
     id: midea_unit
     uart_id: uart0
     name: Midea Climate

From f643a46bbf764c138875190393ec437b8ba78c02 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Krzysztof=20Bia=C5=82ek?= 
Date: Sun, 14 Nov 2021 14:59:34 +0100
Subject: [PATCH 327/549] Allow setting custom command_topic for Select and
 Number components (#2714)

---
 esphome/components/number/__init__.py |  2 +-
 esphome/components/select/__init__.py |  2 +-
 tests/test1.yaml                      | 20 ++++++++++++++++++++
 3 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py
index bb3427e4bd..1da25caafe 100644
--- a/esphome/components/number/__init__.py
+++ b/esphome/components/number/__init__.py
@@ -41,7 +41,7 @@ NumberInRangeCondition = number_ns.class_(
 
 icon = cv.icon
 
-NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
+NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
     {
         cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent),
         cv.GenerateID(): cv.declare_id(Number),
diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py
index 8ea159d657..c15036e9f9 100644
--- a/esphome/components/select/__init__.py
+++ b/esphome/components/select/__init__.py
@@ -30,7 +30,7 @@ SelectSetAction = select_ns.class_("SelectSetAction", automation.Action)
 
 icon = cv.icon
 
-SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
+SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
     {
         cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent),
         cv.GenerateID(): cv.declare_id(Select),
diff --git a/tests/test1.yaml b/tests/test1.yaml
index f10922a7b9..263754dc4f 100644
--- a/tests/test1.yaml
+++ b/tests/test1.yaml
@@ -2514,3 +2514,23 @@ teleinfo:
   uart_id: uart0
   update_interval: 60s
   historical_mode: true
+
+number:
+  - platform: template
+    id: test_number
+    state_topic: livingroom/custom_state_topic
+    command_topic: livingroom/custom_command_topic
+    min_value: 0
+    step: 1
+    max_value: 10
+    optimistic: true
+
+select:
+  - platform: template
+    id: test_select
+    state_topic: livingroom/custom_state_topic
+    command_topic: livingroom/custom_command_topic
+    options:
+      - one
+      - two
+    optimistic: true

From 4eaa6afa4d43299aedd277d7508b6c3ca012a0cf Mon Sep 17 00:00:00 2001
From: Clifford Roche 
Date: Sun, 14 Nov 2021 10:11:21 -0500
Subject: [PATCH 328/549] Add greeyac protocol to IR Climate / HeatpumpIR
 (#2694)

---
 esphome/components/heatpumpir/climate.py        |  5 ++++-
 esphome/components/heatpumpir/heatpumpir.cpp    | 17 +++++++++++++++--
 esphome/components/heatpumpir/heatpumpir.h      |  1 +
 .../components/heatpumpir/ir_sender_esphome.h   |  4 ++--
 platformio.ini                                  |  4 +++-
 5 files changed, 25 insertions(+), 6 deletions(-)

diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py
index 36e56aa5da..592e03f959 100644
--- a/esphome/components/heatpumpir/climate.py
+++ b/esphome/components/heatpumpir/climate.py
@@ -30,6 +30,7 @@ PROTOCOLS = {
     "gree": Protocol.PROTOCOL_GREE,
     "greeya": Protocol.PROTOCOL_GREEYAA,
     "greeyan": Protocol.PROTOCOL_GREEYAN,
+    "greeyac": Protocol.PROTOCOL_GREEYAC,
     "hisense_aud": Protocol.PROTOCOL_HISENSE_AUD,
     "hitachi": Protocol.PROTOCOL_HITACHI,
     "hyundai": Protocol.PROTOCOL_HYUNDAI,
@@ -111,4 +112,6 @@ def to_code(config):
     cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE]))
     cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE]))
 
-    cg.add_library("tonia/HeatpumpIR", "1.0.15")
+    # PIO isn't updating releases, so referencing the release tag directly. See:
+    # https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd
+    cg.add_library("", "", "https://github.com/ToniA/arduino-heatpumpir.git#1.0.18")
diff --git a/esphome/components/heatpumpir/heatpumpir.cpp b/esphome/components/heatpumpir/heatpumpir.cpp
index 8d9fc962c0..ad3731b955 100644
--- a/esphome/components/heatpumpir/heatpumpir.cpp
+++ b/esphome/components/heatpumpir/heatpumpir.cpp
@@ -25,6 +25,7 @@ const std::map> PROTOCOL_CONSTRUCTOR_MAP
     {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }},                           // NOLINT
     {PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }},                            // NOLINT
     {PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }},                            // NOLINT
+    {PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }},                            // NOLINT
     {PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }},                        // NOLINT
     {PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }},                            // NOLINT
     {PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }},                            // NOLINT
@@ -61,6 +62,19 @@ void HeatpumpIRClimate::setup() {
   }
   this->heatpump_ir_ = protocol_constructor->second();
   climate_ir::ClimateIR::setup();
+  if (this->sensor_) {
+    this->sensor_->add_on_state_callback([this](float state) {
+      this->current_temperature = state;
+
+      IRSenderESPHome esp_sender(this->transmitter_);
+      this->heatpump_ir_->send(esp_sender, uint8_t(lround(this->current_temperature + 0.5)));
+
+      // current temperature changed, publish state
+      this->publish_state();
+    });
+    this->current_temperature = this->sensor_->state;
+  } else
+    this->current_temperature = NAN;
 }
 
 void HeatpumpIRClimate::transmit_state() {
@@ -171,8 +185,7 @@ void HeatpumpIRClimate::transmit_state() {
 
   temperature_cmd = (uint8_t) clamp(this->target_temperature, this->min_temperature_, this->max_temperature_);
 
-  IRSenderESPHome esp_sender(0, this->transmitter_);
-
+  IRSenderESPHome esp_sender(this->transmitter_);
   heatpump_ir_->send(esp_sender, power_mode_cmd, operating_mode_cmd, fan_speed_cmd, temperature_cmd, swing_v_cmd,
                      swing_h_cmd);
 }
diff --git a/esphome/components/heatpumpir/heatpumpir.h b/esphome/components/heatpumpir/heatpumpir.h
index e2d2b45dc4..18d9b5040f 100644
--- a/esphome/components/heatpumpir/heatpumpir.h
+++ b/esphome/components/heatpumpir/heatpumpir.h
@@ -25,6 +25,7 @@ enum Protocol {
   PROTOCOL_GREE,
   PROTOCOL_GREEYAA,
   PROTOCOL_GREEYAN,
+  PROTOCOL_GREEYAC,
   PROTOCOL_HISENSE_AUD,
   PROTOCOL_HITACHI,
   PROTOCOL_HYUNDAI,
diff --git a/esphome/components/heatpumpir/ir_sender_esphome.h b/esphome/components/heatpumpir/ir_sender_esphome.h
index 24e8ba9883..7546d990ea 100644
--- a/esphome/components/heatpumpir/ir_sender_esphome.h
+++ b/esphome/components/heatpumpir/ir_sender_esphome.h
@@ -11,8 +11,8 @@ namespace heatpumpir {
 
 class IRSenderESPHome : public IRSender {
  public:
-  IRSenderESPHome(uint8_t pin, remote_transmitter::RemoteTransmitterComponent *transmitter)
-      : IRSender(pin), transmit_(transmitter->transmit()){};
+  IRSenderESPHome(remote_transmitter::RemoteTransmitterComponent *transmitter)
+      : IRSender(0), transmit_(transmitter->transmit()){};
   void setFrequency(int frequency) override;  // NOLINT(readability-identifier-naming)
   void space(int space_length) override;
   void mark(int mark_length) override;
diff --git a/platformio.ini b/platformio.ini
index 5e89afe8e6..2ac6d0bfc8 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -49,7 +49,9 @@ lib_deps =
     glmnet/Dsmr@0.5                                       ; dsmr
     rweather/Crypto@0.2.0                                 ; dsmr
     dudanov/MideaUART@1.1.8                               ; midea
-    tonia/HeatpumpIR@1.0.15                               ; heatpumpir
+    ; PIO isn't update releases correctly, see:
+    ; https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd
+    https://github.com/ToniA/arduino-heatpumpir.git#1.0.18  ; heatpumpir
 build_flags =
     ${common.build_flags}
     -DUSE_ARDUINO

From 108b8e6705a6f5080fa2cdc408437d2b4d3c525a Mon Sep 17 00:00:00 2001
From: Maurice Makaay 
Date: Sun, 14 Nov 2021 16:17:13 +0100
Subject: [PATCH 329/549] Fix rom/rtc.h deprecation compile warning for debug
 component (#2520)

---
 esphome/components/debug/debug_component.cpp | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp
index b856733121..40eb20fa6e 100644
--- a/esphome/components/debug/debug_component.cpp
+++ b/esphome/components/debug/debug_component.cpp
@@ -4,20 +4,23 @@
 #include "esphome/core/defines.h"
 #include "esphome/core/version.h"
 
+#ifdef USE_ESP_IDF
+#include 
+#include 
+#endif
+
 #ifdef USE_ESP32
+#if ESP_IDF_VERSION_MAJOR >= 4
+#include 
+#else
 #include 
-#include 
+#endif
 #endif
 
 #ifdef USE_ARDUINO
 #include 
 #endif
 
-#ifdef USE_ESP_IDF
-#include 
-#include 
-#endif
-
 namespace esphome {
 namespace debug {
 

From 66cebfc992120212bb6032f3cd6f8ce6eff5f80a Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Sun, 14 Nov 2021 20:05:11 +0100
Subject: [PATCH 330/549] Restore InterruptLock on wifi-less ESP8266 (#2712)

---
 esphome/core/helpers.cpp | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp
index daca3ffd32..27608a84c1 100644
--- a/esphome/core/helpers.cpp
+++ b/esphome/core/helpers.cpp
@@ -9,6 +9,7 @@
 #ifdef USE_WIFI
 #include 
 #endif
+#include 
 #include 
 #elif defined(USE_ESP32_FRAMEWORK_ARDUINO)
 #include 
@@ -430,13 +431,8 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green
 }
 
 #ifdef USE_ESP8266
-#ifdef USE_WIFI
 IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); }
 IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); }
-#else
-IRAM_ATTR InterruptLock::InterruptLock() {}
-IRAM_ATTR InterruptLock::~InterruptLock() {}
-#endif
 #endif
 #ifdef USE_ESP32
 IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }

From 14299bb2cc9af365d0b45080c0eb31d1a2edc11a Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Sun, 14 Nov 2021 20:07:58 +0100
Subject: [PATCH 331/549] Drop unused constants from const.py (#2718)

---
 esphome/const.py | 22 ----------------------
 1 file changed, 22 deletions(-)

diff --git a/esphome/const.py b/esphome/const.py
index 017feb0268..fb241f04b5 100644
--- a/esphome/const.py
+++ b/esphome/const.py
@@ -8,29 +8,7 @@ PLATFORM_ESP32 = "esp32"
 PLATFORM_ESP8266 = "esp8266"
 
 TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_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",
-    "3.0.1": "platformio/espressif8266@3.1.0",
-    "3.0.0": "platformio/espressif8266@3.0.0",
-    "2.7.4": "platformio/espressif8266@2.6.2",
-    "2.7.3": "platformio/espressif8266@2.6.1",
-    "2.7.2": "platformio/espressif8266@2.6.0",
-    "2.7.1": "platformio/espressif8266@2.5.3",
-    "2.7.0": "platformio/espressif8266@2.5.0",
-    "2.6.3": "platformio/espressif8266@2.4.0",
-    "2.6.2": "platformio/espressif8266@2.3.1",
-    "2.6.1": "platformio/espressif8266@2.3.0",
-    "2.5.2": "platformio/espressif8266@2.2.3",
-    "2.5.1": "platformio/espressif8266@2.1.1",
-    "2.5.0": "platformio/espressif8266@2.0.4",
-    "2.4.2": "platformio/espressif8266@1.8.0",
-    "2.4.1": "platformio/espressif8266@1.7.3",
-    "2.4.0": "platformio/espressif8266@1.6.0",
-    "2.3.0": "platformio/espressif8266@1.5.0",
-}
 SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}
 HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}
 

From 6a7440f7d325cbb9a53c48df3df89df00ed55e52 Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Sun, 14 Nov 2021 21:45:25 +0100
Subject: [PATCH 332/549] Feed WDT between doing ESP32 touchpad measurements
 (#2720)

---
 esphome/components/esp32_touch/esp32_touch.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp
index cb72820900..85f4058eee 100644
--- a/esphome/components/esp32_touch/esp32_touch.cpp
+++ b/esphome/components/esp32_touch/esp32_touch.cpp
@@ -1,6 +1,7 @@
 #ifdef USE_ESP32
 
 #include "esp32_touch.h"
+#include "esphome/core/application.h"
 #include "esphome/core/log.h"
 #include "esphome/core/hal.h"
 
@@ -125,6 +126,8 @@ void ESP32TouchComponent::loop() {
     if (should_print) {
       ESP_LOGD(TAG, "Touch Pad '%s' (T%u): %u", child->get_name().c_str(), child->get_touch_pad(), value);
     }
+
+    App.feed_wdt();
   }
 
   if (should_print) {

From 04740fbcbb4ea7ccff9ebbe89a801bda90f46168 Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Sun, 14 Nov 2021 22:04:43 +0100
Subject: [PATCH 333/549] Install test requirements in lint Docker image
 (#2719)

---
 docker/Dockerfile | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docker/Dockerfile b/docker/Dockerfile
index 0d30bb0267..62a64c851d 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -147,9 +147,9 @@ RUN \
         /var/{cache,log}/* \
         /var/lib/apt/lists/*
 
-COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
+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 \
+    pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \
     && /platformio_install_deps.py /platformio.ini
 
 VOLUME ["/esphome"]

From d99c5ed8901279def13815d9f2b22f9f8a203a9f Mon Sep 17 00:00:00 2001
From: "Sergey V. DUDANOV" 
Date: Mon, 15 Nov 2021 01:40:35 +0400
Subject: [PATCH 334/549] RemoteTransmitter fix. Bug from version 2021.10. Some
 changes. (#2706)

---
 .../midea/{adapter.cpp => ac_adapter.cpp}     |   4 +-
 .../midea/{adapter.h => ac_adapter.h}         |   6 +-
 .../midea/{automations.h => ac_automations.h} |   2 +
 esphome/components/midea/air_conditioner.cpp  |  13 +--
 esphome/components/midea/air_conditioner.h    |  21 +++-
 esphome/components/midea/appliance_base.h     | 109 ++++++++++--------
 esphome/components/midea/climate.py           |  20 ++--
 .../midea/{midea_ir.h => ir_transmitter.h}    |  15 +++
 8 files changed, 122 insertions(+), 68 deletions(-)
 rename esphome/components/midea/{adapter.cpp => ac_adapter.cpp} (98%)
 rename esphome/components/midea/{adapter.h => ac_adapter.h} (95%)
 rename esphome/components/midea/{automations.h => ac_automations.h} (97%)
 rename esphome/components/midea/{midea_ir.h => ir_transmitter.h} (73%)

diff --git a/esphome/components/midea/adapter.cpp b/esphome/components/midea/ac_adapter.cpp
similarity index 98%
rename from esphome/components/midea/adapter.cpp
rename to esphome/components/midea/ac_adapter.cpp
index a3f19dbda8..2837713c35 100644
--- a/esphome/components/midea/adapter.cpp
+++ b/esphome/components/midea/ac_adapter.cpp
@@ -1,10 +1,11 @@
 #ifdef USE_ARDUINO
 
 #include "esphome/core/log.h"
-#include "adapter.h"
+#include "ac_adapter.h"
 
 namespace esphome {
 namespace midea {
+namespace ac {
 
 const char *const Constants::TAG = "midea";
 const std::string Constants::FREEZE_PROTECTION = "freeze protection";
@@ -171,6 +172,7 @@ void Converters::to_climate_traits(ClimateTraits &traits, const dudanov::midea::
     traits.add_supported_custom_preset(Constants::FREEZE_PROTECTION);
 }
 
+}  // namespace ac
 }  // namespace midea
 }  // namespace esphome
 
diff --git a/esphome/components/midea/adapter.h b/esphome/components/midea/ac_adapter.h
similarity index 95%
rename from esphome/components/midea/adapter.h
rename to esphome/components/midea/ac_adapter.h
index 2497cbbe5b..c17894ae31 100644
--- a/esphome/components/midea/adapter.h
+++ b/esphome/components/midea/ac_adapter.h
@@ -2,12 +2,15 @@
 
 #ifdef USE_ARDUINO
 
+// MideaUART
 #include 
+
 #include "esphome/components/climate/climate_traits.h"
-#include "appliance_base.h"
+#include "air_conditioner.h"
 
 namespace esphome {
 namespace midea {
+namespace ac {
 
 using MideaMode = dudanov::midea::ac::Mode;
 using MideaSwingMode = dudanov::midea::ac::SwingMode;
@@ -41,6 +44,7 @@ class Converters {
   static void to_climate_traits(ClimateTraits &traits, const dudanov::midea::ac::Capabilities &capabilities);
 };
 
+}  // namespace ac
 }  // namespace midea
 }  // namespace esphome
 
diff --git a/esphome/components/midea/automations.h b/esphome/components/midea/ac_automations.h
similarity index 97%
rename from esphome/components/midea/automations.h
rename to esphome/components/midea/ac_automations.h
index 5b638286ac..d4ed2e7168 100644
--- a/esphome/components/midea/automations.h
+++ b/esphome/components/midea/ac_automations.h
@@ -7,6 +7,7 @@
 
 namespace esphome {
 namespace midea {
+namespace ac {
 
 template class MideaActionBase : public Action {
  public:
@@ -55,6 +56,7 @@ template class PowerOffAction : public MideaActionBase {
   void play(Ts... x) override { this->parent_->do_power_off(); }
 };
 
+}  // namespace ac
 }  // namespace midea
 }  // namespace esphome
 
diff --git a/esphome/components/midea/air_conditioner.cpp b/esphome/components/midea/air_conditioner.cpp
index 103b852936..dd48f640a2 100644
--- a/esphome/components/midea/air_conditioner.cpp
+++ b/esphome/components/midea/air_conditioner.cpp
@@ -2,13 +2,11 @@
 
 #include "esphome/core/log.h"
 #include "air_conditioner.h"
-#include "adapter.h"
-#ifdef USE_REMOTE_TRANSMITTER
-#include "midea_ir.h"
-#endif
+#include "ac_adapter.h"
 
 namespace esphome {
 namespace midea {
+namespace ac {
 
 static void set_sensor(Sensor *sensor, float value) {
   if (sensor != nullptr && (!sensor->has_state() || sensor->get_raw_state() != value))
@@ -122,7 +120,7 @@ void AirConditioner::dump_config() {
 void AirConditioner::do_follow_me(float temperature, bool beeper) {
 #ifdef USE_REMOTE_TRANSMITTER
   IrFollowMeData data(static_cast(lroundf(temperature)), beeper);
-  this->transmit_ir(data);
+  this->transmitter_.transmit(data);
 #else
   ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
 #endif
@@ -131,7 +129,7 @@ void AirConditioner::do_follow_me(float temperature, bool beeper) {
 void AirConditioner::do_swing_step() {
 #ifdef USE_REMOTE_TRANSMITTER
   IrSpecialData data(0x01);
-  this->transmit_ir(data);
+  this->transmitter_.transmit(data);
 #else
   ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
 #endif
@@ -143,13 +141,14 @@ void AirConditioner::do_display_toggle() {
   } else {
 #ifdef USE_REMOTE_TRANSMITTER
     IrSpecialData data(0x08);
-    this->transmit_ir(data);
+    this->transmitter_.transmit(data);
 #else
     ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
 #endif
   }
 }
 
+}  // namespace ac
 }  // namespace midea
 }  // namespace esphome
 
diff --git a/esphome/components/midea/air_conditioner.h b/esphome/components/midea/air_conditioner.h
index 8dfb9dcb3d..a6023b78bb 100644
--- a/esphome/components/midea/air_conditioner.h
+++ b/esphome/components/midea/air_conditioner.h
@@ -2,17 +2,25 @@
 
 #ifdef USE_ARDUINO
 
+// MideaUART
 #include 
+
 #include "appliance_base.h"
 #include "esphome/components/sensor/sensor.h"
 
 namespace esphome {
 namespace midea {
+namespace ac {
 
 using sensor::Sensor;
 using climate::ClimateCall;
+using climate::ClimatePreset;
+using climate::ClimateTraits;
+using climate::ClimateMode;
+using climate::ClimateSwingMode;
+using climate::ClimateFanMode;
 
-class AirConditioner : public ApplianceBase {
+class AirConditioner : public ApplianceBase, public climate::Climate {
  public:
   void dump_config() override;
   void set_outdoor_temperature_sensor(Sensor *sensor) { this->outdoor_sensor_ = sensor; }
@@ -31,15 +39,26 @@ class AirConditioner : public ApplianceBase
   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); }
+  void set_supported_modes(const std::set &modes) { this->supported_modes_ = modes; }
+  void set_supported_swing_modes(const std::set &modes) { this->supported_swing_modes_ = modes; }
+  void set_supported_presets(const std::set &presets) { this->supported_presets_ = presets; }
+  void set_custom_presets(const std::set &presets) { this->supported_custom_presets_ = presets; }
+  void set_custom_fan_modes(const std::set &modes) { this->supported_custom_fan_modes_ = modes; }
 
  protected:
   void control(const ClimateCall &call) override;
   ClimateTraits traits() override;
+  std::set supported_modes_{};
+  std::set supported_swing_modes_{};
+  std::set supported_presets_{};
+  std::set supported_custom_presets_{};
+  std::set supported_custom_fan_modes_{};
   Sensor *outdoor_sensor_{nullptr};
   Sensor *humidity_sensor_{nullptr};
   Sensor *power_sensor_{nullptr};
 };
 
+}  // namespace ac
 }  // namespace midea
 }  // namespace esphome
 
diff --git a/esphome/components/midea/appliance_base.h b/esphome/components/midea/appliance_base.h
index 88a722e389..060cbd996b 100644
--- a/esphome/components/midea/appliance_base.h
+++ b/esphome/components/midea/appliance_base.h
@@ -2,84 +2,97 @@
 
 #ifdef USE_ARDUINO
 
+// MideaUART
+#include 
+#include 
+
+// Include global defines
+#include "esphome/core/defines.h"
+
 #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 
+#include "ir_transmitter.h"
 
 namespace esphome {
 namespace midea {
 
-using climate::ClimatePreset;
-using climate::ClimateTraits;
-using climate::ClimateMode;
-using climate::ClimateSwingMode;
-using climate::ClimateFanMode;
+/* Stream from UART component */
+class UARTStream : public Stream {
+ public:
+  void set_uart(uart::UARTComponent *uart) { this->uart_ = uart; }
 
-template
-class ApplianceBase : public Component, public uart::UARTDevice, public climate::Climate, public Stream {
+  /* Stream interface implementation */
+
+  int available() override { return this->uart_->available(); }
+  int read() override {
+    uint8_t data;
+    this->uart_->read_byte(&data);
+    return data;
+  }
+  int peek() override {
+    uint8_t data;
+    this->uart_->peek_byte(&data);
+    return data;
+  }
+  size_t write(uint8_t data) override {
+    this->uart_->write_byte(data);
+    return 1;
+  }
+  size_t write(const uint8_t *data, size_t size) override {
+    this->uart_->write_array(data, size);
+    return size;
+  }
+  void flush() override { this->uart_->flush(); }
+
+ protected:
+  uart::UARTComponent *uart_;
+};
+
+template class ApplianceBase : public Component {
   static_assert(std::is_base_of::value,
                 "T must derive from dudanov::midea::ApplianceBase class");
 
  public:
   ApplianceBase() {
-    this->base_.setStream(this);
+    this->base_.setStream(&this->stream_);
     this->base_.addOnStateCallback(std::bind(&ApplianceBase::on_status_change, this));
     dudanov::midea::ApplianceBase::setLogger(
         [](int level, const char *tag, int line, const 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(); }
+
+#ifdef USE_REMOTE_TRANSMITTER
+  void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_.set_transmitter(transmitter); }
+#endif
+
+  /* UART communication */
+
+  void set_uart_parent(uart::UARTComponent *parent) { this->stream_.set_uart(parent); }
   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); }
+
+  /* Component methods */
+
+  void setup() override { this->base_.setup(); }
+  void loop() override { this->base_.loop(); }
+  float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; }
+  bool can_proceed() override {
+    return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS;
+  }
+
   void set_beeper_feedback(bool state) { this->base_.setBeeper(state); }
   void set_autoconf(bool value) { this->base_.setAutoconf(value); }
-  void set_supported_modes(const std::set &modes) { this->supported_modes_ = modes; }
-  void set_supported_swing_modes(const std::set &modes) { this->supported_swing_modes_ = modes; }
-  void set_supported_presets(const std::set &presets) { this->supported_presets_ = presets; }
-  void set_custom_presets(const std::set &presets) { this->supported_custom_presets_ = presets; }
-  void set_custom_fan_modes(const std::set &modes) { this->supported_custom_fan_modes_ = 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
-
-  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_{};
-  std::set supported_swing_modes_{};
-  std::set supported_presets_{};
-  std::set supported_custom_presets_{};
-  std::set supported_custom_fan_modes_{};
+  UARTStream stream_;
 #ifdef USE_REMOTE_TRANSMITTER
-  remote_transmitter::RemoteTransmitterComponent *transmitter_{nullptr};
+  IrTransmitter transmitter_;
 #endif
 };
 
diff --git a/esphome/components/midea/climate.py b/esphome/components/midea/climate.py
index 08e82025b6..46c0019efa 100644
--- a/esphome/components/midea/climate.py
+++ b/esphome/components/midea/climate.py
@@ -40,9 +40,9 @@ 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")
+midea_ac_ns = cg.esphome_ns.namespace("midea").namespace("ac")
+AirConditioner = midea_ac_ns.class_("AirConditioner", climate.Climate, cg.Component)
+Capabilities = midea_ac_ns.namespace("Constants")
 
 
 def templatize(value):
@@ -156,13 +156,13 @@ CONFIG_SCHEMA = cv.All(
 )
 
 # 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)
+FollowMeAction = midea_ac_ns.class_("FollowMeAction", automation.Action)
+DisplayToggleAction = midea_ac_ns.class_("DisplayToggleAction", automation.Action)
+SwingStepAction = midea_ac_ns.class_("SwingStepAction", automation.Action)
+BeeperOnAction = midea_ac_ns.class_("BeeperOnAction", automation.Action)
+BeeperOffAction = midea_ac_ns.class_("BeeperOffAction", automation.Action)
+PowerOnAction = midea_ac_ns.class_("PowerOnAction", automation.Action)
+PowerOffAction = midea_ac_ns.class_("PowerOffAction", automation.Action)
 
 MIDEA_ACTION_BASE_SCHEMA = cv.Schema(
     {
diff --git a/esphome/components/midea/midea_ir.h b/esphome/components/midea/ir_transmitter.h
similarity index 73%
rename from esphome/components/midea/midea_ir.h
rename to esphome/components/midea/ir_transmitter.h
index abd4324bcc..34a9f8498e 100644
--- a/esphome/components/midea/midea_ir.h
+++ b/esphome/components/midea/ir_transmitter.h
@@ -7,6 +7,7 @@
 namespace esphome {
 namespace midea {
 
+using remote_base::RemoteTransmitterBase;
 using IrData = remote_base::MideaData;
 
 class IrFollowMeData : public IrData {
@@ -38,6 +39,20 @@ class IrSpecialData : public IrData {
   IrSpecialData(uint8_t code) : IrData({MIDEA_TYPE_SPECIAL, code, 0xFF, 0xFF, 0xFF}) {}
 };
 
+class IrTransmitter {
+ public:
+  void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; }
+  void transmit(IrData &data) {
+    data.finalize();
+    auto transmit = this->transmitter_->transmit();
+    remote_base::MideaProtocol().encode(transmit.get_data(), data);
+    transmit.perform();
+  }
+
+ protected:
+  RemoteTransmitterBase *transmitter_{nullptr};
+};
+
 }  // namespace midea
 }  // namespace esphome
 

From 7333123ba4e108f9e03b021af418abb6877330ea Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Mon, 15 Nov 2021 10:59:48 +1300
Subject: [PATCH 335/549] Fix indentation of write_lambda for modbus_controller
 number (#2722)

---
 .../modbus_controller/number/__init__.py      | 22 +++++++++----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py
index afb69f8798..4de0ffbcea 100644
--- a/esphome/components/modbus_controller/number/__init__.py
+++ b/esphome/components/modbus_controller/number/__init__.py
@@ -129,14 +129,14 @@ async def to_code(config):
             return_type=cg.optional.template(float),
         )
         cg.add(var.set_template(template_))
-        if CONF_WRITE_LAMBDA in config:
-            template_ = await cg.process_lambda(
-                config[CONF_WRITE_LAMBDA],
-                [
-                    (ModbusNumber.operator("ptr"), "item"),
-                    (cg.float_, "x"),
-                    (cg.std_vector.template(cg.uint16).operator("ref"), "payload"),
-                ],
-                return_type=cg.optional.template(float),
-            )
-            cg.add(var.set_write_template(template_))
+    if CONF_WRITE_LAMBDA in config:
+        template_ = await cg.process_lambda(
+            config[CONF_WRITE_LAMBDA],
+            [
+                (ModbusNumber.operator("ptr"), "item"),
+                (cg.float_, "x"),
+                (cg.std_vector.template(cg.uint16).operator("ref"), "payload"),
+            ],
+            return_type=cg.optional.template(float),
+        )
+        cg.add(var.set_write_template(template_))

From 0b193eee432cd9c11dc0ac42d1429b9c48966db2 Mon Sep 17 00:00:00 2001
From: Alexandre-Jacques St-Jacques 
Date: Sun, 14 Nov 2021 17:58:22 -0500
Subject: [PATCH 336/549] Remove unnecessary duplicate touch_pad_filter_start
 (#2724)

---
 esphome/components/esp32_touch/esp32_touch.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp
index 85f4058eee..b225ae1a8a 100644
--- a/esphome/components/esp32_touch/esp32_touch.cpp
+++ b/esphome/components/esp32_touch/esp32_touch.cpp
@@ -94,7 +94,6 @@ void ESP32TouchComponent::dump_config() {
 
   if (this->iir_filter_enabled_()) {
     ESP_LOGCONFIG(TAG, "    IIR Filter: %ums", this->iir_filter_);
-    touch_pad_filter_start(this->iir_filter_);
   } else {
     ESP_LOGCONFIG(TAG, "  IIR Filter DISABLED");
   }

From 5404163be08ec98b95970de204105ab424d21aaf Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Mon, 15 Nov 2021 15:48:16 +0100
Subject: [PATCH 337/549] Clean-up MAC address helpers (#2713)

---
 esphome/core/helpers.cpp | 28 +++++++++-------------------
 esphome/core/helpers.h   |  7 +++----
 2 files changed, 12 insertions(+), 23 deletions(-)

diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp
index 27608a84c1..3e614eb515 100644
--- a/esphome/core/helpers.cpp
+++ b/esphome/core/helpers.cpp
@@ -6,11 +6,10 @@
 #include 
 
 #if defined(USE_ESP8266)
-#ifdef USE_WIFI
-#include 
-#endif
-#include 
 #include 
+#include 
+// for xt_rsil()/xt_wsr_ps()
+#include 
 #elif defined(USE_ESP32_FRAMEWORK_ARDUINO)
 #include 
 #elif defined(USE_ESP_IDF)
@@ -31,8 +30,8 @@ namespace esphome {
 static const char *const TAG = "helpers";
 
 void get_mac_address_raw(uint8_t *mac) {
-#ifdef USE_ESP32
-#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC
+#if defined(USE_ESP32)
+#if defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC)
   // On some devices, the MAC address that is burnt into EFuse does not
   // match the CRC that goes along with it. For those devices, this
   // work-around reads and uses the MAC address as-is from EFuse,
@@ -41,30 +40,21 @@ void get_mac_address_raw(uint8_t *mac) {
 #else
   esp_efuse_mac_get_default(mac);
 #endif
-#endif
-#if (defined USE_ESP8266 && defined USE_WIFI)
-  WiFi.macAddress(mac);
+#elif defined(USE_ESP8266)
+  wifi_get_macaddr(STATION_IF, mac);
 #endif
 }
 
 std::string get_mac_address() {
-  char tmp[20];
   uint8_t mac[6];
   get_mac_address_raw(mac);
-#ifdef USE_WIFI
-  sprintf(tmp, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
-#else
-  return "";
-#endif
-  return std::string(tmp);
+  return str_sprintf("%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
 }
 
 std::string get_mac_address_pretty() {
-  char tmp[20];
   uint8_t mac[6];
   get_mac_address_raw(mac);
-  sprintf(tmp, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
-  return std::string(tmp);
+  return str_sprintf("%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
 }
 
 #ifdef USE_ESP32
diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h
index c67ad8eea3..120642c62e 100644
--- a/esphome/core/helpers.h
+++ b/esphome/core/helpers.h
@@ -25,14 +25,13 @@
 
 namespace esphome {
 
-/// Read the raw MAC address into the provided byte array (6 bytes).
+/// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes).
 void get_mac_address_raw(uint8_t *mac);
 
-/// Get the MAC address as a string, using lower case hex notation.
-/// This can be used as way to identify this ESP.
+/// Get the device MAC address as a string, in lowercase hex notation.
 std::string get_mac_address();
 
-/// Get the MAC address as a string, using colon-separated upper case hex notation.
+/// Get the device MAC address as a string, in colon-separated uppercase hex notation.
 std::string get_mac_address_pretty();
 
 #ifdef USE_ESP32

From 515519bc8745a627dd02cf33c53933d5d4e4eeb1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Krzysztof=20Bia=C5=82ek?= 
Date: Mon, 15 Nov 2021 15:49:18 +0100
Subject: [PATCH 338/549] Provide an option to select MQTT unique_id generator
 (#2701)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Oxan van Leeuwen 
---
 esphome/components/mqtt/__init__.py        | 23 ++++++++++++++++++++--
 esphome/components/mqtt/mqtt_client.cpp    |  4 +++-
 esphome/components/mqtt/mqtt_client.h      | 11 ++++++++++-
 esphome/components/mqtt/mqtt_component.cpp | 14 ++++++++++---
 esphome/const.py                           |  1 +
 tests/test1.yaml                           |  1 +
 6 files changed, 47 insertions(+), 7 deletions(-)

diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py
index 0f7d246473..73af0bad90 100644
--- a/esphome/components/mqtt/__init__.py
+++ b/esphome/components/mqtt/__init__.py
@@ -14,6 +14,7 @@ from esphome.const import (
     CONF_DISCOVERY,
     CONF_DISCOVERY_PREFIX,
     CONF_DISCOVERY_RETAIN,
+    CONF_DISCOVERY_UNIQUE_ID_GENERATOR,
     CONF_ID,
     CONF_KEEPALIVE,
     CONF_LEVEL,
@@ -95,6 +96,12 @@ MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent)
 MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent)
 MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent)
 
+MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator")
+MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = {
+    "legacy": MQTTDiscoveryUniqueIdGenerator.MQTT_LEGACY_UNIQUE_ID_GENERATOR,
+    "mac": MQTTDiscoveryUniqueIdGenerator.MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR,
+}
+
 
 def validate_config(value):
     # Populate default fields
@@ -153,6 +160,9 @@ CONFIG_SCHEMA = cv.All(
             cv.Optional(
                 CONF_DISCOVERY_PREFIX, default="homeassistant"
             ): cv.publish_topic,
+            cv.Optional(CONF_DISCOVERY_UNIQUE_ID_GENERATOR, default="legacy"): cv.enum(
+                MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS
+            ),
             cv.Optional(CONF_USE_ABBREVIATIONS, default=True): cv.boolean,
             cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA,
             cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA,
@@ -231,13 +241,22 @@ async def to_code(config):
     discovery = config[CONF_DISCOVERY]
     discovery_retain = config[CONF_DISCOVERY_RETAIN]
     discovery_prefix = config[CONF_DISCOVERY_PREFIX]
+    discovery_unique_id_generator = config[CONF_DISCOVERY_UNIQUE_ID_GENERATOR]
 
     if not discovery:
         cg.add(var.disable_discovery())
     elif discovery == "CLEAN":
-        cg.add(var.set_discovery_info(discovery_prefix, discovery_retain, True))
+        cg.add(
+            var.set_discovery_info(
+                discovery_prefix, discovery_unique_id_generator, discovery_retain, True
+            )
+        )
     elif CONF_DISCOVERY_RETAIN in config or CONF_DISCOVERY_PREFIX in config:
-        cg.add(var.set_discovery_info(discovery_prefix, discovery_retain))
+        cg.add(
+            var.set_discovery_info(
+                discovery_prefix, discovery_unique_id_generator, discovery_retain
+            )
+        )
 
     cg.add(var.set_topic_prefix(config[CONF_TOPIC_PREFIX]))
 
diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp
index 040b0001fe..43c49e9f7f 100644
--- a/esphome/components/mqtt/mqtt_client.cpp
+++ b/esphome/components/mqtt/mqtt_client.cpp
@@ -535,8 +535,10 @@ void MQTTClientComponent::set_birth_message(MQTTMessage &&message) {
 
 void MQTTClientComponent::set_shutdown_message(MQTTMessage &&message) { this->shutdown_message_ = std::move(message); }
 
-void MQTTClientComponent::set_discovery_info(std::string &&prefix, bool retain, bool clean) {
+void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator,
+                                             bool retain, bool clean) {
   this->discovery_info_.prefix = std::move(prefix);
+  this->discovery_info_.unique_id_generator = unique_id_generator;
   this->discovery_info_.retain = retain;
   this->discovery_info_.clean = clean;
 }
diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h
index fa689eaa04..d6194da794 100644
--- a/esphome/components/mqtt/mqtt_client.h
+++ b/esphome/components/mqtt/mqtt_client.h
@@ -55,6 +55,12 @@ struct Availability {
   std::string payload_not_available;
 };
 
+/// available discovery unique_id generators
+enum MQTTDiscoveryUniqueIdGenerator {
+  MQTT_LEGACY_UNIQUE_ID_GENERATOR = 0,
+  MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR,
+};
+
 /** Internal struct for MQTT Home Assistant discovery
  *
  * See MQTT Discovery.
@@ -63,6 +69,7 @@ struct MQTTDiscoveryInfo {
   std::string prefix;  ///< The Home Assistant discovery prefix. Empty means disabled.
   bool retain;         ///< Whether to retain discovery messages.
   bool clean;
+  MQTTDiscoveryUniqueIdGenerator unique_id_generator;
 };
 
 enum MQTTClientState {
@@ -98,9 +105,11 @@ class MQTTClientComponent : public Component {
    *
    * See MQTT Discovery.
    * @param prefix The Home Assistant discovery prefix.
+   * @param unique_id_generator Controls how UniqueId is generated.
    * @param retain Whether to retain discovery messages.
    */
-  void set_discovery_info(std::string &&prefix, bool retain, bool clean = false);
+  void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, bool retain,
+                          bool clean = false);
   /// Get Home Assistant discovery info.
   const MQTTDiscoveryInfo &get_discovery_info() const;
   /// Globally disable Home Assistant discovery.
diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp
index e3ae4dea50..bf9f5e34b8 100644
--- a/esphome/components/mqtt/mqtt_component.cpp
+++ b/esphome/components/mqtt/mqtt_component.cpp
@@ -114,9 +114,17 @@ bool MQTTComponent::send_discovery_() {
         if (!unique_id.empty()) {
           root[MQTT_UNIQUE_ID] = unique_id;
         } else {
-          // default to almost-unique ID. It's a hack but the only way to get that
-          // gorgeous device registry view.
-          root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_();
+          const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
+          if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
+            char friendly_name_hash[9];
+            sprintf(friendly_name_hash, "%08x", fnv1_hash(this->friendly_name()));
+            friendly_name_hash[8] = 0;  // ensure the hash-string ends with null
+            root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash;
+          } else {
+            // default to almost-unique ID. It's a hack but the only way to get that
+            // gorgeous device registry view.
+            root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_();
+          }
         }
 
         JsonObject &device_info = root.createNestedObject(MQTT_DEVICE);
diff --git a/esphome/const.py b/esphome/const.py
index fb241f04b5..9a778edfeb 100644
--- a/esphome/const.py
+++ b/esphome/const.py
@@ -167,6 +167,7 @@ CONF_DISABLED_BY_DEFAULT = "disabled_by_default"
 CONF_DISCOVERY = "discovery"
 CONF_DISCOVERY_PREFIX = "discovery_prefix"
 CONF_DISCOVERY_RETAIN = "discovery_retain"
+CONF_DISCOVERY_UNIQUE_ID_GENERATOR = "discovery_unique_id_generator"
 CONF_DISTANCE = "distance"
 CONF_DITHER = "dither"
 CONF_DIV_RATIO = "div_ratio"
diff --git a/tests/test1.yaml b/tests/test1.yaml
index 263754dc4f..18c6610b08 100644
--- a/tests/test1.yaml
+++ b/tests/test1.yaml
@@ -98,6 +98,7 @@ mqtt:
   discovery: True
   discovery_retain: False
   discovery_prefix: discovery
+  discovery_unique_id_generator: legacy
   topic_prefix: helloworld
   log_topic:
     topic: helloworld/hi

From b386284180f33c0446fbb5033a99fe8e85d2fdd9 Mon Sep 17 00:00:00 2001
From: cvwillegen 
Date: Mon, 15 Nov 2021 20:06:55 +0100
Subject: [PATCH 339/549] Ignore secrets.yaml on command line (#2715)

---
 esphome/__main__.py | 16 ++++++++--------
 esphome/const.py    |  1 +
 2 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/esphome/__main__.py b/esphome/__main__.py
index c2a6dd343f..7c7c22dd1f 100644
--- a/esphome/__main__.py
+++ b/esphome/__main__.py
@@ -18,6 +18,7 @@ from esphome.const import (
     CONF_PORT,
     CONF_ESPHOME,
     CONF_PLATFORMIO_OPTIONS,
+    SECRETS_FILES,
 )
 from esphome.core import CORE, EsphomeError, coroutine
 from esphome.helpers import indent
@@ -200,8 +201,7 @@ def upload_using_esptool(config, port):
         firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
         flash_images = [
             platformio_api.FlashImage(
-                path=idedata.firmware_bin_path,
-                offset=firmware_offset,
+                path=idedata.firmware_bin_path, offset=firmware_offset
             ),
             *idedata.extra_flash_images,
         ]
@@ -607,10 +607,7 @@ def parse_args(argv):
         "wizard",
         help="A helpful setup wizard that will guide you through setting up ESPHome.",
     )
-    parser_wizard.add_argument(
-        "configuration",
-        help="Your YAML configuration file.",
-    )
+    parser_wizard.add_argument("configuration", help="Your YAML configuration file.")
 
     parser_fingerprint = subparsers.add_parser(
         "mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
@@ -632,8 +629,7 @@ def parse_args(argv):
         "dashboard", help="Create a simple web server for a dashboard."
     )
     parser_dashboard.add_argument(
-        "configuration",
-        help="Your YAML configuration file directory.",
+        "configuration", help="Your YAML configuration file directory."
     )
     parser_dashboard.add_argument(
         "--port",
@@ -789,6 +785,10 @@ def run_esphome(argv):
             return 1
 
     for conf_path in args.configuration:
+        if any(os.path.basename(conf_path) == x for x in SECRETS_FILES):
+            _LOGGER.warning("Skipping secrets file %s", conf_path)
+            continue
+
         CORE.config_path = conf_path
         CORE.dashboard = args.dashboard
 
diff --git a/esphome/const.py b/esphome/const.py
index 9a778edfeb..2a7942a2b4 100644
--- a/esphome/const.py
+++ b/esphome/const.py
@@ -11,6 +11,7 @@ TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266]
 
 SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}
 HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}
+SECRETS_FILES = {"secrets.yaml", "secrets.yml"}
 
 
 CONF_ABOVE = "above"

From 0809673ba9fa9d3a024653522d5b9aa8aea470b2 Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Tue, 16 Nov 2021 09:53:52 +1300
Subject: [PATCH 340/549] Add zeroconf as a direct dependency and lock the
 version (#2729)

---
 requirements.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/requirements.txt b/requirements.txt
index 6e1fe56057..c4b211283d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -11,6 +11,7 @@ esptool==3.2
 click==8.0.3
 esphome-dashboard==20211021.1
 aioesphomeapi==10.2.0
+zeroconf==0.36.13
 
 # esp-idf requires this, but doesn't bundle it by default
 # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24

From 9e4fa5dcf1b7f8526fe40f68cbbec3e1155dfa18 Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Tue, 16 Nov 2021 11:02:45 +1300
Subject: [PATCH 341/549] Improv serial/checksum changes (#2731)

Co-authored-by: Paulus Schoutsen 
---
 esphome/components/improv/improv.cpp          | 54 ++++++++++---------
 esphome/components/improv/improv.h            |  9 ++--
 .../improv_serial/improv_serial_component.cpp | 35 ++++++++----
 3 files changed, 61 insertions(+), 37 deletions(-)

diff --git a/esphome/components/improv/improv.cpp b/esphome/components/improv/improv.cpp
index 94068bc626..759962b51a 100644
--- a/esphome/components/improv/improv.cpp
+++ b/esphome/components/improv/improv.cpp
@@ -2,30 +2,32 @@
 
 namespace improv {
 
-ImprovCommand parse_improv_data(const std::vector &data) {
-  return parse_improv_data(data.data(), data.size());
+ImprovCommand parse_improv_data(const std::vector &data, bool check_checksum) {
+  return parse_improv_data(data.data(), data.size(), check_checksum);
 }
 
-ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
+ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum) {
   ImprovCommand improv_command;
   Command command = (Command) data[0];
   uint8_t data_length = data[1];
 
-  if (data_length != length - 3) {
+  if (data_length != length - 2 - check_checksum) {
     improv_command.command = UNKNOWN;
     return improv_command;
   }
 
-  uint8_t checksum = data[length - 1];
+  if (check_checksum) {
+    uint8_t checksum = data[length - 1];
 
-  uint32_t calculated_checksum = 0;
-  for (uint8_t i = 0; i < length - 1; i++) {
-    calculated_checksum += data[i];
-  }
+    uint32_t calculated_checksum = 0;
+    for (uint8_t i = 0; i < length - 1; i++) {
+      calculated_checksum += data[i];
+    }
 
-  if ((uint8_t) calculated_checksum != checksum) {
-    improv_command.command = BAD_CHECKSUM;
-    return improv_command;
+    if ((uint8_t) calculated_checksum != checksum) {
+      improv_command.command = BAD_CHECKSUM;
+      return improv_command;
+    }
   }
 
   if (command == WIFI_SETTINGS) {
@@ -46,7 +48,7 @@ ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
   return improv_command;
 }
 
-std::vector build_rpc_response(Command command, const std::vector &datum) {
+std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum) {
   std::vector out;
   uint32_t length = 0;
   out.push_back(command);
@@ -58,17 +60,19 @@ std::vector build_rpc_response(Command command, const std::vector build_rpc_response(Command command, const std::vector &datum) {
+#ifdef ARDUINO
+std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum) {
   std::vector out;
   uint32_t length = 0;
   out.push_back(command);
@@ -80,14 +84,16 @@ std::vector build_rpc_response(Command command, const std::vector &data);
-ImprovCommand parse_improv_data(const uint8_t *data, size_t length);
+ImprovCommand parse_improv_data(const std::vector &data, bool check_checksum = true);
+ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum = true);
 
-std::vector build_rpc_response(Command command, const std::vector &datum);
+std::vector build_rpc_response(Command command, const std::vector &datum,
+                                        bool add_checksum = true);
 #ifdef ARDUINO
-std::vector build_rpc_response(Command command, const std::vector &datum);
+std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum = true);
 #endif  // ARDUINO
 
 }  // namespace improv
diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp
index abbb76ab11..a9a7467125 100644
--- a/esphome/components/improv_serial/improv_serial_component.cpp
+++ b/esphome/components/improv_serial/improv_serial_component.cpp
@@ -98,13 +98,13 @@ std::vector ImprovSerialComponent::build_rpc_settings_response_(improv:
   std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
   urls.push_back(webserver_url);
 #endif
-  std::vector data = improv::build_rpc_response(command, urls);
+  std::vector data = improv::build_rpc_response(command, urls, false);
   return data;
 }
 
 std::vector ImprovSerialComponent::build_version_info_() {
   std::vector infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
-  std::vector data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos);
+  std::vector data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
   return data;
 };
 
@@ -140,22 +140,33 @@ bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
   if (at < 8 + data_len)
     return true;
 
-  if (at == 8 + data_len) {
+  if (at == 8 + data_len)
+    return true;
+
+  if (at == 8 + data_len + 1) {
+    uint8_t checksum = 0x00;
+    for (uint8_t i = 0; i < at; i++)
+      checksum += raw[i];
+
+    if (checksum != byte) {
+      ESP_LOGW(TAG, "Error decoding Improv payload");
+      this->set_error_(improv::ERROR_INVALID_RPC);
+      return false;
+    }
+
     if (type == TYPE_RPC) {
       this->set_error_(improv::ERROR_NONE);
-      auto command = improv::parse_improv_data(&raw[9], data_len);
+      auto command = improv::parse_improv_data(&raw[9], data_len, false);
       return this->parse_improv_payload_(command);
     }
   }
-  return true;
+
+  // If we got here then the command coming is is improv, but not an RPC command
+  return false;
 }
 
 bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
   switch (command.command) {
-    case improv::BAD_CHECKSUM:
-      ESP_LOGW(TAG, "Error decoding Improv payload");
-      this->set_error_(improv::ERROR_INVALID_RPC);
-      return false;
     case improv::WIFI_SETTINGS: {
       wifi::WiFiAP sta{};
       sta.set_ssid(command.ssid);
@@ -232,6 +243,12 @@ void ImprovSerialComponent::send_response_(std::vector &response) {
   data[7] = TYPE_RPC_RESPONSE;
   data[8] = response.size();
   data.insert(data.end(), response.begin(), response.end());
+
+  uint8_t checksum = 0x00;
+  for (uint8_t d : data)
+    checksum += d;
+  data.push_back(checksum);
+
   this->write_data_(data);
 }
 

From f1954df57337fe4ed08cbd9a59d4bd156278113f Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Tue, 16 Nov 2021 12:47:06 +1300
Subject: [PATCH 342/549] Fix zeroconf time comparisons (#2733)

Co-authored-by: J. Nick Koston 
---
 esphome/zeroconf.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py
index a19fc143ec..1fbdf7e93f 100644
--- a/esphome/zeroconf.py
+++ b/esphome/zeroconf.py
@@ -13,8 +13,9 @@ from zeroconf import (
     RecordUpdateListener,
     Zeroconf,
     ServiceBrowser,
+    ServiceStateChange,
+    current_time_millis,
 )
-from zeroconf._services import ServiceStateChange
 
 _CLASS_IN = 1
 _FLAGS_QR_QUERY = 0x0000  # query
@@ -88,7 +89,7 @@ class DashboardStatus(threading.Thread):
         entries = self.zc.cache.entries_with_name(key)
         if not entries:
             return False
-        now = time.time() * 1000
+        now = current_time_millis()
 
         return any(
             (entry.created + DashboardStatus.OFFLINE_AFTER) >= now for entry in entries
@@ -99,7 +100,7 @@ class DashboardStatus(threading.Thread):
             self.on_update(
                 {key: self.host_status(host) for key, host in self.key_to_host.items()}
             )
-            now = time.time() * 1000
+            now = current_time_millis()
             for host in self.query_hosts:
                 entries = self.zc.cache.entries_with_name(host)
                 if not entries or all(

From b35f5097848b94e14f45816ddfd8036057dcb770 Mon Sep 17 00:00:00 2001
From: Jan Harkes 
Date: Tue, 16 Nov 2021 03:16:43 -0500
Subject: [PATCH 343/549] Allow for subsecond sampling of hmc5883l (#2735)

---
 esphome/components/hmc5883l/sensor.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/esphome/components/hmc5883l/sensor.py b/esphome/components/hmc5883l/sensor.py
index 73e7472dcf..9d8701079e 100644
--- a/esphome/components/hmc5883l/sensor.py
+++ b/esphome/components/hmc5883l/sensor.py
@@ -114,8 +114,8 @@ CONFIG_SCHEMA = (
 
 
 def auto_data_rate(config):
-    interval_sec = config[CONF_UPDATE_INTERVAL].seconds
-    interval_hz = 1.0 / interval_sec
+    interval_msec = config[CONF_UPDATE_INTERVAL].total_milliseconds
+    interval_hz = 1000.0 / interval_msec
     for datarate in sorted(HMC5883LDatarates.keys()):
         if float(datarate) >= interval_hz:
             return HMC5883LDatarates[datarate]

From 8ece639987339a7082fdcea68a4d766906e786d9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=2E=20=C3=81rkosi=20R=C3=B3bert?= 
Date: Tue, 16 Nov 2021 11:28:12 +0100
Subject: [PATCH 344/549] Change log level from DEBUG to INFO for sniffing
 services (#2736)

Sniffing for codes only happens if the user deliberately asked for it with the related service through HA - to find out the codes present in the air. The resulted data shouldn't be printed out only in debug mode, as this is information required to be known on demand for later use, not actually a debug info. Changing log level from DEBUG to INFO for sniffing services has two benefits:
- no need to run firmware with DEBUG enabled for occasional sniffing with devices in production (no need to flash back and forth with different log levels set just for this reason)
- if the user still wants DEBUG enabled, sniffed data appears in different color, it's easier to find between the lines.
---
 esphome/components/rf_bridge/rf_bridge.cpp | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/esphome/components/rf_bridge/rf_bridge.cpp b/esphome/components/rf_bridge/rf_bridge.cpp
index a4259e5aa2..d8c8047496 100644
--- a/esphome/components/rf_bridge/rf_bridge.cpp
+++ b/esphome/components/rf_bridge/rf_bridge.cpp
@@ -52,7 +52,7 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) {
       if (action == RF_CODE_LEARN_OK)
         ESP_LOGD(TAG, "Learning success");
 
-      ESP_LOGD(TAG, "Received RFBridge Code: sync=0x%04X low=0x%04X high=0x%04X code=0x%06X", data.sync, data.low,
+      ESP_LOGI(TAG, "Received RFBridge Code: sync=0x%04X low=0x%04X high=0x%04X code=0x%06X", data.sync, data.low,
                data.high, data.code);
       this->data_callback_.call(data);
       break;
@@ -73,7 +73,7 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) {
         data.code += next_byte;
       }
 
-      ESP_LOGD(TAG, "Received RFBridge Advanced Code: length=0x%02X protocol=0x%02X code=0x%s", data.length,
+      ESP_LOGI(TAG, "Received RFBridge Advanced Code: length=0x%02X protocol=0x%02X code=0x%s", data.length,
                data.protocol, data.code.c_str());
       this->advanced_data_callback_.call(data);
       break;
@@ -97,7 +97,7 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) {
           str += " ";
         }
       }
-      ESP_LOGD(TAG, "Received RFBridge Bucket: %s", str.c_str());
+      ESP_LOGI(TAG, "Received RFBridge Bucket: %s", str.c_str());
       break;
     }
     default:
@@ -186,7 +186,7 @@ void RFBridgeComponent::dump_config() {
 }
 
 void RFBridgeComponent::start_advanced_sniffing() {
-  ESP_LOGD(TAG, "Advanced Sniffing on");
+  ESP_LOGI(TAG, "Advanced Sniffing on");
   this->write(RF_CODE_START);
   this->write(RF_CODE_SNIFFING_ON);
   this->write(RF_CODE_STOP);
@@ -194,7 +194,7 @@ void RFBridgeComponent::start_advanced_sniffing() {
 }
 
 void RFBridgeComponent::stop_advanced_sniffing() {
-  ESP_LOGD(TAG, "Advanced Sniffing off");
+  ESP_LOGI(TAG, "Advanced Sniffing off");
   this->write(RF_CODE_START);
   this->write(RF_CODE_SNIFFING_OFF);
   this->write(RF_CODE_STOP);
@@ -202,7 +202,7 @@ void RFBridgeComponent::stop_advanced_sniffing() {
 }
 
 void RFBridgeComponent::start_bucket_sniffing() {
-  ESP_LOGD(TAG, "Raw Bucket Sniffing on");
+  ESP_LOGI(TAG, "Raw Bucket Sniffing on");
   this->write(RF_CODE_START);
   this->write(RF_CODE_RFIN_BUCKET);
   this->write(RF_CODE_STOP);

From f565ff5def4620afe0ced65499310275d81d86aa Mon Sep 17 00:00:00 2001
From: Ryan Hoffman 
Date: Tue, 16 Nov 2021 12:53:36 -0500
Subject: [PATCH 345/549] Use as_reversed_hex_array in ble_sensor to fix UUID
 parsing (#2737)

#1627 renamed as_hex_array to as_reversed_hex_array but forgot to rename these users.
---
 esphome/components/ble_client/sensor/__init__.py | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/esphome/components/ble_client/sensor/__init__.py b/esphome/components/ble_client/sensor/__init__.py
index efe4bf0e9a..4aa6a92ba5 100644
--- a/esphome/components/ble_client/sensor/__init__.py
+++ b/esphome/components/ble_client/sensor/__init__.py
@@ -67,7 +67,7 @@ async def to_code(config):
             var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
         )
     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 len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
@@ -87,7 +87,9 @@ async def to_code(config):
     elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
         esp32_ble_tracker.bt_uuid128_format
     ):
-        uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_CHARACTERISTIC_UUID])
+        uuid128 = esp32_ble_tracker.as_reversed_hex_array(
+            config[CONF_CHARACTERISTIC_UUID]
+        )
         cg.add(var.set_char_uuid128(uuid128))
 
     if CONF_DESCRIPTOR_UUID in config:
@@ -108,7 +110,9 @@ async def to_code(config):
         elif len(config[CONF_DESCRIPTOR_UUID]) == len(
             esp32_ble_tracker.bt_uuid128_format
         ):
-            uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_DESCRIPTOR_UUID])
+            uuid128 = esp32_ble_tracker.as_reversed_hex_array(
+                config[CONF_DESCRIPTOR_UUID]
+            )
             cg.add(var.set_descr_uuid128(uuid128))
 
     if CONF_LAMBDA in config:

From 57bdc2b88540402f57dd2ef7774b162191f5b95e Mon Sep 17 00:00:00 2001
From: Ryan Hoffman 
Date: Tue, 16 Nov 2021 13:30:42 -0500
Subject: [PATCH 346/549] Add ble_client binary_output (#2200)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
---
 esphome/components/ble_client/ble_client.cpp  |  9 +++
 esphome/components/ble_client/ble_client.h    |  2 +-
 .../components/ble_client/output/__init__.py  | 67 +++++++++++++++++
 .../ble_client/output/ble_binary_output.cpp   | 71 +++++++++++++++++++
 .../ble_client/output/ble_binary_output.h     | 39 ++++++++++
 5 files changed, 187 insertions(+), 1 deletion(-)
 create mode 100644 esphome/components/ble_client/output/__init__.py
 create mode 100644 esphome/components/ble_client/output/ble_binary_output.cpp
 create mode 100644 esphome/components/ble_client/output/ble_binary_output.h

diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp
index e6cdb0c23d..407f1a1d17 100644
--- a/esphome/components/ble_client/ble_client.cpp
+++ b/esphome/components/ble_client/ble_client.cpp
@@ -388,6 +388,15 @@ BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
   return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
 }
 
+void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
+  auto client = this->service->client;
+  auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val,
+                                         ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
+  if (status) {
+    ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
+  }
+}
+
 }  // namespace ble_client
 }  // namespace esphome
 
diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h
index 23123914e8..5680b69f72 100644
--- a/esphome/components/ble_client/ble_client.h
+++ b/esphome/components/ble_client/ble_client.h
@@ -59,7 +59,7 @@ class BLECharacteristic {
   void parse_descriptors();
   BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
   BLEDescriptor *get_descriptor(uint16_t uuid);
-
+  void write_value(uint8_t *new_val, int16_t new_val_size);
   BLEService *service;
 };
 
diff --git a/esphome/components/ble_client/output/__init__.py b/esphome/components/ble_client/output/__init__.py
new file mode 100644
index 0000000000..fe5835ca82
--- /dev/null
+++ b/esphome/components/ble_client/output/__init__.py
@@ -0,0 +1,67 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome.components import output, ble_client, esp32_ble_tracker
+from esphome.const import CONF_ID, CONF_SERVICE_UUID
+from .. import ble_client_ns
+
+
+DEPENDENCIES = ["ble_client"]
+
+CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
+
+BLEBinaryOutput = ble_client_ns.class_(
+    "BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component
+)
+
+CONFIG_SCHEMA = cv.All(
+    output.BINARY_OUTPUT_SCHEMA.extend(
+        {
+            cv.Required(CONF_ID): cv.declare_id(BLEBinaryOutput),
+            cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
+            cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
+        }
+    )
+    .extend(cv.COMPONENT_SCHEMA)
+    .extend(ble_client.BLE_CLIENT_SCHEMA)
+)
+
+
+def to_code(config):
+    var = cg.new_Pvariable(config[CONF_ID])
+    if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
+        cg.add(
+            var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
+        )
+    elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
+        cg.add(
+            var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
+        )
+    elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
+        uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
+        cg.add(var.set_service_uuid128(uuid128))
+
+    if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
+        cg.add(
+            var.set_char_uuid16(
+                esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
+            )
+        )
+    elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
+        esp32_ble_tracker.bt_uuid32_format
+    ):
+        cg.add(
+            var.set_char_uuid32(
+                esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
+            )
+        )
+    elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
+        esp32_ble_tracker.bt_uuid128_format
+    ):
+        uuid128 = esp32_ble_tracker.as_reversed_hex_array(
+            config[CONF_CHARACTERISTIC_UUID]
+        )
+        cg.add(var.set_char_uuid128(uuid128))
+
+    yield output.register_output(var, config)
+    yield ble_client.register_ble_node(var, config)
+    yield cg.register_component(var, config)
diff --git a/esphome/components/ble_client/output/ble_binary_output.cpp b/esphome/components/ble_client/output/ble_binary_output.cpp
new file mode 100644
index 0000000000..ff3711e842
--- /dev/null
+++ b/esphome/components/ble_client/output/ble_binary_output.cpp
@@ -0,0 +1,71 @@
+#include "ble_binary_output.h"
+#include "esphome/core/log.h"
+#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
+
+#ifdef USE_ESP32
+namespace esphome {
+namespace ble_client {
+
+static const char *const TAG = "ble_binary_output";
+
+void BLEBinaryOutput::dump_config() {
+  ESP_LOGCONFIG(TAG, "BLE Binary Output:");
+  ESP_LOGCONFIG(TAG, "  MAC address        : %s", this->parent_->address_str().c_str());
+  ESP_LOGCONFIG(TAG, "  Service UUID       : %s", this->service_uuid_.to_string().c_str());
+  ESP_LOGCONFIG(TAG, "  Characteristic UUID: %s", this->char_uuid_.to_string().c_str());
+  LOG_BINARY_OUTPUT(this);
+}
+
+void BLEBinaryOutput::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->client_state_ = espbt::ClientState::ESTABLISHED;
+      ESP_LOGW(TAG, "[%s] Connected successfully!", this->char_uuid_.to_string().c_str());
+      break;
+    case ESP_GATTC_DISCONNECT_EVT:
+      ESP_LOGW(TAG, "[%s] Disconnected", this->char_uuid_.to_string().c_str());
+      this->client_state_ = espbt::ClientState::IDLE;
+      break;
+    case ESP_GATTC_WRITE_CHAR_EVT: {
+      if (param->write.status == 0) {
+        break;
+      }
+
+      auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
+      if (chr == nullptr) {
+        ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str());
+        break;
+      }
+      if (param->write.handle == chr->handle) {
+        ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status);
+      }
+      break;
+    }
+    default:
+      break;
+  }
+}
+
+void BLEBinaryOutput::write_state(bool state) {
+  if (this->client_state_ != espbt::ClientState::ESTABLISHED) {
+    ESP_LOGW(TAG, "[%s] Not connected to BLE client.  State update can not be written.",
+             this->char_uuid_.to_string().c_str());
+    return;
+  }
+
+  auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
+  if (chr == nullptr) {
+    ESP_LOGW(TAG, "[%s] Characteristic not found.  State update can not be written.",
+             this->char_uuid_.to_string().c_str());
+    return;
+  }
+
+  uint8_t state_as_uint = (uint8_t) state;
+  ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint);
+  chr->write_value(&state_as_uint, sizeof(state_as_uint));
+}
+
+}  // namespace ble_client
+}  // namespace esphome
+#endif
diff --git a/esphome/components/ble_client/output/ble_binary_output.h b/esphome/components/ble_client/output/ble_binary_output.h
new file mode 100644
index 0000000000..e1d62a267b
--- /dev/null
+++ b/esphome/components/ble_client/output/ble_binary_output.h
@@ -0,0 +1,39 @@
+#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/output/binary_output.h"
+
+#ifdef USE_ESP32
+#include 
+namespace esphome {
+namespace ble_client {
+
+namespace espbt = esphome::esp32_ble_tracker;
+
+class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, public Component {
+ public:
+  void dump_config() override;
+  void loop() override {}
+  float get_setup_priority() const override { return setup_priority::DATA; }
+  void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
+  void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
+  void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
+  void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
+  void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
+  void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
+  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
+                           esp_ble_gattc_cb_param_t *param) override;
+
+ protected:
+  void write_state(bool state) override;
+  espbt::ESPBTUUID service_uuid_;
+  espbt::ESPBTUUID char_uuid_;
+  espbt::ClientState client_state_;
+};
+
+}  // namespace ble_client
+}  // namespace esphome
+
+#endif

From df68403b6d7fbde9677015189dc657ebff056adb Mon Sep 17 00:00:00 2001
From: rotarykite 
Date: Wed, 17 Nov 2021 02:57:03 +0800
Subject: [PATCH 347/549] Fix senseair component uart read timeout (#2658)

Co-authored-by: DAVe3283 
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Chua Jun Chieh 
---
 esphome/components/senseair/senseair.cpp | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/esphome/components/senseair/senseair.cpp b/esphome/components/senseair/senseair.cpp
index 610892dd9e..50b9e01f17 100644
--- a/esphome/components/senseair/senseair.cpp
+++ b/esphome/components/senseair/senseair.cpp
@@ -141,12 +141,16 @@ void SenseAirComponent::abc_get_period() {
 }
 
 bool SenseAirComponent::senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length) {
+  // Verify we have somewhere to store the response
+  if (response == nullptr) {
+    return false;
+  }
+  // Write wake up byte required by some S8 sensor models
+  this->write_byte(0);
   this->flush();
+  delay(5);
   this->write_array(command, SENSEAIR_REQUEST_LENGTH);
 
-  if (response == nullptr)
-    return true;
-
   bool ret = this->read_array(response, response_length);
   this->flush();
   return ret;

From dbcfa7b5990d7b9f0e2b70b9d807b16e8e6a8f64 Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Wed, 17 Nov 2021 16:22:38 +1300
Subject: [PATCH 348/549] Remove duplicated const data in esp8266 boards
 (#2740)

---
 esphome/components/esp8266/boards.py | 58 ----------------------------
 1 file changed, 58 deletions(-)

diff --git a/esphome/components/esp8266/boards.py b/esphome/components/esp8266/boards.py
index c49aae4ffa..410e934615 100644
--- a/esphome/components/esp8266/boards.py
+++ b/esphome/components/esp8266/boards.py
@@ -206,61 +206,3 @@ ESP8266_BOARD_PINS = {
     "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"),
-}

From 0469e19f54e10a5f55e18db19f7686fc6474b04c Mon Sep 17 00:00:00 2001
From: Evgeny 
Date: Wed, 17 Nov 2021 09:52:40 +0100
Subject: [PATCH 349/549] Fix HM3301 AQI index calculator (#2739)

---
 esphome/components/hm3301/aqi_calculator.h  | 2 +-
 esphome/components/hm3301/caqi_calculator.h | 4 +---
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/esphome/components/hm3301/aqi_calculator.h b/esphome/components/hm3301/aqi_calculator.h
index 1410eac72b..a3839b643c 100644
--- a/esphome/components/hm3301/aqi_calculator.h
+++ b/esphome/components/hm3301/aqi_calculator.h
@@ -33,7 +33,7 @@ class AQICalculator : public AbstractAQICalculator {
     int conc_lo = array[grid_index][0];
     int conc_hi = array[grid_index][1];
 
-    return ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo;
+    return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
   }
 
   int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
diff --git a/esphome/components/hm3301/caqi_calculator.h b/esphome/components/hm3301/caqi_calculator.h
index 51158454d0..a7f5460e0a 100644
--- a/esphome/components/hm3301/caqi_calculator.h
+++ b/esphome/components/hm3301/caqi_calculator.h
@@ -37,9 +37,7 @@ class CAQICalculator : public AbstractAQICalculator {
     int conc_lo = array[grid_index][0];
     int conc_hi = array[grid_index][1];
 
-    int aqi = ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo;
-
-    return aqi;
+    return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
   }
 
   int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {

From 6c1ef398bb7e981a6cd281175c89f702b21f19a0 Mon Sep 17 00:00:00 2001
From: Franck Nijhof 
Date: Wed, 17 Nov 2021 11:28:31 +0100
Subject: [PATCH 350/549] Re-instate device class update for binary sensors
 (#2743)

---
 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 3f11e18e45..1eab76d54e 100644
--- a/esphome/components/binary_sensor/__init__.py
+++ b/esphome/components/binary_sensor/__init__.py
@@ -49,6 +49,7 @@ from esphome.const import (
     DEVICE_CLASS_SMOKE,
     DEVICE_CLASS_SOUND,
     DEVICE_CLASS_TAMPER,
+    DEVICE_CLASS_UPDATE,
     DEVICE_CLASS_VIBRATION,
     DEVICE_CLASS_WINDOW,
 )
@@ -82,6 +83,7 @@ DEVICE_CLASSES = [
     DEVICE_CLASS_SMOKE,
     DEVICE_CLASS_SOUND,
     DEVICE_CLASS_TAMPER,
+    DEVICE_CLASS_UPDATE,
     DEVICE_CLASS_VIBRATION,
     DEVICE_CLASS_WINDOW,
 ]
diff --git a/esphome/const.py b/esphome/const.py
index 2a7942a2b4..f7beee8245 100644
--- a/esphome/const.py
+++ b/esphome/const.py
@@ -863,6 +863,7 @@ DEVICE_CLASS_SAFETY = "safety"
 DEVICE_CLASS_SMOKE = "smoke"
 DEVICE_CLASS_SOUND = "sound"
 DEVICE_CLASS_TAMPER = "tamper"
+DEVICE_CLASS_UPDATE = "update"
 DEVICE_CLASS_VIBRATION = "vibration"
 DEVICE_CLASS_WINDOW = "window"
 # device classes of both binary_sensor and sensor component

From df6730be55a19b5a3e1d8974beb4740fdc29e8fc Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Thu, 18 Nov 2021 06:23:17 +1300
Subject: [PATCH 351/549] Move to use improv lib from platformio (#2741)

---
 CODEOWNERS                                    |  1 -
 esphome/components/esp32_improv/__init__.py   |  3 +-
 .../esp32_improv/esp32_improv_component.h     |  5 +-
 esphome/components/improv/__init__.py         |  1 -
 esphome/components/improv/improv.cpp          | 99 -------------------
 esphome/components/improv/improv.h            | 63 ------------
 esphome/components/improv_serial/__init__.py  |  2 +-
 .../improv_serial/improv_serial_component.h   |  3 +-
 platformio.ini                                |  1 +
 9 files changed, 9 insertions(+), 169 deletions(-)
 delete mode 100644 esphome/components/improv/__init__.py
 delete mode 100644 esphome/components/improv/improv.cpp
 delete mode 100644 esphome/components/improv/improv.h

diff --git a/CODEOWNERS b/CODEOWNERS
index 18b4564280..80c5dc34c1 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -72,7 +72,6 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal
 esphome/components/homeassistant/* @OttoWinter
 esphome/components/hrxl_maxsonar_wr/* @netmikey
 esphome/components/i2c/* @esphome/core
-esphome/components/improv/* @jesserockz
 esphome/components/improv_serial/* @esphome/core
 esphome/components/inkbird_ibsth1_mini/* @fkirill
 esphome/components/inkplate6/* @jesserockz
diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py
index 0b0214c63e..80c53f7c2a 100644
--- a/esphome/components/esp32_improv/__init__.py
+++ b/esphome/components/esp32_improv/__init__.py
@@ -4,7 +4,7 @@ from esphome.components import binary_sensor, output, esp32_ble_server
 from esphome.const import CONF_ID
 
 
-AUTO_LOAD = ["binary_sensor", "output", "improv", "esp32_ble_server"]
+AUTO_LOAD = ["binary_sensor", "output", "esp32_ble_server"]
 CODEOWNERS = ["@jesserockz"]
 CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
 DEPENDENCIES = ["wifi", "esp32"]
@@ -56,6 +56,7 @@ async def to_code(config):
     cg.add(ble_server.register_service_component(var))
 
     cg.add_define("USE_IMPROV")
+    cg.add_library("esphome/Improv", "1.0.0")
 
     cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
     cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))
diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h
index 3a5d150fbe..45639f2f63 100644
--- a/esphome/components/esp32_improv/esp32_improv_component.h
+++ b/esphome/components/esp32_improv/esp32_improv_component.h
@@ -1,9 +1,8 @@
 #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/esp32_ble_server/ble_server.h"
 #include "esphome/components/output/binary_output.h"
 #include "esphome/components/wifi/wifi_component.h"
 #include "esphome/core/component.h"
@@ -12,6 +11,8 @@
 
 #ifdef USE_ESP32
 
+#include 
+
 namespace esphome {
 namespace esp32_improv {
 
diff --git a/esphome/components/improv/__init__.py b/esphome/components/improv/__init__.py
deleted file mode 100644
index b1de57df8f..0000000000
--- a/esphome/components/improv/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-CODEOWNERS = ["@jesserockz"]
diff --git a/esphome/components/improv/improv.cpp b/esphome/components/improv/improv.cpp
deleted file mode 100644
index 759962b51a..0000000000
--- a/esphome/components/improv/improv.cpp
+++ /dev/null
@@ -1,99 +0,0 @@
-#include "improv.h"
-
-namespace improv {
-
-ImprovCommand parse_improv_data(const std::vector &data, bool check_checksum) {
-  return parse_improv_data(data.data(), data.size(), check_checksum);
-}
-
-ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum) {
-  ImprovCommand improv_command;
-  Command command = (Command) data[0];
-  uint8_t data_length = data[1];
-
-  if (data_length != length - 2 - check_checksum) {
-    improv_command.command = UNKNOWN;
-    return improv_command;
-  }
-
-  if (check_checksum) {
-    uint8_t checksum = data[length - 1];
-
-    uint32_t calculated_checksum = 0;
-    for (uint8_t i = 0; i < length - 1; i++) {
-      calculated_checksum += data[i];
-    }
-
-    if ((uint8_t) calculated_checksum != checksum) {
-      improv_command.command = BAD_CHECKSUM;
-      return improv_command;
-    }
-  }
-
-  if (command == WIFI_SETTINGS) {
-    uint8_t ssid_length = data[2];
-    uint8_t ssid_start = 3;
-    size_t ssid_end = ssid_start + ssid_length;
-
-    uint8_t pass_length = data[ssid_end];
-    size_t pass_start = ssid_end + 1;
-    size_t pass_end = pass_start + pass_length;
-
-    std::string ssid(data + ssid_start, data + ssid_end);
-    std::string password(data + pass_start, data + pass_end);
-    return {.command = command, .ssid = ssid, .password = password};
-  }
-
-  improv_command.command = command;
-  return improv_command;
-}
-
-std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum) {
-  std::vector out;
-  uint32_t length = 0;
-  out.push_back(command);
-  for (auto str : datum) {
-    uint8_t len = str.length();
-    length += len;
-    out.push_back(len);
-    out.insert(out.end(), str.begin(), str.end());
-  }
-  out.insert(out.begin() + 1, length);
-
-  if (add_checksum) {
-    uint32_t calculated_checksum = 0;
-
-    for (uint8_t byte : out) {
-      calculated_checksum += byte;
-    }
-    out.push_back(calculated_checksum);
-  }
-  return out;
-}
-
-#ifdef ARDUINO
-std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum) {
-  std::vector out;
-  uint32_t length = 0;
-  out.push_back(command);
-  for (auto str : datum) {
-    uint8_t len = str.length();
-    length += len;
-    out.push_back(len);
-    out.insert(out.end(), str.begin(), str.end());
-  }
-  out.insert(out.begin() + 1, length);
-
-  if (add_checksum) {
-    uint32_t calculated_checksum = 0;
-
-    for (uint8_t byte : out) {
-      calculated_checksum += byte;
-    }
-    out.push_back(calculated_checksum);
-  }
-  return out;
-}
-#endif  // ARDUINO
-
-}  // namespace improv
diff --git a/esphome/components/improv/improv.h b/esphome/components/improv/improv.h
deleted file mode 100644
index 9d1886ddaf..0000000000
--- a/esphome/components/improv/improv.h
+++ /dev/null
@@ -1,63 +0,0 @@
-#pragma once
-
-#ifdef ARDUINO
-#include "WString.h"
-#endif  // ARDUINO
-
-#include 
-#include 
-#include 
-
-namespace improv {
-
-static const char *const SERVICE_UUID = "00467768-6228-2272-4663-277478268000";
-static const char *const STATUS_UUID = "00467768-6228-2272-4663-277478268001";
-static const char *const ERROR_UUID = "00467768-6228-2272-4663-277478268002";
-static const char *const RPC_COMMAND_UUID = "00467768-6228-2272-4663-277478268003";
-static const char *const RPC_RESULT_UUID = "00467768-6228-2272-4663-277478268004";
-static const char *const CAPABILITIES_UUID = "00467768-6228-2272-4663-277478268005";
-
-enum Error : uint8_t {
-  ERROR_NONE = 0x00,
-  ERROR_INVALID_RPC = 0x01,
-  ERROR_UNKNOWN_RPC = 0x02,
-  ERROR_UNABLE_TO_CONNECT = 0x03,
-  ERROR_NOT_AUTHORIZED = 0x04,
-  ERROR_UNKNOWN = 0xFF,
-};
-
-enum State : uint8_t {
-  STATE_STOPPED = 0x00,
-  STATE_AWAITING_AUTHORIZATION = 0x01,
-  STATE_AUTHORIZED = 0x02,
-  STATE_PROVISIONING = 0x03,
-  STATE_PROVISIONED = 0x04,
-};
-
-enum Command : uint8_t {
-  UNKNOWN = 0x00,
-  WIFI_SETTINGS = 0x01,
-  IDENTIFY = 0x02,
-  GET_CURRENT_STATE = 0x02,
-  GET_DEVICE_INFO = 0x03,
-  BAD_CHECKSUM = 0xFF,
-};
-
-static const uint8_t CAPABILITY_IDENTIFY = 0x01;
-
-struct ImprovCommand {
-  Command command;
-  std::string ssid;
-  std::string password;
-};
-
-ImprovCommand parse_improv_data(const std::vector &data, bool check_checksum = true);
-ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum = true);
-
-std::vector build_rpc_response(Command command, const std::vector &datum,
-                                        bool add_checksum = true);
-#ifdef ARDUINO
-std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum = true);
-#endif  // ARDUINO
-
-}  // namespace improv
diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py
index b1cdc2d93e..ed7c382a2f 100644
--- a/esphome/components/improv_serial/__init__.py
+++ b/esphome/components/improv_serial/__init__.py
@@ -5,7 +5,6 @@ import esphome.final_validate as fv
 
 CODEOWNERS = ["@esphome/core"]
 DEPENDENCIES = ["logger", "wifi"]
-AUTO_LOAD = ["improv"]
 
 improv_serial_ns = cg.esphome_ns.namespace("improv_serial")
 
@@ -31,3 +30,4 @@ FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate
 async def to_code(config):
     var = cg.new_Pvariable(config[CONF_ID])
     await cg.register_component(var, config)
+    cg.add_library("esphome/Improv", "1.0.0")
diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h
index 539674e2d3..304afdaf75 100644
--- a/esphome/components/improv_serial/improv_serial_component.h
+++ b/esphome/components/improv_serial/improv_serial_component.h
@@ -1,11 +1,12 @@
 #pragma once
 
-#include "esphome/components/improv/improv.h"
 #include "esphome/components/wifi/wifi_component.h"
 #include "esphome/core/component.h"
 #include "esphome/core/defines.h"
 #include "esphome/core/helpers.h"
 
+#include 
+
 #ifdef USE_ARDUINO
 #include 
 #endif
diff --git a/platformio.ini b/platformio.ini
index 2ac6d0bfc8..b49438b097 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -28,6 +28,7 @@ build_flags =
 lib_deps =
     esphome/noise-c@0.1.4     ; api
     makuna/NeoPixelBus@2.6.9  ; neopixelbus
+    esphome/Improv@1.0.0      ; improv_serial / esp32_improv
 build_flags =
     -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE
 src_filter =

From dee5d639e2771606906b0bf94b94f0f3331a6132 Mon Sep 17 00:00:00 2001
From: Maurice Makaay 
Date: Wed, 17 Nov 2021 18:24:02 +0100
Subject: [PATCH 352/549] Add max_telegram_length option to dsmr (#2674)

Co-authored-by: Maurice Makaay 
Co-authored-by: Oxan van Leeuwen 
---
 esphome/components/dsmr/__init__.py |  3 ++
 esphome/components/dsmr/dsmr.cpp    | 63 +++++++++++++++++------------
 esphome/components/dsmr/dsmr.h      | 10 ++++-
 tests/test3.yaml                    |  1 +
 4 files changed, 50 insertions(+), 27 deletions(-)

diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py
index dd6e6051aa..1cfc21a4ac 100644
--- a/esphome/components/dsmr/__init__.py
+++ b/esphome/components/dsmr/__init__.py
@@ -15,6 +15,7 @@ CONF_DSMR_ID = "dsmr_id"
 CONF_DECRYPTION_KEY = "decryption_key"
 CONF_CRC_CHECK = "crc_check"
 CONF_GAS_MBUS_ID = "gas_mbus_id"
+CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length"
 
 # Hack to prevent compile error due to ambiguity with lib namespace
 dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr")
@@ -46,6 +47,7 @@ CONFIG_SCHEMA = cv.All(
             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_,
+            cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_,
         }
     ).extend(uart.UART_DEVICE_SCHEMA),
     cv.only_with_arduino,
@@ -55,6 +57,7 @@ CONFIG_SCHEMA = cv.All(
 async def to_code(config):
     uart_component = await cg.get_variable(config[CONF_UART_ID])
     var = cg.new_Pvariable(config[CONF_ID], uart_component, config[CONF_CRC_CHECK])
+    cg.add(var.set_max_telegram_length(config[CONF_MAX_TELEGRAM_LENGTH]))
     if CONF_DECRYPTION_KEY in config:
         cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY]))
     await cg.register_component(var, config)
diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp
index ea852e626e..631b18a1f4 100644
--- a/esphome/components/dsmr/dsmr.cpp
+++ b/esphome/components/dsmr/dsmr.cpp
@@ -12,11 +12,15 @@ namespace dsmr {
 
 static const char *const TAG = "dsmr";
 
+void Dsmr::setup() {
+  telegram_ = new char[max_telegram_len_];  // NOLINT
+}
+
 void Dsmr::loop() {
-  if (this->decryption_key_.empty())
-    this->receive_telegram_();
+  if (decryption_key_.empty())
+    receive_telegram_();
   else
-    this->receive_encrypted_();
+    receive_encrypted_();
 }
 
 bool Dsmr::available_within_timeout_() {
@@ -51,10 +55,10 @@ void Dsmr::receive_telegram_() {
       continue;
 
     // Check for buffer overflow.
-    if (telegram_len_ >= MAX_TELEGRAM_LENGTH) {
+    if (telegram_len_ >= max_telegram_len_) {
       header_found_ = false;
       footer_found_ = false;
-      ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH);
+      ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", max_telegram_len_);
       return;
     }
 
@@ -86,9 +90,7 @@ void Dsmr::receive_telegram_() {
 }
 
 void Dsmr::receive_encrypted_() {
-  // Encrypted buffer
-  uint8_t buffer[MAX_TELEGRAM_LENGTH];
-  size_t buffer_length = 0;
+  encrypted_telegram_len_ = 0;
   size_t packet_size = 0;
 
   while (true) {
@@ -114,39 +116,39 @@ void Dsmr::receive_encrypted_() {
     }
 
     // Check for buffer overflow.
-    if (buffer_length >= MAX_TELEGRAM_LENGTH) {
+    if (encrypted_telegram_len_ >= max_telegram_len_) {
       header_found_ = false;
-      ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH);
+      ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", max_telegram_len_);
       return;
     }
 
-    buffer[buffer_length++] = c;
+    encrypted_telegram_[encrypted_telegram_len_++] = c;
 
-    if (packet_size == 0 && buffer_length > 20) {
+    if (packet_size == 0 && encrypted_telegram_len_ > 20) {
       // Complete header + data bytes
-      packet_size = 13 + (buffer[11] << 8 | buffer[12]);
+      packet_size = 13 + (encrypted_telegram_[11] << 8 | encrypted_telegram_[12]);
       ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size);
     }
-    if (buffer_length == packet_size && packet_size > 0) {
+    if (encrypted_telegram_len_ == packet_size && packet_size > 0) {
       ESP_LOGV(TAG, "End of encrypted telegram found");
       GCM *gcmaes128{new GCM()};
-      gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
+      gcmaes128->setKey(decryption_key_.data(), gcmaes128->keySize());
       // the iv is 8 bytes of the system title + 4 bytes frame counter
       // system title is at byte 2 and frame counter at byte 15
       for (int i = 10; i < 14; i++)
-        buffer[i] = buffer[i + 4];
+        encrypted_telegram_[i] = encrypted_telegram_[i + 4];
       constexpr uint16_t iv_size{12};
-      gcmaes128->setIV(&buffer[2], iv_size);
-      gcmaes128->decrypt(reinterpret_cast(this->telegram_),
+      gcmaes128->setIV(&encrypted_telegram_[2], iv_size);
+      gcmaes128->decrypt(reinterpret_cast(telegram_),
                          // the ciphertext start at byte 18
-                         &buffer[18],
+                         &encrypted_telegram_[18],
                          // cipher size
-                         buffer_length - 17);
+                         encrypted_telegram_len_ - 17);
       delete gcmaes128;  // NOLINT(cppcoreguidelines-owning-memory)
 
-      telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_));
+      telegram_len_ = strnlen(telegram_, max_telegram_len_);
       ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_);
-      ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
+      ESP_LOGVV(TAG, "Decrypted telegram: %s", telegram_);
 
       parse_telegram();
 
@@ -162,7 +164,7 @@ bool Dsmr::parse_telegram() {
   ESP_LOGV(TAG, "Trying to parse telegram");
   ::dsmr::ParseResult res =
       ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false,
-                              this->crc_check_);  // Parse telegram according to data definition. Ignore unknown values.
+                              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_);
@@ -177,6 +179,7 @@ bool Dsmr::parse_telegram() {
 
 void Dsmr::dump_config() {
   ESP_LOGCONFIG(TAG, "DSMR:");
+  ESP_LOGCONFIG(TAG, "  Max telegram length: %d", max_telegram_len_);
 
 #define DSMR_LOG_SENSOR(s) LOG_SENSOR("  ", #s, this->s_##s##_);
   DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, )
@@ -188,7 +191,11 @@ void Dsmr::dump_config() {
 void Dsmr::set_decryption_key(const std::string &decryption_key) {
   if (decryption_key.length() == 0) {
     ESP_LOGI(TAG, "Disabling decryption");
-    this->decryption_key_.clear();
+    decryption_key_.clear();
+    if (encrypted_telegram_ != nullptr) {
+      delete[] encrypted_telegram_;
+      encrypted_telegram_ = nullptr;
+    }
     return;
   }
 
@@ -196,7 +203,7 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
     ESP_LOGE(TAG, "Error, decryption key must be 32 character long");
     return;
   }
-  this->decryption_key_.clear();
+  decryption_key_.clear();
 
   ESP_LOGI(TAG, "Decryption key is set");
   // Verbose level prints decryption key
@@ -207,8 +214,14 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
     strncpy(temp, &(decryption_key.c_str()[i * 2]), 2);
     decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
   }
+
+  if (encrypted_telegram_ == nullptr) {
+    encrypted_telegram_ = new uint8_t[max_telegram_len_];  // NOLINT
+  }
 }
 
+void Dsmr::set_max_telegram_length(size_t length) { max_telegram_len_ = length; }
+
 }  // namespace dsmr
 }  // namespace esphome
 
diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h
index ca2c0f0877..5943e4d47f 100644
--- a/esphome/components/dsmr/dsmr.h
+++ b/esphome/components/dsmr/dsmr.h
@@ -16,7 +16,6 @@
 namespace esphome {
 namespace dsmr {
 
-static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500;
 static constexpr uint32_t READ_TIMEOUT_MS = 200;
 
 using namespace ::dsmr::fields;
@@ -52,6 +51,8 @@ class Dsmr : public Component, public uart::UARTDevice {
  public:
   Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {}
 
+  void setup() override;
+
   void loop() override;
 
   bool parse_telegram();
@@ -72,6 +73,8 @@ class Dsmr : public Component, public uart::UARTDevice {
 
   void set_decryption_key(const std::string &decryption_key);
 
+  void set_max_telegram_length(size_t length);
+
 // Sensor setters
 #define DSMR_SET_SENSOR(s) \
   void set_##s(sensor::Sensor *sensor) { s_##s##_ = sensor; }
@@ -97,8 +100,11 @@ class Dsmr : public Component, public uart::UARTDevice {
   bool available_within_timeout_();
 
   // Telegram buffer
-  char telegram_[MAX_TELEGRAM_LENGTH];
+  size_t max_telegram_len_;
+  char *telegram_{nullptr};
   int telegram_len_{0};
+  uint8_t *encrypted_telegram_{nullptr};
+  int encrypted_telegram_len_{0};
 
   // Serial parser
   bool header_found_{false};
diff --git a/tests/test3.yaml b/tests/test3.yaml
index cf80c06aa8..0b9297a0b8 100644
--- a/tests/test3.yaml
+++ b/tests/test3.yaml
@@ -1308,6 +1308,7 @@ fingerprint_grow:
 dsmr:
   decryption_key: 00112233445566778899aabbccddeeff
   uart_id: uart6
+  max_telegram_length: 1000
 
 daly_bms:
   update_interval: 20s

From 06994c0dfc45270cb34327e61f758d7c55b7dded Mon Sep 17 00:00:00 2001
From: spattinson 
Date: Wed, 17 Nov 2021 17:28:36 +0000
Subject: [PATCH 353/549] Change LUT for ttgo t5 2.13inch to improve partial
 refresh (#2475)

---
 esphome/components/waveshare_epaper/waveshare_epaper.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp
index 92fa289cfa..ee3fb2fe47 100644
--- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp
+++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp
@@ -1183,7 +1183,7 @@ 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,
+    0xF, 0x0,  0x0, 0x0, 0x0,  0x0,  0x0,  0x4,  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,

From 6f9439e1bc60807d7fe7d8f2d009b8fadda4c02c Mon Sep 17 00:00:00 2001
From: "Sergey V. DUDANOV" 
Date: Wed, 17 Nov 2021 21:35:50 +0400
Subject: [PATCH 354/549] Fix byte order in NEC protocol implementation (#2534)

---
 esphome/components/remote_base/nec_protocol.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/esphome/components/remote_base/nec_protocol.cpp b/esphome/components/remote_base/nec_protocol.cpp
index 79a30903a4..47b4d676dd 100644
--- a/esphome/components/remote_base/nec_protocol.cpp
+++ b/esphome/components/remote_base/nec_protocol.cpp
@@ -17,14 +17,14 @@ void NECProtocol::encode(RemoteTransmitData *dst, const NECData &data) {
   dst->set_carrier_frequency(38000);
 
   dst->item(HEADER_HIGH_US, HEADER_LOW_US);
-  for (uint32_t mask = 1UL << 15; mask; mask >>= 1) {
+  for (uint16_t mask = 1; mask; mask <<= 1) {
     if (data.address & mask)
       dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
     else
       dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
   }
 
-  for (uint32_t mask = 1UL << 15; mask; mask >>= 1) {
+  for (uint16_t mask = 1; mask; mask <<= 1) {
     if (data.command & mask)
       dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
     else
@@ -41,7 +41,7 @@ optional NECProtocol::decode(RemoteReceiveData src) {
   if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US))
     return {};
 
-  for (uint32_t mask = 1UL << 15; mask != 0; mask >>= 1) {
+  for (uint16_t mask = 1; mask; mask <<= 1) {
     if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
       data.address |= mask;
     } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
@@ -51,7 +51,7 @@ optional NECProtocol::decode(RemoteReceiveData src) {
     }
   }
 
-  for (uint32_t mask = 1UL << 15; mask != 0; mask >>= 1) {
+  for (uint16_t mask = 1; mask; mask <<= 1) {
     if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
       data.command |= mask;
     } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {

From 8267f01ccdcb4d9564d262f3ec0a02fb0650c107 Mon Sep 17 00:00:00 2001
From: Martin <25747549+martgras@users.noreply.github.com>
Date: Wed, 17 Nov 2021 20:03:46 +0100
Subject: [PATCH 355/549] Remove arduino dependency from hm3301 (#2745)

---
 .../hm3301/abstract_aqi_calculator.h           |  3 ---
 esphome/components/hm3301/aqi_calculator.h     |  4 ----
 .../components/hm3301/aqi_calculator_factory.h |  4 ----
 esphome/components/hm3301/caqi_calculator.h    |  4 ----
 esphome/components/hm3301/hm3301.cpp           | 13 +++----------
 esphome/components/hm3301/hm3301.h             | 18 ++++++++----------
 esphome/components/hm3301/sensor.py            |  4 ----
 platformio.ini                                 |  1 -
 8 files changed, 11 insertions(+), 40 deletions(-)

diff --git a/esphome/components/hm3301/abstract_aqi_calculator.h b/esphome/components/hm3301/abstract_aqi_calculator.h
index fb41b921d9..42d900a262 100644
--- a/esphome/components/hm3301/abstract_aqi_calculator.h
+++ b/esphome/components/hm3301/abstract_aqi_calculator.h
@@ -1,6 +1,5 @@
 #pragma once
 
-#ifdef USE_ARDUINO
 #include 
 
 namespace esphome {
@@ -13,5 +12,3 @@ 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 a3839b643c..08d1dc2921 100644
--- a/esphome/components/hm3301/aqi_calculator.h
+++ b/esphome/components/hm3301/aqi_calculator.h
@@ -1,7 +1,5 @@
 #pragma once
 
-#ifdef USE_ARDUINO
-
 #include "abstract_aqi_calculator.h"
 
 namespace esphome {
@@ -48,5 +46,3 @@ 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 3c6f9709b6..55608b6e51 100644
--- a/esphome/components/hm3301/aqi_calculator_factory.h
+++ b/esphome/components/hm3301/aqi_calculator_factory.h
@@ -1,7 +1,5 @@
 #pragma once
 
-#ifdef USE_ARDUINO
-
 #include "caqi_calculator.h"
 #include "aqi_calculator.h"
 
@@ -29,5 +27,3 @@ 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 a7f5460e0a..1ec61f2416 100644
--- a/esphome/components/hm3301/caqi_calculator.h
+++ b/esphome/components/hm3301/caqi_calculator.h
@@ -1,7 +1,5 @@
 #pragma once
 
-#ifdef USE_ARDUINO
-
 #include "esphome/core/log.h"
 #include "abstract_aqi_calculator.h"
 
@@ -52,5 +50,3 @@ 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 759157f330..a2bef2a01d 100644
--- a/esphome/components/hm3301/hm3301.cpp
+++ b/esphome/components/hm3301/hm3301.cpp
@@ -1,5 +1,3 @@
-#ifdef USE_ARDUINO
-
 #include "esphome/core/log.h"
 #include "hm3301.h"
 
@@ -14,9 +12,8 @@ static const uint8_t PM_10_0_VALUE_INDEX = 7;
 
 void HM3301Component::setup() {
   ESP_LOGCONFIG(TAG, "Setting up HM3301...");
-  hm3301_ = make_unique();
-  error_code_ = hm3301_->init();
-  if (error_code_ != NO_ERROR) {
+  if (i2c::ERROR_OK != this->write(&SELECT_COMM_CMD, 1)) {
+    error_code_ = ERROR_COMM;
     this->mark_failed();
     return;
   }
@@ -38,7 +35,7 @@ void HM3301Component::dump_config() {
 float HM3301Component::get_setup_priority() const { return setup_priority::DATA; }
 
 void HM3301Component::update() {
-  if (!this->read_sensor_value_(data_buffer_)) {
+  if (this->read(data_buffer_, 29) != i2c::ERROR_OK) {
     ESP_LOGW(TAG, "Read result failed");
     this->status_set_warning();
     return;
@@ -87,8 +84,6 @@ void HM3301Component::update() {
   this->status_clear_warning();
 }
 
-bool HM3301Component::read_sensor_value_(uint8_t *data) { return !hm3301_->read_sensor_value(data, 29); }
-
 bool HM3301Component::validate_checksum_(const uint8_t *data) {
   uint8_t sum = 0;
   for (int i = 0; i < 28; i++) {
@@ -104,5 +99,3 @@ 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 61bbf7e4ab..e13ffa466e 100644
--- a/esphome/components/hm3301/hm3301.h
+++ b/esphome/components/hm3301/hm3301.h
@@ -1,17 +1,15 @@
 #pragma once
 
-#ifdef USE_ARDUINO
-
 #include "esphome/core/component.h"
 #include "esphome/components/sensor/sensor.h"
 #include "esphome/components/i2c/i2c.h"
 #include "aqi_calculator_factory.h"
 
-#include 
-
 namespace esphome {
 namespace hm3301 {
 
+static const uint8_t SELECT_COMM_CMD = 0X88;
+
 class HM3301Component : public PollingComponent, public i2c::I2CDevice {
  public:
   HM3301Component() = default;
@@ -29,9 +27,12 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice {
   void update() override;
 
  protected:
-  std::unique_ptr hm3301_;
-
-  HM330XErrorCode error_code_{NO_ERROR};
+  enum {
+    NO_ERROR = 0,
+    ERROR_PARAM = -1,
+    ERROR_COMM = -2,
+    ERROR_OTHERS = -128,
+  } error_code_{NO_ERROR};
 
   uint8_t data_buffer_[30];
 
@@ -43,12 +44,9 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice {
   AQICalculatorType aqi_calc_type_;
   AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory();
 
-  bool read_sensor_value_(uint8_t *);
   bool validate_checksum_(const uint8_t *);
   uint16_t get_sensor_value_(const uint8_t *, uint8_t);
 };
 
 }  // namespace hm3301
 }  // namespace esphome
-
-#endif  // USE_ARDUINO
diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py
index 976a0488e1..8e9ee4c6fb 100644
--- a/esphome/components/hm3301/sensor.py
+++ b/esphome/components/hm3301/sensor.py
@@ -84,7 +84,6 @@ CONFIG_SCHEMA = cv.All(
     .extend(cv.polling_component_schema("60s"))
     .extend(i2c.i2c_device_schema(0x40)),
     _validate,
-    cv.only_with_arduino,
 )
 
 
@@ -109,6 +108,3 @@ async def to_code(config):
         sens = await sensor.new_sensor(config[CONF_AQI])
         cg.add(var.set_aqi_sensor(sens))
         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("seeed-studio/Grove - Laser PM2.5 Sensor HM3301", "1.0.3")
diff --git a/platformio.ini b/platformio.ini
index b49438b097..0f80d6d8d3 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -46,7 +46,6 @@ lib_deps =
     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.8                               ; midea

From 448e1690aac902cca7d614b2902514fa59e8d6ab Mon Sep 17 00:00:00 2001
From: Martin <25747549+martgras@users.noreply.github.com>
Date: Wed, 17 Nov 2021 23:59:40 +0100
Subject: [PATCH 356/549] Add retry handler (#2721)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Oxan van Leeuwen 
---
 esphome/core/component.cpp | 13 ++++++++
 esphome/core/component.h   | 34 +++++++++++++++++++-
 esphome/core/scheduler.cpp | 63 ++++++++++++++++++++++++++++++--------
 esphome/core/scheduler.h   | 27 ++++++++++++++--
 4 files changed, 122 insertions(+), 15 deletions(-)

diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp
index 5692194a91..f97818cfb1 100644
--- a/esphome/core/component.cpp
+++ b/esphome/core/component.cpp
@@ -55,6 +55,15 @@ bool Component::cancel_interval(const std::string &name) {  // NOLINT
   return App.scheduler.cancel_interval(this, name);
 }
 
+void Component::set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,
+                          std::function &&f, float backoff_increase_factor) {  // NOLINT
+  App.scheduler.set_retry(this, name, initial_wait_time, max_attempts, std::move(f), backoff_increase_factor);
+}
+
+bool Component::cancel_retry(const std::string &name) {  // NOLINT
+  return App.scheduler.cancel_retry(this, name);
+}
+
 void Component::set_timeout(const std::string &name, uint32_t timeout, std::function &&f) {  // NOLINT
   return App.scheduler.set_timeout(this, name, timeout, std::move(f));
 }
@@ -120,6 +129,10 @@ void Component::set_timeout(uint32_t timeout, std::function &&f) {  // N
 void Component::set_interval(uint32_t interval, std::function &&f) {  // NOLINT
   App.scheduler.set_interval(this, "", interval, std::move(f));
 }
+void Component::set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function &&f,
+                          float backoff_increase_factor) {  // NOLINT
+  App.scheduler.set_retry(this, "", initial_wait_time, max_attempts, std::move(f), backoff_increase_factor);
+}
 bool Component::is_failed() { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; }
 bool Component::can_proceed() { return true; }
 bool Component::status_has_warning() { return this->component_state_ & STATUS_LED_WARNING; }
diff --git a/esphome/core/component.h b/esphome/core/component.h
index a1afc17c2c..c3a4ac3782 100644
--- a/esphome/core/component.h
+++ b/esphome/core/component.h
@@ -61,6 +61,8 @@ extern const uint32_t STATUS_LED_OK;
 extern const uint32_t STATUS_LED_WARNING;
 extern const uint32_t STATUS_LED_ERROR;
 
+enum RetryResult { DONE, RETRY };
+
 class Component {
  public:
   /** Where the component's initialization should happen.
@@ -180,7 +182,35 @@ class Component {
    */
   bool cancel_interval(const std::string &name);  // NOLINT
 
-  void set_timeout(uint32_t timeout, std::function &&f);  // NOLINT
+  /** Set an retry function with a unique name. Empty name means no cancelling possible.
+   *
+   * This will call f. If f returns RetryResult::RETRY f is called again after initial_wait_time ms.
+   * f should return RetryResult::DONE if no repeat is required. The initial wait time will be increased
+   * by backoff_increase_factor for each iteration. Default is doubling the time between iterations
+   * Can be cancelled via cancel_retry().
+   *
+   * IMPORTANT: Do not rely on this having correct timing. This is only called from
+   * loop() and therefore can be significantly delayed.
+   *
+   * @param name The identifier for this retry function.
+   * @param initial_wait_time The time in ms before f is called again
+   * @param max_attempts The maximum number of retries
+   * @param f The function (or lambda) that should be called
+   * @param backoff_increase_factor time between retries is increased by this factor on every retry
+   * @see cancel_retry()
+   */
+  void set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,  // NOLINT
+                 std::function &&f, float backoff_increase_factor = 1.0f);    // NOLINT
+
+  void set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function &&f,  // NOLINT
+                 float backoff_increase_factor = 1.0f);                                               // NOLINT
+
+  /** Cancel a retry function.
+   *
+   * @param name The identifier for this retry function.
+   * @return Whether a retry function was deleted.
+   */
+  bool cancel_retry(const std::string &name);  // NOLINT
 
   /** Set a timeout function with a unique name.
    *
@@ -198,6 +228,8 @@ class Component {
    */
   void set_timeout(const std::string &name, uint32_t timeout, std::function &&f);  // NOLINT
 
+  void set_timeout(uint32_t timeout, std::function &&f);  // NOLINT
+
   /** Cancel a timeout function.
    *
    * @param name The identifier for this timeout function.
diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp
index a6d3e0307e..3fe07f94b5 100644
--- a/esphome/core/scheduler.cpp
+++ b/esphome/core/scheduler.cpp
@@ -32,7 +32,7 @@ void HOT Scheduler::set_timeout(Component *component, const std::string &name, u
   item->timeout = timeout;
   item->last_execution = now;
   item->last_execution_major = this->millis_major_;
-  item->f = std::move(func);
+  item->void_callback = std::move(func);
   item->remove = false;
   this->push_(std::move(item));
 }
@@ -65,13 +65,47 @@ void HOT Scheduler::set_interval(Component *component, const std::string &name,
   item->last_execution_major = this->millis_major_;
   if (item->last_execution > now)
     item->last_execution_major--;
-  item->f = std::move(func);
+  item->void_callback = std::move(func);
   item->remove = false;
   this->push_(std::move(item));
 }
 bool HOT Scheduler::cancel_interval(Component *component, const std::string &name) {
   return this->cancel_item_(component, name, SchedulerItem::INTERVAL);
 }
+
+void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time,
+                              uint8_t max_attempts, std::function &&func,
+                              float backoff_increase_factor) {
+  const uint32_t now = this->millis_();
+
+  if (!name.empty())
+    this->cancel_retry(component, name);
+
+  if (initial_wait_time == SCHEDULER_DONT_RUN)
+    return;
+
+  ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%u,max_attempts=%u, backoff_factor=%0.1f)", name.c_str(),
+            initial_wait_time, max_attempts, backoff_increase_factor);
+
+  auto item = make_unique();
+  item->component = component;
+  item->name = name;
+  item->type = SchedulerItem::RETRY;
+  item->interval = initial_wait_time;
+  item->retry_countdown = max_attempts;
+  item->backoff_multiplier = backoff_increase_factor;
+  item->last_execution = now - initial_wait_time;
+  item->last_execution_major = this->millis_major_;
+  if (item->last_execution > now)
+    item->last_execution_major--;
+  item->retry_callback = std::move(func);
+  item->remove = false;
+  this->push_(std::move(item));
+}
+bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) {
+  return this->cancel_item_(component, name, SchedulerItem::RETRY);
+}
+
 optional HOT Scheduler::next_schedule_in() {
   if (this->empty_())
     return {};
@@ -95,10 +129,9 @@ void IRAM_ATTR HOT Scheduler::call() {
     ESP_LOGVV(TAG, "Items: count=%u, now=%u", this->items_.size(), now);
     while (!this->empty_()) {
       auto item = std::move(this->items_[0]);
-      const char *type = item->type == SchedulerItem::INTERVAL ? "interval" : "timeout";
-      ESP_LOGVV(TAG, "  %s '%s' interval=%u last_execution=%u (%u) next=%u (%u)", type, item->name.c_str(),
-                item->interval, item->last_execution, item->last_execution_major, item->next_execution(),
-                item->next_execution_major());
+      ESP_LOGVV(TAG, "  %s '%s' interval=%u last_execution=%u (%u) next=%u (%u)", item->get_type_str(),
+                item->name.c_str(), item->interval, item->last_execution, item->last_execution_major,
+                item->next_execution(), item->next_execution_major());
 
       this->pop_raw_();
       old_items.push_back(std::move(item));
@@ -129,6 +162,7 @@ void IRAM_ATTR HOT Scheduler::call() {
   }
 
   while (!this->empty_()) {
+    RetryResult retry_result = RETRY;
     // use scoping to indicate visibility of `item` variable
     {
       // Don't copy-by value yet
@@ -147,17 +181,19 @@ void IRAM_ATTR HOT Scheduler::call() {
       }
 
 #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
-      const char *type = item->type == SchedulerItem::INTERVAL ? "interval" : "timeout";
-      ESP_LOGVV(TAG, "Running %s '%s' with interval=%u last_execution=%u (now=%u)", type, item->name.c_str(),
-                item->interval, item->last_execution, now);
+      ESP_LOGVV(TAG, "Running %s '%s' with interval=%u last_execution=%u (now=%u)", item->get_type_str(),
+                item->name.c_str(), item->interval, item->last_execution, now);
 #endif
 
-      // Warning: During f(), a lot of stuff can happen, including:
+      // Warning: During callback(), a lot of stuff can happen, including:
       //  - timeouts/intervals get added, potentially invalidating vector pointers
       //  - timeouts/intervals get cancelled
       {
         WarnIfComponentBlockingGuard guard{item->component};
-        item->f();
+        if (item->type == SchedulerItem::RETRY)
+          retry_result = item->retry_callback();
+        else
+          item->void_callback();
       }
     }
 
@@ -175,13 +211,16 @@ void IRAM_ATTR HOT Scheduler::call() {
         continue;
       }
 
-      if (item->type == SchedulerItem::INTERVAL) {
+      if (item->type == SchedulerItem::INTERVAL ||
+          (item->type == SchedulerItem::RETRY && (--item->retry_countdown > 0 && retry_result != RetryResult::DONE))) {
         if (item->interval != 0) {
           const uint32_t before = item->last_execution;
           const uint32_t amount = (now - item->last_execution) / item->interval;
           item->last_execution += amount * item->interval;
           if (item->last_execution < before)
             item->last_execution_major++;
+          if (item->type == SchedulerItem::RETRY)
+            item->interval *= item->backoff_multiplier;
         }
         this->push_(std::move(item));
       }
diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h
index d1839cb4a7..dc96d58329 100644
--- a/esphome/core/scheduler.h
+++ b/esphome/core/scheduler.h
@@ -15,6 +15,10 @@ class Scheduler {
   void set_interval(Component *component, const std::string &name, uint32_t interval, std::function &&func);
   bool cancel_interval(Component *component, const std::string &name);
 
+  void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,
+                 std::function &&func, float backoff_increase_factor = 1.0f);
+  bool cancel_retry(Component *component, const std::string &name);
+
   optional next_schedule_in();
 
   void call();
@@ -25,13 +29,20 @@ class Scheduler {
   struct SchedulerItem {
     Component *component;
     std::string name;
-    enum Type { TIMEOUT, INTERVAL } type;
+    enum Type { TIMEOUT, INTERVAL, RETRY } type;
     union {
       uint32_t interval;
       uint32_t timeout;
     };
     uint32_t last_execution;
-    std::function f;
+    // Ideally this should be a union or std::variant
+    // but unions don't work with object like std::function
+    //  union CallBack_{
+    std::function void_callback;
+    std::function retry_callback;
+    //  };
+    uint8_t retry_countdown{3};
+    float backoff_multiplier{1.0f};
     bool remove;
     uint8_t last_execution_major;
 
@@ -45,6 +56,18 @@ class Scheduler {
     }
 
     static bool cmp(const std::unique_ptr &a, const std::unique_ptr &b);
+    const char *get_type_str() {
+      switch (this->type) {
+        case SchedulerItem::INTERVAL:
+          return "interval";
+        case SchedulerItem::RETRY:
+          return "retry";
+        case SchedulerItem::TIMEOUT:
+          return "timeout";
+        default:
+          return "";
+      }
+    }
   };
 
   uint32_t millis_();

From 9e1c3e8f01c82f8ee5c227ff0c3b6d8359bf9c3b Mon Sep 17 00:00:00 2001
From: Maurice Makaay 
Date: Thu, 18 Nov 2021 22:41:26 +0100
Subject: [PATCH 357/549] Allow UART debug configuration with no after:
 definition (#2753)

---
 esphome/components/uart/__init__.py | 10 +++++++---
 tests/test2.yaml                    |  6 ++++++
 2 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py
index 53209dfc7b..159b08d2d9 100644
--- a/esphome/components/uart/__init__.py
+++ b/esphome/components/uart/__init__.py
@@ -94,17 +94,21 @@ UART_DIRECTIONS = {
     "BOTH": UARTDirection.UART_DIRECTION_BOTH,
 }
 
+AFTER_DEFAULTS = {CONF_BYTES: 256, CONF_TIMEOUT: "100ms"}
+
 DEBUG_SCHEMA = cv.Schema(
     {
         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UARTDebugger),
         cv.Optional(CONF_DIRECTION, default="BOTH"): cv.enum(
             UART_DIRECTIONS, upper=True
         ),
-        cv.Optional(CONF_AFTER): cv.Schema(
+        cv.Optional(CONF_AFTER, default=AFTER_DEFAULTS): cv.Schema(
             {
-                cv.Optional(CONF_BYTES, default=256): cv.validate_bytes,
                 cv.Optional(
-                    CONF_TIMEOUT, default="100ms"
+                    CONF_BYTES, default=AFTER_DEFAULTS[CONF_BYTES]
+                ): cv.validate_bytes,
+                cv.Optional(
+                    CONF_TIMEOUT, default=AFTER_DEFAULTS[CONF_TIMEOUT]
                 ): cv.positive_time_period_milliseconds,
                 cv.Optional(CONF_DELIMITER): cv.templatable(validate_raw_data),
             }
diff --git a/tests/test2.yaml b/tests/test2.yaml
index f90e522b1e..3afef9501d 100644
--- a/tests/test2.yaml
+++ b/tests/test2.yaml
@@ -39,6 +39,12 @@ uart:
   tx_pin: GPIO22
   rx_pin: GPIO23
   baud_rate: 115200
+  # Specifically added for testing debug with no after: definition.
+  debug:
+    dummy_receiver: false
+    direction: rx
+    sequence:
+      - lambda: UARTDebug::log_hex(direction, bytes, ':');
 
 ota:
   safe_mode: True

From e5cb5756aa1feb7ccb47dfc20d9039b0985b09cb Mon Sep 17 00:00:00 2001
From: Dave T <17680170+davet2001@users.noreply.github.com>
Date: Thu, 18 Nov 2021 22:20:32 +0000
Subject: [PATCH 358/549] Fix frame scaling for animated gifs (#2750)

---
 esphome/components/animation/__init__.py | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py
index 3f03e5c185..1780bdf72e 100644
--- a/esphome/components/animation/__init__.py
+++ b/esphome/components/animation/__init__.py
@@ -60,6 +60,10 @@ async def to_code(config):
             image.seek(frameIndex)
             frame = image.convert("L", dither=Image.NONE)
             pixels = list(frame.getdata())
+            if len(pixels) != height * width:
+                raise core.EsphomeError(
+                    f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}"
+                )
             for pix in pixels:
                 data[pos] = pix
                 pos += 1
@@ -69,8 +73,14 @@ async def to_code(config):
         pos = 0
         for frameIndex in range(frames):
             image.seek(frameIndex)
+            if CONF_RESIZE in config:
+                image.thumbnail(config[CONF_RESIZE])
             frame = image.convert("RGB")
             pixels = list(frame.getdata())
+            if len(pixels) != height * width:
+                raise core.EsphomeError(
+                    f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}"
+                )
             for pix in pixels:
                 data[pos] = pix[0]
                 pos += 1

From 61ec16cdfc2ffeb69a7bbdc06c67f88ea79793fe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= 
Date: Mon, 22 Nov 2021 00:09:11 +0100
Subject: [PATCH 359/549] esp32_camera_web_server: Improve support for
 MotionEye (#2777)

---
 .../camera_web_server.cpp                     | 26 ++++++++++++-------
 1 file changed, 17 insertions(+), 9 deletions(-)

diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.cpp b/esphome/components/esp32_camera_web_server/camera_web_server.cpp
index c9a684c7e5..653a274bf4 100644
--- a/esphome/components/esp32_camera_web_server/camera_web_server.cpp
+++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp
@@ -21,12 +21,19 @@ static const char *const TAG = "esp32_camera_web_server";
 #define CONTENT_TYPE "image/jpeg"
 #define CONTENT_LENGTH "Content-Length"
 
-static const char *const STREAM_HEADER =
-    "HTTP/1.1 200\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY
-    "\r\n";
-static const char *const STREAM_500 = "HTTP/1.1 500\r\nContent-Type: text/plain\r\n\r\nNo frames send.\r\n";
-static const char *const STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
+static const char *const STREAM_HEADER = "HTTP/1.0 200 OK\r\n"
+                                         "Access-Control-Allow-Origin: *\r\n"
+                                         "Connection: close\r\n"
+                                         "Content-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY "\r\n"
+                                         "\r\n"
+                                         "--" PART_BOUNDARY "\r\n";
+static const char *const STREAM_ERROR = "Content-Type: text/plain\r\n"
+                                        "\r\n"
+                                        "No frames send.\r\n"
+                                        "--" PART_BOUNDARY "\r\n";
 static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n";
+static const char *const STREAM_BOUNDARY = "\r\n"
+                                           "--" PART_BOUNDARY "\r\n";
 
 CameraWebServer::CameraWebServer() {}
 
@@ -45,6 +52,7 @@ void CameraWebServer::setup() {
   config.ctrl_port = this->port_;
   config.max_open_sockets = 1;
   config.backlog_conn = 2;
+  config.lru_purge_enable = true;
 
   if (httpd_start(&this->httpd_, &config) != ESP_OK) {
     mark_failed();
@@ -172,9 +180,6 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
       ESP_LOGW(TAG, "STREAM: failed to acquire frame");
       res = ESP_FAIL;
     }
-    if (res == ESP_OK) {
-      res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
-    }
     if (res == ESP_OK) {
       size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length());
       res = httpd_send_all(req, part_buf, hlen);
@@ -182,6 +187,9 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
     if (res == ESP_OK) {
       res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length());
     }
+    if (res == ESP_OK) {
+      res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
+    }
     if (res == ESP_OK) {
       frames++;
       int64_t frame_time = millis() - last_frame;
@@ -193,7 +201,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
   }
 
   if (!frames) {
-    res = httpd_send_all(req, STREAM_500, strlen(STREAM_500));
+    res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR));
   }
 
   ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames);

From 1424091ee51a31d75558f8e4a029ea80edb4d415 Mon Sep 17 00:00:00 2001
From: Samuel Sieb 
Date: Sun, 21 Nov 2021 15:11:36 -0800
Subject: [PATCH 360/549] Remove floating point ops from the ISR (#2751)

Co-authored-by: Samuel Sieb 
---
 esphome/components/zyaura/zyaura.cpp | 40 +++++++++++++++++-----------
 esphome/components/zyaura/zyaura.h   |  8 +++---
 2 files changed, 28 insertions(+), 20 deletions(-)

diff --git a/esphome/components/zyaura/zyaura.cpp b/esphome/components/zyaura/zyaura.cpp
index 11643a5c23..621439aa0c 100644
--- a/esphome/components/zyaura/zyaura.cpp
+++ b/esphome/components/zyaura/zyaura.cpp
@@ -57,38 +57,46 @@ void IRAM_ATTR ZaSensorStore::interrupt(ZaSensorStore *arg) {
 void IRAM_ATTR ZaSensorStore::set_data_(ZaMessage *message) {
   switch (message->type) {
     case HUMIDITY:
-      this->humidity = (message->value > 10000) ? NAN : (message->value / 100.0f);
+      this->humidity = message->value;
       break;
-
     case TEMPERATURE:
-      this->temperature = (message->value > 5970) ? NAN : (message->value / 16.0f - 273.15f);
+      this->temperature = message->value;
       break;
-
     case CO2:
-      this->co2 = (message->value > 10000) ? NAN : message->value;
-      break;
-
-    default:
+      this->co2 = message->value;
       break;
   }
 }
 
-bool ZyAuraSensor::publish_state_(sensor::Sensor *sensor, float *value) {
-  // Sensor doesn't added to configuration
+bool ZyAuraSensor::publish_state_(ZaDataType data_type, sensor::Sensor *sensor, uint16_t *data_value) {
+  // Sensor wasn't added to configuration
   if (sensor == nullptr) {
     return true;
   }
 
-  sensor->publish_state(*value);
+  float value = NAN;
+  switch (data_type) {
+    case HUMIDITY:
+      value = (*data_value > 10000) ? NAN : (*data_value / 100.0f);
+      break;
+    case TEMPERATURE:
+      value = (*data_value > 5970) ? NAN : (*data_value / 16.0f - 273.15f);
+      break;
+    case CO2:
+      value = (*data_value > 10000) ? NAN : *data_value;
+      break;
+  }
+
+  sensor->publish_state(value);
 
   // Sensor reported wrong value
-  if (std::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;
   }
 
-  *value = NAN;
+  *data_value = -1;
   return true;
 }
 
@@ -104,9 +112,9 @@ void ZyAuraSensor::dump_config() {
 }
 
 void ZyAuraSensor::update() {
-  bool co2_result = this->publish_state_(this->co2_sensor_, &this->store_.co2);
-  bool temperature_result = this->publish_state_(this->temperature_sensor_, &this->store_.temperature);
-  bool humidity_result = this->publish_state_(this->humidity_sensor_, &this->store_.humidity);
+  bool co2_result = this->publish_state_(CO2, this->co2_sensor_, &this->store_.co2);
+  bool temperature_result = this->publish_state_(TEMPERATURE, this->temperature_sensor_, &this->store_.temperature);
+  bool humidity_result = this->publish_state_(HUMIDITY, this->humidity_sensor_, &this->store_.humidity);
 
   if (co2_result && temperature_result && humidity_result) {
     this->status_clear_warning();
diff --git a/esphome/components/zyaura/zyaura.h b/esphome/components/zyaura/zyaura.h
index 2b9e3fbb35..85c31ec75a 100644
--- a/esphome/components/zyaura/zyaura.h
+++ b/esphome/components/zyaura/zyaura.h
@@ -42,9 +42,9 @@ class ZaDataProcessor {
 
 class ZaSensorStore {
  public:
-  float co2 = NAN;
-  float temperature = NAN;
-  float humidity = NAN;
+  uint16_t co2 = -1;
+  uint16_t temperature = -1;
+  uint16_t humidity = -1;
 
   void setup(InternalGPIOPin *pin_clock, InternalGPIOPin *pin_data);
   static void interrupt(ZaSensorStore *arg);
@@ -79,7 +79,7 @@ class ZyAuraSensor : public PollingComponent {
   sensor::Sensor *temperature_sensor_{nullptr};
   sensor::Sensor *humidity_sensor_{nullptr};
 
-  bool publish_state_(sensor::Sensor *sensor, float *value);
+  bool publish_state_(ZaDataType data_type, sensor::Sensor *sensor, uint16_t *data_value);
 };
 
 }  // namespace zyaura

From 897277992b3bf6608c1622780de5c5f209ca3102 Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Tue, 23 Nov 2021 08:30:49 +0100
Subject: [PATCH 361/549] Introduce str_snprintf helper function (#2780)

---
 esphome/core/helpers.cpp | 18 ++++++++++++++++--
 esphome/core/helpers.h   |  5 ++++-
 2 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp
index 3e614eb515..37d9cdc24b 100644
--- a/esphome/core/helpers.cpp
+++ b/esphome/core/helpers.cpp
@@ -48,13 +48,13 @@ void get_mac_address_raw(uint8_t *mac) {
 std::string get_mac_address() {
   uint8_t mac[6];
   get_mac_address_raw(mac);
-  return str_sprintf("%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+  return str_snprintf("%02x%02x%02x%02x%02x%02x", 12, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
 }
 
 std::string get_mac_address_pretty() {
   uint8_t mac[6];
   get_mac_address_raw(mac);
-  return str_sprintf("%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+  return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
 }
 
 #ifdef USE_ESP32
@@ -325,6 +325,20 @@ bool str_startswith(const std::string &full, const std::string &start) { return
 bool str_endswith(const std::string &full, const std::string &ending) {
   return full.rfind(ending) == (full.size() - ending.size());
 }
+std::string str_snprintf(const char *fmt, size_t length, ...) {
+  std::string str;
+  va_list args;
+
+  str.resize(length);
+  va_start(args, length);
+  size_t out_length = vsnprintf(&str[0], length + 1, fmt, args);
+  va_end(args);
+
+  if (out_length < length)
+    str.resize(out_length);
+
+  return str;
+}
 std::string str_sprintf(const char *fmt, ...) {
   std::string str;
   va_list args;
diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h
index 120642c62e..4ce1f08074 100644
--- a/esphome/core/helpers.h
+++ b/esphome/core/helpers.h
@@ -57,7 +57,10 @@ bool str_equals_case_insensitive(const std::string &a, const std::string &b);
 bool str_startswith(const std::string &full, const std::string &start);
 bool str_endswith(const std::string &full, const std::string &ending);
 
-/// sprintf-like function returning std::string instead of writing to char array.
+/// snprintf-like function returning std::string with a given maximum length.
+std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t length, ...);
+
+/// sprintf-like function returning std::string.
 std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...);
 
 class HighFrequencyLoopRequester {

From 3e5331a263fcf585c88c359cdb0105ea3db79897 Mon Sep 17 00:00:00 2001
From: cvwillegen 
Date: Tue, 23 Nov 2021 09:20:20 +0100
Subject: [PATCH 362/549] Prettier date time display after time sync (#2778)

---
 esphome/components/sntp/sntp_component.cpp  | 2 +-
 esphome/components/time/real_time_clock.cpp | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp
index 96be0e9709..21fcb96842 100644
--- a/esphome/components/sntp/sntp_component.cpp
+++ b/esphome/components/sntp/sntp_component.cpp
@@ -71,7 +71,7 @@ void SNTPComponent::loop() {
   if (!time.is_valid())
     return;
 
-  ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour,
+  ESP_LOGD(TAG, "Synchronized time: %04d-%02d-%02d %02d:%02d:%02d", 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 064e6f899c..6f6739d293 100644
--- a/esphome/components/time/real_time_clock.cpp
+++ b/esphome/components/time/real_time_clock.cpp
@@ -35,7 +35,7 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
   }
 
   auto time = this->now();
-  ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%d:%d", time.year, time.month, time.day_of_month, time.hour,
+  ESP_LOGD(TAG, "Synchronized time: %04d-%02d-%02d %02d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour,
            time.minute, time.second);
 
   this->time_sync_callback_.call();

From 07b882c80194020182c41afd91f4dc2486b4f8cb Mon Sep 17 00:00:00 2001
From: Dave T <17680170+davet2001@users.noreply.github.com>
Date: Tue, 23 Nov 2021 08:20:36 +0000
Subject: [PATCH 363/549] Fix distorted gif frames when resizing (#2774)

---
 esphome/components/animation/__init__.py | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py
index 1780bdf72e..7c9ff07f97 100644
--- a/esphome/components/animation/__init__.py
+++ b/esphome/components/animation/__init__.py
@@ -44,8 +44,9 @@ async def to_code(config):
     width, height = image.size
     frames = image.n_frames
     if CONF_RESIZE in config:
-        image.thumbnail(config[CONF_RESIZE])
-        width, height = image.size
+        new_width_max, new_height_max = config[CONF_RESIZE]
+        ratio = min(new_width_max / width, new_height_max / height)
+        width, height = int(width * ratio), int(height * ratio)
     else:
         if width > 500 or height > 500:
             _LOGGER.warning(
@@ -59,10 +60,12 @@ async def to_code(config):
         for frameIndex in range(frames):
             image.seek(frameIndex)
             frame = image.convert("L", dither=Image.NONE)
+            if CONF_RESIZE in config:
+                frame = frame.resize([width, height])
             pixels = list(frame.getdata())
             if len(pixels) != height * width:
                 raise core.EsphomeError(
-                    f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}"
+                    f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
                 )
             for pix in pixels:
                 data[pos] = pix
@@ -73,13 +76,13 @@ async def to_code(config):
         pos = 0
         for frameIndex in range(frames):
             image.seek(frameIndex)
-            if CONF_RESIZE in config:
-                image.thumbnail(config[CONF_RESIZE])
             frame = image.convert("RGB")
+            if CONF_RESIZE in config:
+                frame = frame.resize([width, height])
             pixels = list(frame.getdata())
             if len(pixels) != height * width:
                 raise core.EsphomeError(
-                    f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}"
+                    f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
                 )
             for pix in pixels:
                 data[pos] = pix[0]
@@ -95,6 +98,8 @@ async def to_code(config):
         for frameIndex in range(frames):
             image.seek(frameIndex)
             frame = image.convert("1", dither=Image.NONE)
+            if CONF_RESIZE in config:
+                frame = frame.resize([width, height])
             for y in range(height):
                 for x in range(width):
                     if frame.getpixel((x, y)):

From 710096b1c6035d71c7ed4912e8940fdb83ead3ae Mon Sep 17 00:00:00 2001
From: Andreas Hergert <36455093+andreashergert1984@users.noreply.github.com>
Date: Tue, 23 Nov 2021 09:20:55 +0100
Subject: [PATCH 364/549] Fixed wrong setup of tc9548a (#2766)

---
 esphome/components/tca9548a/tca9548a.cpp | 2 +-
 esphome/components/tca9548a/tca9548a.h   | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/esphome/components/tca9548a/tca9548a.cpp b/esphome/components/tca9548a/tca9548a.cpp
index e902eb5ed4..f3f8685287 100644
--- a/esphome/components/tca9548a/tca9548a.cpp
+++ b/esphome/components/tca9548a/tca9548a.cpp
@@ -22,7 +22,7 @@ i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffer
 void TCA9548AComponent::setup() {
   ESP_LOGCONFIG(TAG, "Setting up TCA9548A...");
   uint8_t status = 0;
-  if (this->read_register(0x00, &status, 1) != i2c::ERROR_OK) {
+  if (this->read(&status, 1) != i2c::ERROR_OK) {
     ESP_LOGI(TAG, "TCA9548A failed");
     this->mark_failed();
     return;
diff --git a/esphome/components/tca9548a/tca9548a.h b/esphome/components/tca9548a/tca9548a.h
index 314346d317..39d07c2eb4 100644
--- a/esphome/components/tca9548a/tca9548a.h
+++ b/esphome/components/tca9548a/tca9548a.h
@@ -24,6 +24,7 @@ class TCA9548AComponent : public Component, public i2c::I2CDevice {
  public:
   void setup() override;
   void dump_config() override;
+  float get_setup_priority() const override { return setup_priority::IO; }
   void update();
 
   i2c::ErrorCode switch_to_channel(uint8_t channel);

From 05fe5db030a05d9bc2877927494d8cd6b30b2bdd Mon Sep 17 00:00:00 2001
From: Paul Monigatti 
Date: Tue, 23 Nov 2021 21:21:14 +1300
Subject: [PATCH 365/549] Relax the icon validator to allow non-mdi icons
 (#2764)

---
 esphome/config_validation.py               | 6 ++++--
 tests/unit_tests/test_config_validation.py | 4 ++--
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/esphome/config_validation.py b/esphome/config_validation.py
index 2bb45487fa..8df74ba861 100644
--- a/esphome/config_validation.py
+++ b/esphome/config_validation.py
@@ -296,9 +296,11 @@ def icon(value):
     value = string_strict(value)
     if not value:
         return value
-    if value.startswith("mdi:"):
+    if re.match("^[\\w\\-]+:[\\w\\-]+$", value):
         return value
-    raise Invalid('Icons should start with prefix "mdi:"')
+    raise Invalid(
+        'Icons must match the format "[icon pack]:[icon]", e.g. "mdi:home-assistant"'
+    )
 
 
 def boolean(value):
diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py
index 16cfb16e94..9e9af52d00 100644
--- a/tests/unit_tests/test_config_validation.py
+++ b/tests/unit_tests/test_config_validation.py
@@ -66,7 +66,7 @@ def test_string_string__invalid(value):
         config_validation.string_strict(value)
 
 
-@given(builds(lambda v: "mdi:" + v, text()))
+@given(builds(lambda v: "mdi:" + v, text(alphabet=string.ascii_letters + string.digits + "-_", min_size=1, max_size=20)))
 @example("")
 def test_icon__valid(value):
     actual = config_validation.icon(value)
@@ -75,7 +75,7 @@ def test_icon__valid(value):
 
 
 def test_icon__invalid():
-    with pytest.raises(Invalid, match="Icons should start with prefix"):
+    with pytest.raises(Invalid, match="Icons must match the format "):
         config_validation.icon("foo")
 
 

From 335e69e6cd887ead7c22d3d76ddd9391dedef6cc Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 23 Nov 2021 09:24:28 +0100
Subject: [PATCH 366/549] Bump black from 21.10b0 to 21.11b1 (#2760)

---
 requirements_test.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements_test.txt b/requirements_test.txt
index 03879c5d0e..cc3fcfb2ec 100644
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -1,6 +1,6 @@
 pylint==2.11.1
 flake8==4.0.1
-black==21.10b0
+black==21.11b1
 pexpect==4.8.0
 pre-commit
 

From 598f5b241f3f1b4746559ea82178f8da058031cf Mon Sep 17 00:00:00 2001
From: krunkel 
Date: Tue, 23 Nov 2021 09:26:16 +0100
Subject: [PATCH 367/549] Remove unnecessary write in AHT10 update (#2675)

---
 esphome/components/aht10/aht10.cpp | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp
index e5e04ac181..78f98cb14f 100644
--- a/esphome/components/aht10/aht10.cpp
+++ b/esphome/components/aht10/aht10.cpp
@@ -73,13 +73,6 @@ void AHT10Component::update() {
   bool success = false;
   for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
     ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis());
-    delayMicroseconds(4);
-
-    uint8_t reg = 0;
-    if (this->write(®, 1) != i2c::ERROR_OK) {
-      ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
-      continue;
-    }
     delay(delay_ms);
     if (this->read(data, 6) != i2c::ERROR_OK) {
       ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");

From 15cd602e8b196b5a0948a8abb5e65a16d530ecf9 Mon Sep 17 00:00:00 2001
From: Maurice Makaay 
Date: Tue, 23 Nov 2021 09:34:10 +0100
Subject: [PATCH 368/549] Add support for P1 Data Request pin control (#2676)

---
 esphome/components/dsmr/__init__.py |  17 ++-
 esphome/components/dsmr/dsmr.cpp    | 210 +++++++++++++++++++---------
 esphome/components/dsmr/dsmr.h      |  14 +-
 tests/test3.yaml                    |   2 +
 4 files changed, 174 insertions(+), 69 deletions(-)

diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py
index 1cfc21a4ac..06b022c513 100644
--- a/esphome/components/dsmr/__init__.py
+++ b/esphome/components/dsmr/__init__.py
@@ -1,5 +1,6 @@
 import esphome.codegen as cg
 import esphome.config_validation as cv
+from esphome import pins
 from esphome.components import uart
 from esphome.const import (
     CONF_ID,
@@ -11,11 +12,13 @@ CODEOWNERS = ["@glmnet", "@zuidwijk"]
 DEPENDENCIES = ["uart"]
 AUTO_LOAD = ["sensor", "text_sensor"]
 
-CONF_DSMR_ID = "dsmr_id"
-CONF_DECRYPTION_KEY = "decryption_key"
 CONF_CRC_CHECK = "crc_check"
+CONF_DECRYPTION_KEY = "decryption_key"
+CONF_DSMR_ID = "dsmr_id"
 CONF_GAS_MBUS_ID = "gas_mbus_id"
 CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length"
+CONF_REQUEST_INTERVAL = "request_interval"
+CONF_REQUEST_PIN = "request_pin"
 
 # Hack to prevent compile error due to ambiguity with lib namespace
 dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr")
@@ -48,6 +51,8 @@ CONFIG_SCHEMA = cv.All(
             cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean,
             cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
             cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_,
+            cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema,
+            cv.Optional(CONF_REQUEST_INTERVAL): cv.positive_time_period_milliseconds,
         }
     ).extend(uart.UART_DEVICE_SCHEMA),
     cv.only_with_arduino,
@@ -62,6 +67,14 @@ async def to_code(config):
         cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY]))
     await cg.register_component(var, config)
 
+    if CONF_REQUEST_PIN in config:
+        request_pin = await cg.gpio_pin_expression(config[CONF_REQUEST_PIN])
+        cg.add(var.set_request_pin(request_pin))
+    if CONF_REQUEST_INTERVAL in config:
+        cg.add(
+            var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds)
+        )
+
     cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID])
 
     # DSMR Parser
diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp
index 631b18a1f4..03e418662a 100644
--- a/esphome/components/dsmr/dsmr.cpp
+++ b/esphome/components/dsmr/dsmr.cpp
@@ -13,147 +13,217 @@ namespace dsmr {
 static const char *const TAG = "dsmr";
 
 void Dsmr::setup() {
-  telegram_ = new char[max_telegram_len_];  // NOLINT
+  this->telegram_ = new char[this->max_telegram_len_];  // NOLINT
+  if (this->request_pin_ != nullptr) {
+    this->request_pin_->setup();
+  }
 }
 
 void Dsmr::loop() {
-  if (decryption_key_.empty())
-    receive_telegram_();
-  else
-    receive_encrypted_();
+  if (this->ready_to_request_data_()) {
+    if (this->decryption_key_.empty()) {
+      this->receive_telegram_();
+    } else {
+      this->receive_encrypted_();
+    }
+  }
+}
+
+bool Dsmr::ready_to_request_data_() {
+  // When using a request pin, then wait for the next request interval.
+  if (this->request_pin_ != nullptr) {
+    if (!this->requesting_data_ && this->request_interval_reached_()) {
+      this->start_requesting_data_();
+    }
+  }
+  // Otherwise, sink serial data until next request interval.
+  else {
+    if (this->request_interval_reached_()) {
+      this->start_requesting_data_();
+    }
+    if (!this->requesting_data_) {
+      while (this->available()) {
+        this->read();
+      }
+    }
+  }
+  return this->requesting_data_;
+}
+
+bool Dsmr::request_interval_reached_() {
+  if (this->last_request_time_ == 0) {
+    return true;
+  }
+  return millis() - this->last_request_time_ > this->request_interval_;
 }
 
 bool Dsmr::available_within_timeout_() {
   uint8_t tries = READ_TIMEOUT_MS / 5;
   while (tries--) {
     delay(5);
-    if (available()) {
+    if (this->available()) {
       return true;
     }
   }
   return false;
 }
 
+void Dsmr::start_requesting_data_() {
+  if (!this->requesting_data_) {
+    if (this->request_pin_ != nullptr) {
+      ESP_LOGV(TAG, "Start requesting data from P1 port");
+      this->request_pin_->digital_write(true);
+    } else {
+      ESP_LOGV(TAG, "Start reading data from P1 port");
+    }
+    this->requesting_data_ = true;
+    this->last_request_time_ = millis();
+  }
+}
+
+void Dsmr::stop_requesting_data_() {
+  if (this->requesting_data_) {
+    if (this->request_pin_ != nullptr) {
+      ESP_LOGV(TAG, "Stop requesting data from P1 port");
+      this->request_pin_->digital_write(false);
+    } else {
+      ESP_LOGV(TAG, "Stop reading data from P1 port");
+    }
+    while (this->available()) {
+      this->read();
+    }
+    this->requesting_data_ = false;
+  }
+}
+
 void Dsmr::receive_telegram_() {
   while (true) {
-    if (!available()) {
-      if (!header_found_ || !available_within_timeout_()) {
+    if (!this->available()) {
+      if (!this->header_found_ || !this->available_within_timeout_()) {
         return;
       }
     }
 
-    const char c = read();
+    const char c = this->read();
 
     // Find a new telegram header, i.e. forward slash.
     if (c == '/') {
       ESP_LOGV(TAG, "Header of telegram found");
-      header_found_ = true;
-      footer_found_ = false;
-      telegram_len_ = 0;
+      this->header_found_ = true;
+      this->footer_found_ = false;
+      this->telegram_len_ = 0;
     }
-    if (!header_found_)
+    if (!this->header_found_)
       continue;
 
     // Check for buffer overflow.
-    if (telegram_len_ >= max_telegram_len_) {
-      header_found_ = false;
-      footer_found_ = false;
-      ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", max_telegram_len_);
+    if (this->telegram_len_ >= this->max_telegram_len_) {
+      this->header_found_ = false;
+      this->footer_found_ = false;
+      ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_);
       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_--;
+    // in a new line, while the value belongs to the previous ObisId. For
+    // proper parsing, remove these new line characters.
+    if (c == '(') {
+      while (true) {
+        auto previous_char = this->telegram_[this->telegram_len_ - 1];
+        if (previous_char == '\n' || previous_char == '\r') {
+          this->telegram_len_--;
+        } else {
+          break;
+        }
+      }
+    }
 
     // Store the byte in the buffer.
-    telegram_[telegram_len_] = c;
-    telegram_len_++;
+    this->telegram_[this->telegram_len_] = c;
+    this->telegram_len_++;
 
     // Check for a footer, i.e. exlamation mark, followed by a hex checksum.
     if (c == '!') {
       ESP_LOGV(TAG, "Footer of telegram found");
-      footer_found_ = true;
+      this->footer_found_ = true;
       continue;
     }
     // Check for the end of the hex checksum, i.e. a newline.
-    if (footer_found_ && c == '\n') {
+    if (this->footer_found_ && c == '\n') {
       // Parse the telegram and publish sensor values.
-      parse_telegram();
+      this->parse_telegram();
 
-      header_found_ = false;
+      this->header_found_ = false;
       return;
     }
   }
 }
 
 void Dsmr::receive_encrypted_() {
-  encrypted_telegram_len_ = 0;
+  this->encrypted_telegram_len_ = 0;
   size_t packet_size = 0;
 
   while (true) {
-    if (!available()) {
-      if (!header_found_) {
+    if (!this->available()) {
+      if (!this->header_found_) {
         return;
       }
-      if (!available_within_timeout_()) {
+      if (!this->available_within_timeout_()) {
         ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram");
         return;
       }
     }
 
-    const char c = read();
+    const char c = this->read();
 
     // Find a new telegram start byte.
-    if (!header_found_) {
+    if (!this->header_found_) {
       if ((uint8_t) c != 0xDB) {
         continue;
       }
       ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found");
-      header_found_ = true;
+      this->header_found_ = true;
     }
 
     // Check for buffer overflow.
-    if (encrypted_telegram_len_ >= max_telegram_len_) {
-      header_found_ = false;
-      ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", max_telegram_len_);
+    if (this->encrypted_telegram_len_ >= this->max_telegram_len_) {
+      this->header_found_ = false;
+      ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_);
       return;
     }
 
-    encrypted_telegram_[encrypted_telegram_len_++] = c;
+    this->encrypted_telegram_[this->encrypted_telegram_len_++] = c;
 
-    if (packet_size == 0 && encrypted_telegram_len_ > 20) {
+    if (packet_size == 0 && this->encrypted_telegram_len_ > 20) {
       // Complete header + data bytes
-      packet_size = 13 + (encrypted_telegram_[11] << 8 | encrypted_telegram_[12]);
+      packet_size = 13 + (this->encrypted_telegram_[11] << 8 | this->encrypted_telegram_[12]);
       ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size);
     }
-    if (encrypted_telegram_len_ == packet_size && packet_size > 0) {
+    if (this->encrypted_telegram_len_ == packet_size && packet_size > 0) {
       ESP_LOGV(TAG, "End of encrypted telegram found");
       GCM *gcmaes128{new GCM()};
-      gcmaes128->setKey(decryption_key_.data(), gcmaes128->keySize());
+      gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
       // the iv is 8 bytes of the system title + 4 bytes frame counter
       // system title is at byte 2 and frame counter at byte 15
       for (int i = 10; i < 14; i++)
-        encrypted_telegram_[i] = encrypted_telegram_[i + 4];
+        this->encrypted_telegram_[i] = this->encrypted_telegram_[i + 4];
       constexpr uint16_t iv_size{12};
-      gcmaes128->setIV(&encrypted_telegram_[2], iv_size);
-      gcmaes128->decrypt(reinterpret_cast(telegram_),
+      gcmaes128->setIV(&this->encrypted_telegram_[2], iv_size);
+      gcmaes128->decrypt(reinterpret_cast(this->telegram_),
                          // the ciphertext start at byte 18
-                         &encrypted_telegram_[18],
+                         &this->encrypted_telegram_[18],
                          // cipher size
-                         encrypted_telegram_len_ - 17);
+                         this->encrypted_telegram_len_ - 17);
       delete gcmaes128;  // NOLINT(cppcoreguidelines-owning-memory)
 
-      telegram_len_ = strnlen(telegram_, max_telegram_len_);
-      ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_);
-      ESP_LOGVV(TAG, "Decrypted telegram: %s", telegram_);
+      this->telegram_len_ = strnlen(this->telegram_, this->max_telegram_len_);
+      ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->telegram_len_);
+      ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
 
-      parse_telegram();
+      this->parse_telegram();
 
-      header_found_ = false;
-      telegram_len_ = 0;
+      this->header_found_ = false;
+      this->telegram_len_ = 0;
       return;
     }
   }
@@ -162,24 +232,32 @@ void Dsmr::receive_encrypted_() {
 bool Dsmr::parse_telegram() {
   MyData data;
   ESP_LOGV(TAG, "Trying to parse telegram");
+  this->stop_requesting_data_();
   ::dsmr::ParseResult res =
-      ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false,
-                              crc_check_);  // Parse telegram according to data definition. Ignore unknown values.
+      ::dsmr::P1Parser::parse(&data, this->telegram_, this->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_);
+    auto err_str = res.fullError(this->telegram_, this->telegram_ + this->telegram_len_);
     ESP_LOGE(TAG, "%s", err_str.c_str());
     return false;
   } else {
     this->status_clear_warning();
-    publish_sensors(data);
+    this->publish_sensors(data);
     return true;
   }
 }
 
 void Dsmr::dump_config() {
   ESP_LOGCONFIG(TAG, "DSMR:");
-  ESP_LOGCONFIG(TAG, "  Max telegram length: %d", max_telegram_len_);
+  ESP_LOGCONFIG(TAG, "  Max telegram length: %d", this->max_telegram_len_);
+
+  if (this->request_pin_ != nullptr) {
+    LOG_PIN("  Request Pin: ", this->request_pin_);
+  }
+  if (this->request_interval_ > 0) {
+    ESP_LOGCONFIG(TAG, "  Request Interval: %.1fs", this->request_interval_ / 1e3f);
+  }
 
 #define DSMR_LOG_SENSOR(s) LOG_SENSOR("  ", #s, this->s_##s##_);
   DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, )
@@ -191,10 +269,10 @@ void Dsmr::dump_config() {
 void Dsmr::set_decryption_key(const std::string &decryption_key) {
   if (decryption_key.length() == 0) {
     ESP_LOGI(TAG, "Disabling decryption");
-    decryption_key_.clear();
-    if (encrypted_telegram_ != nullptr) {
-      delete[] encrypted_telegram_;
-      encrypted_telegram_ = nullptr;
+    this->decryption_key_.clear();
+    if (this->encrypted_telegram_ != nullptr) {
+      delete[] this->encrypted_telegram_;
+      this->encrypted_telegram_ = nullptr;
     }
     return;
   }
@@ -203,7 +281,7 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
     ESP_LOGE(TAG, "Error, decryption key must be 32 character long");
     return;
   }
-  decryption_key_.clear();
+  this->decryption_key_.clear();
 
   ESP_LOGI(TAG, "Decryption key is set");
   // Verbose level prints decryption key
@@ -212,11 +290,11 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
   char temp[3] = {0};
   for (int i = 0; i < 16; i++) {
     strncpy(temp, &(decryption_key.c_str()[i * 2]), 2);
-    decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
+    this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
   }
 
-  if (encrypted_telegram_ == nullptr) {
-    encrypted_telegram_ = new uint8_t[max_telegram_len_];  // NOLINT
+  if (this->encrypted_telegram_ == nullptr) {
+    this->encrypted_telegram_ = new uint8_t[this->max_telegram_len_];  // NOLINT
   }
 }
 
diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h
index 5943e4d47f..0430eb93ed 100644
--- a/esphome/components/dsmr/dsmr.h
+++ b/esphome/components/dsmr/dsmr.h
@@ -52,7 +52,6 @@ class Dsmr : public Component, public uart::UARTDevice {
   Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {}
 
   void setup() override;
-
   void loop() override;
 
   bool parse_telegram();
@@ -75,6 +74,9 @@ class Dsmr : public Component, public uart::UARTDevice {
 
   void set_max_telegram_length(size_t length);
 
+  void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; }
+  void set_request_interval(uint32_t interval) { this->request_interval_ = interval; }
+
 // Sensor setters
 #define DSMR_SET_SENSOR(s) \
   void set_##s(sensor::Sensor *sensor) { s_##s##_ = sensor; }
@@ -99,6 +101,16 @@ class Dsmr : public Component, public uart::UARTDevice {
   /// lost in the process.
   bool available_within_timeout_();
 
+  // Data request
+  GPIOPin *request_pin_{nullptr};
+  uint32_t request_interval_{0};
+  uint32_t last_request_time_{0};
+  bool requesting_data_{false};
+  bool ready_to_request_data_();
+  bool request_interval_reached_();
+  void start_requesting_data_();
+  void stop_requesting_data_();
+
   // Telegram buffer
   size_t max_telegram_len_;
   char *telegram_{nullptr};
diff --git a/tests/test3.yaml b/tests/test3.yaml
index 0b9297a0b8..27a0c148d9 100644
--- a/tests/test3.yaml
+++ b/tests/test3.yaml
@@ -1309,6 +1309,8 @@ dsmr:
   decryption_key: 00112233445566778899aabbccddeeff
   uart_id: uart6
   max_telegram_length: 1000
+  request_pin: D5
+  request_interval: 20s
 
 daly_bms:
   update_interval: 20s

From 7e8012c1a02b2e9c10f9a76ac36f786d8ba5a24a Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Thu, 25 Nov 2021 07:59:32 +1300
Subject: [PATCH 369/549] Allow specifying the dashboard bind address (#2787)

---
 esphome/__main__.py            | 6 ++++++
 esphome/dashboard/dashboard.py | 7 ++++---
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/esphome/__main__.py b/esphome/__main__.py
index 7c7c22dd1f..2f06a71b5f 100644
--- a/esphome/__main__.py
+++ b/esphome/__main__.py
@@ -637,6 +637,12 @@ def parse_args(argv):
         type=int,
         default=6052,
     )
+    parser_dashboard.add_argument(
+        "--address",
+        help="The address to bind to.",
+        type=str,
+        default="0.0.0.0",
+    )
     parser_dashboard.add_argument(
         "--username",
         help="The optional username to require for authentication.",
diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py
index 11571ec889..c98047d9e5 100644
--- a/esphome/dashboard/dashboard.py
+++ b/esphome/dashboard/dashboard.py
@@ -970,16 +970,17 @@ def start_web_server(args):
         server.add_socket(socket)
     else:
         _LOGGER.info(
-            "Starting dashboard web server on http://0.0.0.0:%s and configuration dir %s...",
+            "Starting dashboard web server on http://%s:%s and configuration dir %s...",
+            args.address,
             args.port,
             settings.config_dir,
         )
-        app.listen(args.port)
+        app.listen(args.port, args.address)
 
         if args.open_ui:
             import webbrowser
 
-            webbrowser.open(f"localhost:{args.port}")
+            webbrowser.open(f"http://{args.address}:{args.port}")
 
     if settings.status_use_ping:
         status_thread = PingStatusThread()

From 4b1d73791d4a13bd1486083a60b0940bea266c63 Mon Sep 17 00:00:00 2001
From: Martin <25747549+martgras@users.noreply.github.com>
Date: Wed, 24 Nov 2021 20:06:08 +0100
Subject: [PATCH 370/549] remove LEDC_HIGH_SPEED_MODE for C3, S2, S3 (#2791)

---
 esphome/components/ledc/ledc_output.cpp | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp
index 21a747e34d..13ad32ab70 100644
--- a/esphome/components/ledc/ledc_output.cpp
+++ b/esphome/components/ledc/ledc_output.cpp
@@ -15,6 +15,18 @@ namespace ledc {
 
 static const char *const TAG = "ledc.output";
 
+#ifdef USE_ESP_IDF
+#if SOC_LEDC_SUPPORT_HS_MODE
+// Only ESP32 has LEDC_HIGH_SPEED_MODE
+inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; }
+#else
+// S2, C3, S3 only support LEDC_LOW_SPEED_MODE
+// See
+// https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html#functionality-overview
+inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; }
+#endif
+#endif
+
 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;
@@ -48,7 +60,7 @@ void LEDCOutput::write_state(float state) {
   ledcWrite(this->channel_, duty);
 #endif
 #ifdef USE_ESP_IDF
-  auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE;
+  auto speed_mode = get_speed_mode(channel_);
   auto chan_num = static_cast(channel_ % 8);
   ledc_set_duty(speed_mode, chan_num, duty);
   ledc_update_duty(speed_mode, chan_num);
@@ -63,7 +75,7 @@ void LEDCOutput::setup() {
   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 speed_mode = get_speed_mode(channel_);
   auto timer_num = static_cast((channel_ % 8) / 2);
   auto chan_num = static_cast(channel_ % 8);
 
@@ -114,7 +126,7 @@ void LEDCOutput::update_frequency(float frequency) {
     ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!");
     return;
   }
-  auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE;
+  auto speed_mode = get_speed_mode(channel_);
   auto timer_num = static_cast((channel_ % 8) / 2);
 
   ledc_timer_config_t timer_conf{};

From 290da8df2ded1cc82268d2a22e24a163e2a27881 Mon Sep 17 00:00:00 2001
From: rsumner 
Date: Wed, 24 Nov 2021 16:22:51 -0600
Subject: [PATCH 371/549] Fix LEDC resolution calculation on ESP32-C3/S2/S3
 (#2794)

Co-authored-by: Oxan van Leeuwen 
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
---
 esphome/components/ledc/ledc_output.cpp | 22 +++++++++++++++++-----
 1 file changed, 17 insertions(+), 5 deletions(-)

diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp
index 13ad32ab70..a56dccfd72 100644
--- a/esphome/components/ledc/ledc_output.cpp
+++ b/esphome/components/ledc/ledc_output.cpp
@@ -16,6 +16,7 @@ namespace ledc {
 static const char *const TAG = "ledc.output";
 
 #ifdef USE_ESP_IDF
+static const int MAX_RES_BITS = LEDC_TIMER_BIT_MAX - 1;
 #if SOC_LEDC_SUPPORT_HS_MODE
 // Only ESP32 has LEDC_HIGH_SPEED_MODE
 inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; }
@@ -25,19 +26,26 @@ inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_H
 // https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html#functionality-overview
 inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; }
 #endif
+#else
+static const int MAX_RES_BITS = 20;
 #endif
 
 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;
+
+float ledc_min_frequency_for_bit_depth(uint8_t bit_depth, bool low_frequency) {
+  const float max_div_num = ((1 << MAX_RES_BITS) - 1) / (low_frequency ? 32.0f : 256.0f);
   return 80e6f / (max_div_num * float(1 << bit_depth));
 }
+
 optional ledc_bit_depth_for_frequency(float frequency) {
-  for (int i = 20; i >= 1; i--) {
-    const float min_frequency = ledc_min_frequency_for_bit_depth(i);
+  ESP_LOGD(TAG, "Calculating resolution bit-depth for frequency %f", frequency);
+  for (int i = MAX_RES_BITS; i >= 1; i--) {
+    const float min_frequency = ledc_min_frequency_for_bit_depth(i, (frequency < 100));
     const float max_frequency = ledc_max_frequency_for_bit_depth(i);
-    if (min_frequency <= frequency && frequency <= max_frequency)
+    if (min_frequency <= frequency && frequency <= max_frequency) {
+      ESP_LOGD(TAG, "Resolution calculated as %d", i);
       return i;
+    }
   }
   return {};
 }
@@ -80,6 +88,10 @@ void LEDCOutput::setup() {
   auto chan_num = static_cast(channel_ % 8);
 
   bit_depth_ = *ledc_bit_depth_for_frequency(frequency_);
+  if (bit_depth_ < 1) {
+    ESP_LOGW(TAG, "Frequency %f can't be achieved with any bit depth", frequency_);
+    this->status_set_warning();
+  }
 
   ledc_timer_config_t timer_conf{};
   timer_conf.speed_mode = speed_mode;

From ccfa1e23f0211f5421bef22b328ec3fb5b4204ee Mon Sep 17 00:00:00 2001
From: Martin <25747549+martgras@users.noreply.github.com>
Date: Wed, 24 Nov 2021 23:28:19 +0100
Subject: [PATCH 372/549] Add support for sdp8xx (#2779)

---
 esphome/components/sdp3x/sdp3x.cpp | 106 ++++++++++++++++++-----------
 esphome/components/sdp3x/sdp3x.h   |   6 +-
 esphome/components/sdp3x/sensor.py |  12 ++++
 3 files changed, 83 insertions(+), 41 deletions(-)

diff --git a/esphome/components/sdp3x/sdp3x.cpp b/esphome/components/sdp3x/sdp3x.cpp
index b0d8bcc6c4..107ed2902f 100644
--- a/esphome/components/sdp3x/sdp3x.cpp
+++ b/esphome/components/sdp3x/sdp3x.cpp
@@ -11,6 +11,7 @@ static const uint8_t SDP3X_SOFT_RESET[2] = {0x00, 0x06};
 static const uint8_t SDP3X_READ_ID1[2] = {0x36, 0x7C};
 static const uint8_t SDP3X_READ_ID2[2] = {0xE1, 0x02};
 static const uint8_t SDP3X_START_DP_AVG[2] = {0x36, 0x15};
+static const uint8_t SDP3X_START_MASS_FLOW_AVG[2] = {0x36, 0x03};
 static const uint8_t SDP3X_STOP_MEAS[2] = {0x3F, 0xF9};
 
 void SDP3XComponent::update() { this->read_pressure_(); }
@@ -26,46 +27,69 @@ void SDP3XComponent::setup() {
     ESP_LOGW(TAG, "Soft Reset SDP3X failed!");  // This sometimes fails for no good reason
   }
 
-  delayMicroseconds(20000);
+  this->set_timeout(20, [this] {
+    if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) {
+      ESP_LOGE(TAG, "Read ID1 SDP3X failed!");
+      this->mark_failed();
+      return;
+    }
+    if (this->write(SDP3X_READ_ID2, 2) != i2c::ERROR_OK) {
+      ESP_LOGE(TAG, "Read ID2 SDP3X failed!");
+      this->mark_failed();
+      return;
+    }
 
-  if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) {
-    ESP_LOGE(TAG, "Read ID1 SDP3X failed!");
-    this->mark_failed();
-    return;
-  }
-  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(data, 18) != i2c::ERROR_OK) {
+      ESP_LOGE(TAG, "Read ID SDP3X failed!");
+      this->mark_failed();
+      return;
+    }
+    if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]))) {
+      ESP_LOGE(TAG, "CRC ID SDP3X failed!");
+      this->mark_failed();
+      return;
+    }
 
-  uint8_t data[18];
-  if (this->read(data, 18) != i2c::ERROR_OK) {
-    ESP_LOGE(TAG, "Read ID SDP3X failed!");
-    this->mark_failed();
-    return;
-  }
+    // SDP8xx
+    // ref:
+    // https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/8_Differential_Pressure/Datasheets/Sensirion_Differential_Pressure_Datasheet_SDP8xx_Digital.pdf
+    if (data[2] == 0x02) {
+      switch (data[3]) {
+        case 0x01:  // SDP800-500Pa
+          ESP_LOGCONFIG(TAG, "Sensor is SDP800-500Pa");
+          break;
+        case 0x0A:  // SDP810-500Pa
+          ESP_LOGCONFIG(TAG, "Sensor is SDP810-500Pa");
+          break;
+        case 0x04:  // SDP801-500Pa
+          ESP_LOGCONFIG(TAG, "Sensor is SDP801-500Pa");
+          break;
+        case 0x0D:  // SDP811-500Pa
+          ESP_LOGCONFIG(TAG, "Sensor is SDP811-500Pa");
+          break;
+        case 0x02:  // SDP800-125Pa
+          ESP_LOGCONFIG(TAG, "Sensor is SDP800-125Pa");
+          break;
+        case 0x0B:  // SDP810-125Pa
+          ESP_LOGCONFIG(TAG, "Sensor is SDP810-125Pa");
+          break;
+      }
+    } else if (data[2] == 0x01) {
+      if (data[3] == 0x01) {
+        ESP_LOGCONFIG(TAG, "Sensor is SDP31-500Pa");
+      } else if (data[3] == 0x02) {
+        ESP_LOGCONFIG(TAG, "Sensor is SDP32-125Pa");
+      }
+    }
 
-  if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]))) {
-    ESP_LOGE(TAG, "CRC ID SDP3X failed!");
-    this->mark_failed();
-    return;
-  }
-
-  if (data[3] == 0x01) {
-    ESP_LOGCONFIG(TAG, "SDP3X is SDP31");
-    pressure_scale_factor_ = 60.0f * 100.0f;  // Scale factors converted to hPa per count
-  } else if (data[3] == 0x02) {
-    ESP_LOGCONFIG(TAG, "SDP3X is SDP32");
-    pressure_scale_factor_ = 240.0f * 100.0f;
-  }
-
-  if (this->write(SDP3X_START_DP_AVG, 2) != i2c::ERROR_OK) {
-    ESP_LOGE(TAG, "Start Measurements SDP3X failed!");
-    this->mark_failed();
-    return;
-  }
-  ESP_LOGCONFIG(TAG, "SDP3X started!");
+    if (this->write(measurement_mode_ == DP_AVG ? SDP3X_START_DP_AVG : SDP3X_START_MASS_FLOW_AVG, 2) != i2c::ERROR_OK) {
+      ESP_LOGE(TAG, "Start Measurements SDP3X failed!");
+      this->mark_failed();
+      return;
+    }
+    ESP_LOGCONFIG(TAG, "SDP3X started!");
+  });
 }
 void SDP3XComponent::dump_config() {
   LOG_SENSOR("  ", "SDP3X", this);
@@ -91,8 +115,12 @@ void SDP3XComponent::read_pressure_() {
   }
 
   int16_t pressure_raw = encode_uint16(data[0], data[1]);
-  float pressure = pressure_raw / pressure_scale_factor_;
-  ESP_LOGV(TAG, "Got raw pressure=%d, scale factor =%.3f ", pressure_raw, pressure_scale_factor_);
+  int16_t temperature_raw = encode_uint16(data[3], data[4]);
+  int16_t scale_factor_raw = encode_uint16(data[6], data[7]);
+  // scale factor is in Pa - convert to hPa
+  float pressure = pressure_raw / (scale_factor_raw * 100.0f);
+  ESP_LOGV(TAG, "Got raw pressure=%d, raw scale factor =%d, raw temperature=%d ", pressure_raw, scale_factor_raw,
+           temperature_raw);
   ESP_LOGD(TAG, "Got Pressure=%.3f hPa", pressure);
 
   this->publish_state(pressure);
diff --git a/esphome/components/sdp3x/sdp3x.h b/esphome/components/sdp3x/sdp3x.h
index 51c9973c61..0e74d0883d 100644
--- a/esphome/components/sdp3x/sdp3x.h
+++ b/esphome/components/sdp3x/sdp3x.h
@@ -7,6 +7,8 @@
 namespace esphome {
 namespace sdp3x {
 
+enum MeasurementMode { MASS_FLOW_AVG, DP_AVG };
+
 class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor {
  public:
   /// Schedule temperature+pressure readings.
@@ -16,14 +18,14 @@ class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public se
   void dump_config() override;
 
   float get_setup_priority() const override;
+  void set_measurement_mode(MeasurementMode mode) { measurement_mode_ = mode; }
 
  protected:
   /// Internal method to read the pressure from the component after it has been scheduled.
   void read_pressure_();
 
   bool check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum);
-
-  float pressure_scale_factor_ = 0.0f;  // hPa per count
+  MeasurementMode measurement_mode_;
 };
 
 }  // namespace sdp3x
diff --git a/esphome/components/sdp3x/sensor.py b/esphome/components/sdp3x/sensor.py
index 08d7250f6e..45f5cc4d9a 100644
--- a/esphome/components/sdp3x/sensor.py
+++ b/esphome/components/sdp3x/sensor.py
@@ -14,6 +14,14 @@ CODEOWNERS = ["@Azimath"]
 sdp3x_ns = cg.esphome_ns.namespace("sdp3x")
 SDP3XComponent = sdp3x_ns.class_("SDP3XComponent", cg.PollingComponent, i2c.I2CDevice)
 
+
+MeasurementMode = sdp3x_ns.enum("MeasurementMode")
+MEASUREMENT_MODE = {
+    "mass_flow": MeasurementMode.MASS_FLOW_AVG,
+    "differential_pressure": MeasurementMode.DP_AVG,
+}
+CONF_MEASUREMENT_MODE = "measurement_mode"
+
 CONFIG_SCHEMA = (
     sensor.sensor_schema(
         unit_of_measurement=UNIT_HECTOPASCAL,
@@ -24,6 +32,9 @@ CONFIG_SCHEMA = (
     .extend(
         {
             cv.GenerateID(): cv.declare_id(SDP3XComponent),
+            cv.Optional(
+                CONF_MEASUREMENT_MODE, default="differential_pressure"
+            ): cv.enum(MEASUREMENT_MODE),
         }
     )
     .extend(cv.polling_component_schema("60s"))
@@ -36,3 +47,4 @@ async def to_code(config):
     await cg.register_component(var, config)
     await i2c.register_i2c_device(var, config)
     await sensor.register_sensor(var, config)
+    cg.add(var.set_measurement_mode(config[CONF_MEASUREMENT_MODE]))

From ceb9b1d1ff0b989dc196eeb624963d6e6c644427 Mon Sep 17 00:00:00 2001
From: Maurice Makaay 
Date: Wed, 24 Nov 2021 23:51:56 +0100
Subject: [PATCH 373/549] Allow empty UART debug: option, logging in hex format
 by default (#2771)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Maurice Makaay 
---
 esphome/components/uart/__init__.py | 29 ++++++++++++++++++++++++++---
 tests/test3.yaml                    |  2 ++
 2 files changed, 28 insertions(+), 3 deletions(-)

diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py
index 159b08d2d9..61b54044d7 100644
--- a/esphome/components/uart/__init__.py
+++ b/esphome/components/uart/__init__.py
@@ -3,6 +3,7 @@ from typing import Optional
 import esphome.codegen as cg
 import esphome.config_validation as cv
 import esphome.final_validate as fv
+from esphome.yaml_util import make_data_base
 from esphome import pins, automation
 from esphome.const import (
     CONF_BAUD_RATE,
@@ -24,6 +25,7 @@ from esphome.const import (
     CONF_DELIMITER,
     CONF_DUMMY_RECEIVER,
     CONF_DUMMY_RECEIVER_ID,
+    CONF_LAMBDA,
 )
 from esphome.core import CORE
 
@@ -94,7 +96,26 @@ UART_DIRECTIONS = {
     "BOTH": UARTDirection.UART_DIRECTION_BOTH,
 }
 
-AFTER_DEFAULTS = {CONF_BYTES: 256, CONF_TIMEOUT: "100ms"}
+# The reason for having CONF_BYTES at 150 by default:
+#
+# The log message buffer size is 512 bytes by default. About 35 bytes are
+# used for the log prefix. That leaves us with 477 bytes for logging data.
+# The default log output is hex, which uses 3 characters per represented
+# byte (2 hex chars + 1 separator). That means that 477 / 3 = 159 bytes
+# can be represented in a single log line. Using 150, because people love
+# round numbers.
+AFTER_DEFAULTS = {CONF_BYTES: 150, CONF_TIMEOUT: "100ms"}
+
+# By default, log in hex format when no specific sequence is provided.
+DEFAULT_DEBUG_OUTPUT = "UARTDebug::log_hex(direction, bytes, ':');"
+DEFAULT_SEQUENCE = [{CONF_LAMBDA: make_data_base(DEFAULT_DEBUG_OUTPUT)}]
+
+
+def maybe_empty_debug(value):
+    if value is None:
+        value = {}
+    return DEBUG_SCHEMA(value)
+
 
 DEBUG_SCHEMA = cv.Schema(
     {
@@ -113,7 +134,9 @@ DEBUG_SCHEMA = cv.Schema(
                 cv.Optional(CONF_DELIMITER): cv.templatable(validate_raw_data),
             }
         ),
-        cv.Required(CONF_SEQUENCE): automation.validate_automation(),
+        cv.Optional(
+            CONF_SEQUENCE, default=DEFAULT_SEQUENCE
+        ): automation.validate_automation(),
         cv.Optional(CONF_DUMMY_RECEIVER, default=False): cv.boolean,
         cv.GenerateID(CONF_DUMMY_RECEIVER_ID): cv.declare_id(UARTDummyReceiver),
     }
@@ -135,7 +158,7 @@ CONFIG_SCHEMA = cv.All(
             cv.Optional(CONF_INVERT): cv.invalid(
                 "This option has been removed. Please instead use invert in the tx/rx pin schemas."
             ),
-            cv.Optional(CONF_DEBUG): DEBUG_SCHEMA,
+            cv.Optional(CONF_DEBUG): maybe_empty_debug,
         }
     ).extend(cv.COMPONENT_SCHEMA),
     cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN),
diff --git a/tests/test3.yaml b/tests/test3.yaml
index 27a0c148d9..61c7a3dcad 100644
--- a/tests/test3.yaml
+++ b/tests/test3.yaml
@@ -254,6 +254,8 @@ uart:
     tx_pin: GPIO4
     rx_pin: GPIO5
     baud_rate: 38400
+    # Specifically added for testing debug with no options at all.
+    debug:
 
 modbus:
   uart_id: uart1

From 2aea27d2726d06599290bc7afa3288700edfcd3d Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 25 Nov 2021 20:34:11 +0100
Subject: [PATCH 374/549] Bump pylint from 2.11.1 to 2.12.1 (#2798)

---
 requirements_test.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements_test.txt b/requirements_test.txt
index cc3fcfb2ec..ad98ec2a4f 100644
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -1,4 +1,4 @@
-pylint==2.11.1
+pylint==2.12.1
 flake8==4.0.1
 black==21.11b1
 pexpect==4.8.0

From 3637be251e594769080ff6cb683d4afa3221c3ff Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Thu, 25 Nov 2021 21:00:49 +0100
Subject: [PATCH 375/549] Fix parsing numbers from null-terminated buffers
 (#2755)

---
 esphome/components/anova/anova_base.cpp | 23 ++++++++++++-----------
 esphome/components/anova/anova_base.h   |  1 -
 esphome/components/ezo/ezo.cpp          |  4 ++--
 esphome/core/helpers.h                  | 22 ++++++++++++----------
 4 files changed, 26 insertions(+), 24 deletions(-)

diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp
index d55404089e..dcef75e483 100644
--- a/esphome/components/anova/anova_base.cpp
+++ b/esphome/components/anova/anova_base.cpp
@@ -73,51 +73,52 @@ AnovaPacket *AnovaCodec::get_stop_request() {
 }
 
 void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
-  memset(this->buf_, 0, 32);
-  strncpy(this->buf_, (char *) data, length);
+  char buf[32];
+  memset(buf, 0, sizeof(buf));
+  strncpy(buf, (char *) data, std::min(length, sizeof(buf) - 1));
   this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false;
   switch (this->current_query_) {
     case READ_DEVICE_STATUS: {
-      if (!strncmp(this->buf_, "stopped", 7)) {
+      if (!strncmp(buf, "stopped", 7)) {
         this->has_running_ = true;
         this->running_ = false;
       }
-      if (!strncmp(this->buf_, "running", 7)) {
+      if (!strncmp(buf, "running", 7)) {
         this->has_running_ = true;
         this->running_ = true;
       }
       break;
     }
     case START: {
-      if (!strncmp(this->buf_, "start", 5)) {
+      if (!strncmp(buf, "start", 5)) {
         this->has_running_ = true;
         this->running_ = true;
       }
       break;
     }
     case STOP: {
-      if (!strncmp(this->buf_, "stop", 4)) {
+      if (!strncmp(buf, "stop", 4)) {
         this->has_running_ = true;
         this->running_ = false;
       }
       break;
     }
     case READ_TARGET_TEMPERATURE: {
-      this->target_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f);
+      this->target_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f);
       if (this->fahrenheit_)
         this->target_temp_ = ftoc(this->target_temp_);
       this->has_target_temp_ = true;
       break;
     }
     case SET_TARGET_TEMPERATURE: {
-      this->target_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f);
+      this->target_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f);
       if (this->fahrenheit_)
         this->target_temp_ = ftoc(this->target_temp_);
       this->has_target_temp_ = true;
       break;
     }
     case READ_CURRENT_TEMPERATURE: {
-      this->current_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f);
+      this->current_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f);
       if (this->fahrenheit_)
         this->current_temp_ = ftoc(this->current_temp_);
       this->has_current_temp_ = true;
@@ -125,8 +126,8 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
     }
     case SET_UNIT:
     case READ_UNIT: {
-      this->unit_ = this->buf_[0];
-      this->fahrenheit_ = this->buf_[0] == 'f';
+      this->unit_ = buf[0];
+      this->fahrenheit_ = buf[0] == 'f';
       this->has_unit_ = true;
       break;
     }
diff --git a/esphome/components/anova/anova_base.h b/esphome/components/anova/anova_base.h
index 7c1383512d..b831157849 100644
--- a/esphome/components/anova/anova_base.h
+++ b/esphome/components/anova/anova_base.h
@@ -70,7 +70,6 @@ class AnovaCodec {
   bool has_current_temp_;
   bool has_unit_;
   bool has_running_;
-  char buf_[32];
   bool fahrenheit_;
 
   CurrentQuery current_query_;
diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp
index 7f7a41fb41..426f2807c1 100644
--- a/esphome/components/ezo/ezo.cpp
+++ b/esphome/components/ezo/ezo.cpp
@@ -32,7 +32,7 @@ void EZOSensor::update() {
 }
 
 void EZOSensor::loop() {
-  uint8_t buf[20];
+  uint8_t buf[21];
   if (!(this->state_ & EZO_STATE_WAIT)) {
     if (this->state_ & EZO_STATE_SEND_TEMP) {
       int len = sprintf((char *) buf, "T,%0.3f", this->tempcomp_);
@@ -74,7 +74,7 @@ void EZOSensor::loop() {
   if (buf[0] != 1)
     return;
 
-  float val = parse_number((char *) &buf[1], sizeof(buf) - 1).value_or(0);
+  float val = parse_number((char *) &buf[1], sizeof(buf) - 2).value_or(0);
   this->publish_state(val);
 }
 
diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h
index 4ce1f08074..1faf1ac3aa 100644
--- a/esphome/core/helpers.h
+++ b/esphome/core/helpers.h
@@ -364,45 +364,47 @@ std::string str_sanitize(const std::string &str);
 /// @name Parsing & formatting
 ///@{
 
-/// Parse a unsigned decimal number.
+/// Parse an unsigned decimal number (requires null-terminated string).
 template::value && std::is_unsigned::value), int> = 0>
 optional parse_number(const char *str, size_t len) {
   char *end = nullptr;
   unsigned long value = ::strtoul(str, &end, 10);  // NOLINT(google-runtime-int)
-  if (end == nullptr || end != str + len || value > std::numeric_limits::max())
+  if (end == str || *end != '\0' || value > std::numeric_limits::max())
     return {};
   return value;
 }
+/// Parse an unsigned decimal number.
 template::value && std::is_unsigned::value), int> = 0>
 optional parse_number(const std::string &str) {
-  return parse_number(str.c_str(), str.length());
+  return parse_number(str.c_str(), str.length() + 1);
 }
-/// Parse a signed decimal number.
+/// Parse a signed decimal number (requires null-terminated string).
 template::value && std::is_signed::value), int> = 0>
 optional parse_number(const char *str, size_t len) {
   char *end = nullptr;
   signed long value = ::strtol(str, &end, 10);  // NOLINT(google-runtime-int)
-  if (end == nullptr || end != str + len || value < std::numeric_limits::min() ||
-      value > std::numeric_limits::max())
+  if (end == str || *end != '\0' || value < std::numeric_limits::min() || value > std::numeric_limits::max())
     return {};
   return value;
 }
+/// Parse a signed decimal number.
 template::value && std::is_signed::value), int> = 0>
 optional parse_number(const std::string &str) {
-  return parse_number(str.c_str(), str.length());
+  return parse_number(str.c_str(), str.length() + 1);
 }
-/// Parse a decimal floating-point number.
+/// Parse a decimal floating-point number (requires null-terminated string).
 template::value), int> = 0>
 optional parse_number(const char *str, size_t len) {
   char *end = nullptr;
   float value = ::strtof(str, &end);
-  if (end == nullptr || end != str + len || value == HUGE_VALF)
+  if (end == str || *end != '\0' || value == HUGE_VALF)
     return {};
   return value;
 }
+/// Parse a decimal floating-point number.
 template::value), int> = 0>
 optional parse_number(const std::string &str) {
-  return parse_number(str.c_str(), str.length());
+  return parse_number(str.c_str(), str.length() + 1);
 }
 
 ///@}

From 2a78c2970da865bcea82df7f6f8e04afab47d34e Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Thu, 25 Nov 2021 21:27:34 +0100
Subject: [PATCH 376/549] Fix CI cache key for test3.yaml compile (#2757)

---
 .github/workflows/ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 02b64d2bf5..b3e06a3367 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -34,7 +34,7 @@ jobs:
           - id: test
             file: tests/test3.yaml
             name: Test tests/test3.yaml
-            pio_cache_key: test1
+            pio_cache_key: test3
           - id: test
             file: tests/test4.yaml
             name: Test tests/test4.yaml

From 4e448b21ff2eaa9f158889f658d2269ee8f8dce0 Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Thu, 25 Nov 2021 21:27:53 +0100
Subject: [PATCH 377/549] Drop obsolete comment from CI workflow file (#2758)

---
 .github/workflows/ci.yml | 2 --
 1 file changed, 2 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b3e06a3367..d723424326 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,5 +1,3 @@
-# THESE JOBS ARE COPIED IN release.yml and release-dev.yml
-# PLEASE ALSO UPDATE THOSE FILES WHEN CHANGING LINES HERE
 name: CI
 
 on:

From d50bdf619f38ec86e4a026a96a657e04050ac3e1 Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Thu, 25 Nov 2021 21:29:10 +0100
Subject: [PATCH 378/549] Cache virtualenv instead of pip cache between CI runs
 (#2759)

---
 .github/workflows/ci.yml | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d723424326..3daabecc9f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -78,18 +78,23 @@ jobs:
         with:
           python-version: '3.7'
 
-      - name: Cache pip modules
+      - name: Cache virtualenv
         uses: actions/cache@v2
         with:
-          path: ~/.cache/pip
-          key: pip-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
+          path: .venv
+          key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
           restore-keys: |
-            pip-${{ steps.python.outputs.python-version }}-
+            venv-${{ steps.python.outputs.python-version }}-
 
-      - name: Set up python environment
+      - name: Set up virtualenv
         run: |
-          pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
-          pip3 install -e .
+          python -m venv .venv
+          source .venv/bin/activate
+          pip install -U pip
+          pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
+          pip install -e .
+          echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH
+          echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV
 
       # Use per check platformio cache because checks use different parts
       - name: Cache platformio

From b5f660398c509ca73dae1f44a821b591646e9f52 Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Thu, 25 Nov 2021 21:35:33 +0100
Subject: [PATCH 379/549] Add map filter for text sensors (#2761)

---
 esphome/components/text_sensor/__init__.py | 38 ++++++++++++----------
 esphome/components/text_sensor/filter.cpp  |  6 ++++
 esphome/components/text_sensor/filter.h    | 11 +++++++
 tests/test2.yaml                           | 10 ++++++
 4 files changed, 48 insertions(+), 17 deletions(-)

diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py
index a070e6f86d..e0fc6af19c 100644
--- a/esphome/components/text_sensor/__init__.py
+++ b/esphome/components/text_sensor/__init__.py
@@ -49,6 +49,7 @@ ToLowerFilter = text_sensor_ns.class_("ToLowerFilter", Filter)
 AppendFilter = text_sensor_ns.class_("AppendFilter", Filter)
 PrependFilter = text_sensor_ns.class_("PrependFilter", Filter)
 SubstituteFilter = text_sensor_ns.class_("SubstituteFilter", Filter)
+MapFilter = text_sensor_ns.class_("MapFilter", Filter)
 
 
 @FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda)
@@ -79,26 +80,21 @@ async def prepend_filter_to_code(config, filter_id):
     return cg.new_Pvariable(filter_id, config)
 
 
-def validate_substitute(value):
-    if isinstance(value, dict):
-        return cv.Schema(
-            {
-                cv.Required(CONF_FROM): cv.string,
-                cv.Required(CONF_TO): cv.string,
-            }
-        )(value)
-    value = cv.string(value)
-    if "->" not in value:
-        raise cv.Invalid("Substitute mapping must contain '->'")
-    a, b = value.split("->", 1)
-    a, b = a.strip(), b.strip()
-    return validate_substitute({CONF_FROM: cv.string(a), CONF_TO: cv.string(b)})
+def validate_mapping(value):
+    if not isinstance(value, dict):
+        value = cv.string(value)
+        if "->" not in value:
+            raise cv.Invalid("Mapping must contain '->'")
+        a, b = value.split("->", 1)
+        value = {CONF_FROM: a.strip(), CONF_TO: b.strip()}
+
+    return cv.Schema(
+        {cv.Required(CONF_FROM): cv.string, cv.Required(CONF_TO): cv.string}
+    )(value)
 
 
 @FILTER_REGISTRY.register(
-    "substitute",
-    SubstituteFilter,
-    cv.All(cv.ensure_list(validate_substitute), cv.Length(min=2)),
+    "substitute", SubstituteFilter, cv.ensure_list(validate_mapping)
 )
 async def substitute_filter_to_code(config, filter_id):
     from_strings = [conf[CONF_FROM] for conf in config]
@@ -106,6 +102,14 @@ async def substitute_filter_to_code(config, filter_id):
     return cg.new_Pvariable(filter_id, from_strings, to_strings)
 
 
+@FILTER_REGISTRY.register("map", MapFilter, cv.ensure_list(validate_mapping))
+async def map_filter_to_code(config, filter_id):
+    map_ = cg.std_ns.class_("map").template(cg.std_string, cg.std_string)
+    return cg.new_Pvariable(
+        filter_id, map_([(item[CONF_FROM], item[CONF_TO]) for item in config])
+    )
+
+
 icon = cv.icon
 
 
diff --git a/esphome/components/text_sensor/filter.cpp b/esphome/components/text_sensor/filter.cpp
index 14df6238ff..a692378e8b 100644
--- a/esphome/components/text_sensor/filter.cpp
+++ b/esphome/components/text_sensor/filter.cpp
@@ -70,5 +70,11 @@ optional SubstituteFilter::new_value(std::string value) {
   return value;
 }
 
+// Map
+optional MapFilter::new_value(std::string value) {
+  auto item = mappings_.find(value);
+  return item == mappings_.end() ? value : item->second;
+}
+
 }  // namespace text_sensor
 }  // namespace esphome
diff --git a/esphome/components/text_sensor/filter.h b/esphome/components/text_sensor/filter.h
index 6a1d9ab04e..38f35e6172 100644
--- a/esphome/components/text_sensor/filter.h
+++ b/esphome/components/text_sensor/filter.h
@@ -4,6 +4,7 @@
 #include "esphome/core/helpers.h"
 #include 
 #include 
+#include 
 
 namespace esphome {
 namespace text_sensor {
@@ -108,5 +109,15 @@ class SubstituteFilter : public Filter {
   std::vector to_strings_;
 };
 
+/// A filter that maps values from one set to another
+class MapFilter : public Filter {
+ public:
+  MapFilter(std::map mappings) : mappings_(std::move(mappings)) {}
+  optional new_value(std::string value) override;
+
+ protected:
+  std::map mappings_;
+};
+
 }  // namespace text_sensor
 }  // namespace esphome
diff --git a/tests/test2.yaml b/tests/test2.yaml
index 3afef9501d..50743dc643 100644
--- a/tests/test2.yaml
+++ b/tests/test2.yaml
@@ -458,6 +458,16 @@ text_sensor:
     name: 'Template Text Sensor'
     lambda: |-
       return {"Hello World"};
+    filters:
+      - to_upper:
+      - to_lower:
+      - append: "xyz"
+      - prepend: "abcd"
+      - substitute:
+          - Hello -> Goodbye
+      - map:
+          - red -> green
+      - lambda: return {"1234"};
   - platform: homeassistant
     entity_id: sensor.hello_world2
     id: ha_hello_world2

From 5e631bc6ba6ded3fa61df88327b4a74d9d691f36 Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Thu, 25 Nov 2021 21:36:42 +0100
Subject: [PATCH 380/549] Only match GCC warnings from ESPHome source files in
 CI (#2756)

---
 .github/workflows/matchers/gcc.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/matchers/gcc.json b/.github/workflows/matchers/gcc.json
index 899239f816..a00d9c33f4 100644
--- a/.github/workflows/matchers/gcc.json
+++ b/.github/workflows/matchers/gcc.json
@@ -5,7 +5,7 @@
       "severity": "error",
       "pattern": [
         {
-          "regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
+          "regexp": "^src/(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
           "file": 1,
           "line": 2,
           "column": 3,

From 9681dfb45891f891789990f525b6212cd0961552 Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Thu, 25 Nov 2021 21:37:27 +0100
Subject: [PATCH 381/549] Correct constant for dynamic I2S bus in NeoPixelBus
 (#2797)

---
 esphome/components/neopixelbus/_methods.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/esphome/components/neopixelbus/_methods.py b/esphome/components/neopixelbus/_methods.py
index b03544f246..4e3c3ca778 100644
--- a/esphome/components/neopixelbus/_methods.py
+++ b/esphome/components/neopixelbus/_methods.py
@@ -88,8 +88,8 @@ def _esp32_i2s_default_bus():
 
 
 def _validate_esp32_i2s_bus(value):
-    if isinstance(value, str) and value.lower() == CHANNEL_DYNAMIC:
-        value = CHANNEL_DYNAMIC
+    if isinstance(value, str) and value.lower() == BUS_DYNAMIC:
+        value = BUS_DYNAMIC
     else:
         value = cv.int_(value)
     variant_buses = {

From 00965fe19e857a83cb7f500da640c0c80694e79e Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Thu, 25 Nov 2021 21:54:11 +0100
Subject: [PATCH 382/549] Consistently format errors in CI scripts (#2762)

---
 .github/workflows/matchers/ci-custom.json   |  2 +-
 .github/workflows/matchers/lint-python.json |  4 +--
 requirements_test.txt                       |  1 -
 script/ci-custom.py                         | 16 +++++-----
 script/clang-format                         | 16 ++++------
 script/clang-tidy                           | 33 +++++++++------------
 script/helpers.py                           | 19 +++++++-----
 script/lint-python                          | 21 ++++++++-----
 8 files changed, 57 insertions(+), 55 deletions(-)

diff --git a/.github/workflows/matchers/ci-custom.json b/.github/workflows/matchers/ci-custom.json
index 4e1eafff5e..9888dbb440 100644
--- a/.github/workflows/matchers/ci-custom.json
+++ b/.github/workflows/matchers/ci-custom.json
@@ -4,7 +4,7 @@
             "owner": "ci-custom",
             "pattern": [
                 {
-                    "regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$",
+                    "regexp": "^(.*):(\\d+):(\\d+):\\s+(.*)$",
                     "file": 1,
                     "line": 2,
                     "column": 3,
diff --git a/.github/workflows/matchers/lint-python.json b/.github/workflows/matchers/lint-python.json
index decbe36c4a..e88f74f6f6 100644
--- a/.github/workflows/matchers/lint-python.json
+++ b/.github/workflows/matchers/lint-python.json
@@ -5,7 +5,7 @@
       "severity": "error",
       "pattern": [
           {
-          "regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$",
+          "regexp": "^(.*):(\\d+): ([EFCDNW]\\d{3}.*)$",
           "file": 1,
           "line": 2,
           "message": 3
@@ -17,7 +17,7 @@
       "severity": "error",
       "pattern": [
         {
-          "regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
+          "regexp": "^(.*):(\\d+): (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
           "file": 1,
           "line": 2,
           "message": 3
diff --git a/requirements_test.txt b/requirements_test.txt
index ad98ec2a4f..b916e8bb1b 100644
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -1,7 +1,6 @@
 pylint==2.12.1
 flake8==4.0.1
 black==21.11b1
-pexpect==4.8.0
 pre-commit
 
 # Unit tests
diff --git a/script/ci-custom.py b/script/ci-custom.py
index 89550afd3d..3f01fb81bf 100755
--- a/script/ci-custom.py
+++ b/script/ci-custom.py
@@ -1,16 +1,16 @@
 #!/usr/bin/env python3
 
-from helpers import git_ls_files, filter_changed
+from helpers import styled, print_error_for_file, git_ls_files, filter_changed
+import argparse
 import codecs
 import collections
+import colorama
 import fnmatch
+import functools
 import os.path
 import re
-import subprocess
 import sys
 import time
-import functools
-import argparse
 
 sys.path.append(os.path.dirname(__file__))
 
@@ -30,6 +30,8 @@ def find_all(a_str, sub):
             column += len(sub)
 
 
+colorama.init()
+
 parser = argparse.ArgumentParser()
 parser.add_argument(
     "files", nargs="*", default=[], help="files to be processed (regex on path)"
@@ -657,10 +659,8 @@ for fname in files:
 run_checks(LINT_POST_CHECKS, "POST")
 
 for f, errs in sorted(errors.items()):
-    print(f"\033[0;32m************* File \033[1;32m{f}\033[0m")
-    for lineno, col, msg in errs:
-        print(f"ERROR {f}:{lineno}:{col} - {msg}")
-    print()
+    err_str = (f"{styled(colorama.Style.BRIGHT, f'{f}:{lineno}:{col}:')} {msg}\n" for lineno, col, msg in errs)
+    print_error_for_file(f, "\n".join(err_str))
 
 if args.print_slowest:
     lint_times = []
diff --git a/script/clang-format b/script/clang-format
index d6588f1ccb..515df4c027 100755
--- a/script/clang-format
+++ b/script/clang-format
@@ -1,6 +1,9 @@
 #!/usr/bin/env python3
 
+from helpers import print_error_for_file, get_output, git_ls_files, filter_changed
 import argparse
+import click
+import colorama
 import multiprocessing
 import os
 import queue
@@ -9,11 +12,6 @@ import subprocess
 import sys
 import threading
 
-import click
-
-sys.path.append(os.path.dirname(__file__))
-from helpers import get_output, git_ls_files, filter_changed
-
 
 def run_format(args, queue, lock, failed_files):
     """Takes filenames out of queue and runs clang-format on them."""
@@ -29,11 +27,7 @@ def run_format(args, queue, lock, failed_files):
         proc = subprocess.run(invocation, capture_output=True, encoding='utf-8')
         if proc.returncode != 0:
             with lock:
-                print()
-                print("\033[0;32m************* File \033[1;32m{}\033[0m".format(path))
-                print(proc.stdout)
-                print(proc.stderr)
-                print()
+                print_error_for_file(path, proc.stderr)
                 failed_files.append(path)
         queue.task_done()
 
@@ -43,6 +37,8 @@ def progress_bar_show(value):
 
 
 def main():
+    colorama.init()
+
     parser = argparse.ArgumentParser()
     parser.add_argument('-j', '--jobs', type=int,
                         default=multiprocessing.cpu_count(),
diff --git a/script/clang-tidy b/script/clang-tidy
index ad5fdfeb04..7450084634 100755
--- a/script/clang-tidy
+++ b/script/clang-tidy
@@ -1,7 +1,10 @@
 #!/usr/bin/env python3
 
+from helpers import print_error_for_file, get_output, filter_grep, \
+    build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata, basepath
 import argparse
-import json
+import click
+import colorama
 import multiprocessing
 import os
 import queue
@@ -12,13 +15,6 @@ import sys
 import tempfile
 import threading
 
-import click
-import pexpect
-
-sys.path.append(os.path.dirname(__file__))
-from helpers import shlex_quote, get_output, filter_grep, \
-    build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata, basepath
-
 
 def clang_options(idedata):
     cmd = [
@@ -87,23 +83,20 @@ def run_tidy(args, options, tmpdir, queue, lock, failed_files):
             invocation.append(name)
 
         if args.quiet:
-            invocation.append('-quiet')
+            invocation.append('--quiet')
+
+        if sys.stdout.isatty():
+            invocation.append('--use-color')
 
-        invocation.append(os.path.abspath(path))
         invocation.append(f"--header-filter={os.path.abspath(basepath)}/.*")
+        invocation.append(os.path.abspath(path))
         invocation.append('--')
         invocation.extend(options)
-        invocation_s = ' '.join(shlex_quote(x) for x in invocation)
 
-        # Use pexpect for a pseudy-TTY with colored output
-        output, rc = pexpect.run(invocation_s, withexitstatus=True, encoding='utf-8',
-                                 timeout=15 * 60)
-        if rc != 0:
+        proc = subprocess.run(invocation, capture_output=True, encoding='utf-8')
+        if proc.returncode != 0:
             with lock:
-                print()
-                print("\033[0;32m************* File \033[1;32m{}\033[0m".format(path))
-                print(output)
-                print()
+                print_error_for_file(path, proc.stdout)
                 failed_files.append(path)
         queue.task_done()
 
@@ -119,6 +112,8 @@ def split_list(a, n):
 
 
 def main():
+    colorama.init()
+
     parser = argparse.ArgumentParser()
     parser.add_argument('-j', '--jobs', type=int,
                         default=multiprocessing.cpu_count(),
diff --git a/script/helpers.py b/script/helpers.py
index 430d8a8e7f..abf970b8a2 100644
--- a/script/helpers.py
+++ b/script/helpers.py
@@ -1,4 +1,4 @@
-import codecs
+import colorama
 import os.path
 import re
 import subprocess
@@ -11,13 +11,18 @@ temp_folder = os.path.join(root_path, ".temp")
 temp_header_file = os.path.join(temp_folder, "all-include.cpp")
 
 
-def shlex_quote(s):
-    if not s:
-        return "''"
-    if re.search(r"[^\w@%+=:,./-]", s) is None:
-        return s
+def styled(color, msg, reset=True):
+    prefix = ''.join(color) if isinstance(color, tuple) else color
+    suffix = colorama.Style.RESET_ALL if reset else ''
+    return prefix + msg + suffix
 
-    return "'" + s.replace("'", "'\"'\"'") + "'"
+
+def print_error_for_file(file, body):
+    print(styled(colorama.Fore.GREEN, "### File ") + styled((colorama.Fore.GREEN, colorama.Style.BRIGHT), file))
+    print()
+    if body is not None:
+        print(body)
+        print()
 
 
 def build_all_include():
diff --git a/script/lint-python b/script/lint-python
index 41885b9672..8ee038a661 100755
--- a/script/lint-python
+++ b/script/lint-python
@@ -1,15 +1,13 @@
 #!/usr/bin/env python3
 
 from __future__ import print_function
-from helpers import get_output, get_err, git_ls_files, filter_changed
-
+from helpers import styled, print_error_for_file, get_output, get_err, git_ls_files, filter_changed
 import argparse
+import colorama
 import os
 import re
 import sys
 
-sys.path.append(os.path.dirname(__file__))
-
 curfile = None
 
 
@@ -17,14 +15,18 @@ def print_error(file, lineno, msg):
     global curfile
 
     if curfile != file:
-        print()
-        print("\033[0;32m************* File \033[1;32m{}\033[0m".format(file))
+        print_error_for_file(file, None)
         curfile = file
 
-    print("{}:{} - {}".format(file, lineno, msg))
+    if lineno is not None:
+        print(f"{styled(colorama.Style.BRIGHT, f'{file}:{lineno}:')} {msg}")
+    else:
+        print(f"{styled(colorama.Style.BRIGHT, f'{file}:')} {msg}")
 
 
 def main():
+    colorama.init()
+
     parser = argparse.ArgumentParser()
     parser.add_argument(
         "files", nargs="*", default=[], help="files to be processed (regex on path)"
@@ -56,6 +58,7 @@ def main():
 
     cmd = ["black", "--verbose", "--check"] + files
     print("Running black...")
+    print()
     log = get_err(*cmd)
     for line in log.splitlines():
         WOULD_REFORMAT = "would reformat"
@@ -65,7 +68,9 @@ def main():
             errors += 1
 
     cmd = ["flake8"] + files
+    print()
     print("Running flake8...")
+    print()
     log = get_output(*cmd)
     for line in log.splitlines():
         line = line.split(":", 4)
@@ -78,7 +83,9 @@ def main():
         errors += 1
 
     cmd = ["pylint", "-f", "parseable", "--persistent=n"] + files
+    print()
     print("Running pylint...")
+    print()
     log = get_output(*cmd)
     for line in log.splitlines():
         line = line.split(":", 3)

From 2347e043a9dcc5e265328a2d63ffdf140e0281de Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Fri, 26 Nov 2021 10:02:39 +1300
Subject: [PATCH 383/549] Cancel previous workflows for PRs and branches
 (#2800)

---
 .github/workflows/ci.yml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3daabecc9f..93a29874f7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -9,6 +9,10 @@ on:
 permissions:
   contents: read
 
+concurrency:
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+  cancel-in-progress: true
+
 jobs:
   ci:
     name: ${{ matrix.name }}

From e7827a69976a48aff3f9306eba5ae28f9ec81259 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adri=C3=A1n=20Panella?= 
Date: Thu, 25 Nov 2021 15:35:36 -0600
Subject: [PATCH 384/549] total_daily_energy: allow to disable restore mode
 (#2795)

---
 esphome/components/total_daily_energy/sensor.py      |  3 +++
 .../total_daily_energy/total_daily_energy.cpp        | 12 ++++++------
 .../total_daily_energy/total_daily_energy.h          |  2 ++
 3 files changed, 11 insertions(+), 6 deletions(-)

diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py
index 6a8a416b81..0c20ccd27c 100644
--- a/esphome/components/total_daily_energy/sensor.py
+++ b/esphome/components/total_daily_energy/sensor.py
@@ -4,6 +4,7 @@ from esphome.components import sensor, time
 from esphome.const import (
     CONF_ICON,
     CONF_ID,
+    CONF_RESTORE,
     CONF_TIME_ID,
     DEVICE_CLASS_ENERGY,
     CONF_METHOD,
@@ -36,6 +37,7 @@ CONFIG_SCHEMA = (
             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),
+            cv.Optional(CONF_RESTORE, default=True): cv.boolean,
             cv.Optional(
                 CONF_MIN_SAVE_INTERVAL, default="0s"
             ): cv.positive_time_period_milliseconds,
@@ -70,5 +72,6 @@ async def to_code(config):
     cg.add(var.set_parent(sens))
     time_ = await cg.get_variable(config[CONF_TIME_ID])
     cg.add(var.set_time(time_))
+    cg.add(var.set_restore(config[CONF_RESTORE]))
     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 178dc7cbe0..3746301715 100644
--- a/esphome/components/total_daily_energy/total_daily_energy.cpp
+++ b/esphome/components/total_daily_energy/total_daily_energy.cpp
@@ -7,14 +7,14 @@ 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());
+  float initial_value = 0;
 
-  float recovered;
-  if (this->pref_.load(&recovered)) {
-    this->publish_state_and_save(recovered);
-  } else {
-    this->publish_state_and_save(0);
+  if (this->restore_) {
+    this->pref_ = global_preferences->make_preference(this->get_object_id_hash());
+    this->pref_.load(&initial_value);
   }
+  this->publish_state_and_save(initial_value);
+
   this->last_update_ = millis();
   this->last_save_ = this->last_update_;
 
diff --git a/esphome/components/total_daily_energy/total_daily_energy.h b/esphome/components/total_daily_energy/total_daily_energy.h
index fedceafbd3..498f65891e 100644
--- a/esphome/components/total_daily_energy/total_daily_energy.h
+++ b/esphome/components/total_daily_energy/total_daily_energy.h
@@ -17,6 +17,7 @@ enum TotalDailyEnergyMethod {
 
 class TotalDailyEnergy : public sensor::Sensor, public Component {
  public:
+  void set_restore(bool restore) { restore_ = restore; }
   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; }
@@ -41,6 +42,7 @@ class TotalDailyEnergy : public sensor::Sensor, public Component {
   uint32_t last_update_{0};
   uint32_t last_save_{0};
   uint32_t min_save_interval_{0};
+  bool restore_;
   float total_energy_{0.0f};
   float last_power_state_{0.0f};
 };

From 17a37b1de929ccdd9485269a934da3801380da8c Mon Sep 17 00:00:00 2001
From: Martin <25747549+martgras@users.noreply.github.com>
Date: Fri, 26 Nov 2021 00:48:52 +0100
Subject: [PATCH 385/549] Modbus_controller: Add custom command. (#2680)

---
 .../components/modbus_controller/__init__.py  | 111 ++++++++++++++++--
 .../binary_sensor/__init__.py                 |  49 +++-----
 esphome/components/modbus_controller/const.py |   1 +
 .../modbus_controller/modbus_controller.cpp   |  59 +++++++---
 .../modbus_controller/modbus_controller.h     |  33 ++++--
 .../modbus_controller/number/__init__.py      |  64 ++++------
 .../modbus_controller/output/__init__.py      |  33 ++----
 .../modbus_controller/sensor/__init__.py      |  55 +++------
 .../modbus_controller/sensor/modbus_sensor.h  |   1 +
 .../modbus_controller/switch/__init__.py      |  59 +++++-----
 .../switch/modbus_switch.cpp                  |  56 ++++++---
 .../modbus_controller/switch/modbus_switch.h  |   5 +
 .../modbus_controller/text_sensor/__init__.py |  50 +++-----
 13 files changed, 315 insertions(+), 261 deletions(-)

diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py
index 8499cec561..825b91280e 100644
--- a/esphome/components/modbus_controller/__init__.py
+++ b/esphome/components/modbus_controller/__init__.py
@@ -1,10 +1,20 @@
+import binascii
 import esphome.codegen as cg
 import esphome.config_validation as cv
 from esphome.components import modbus
-from esphome.const import CONF_ID, CONF_ADDRESS
+from esphome.const import CONF_ADDRESS, CONF_ID, CONF_NAME, CONF_LAMBDA, CONF_OFFSET
 from esphome.cpp_helpers import logging
 from .const import (
+    CONF_BITMASK,
+    CONF_BYTE_OFFSET,
     CONF_COMMAND_THROTTLE,
+    CONF_CUSTOM_COMMAND,
+    CONF_FORCE_NEW_RANGE,
+    CONF_MODBUS_CONTROLLER_ID,
+    CONF_REGISTER_COUNT,
+    CONF_REGISTER_TYPE,
+    CONF_SKIP_UPDATES,
+    CONF_VALUE_TYPE,
 )
 
 CODEOWNERS = ["@martgras"]
@@ -37,6 +47,7 @@ MODBUS_FUNCTION_CODE = {
 ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType")
 ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType")
 MODBUS_REGISTER_TYPE = {
+    "custom": ModbusRegisterType.CUSTOM,
     "coil": ModbusRegisterType.COIL,
     "discrete_input": ModbusRegisterType.DISCRETE_INPUT,
     "holding": ModbusRegisterType.HOLDING,
@@ -95,6 +106,96 @@ CONFIG_SCHEMA = cv.All(
 )
 
 
+ModbusItemBaseSchema = cv.Schema(
+    {
+        cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
+        cv.Optional(CONF_ADDRESS): cv.positive_int,
+        cv.Optional(CONF_CUSTOM_COMMAND): cv.ensure_list(cv.hex_uint8_t),
+        cv.Exclusive(
+            CONF_OFFSET,
+            "offset",
+            f"{CONF_OFFSET} and {CONF_BYTE_OFFSET} can't be used together",
+        ): cv.positive_int,
+        cv.Exclusive(
+            CONF_BYTE_OFFSET,
+            "offset",
+            f"{CONF_OFFSET} and {CONF_BYTE_OFFSET} can't be used together",
+        ): cv.positive_int,
+        cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t,
+        cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
+        cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
+        cv.Optional(CONF_LAMBDA): cv.returning_lambda,
+    },
+)
+
+
+def validate_modbus_register(config):
+    if CONF_CUSTOM_COMMAND not in config and CONF_ADDRESS not in config:
+        raise cv.Invalid(
+            f" {CONF_ADDRESS} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used"
+        )
+    if CONF_CUSTOM_COMMAND in config and CONF_REGISTER_TYPE in config:
+        raise cv.Invalid(
+            f"can't use '{CONF_REGISTER_TYPE}:' together with '{CONF_CUSTOM_COMMAND}:'",
+        )
+
+    if CONF_CUSTOM_COMMAND not in config and CONF_REGISTER_TYPE not in config:
+        raise cv.Invalid(
+            f" {CONF_REGISTER_TYPE} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used"
+        )
+    return config
+
+
+def modbus_calc_properties(config):
+    byte_offset = 0
+    reg_count = 0
+    if CONF_OFFSET in config:
+        byte_offset = config[CONF_OFFSET]
+    # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
+    if CONF_BYTE_OFFSET in config:
+        byte_offset = config[CONF_BYTE_OFFSET]
+    if CONF_REGISTER_COUNT in config:
+        reg_count = config[CONF_REGISTER_COUNT]
+    if CONF_VALUE_TYPE in config:
+        value_type = config[CONF_VALUE_TYPE]
+        if reg_count == 0:
+            reg_count = TYPE_REGISTER_MAP[value_type]
+    if CONF_CUSTOM_COMMAND in config:
+        if CONF_ADDRESS not in config:
+            # generate a unique modbus address using the hash of the name
+            # CONF_NAME set even if only CONF_ID is used.
+            # a modbus register address is required to add the item to sensormap
+            value = config[CONF_NAME]
+            if isinstance(value, str):
+                value = value.encode()
+            config[CONF_ADDRESS] = binascii.crc_hqx(value, 0)
+        config[CONF_REGISTER_TYPE] = ModbusRegisterType.CUSTOM
+        config[CONF_FORCE_NEW_RANGE] = True
+    return byte_offset, reg_count
+
+
+async def add_modbus_base_properties(
+    var, config, sensor_type, lamdba_param_type=cg.float_, lamdba_return_type=float
+):
+    if CONF_CUSTOM_COMMAND in config:
+        cg.add(var.set_custom_data(config[CONF_CUSTOM_COMMAND]))
+
+    if CONF_LAMBDA in config:
+        template_ = await cg.process_lambda(
+            config[CONF_LAMBDA],
+            [
+                (sensor_type.operator("ptr"), "item"),
+                (lamdba_param_type, "x"),
+                (
+                    cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
+                    "data",
+                ),
+            ],
+            return_type=cg.optional.template(lamdba_return_type),
+        )
+        cg.add(var.set_template(template_))
+
+
 async def to_code(config):
     var = cg.new_Pvariable(config[CONF_ID], config[CONF_COMMAND_THROTTLE])
     cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE]))
@@ -119,11 +220,3 @@ def function_code_to_register(function_code):
         "write_multiple_registers": ModbusRegisterType.HOLDING,
     }
     return FUNCTION_CODE_TYPE_MAP[function_code]
-
-
-def find_by_value(dict, find_value):
-    for (key, value) in MODBUS_REGISTER_TYPE.items():
-        print(find_value, value)
-        if find_value == value:
-            return key
-    return "not found"
diff --git a/esphome/components/modbus_controller/binary_sensor/__init__.py b/esphome/components/modbus_controller/binary_sensor/__init__.py
index d46ff71f2d..99d56fed67 100644
--- a/esphome/components/modbus_controller/binary_sensor/__init__.py
+++ b/esphome/components/modbus_controller/binary_sensor/__init__.py
@@ -2,16 +2,18 @@ from esphome.components import binary_sensor
 import esphome.config_validation as cv
 import esphome.codegen as cg
 
-from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OFFSET
+from esphome.const import CONF_ADDRESS, CONF_ID
 from .. import (
-    SensorItem,
+    add_modbus_base_properties,
     modbus_controller_ns,
-    ModbusController,
+    modbus_calc_properties,
+    validate_modbus_register,
+    ModbusItemBaseSchema,
+    SensorItem,
     MODBUS_REGISTER_TYPE,
 )
 from ..const import (
     CONF_BITMASK,
-    CONF_BYTE_OFFSET,
     CONF_FORCE_NEW_RANGE,
     CONF_MODBUS_CONTROLLER_ID,
     CONF_REGISTER_TYPE,
@@ -27,30 +29,20 @@ ModbusBinarySensor = modbus_controller_ns.class_(
 )
 
 CONFIG_SCHEMA = cv.All(
-    binary_sensor.BINARY_SENSOR_SCHEMA.extend(
+    binary_sensor.BINARY_SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA)
+    .extend(ModbusItemBaseSchema)
+    .extend(
         {
             cv.GenerateID(): cv.declare_id(ModbusBinarySensor),
-            cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
-            cv.Required(CONF_ADDRESS): cv.positive_int,
-            cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
-            cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
-            cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
-            cv.Optional(CONF_BITMASK, default=0x1): cv.hex_uint32_t,
-            cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
-            cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
-            cv.Optional(CONF_LAMBDA): cv.returning_lambda,
+            cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
         }
-    ).extend(cv.COMPONENT_SCHEMA),
+    ),
+    validate_modbus_register,
 )
 
 
 async def to_code(config):
-    byte_offset = 0
-    if CONF_OFFSET in config:
-        byte_offset = config[CONF_OFFSET]
-    # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
-    if CONF_BYTE_OFFSET in config:
-        byte_offset = config[CONF_BYTE_OFFSET]
+    byte_offset, _ = modbus_calc_properties(config)
     var = cg.new_Pvariable(
         config[CONF_ID],
         config[CONF_REGISTER_TYPE],
@@ -65,17 +57,4 @@ async def to_code(config):
 
     paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
     cg.add(paren.add_sensor_item(var))
-    if CONF_LAMBDA in config:
-        template_ = await cg.process_lambda(
-            config[CONF_LAMBDA],
-            [
-                (ModbusBinarySensor.operator("ptr"), "item"),
-                (cg.float_, "x"),
-                (
-                    cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
-                    "data",
-                ),
-            ],
-            return_type=cg.optional.template(bool),
-        )
-        cg.add(var.set_template(template_))
+    await add_modbus_base_properties(var, config, ModbusBinarySensor, cg.float_, bool)
diff --git a/esphome/components/modbus_controller/const.py b/esphome/components/modbus_controller/const.py
index 3cd114e673..8d1676dd38 100644
--- a/esphome/components/modbus_controller/const.py
+++ b/esphome/components/modbus_controller/const.py
@@ -1,6 +1,7 @@
 CONF_BITMASK = "bitmask"
 CONF_BYTE_OFFSET = "byte_offset"
 CONF_COMMAND_THROTTLE = "command_throttle"
+CONF_CUSTOM_COMMAND = "custom_command"
 CONF_FORCE_NEW_RANGE = "force_new_range"
 CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id"
 CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode"
diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp
index 70b5bf8eae..8b96c20691 100644
--- a/esphome/components/modbus_controller/modbus_controller.cpp
+++ b/esphome/components/modbus_controller/modbus_controller.cpp
@@ -28,7 +28,10 @@ bool ModbusController::send_next_command_() {
              command->register_address, command->register_count);
     command->send();
     this->last_command_timestamp_ = millis();
-    if (!command->on_data_func) {  // No handler remove from queue directly after sending
+    // remove from queue if no handler is defined or command was sent too often
+    if (!command->on_data_func || command->send_countdown < 1) {
+      ESP_LOGD(TAG, "Modbus command to device=%d register=0x%02X countdown=%d removed from queue after send",
+               this->address_, command->register_address, command->send_countdown);
       command_queue_.pop_front();
     }
   }
@@ -69,24 +72,30 @@ void ModbusController::on_modbus_error(uint8_t function_code, uint8_t exception_
   }
 }
 
-void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address,
-                                        const std::vector &data) {
-  ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address);
-
+std::map::iterator ModbusController::find_register_(ModbusRegisterType register_type,
+                                                                            uint16_t start_address) {
   auto vec_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) {
     return (r.start_address == start_address && r.register_type == register_type);
   });
 
   if (vec_it == register_ranges_.end()) {
-    ESP_LOGE(TAG, "Handle incoming data : No matching range for sensor found - start_address :  0x%X", start_address);
-    return;
-  }
-  auto map_it = sensormap_.find(vec_it->first_sensorkey);
-  if (map_it == sensormap_.end()) {
-    ESP_LOGE(TAG, "Handle incoming data : No sensor found in at start_address :  0x%X (0x%llX)", start_address,
-             vec_it->first_sensorkey);
-    return;
+    ESP_LOGE(TAG, "No matching range for sensor found - start_address :  0x%X", start_address);
+  } else {
+    auto map_it = sensormap_.find(vec_it->first_sensorkey);
+    if (map_it == sensormap_.end()) {
+      ESP_LOGE(TAG, "No sensor found in at start_address :  0x%X (0x%llX)", start_address, vec_it->first_sensorkey);
+    } else {
+      return sensormap_.find(vec_it->first_sensorkey);
+    }
   }
+  // not found
+  return std::end(sensormap_);
+}
+void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address,
+                                        const std::vector &data) {
+  ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address);
+
+  auto map_it = find_register_(register_type, start_address);
   // loop through all sensors with the same start address
   while (map_it != sensormap_.end() && map_it->second->start_address == start_address) {
     if (map_it->second->register_type == register_type) {
@@ -116,9 +125,23 @@ void ModbusController::update_range_(RegisterRange &r) {
   ESP_LOGV(TAG, "Range : %X Size: %x (%d) skip: %d", r.start_address, r.register_count, (int) r.register_type,
            r.skip_updates_counter);
   if (r.skip_updates_counter == 0) {
-    ModbusCommandItem command_item =
-        ModbusCommandItem::create_read_command(this, r.register_type, r.start_address, r.register_count);
-    queue_command(command_item);
+    // if a custom command is used the user supplied custom_data is only available in the SensorItem.
+    if (r.register_type == ModbusRegisterType::CUSTOM) {
+      auto it = this->find_register_(r.register_type, r.start_address);
+      if (it != sensormap_.end()) {
+        auto command_item = ModbusCommandItem::create_custom_command(
+            this, it->second->custom_data,
+            [this](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) {
+              this->on_register_data(ModbusRegisterType::CUSTOM, start_address, data);
+            });
+        command_item.register_address = it->second->start_address;
+        command_item.register_count = it->second->register_count;
+        command_item.function_code = ModbusFunctionCode::CUSTOM;
+        queue_command(command_item);
+      }
+    } else {
+      queue_command(ModbusCommandItem::create_read_command(this, r.register_type, r.start_address, r.register_count));
+    }
     r.skip_updates_counter = r.skip_updates;  // reset counter to config value
   } else {
     r.skip_updates_counter--;
@@ -422,6 +445,7 @@ bool ModbusCommandItem::send() {
     modbusdevice->send_raw(this->payload);
   }
   ESP_LOGV(TAG, "Command sent %d 0x%X %d", uint8_t(this->function_code), this->register_address, this->register_count);
+  send_countdown--;
   return true;
 }
 
@@ -549,6 +573,9 @@ float payload_to_float(const std::vector &data, SensorValueType sensor_
       ESP_LOGD(TAG, "FP32_R = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value);
       result = raw_to_float.float_value;
     } break;
+    case SensorValueType::RAW:
+      result = NAN;
+      break;
     default:
       break;
   }
diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h
index 222ebbd020..39c0d8026f 100644
--- a/esphome/components/modbus_controller/modbus_controller.h
+++ b/esphome/components/modbus_controller/modbus_controller.h
@@ -247,18 +247,11 @@ float payload_to_float(const std::vector &data, SensorValueType sensor_
 
 class ModbusController;
 
-struct SensorItem {
-  ModbusRegisterType register_type;
-  SensorValueType sensor_value_type;
-  uint16_t start_address;
-  uint32_t bitmask;
-  uint8_t offset;
-  uint8_t register_count;
-  uint8_t skip_updates;
-  bool force_new_range{false};
-
+class SensorItem {
+ public:
   virtual void parse_and_publish(const std::vector &data) = 0;
 
+  void set_custom_data(const std::vector &data) { custom_data = data; }
   uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); }
   size_t virtual get_register_size() const {
     if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT)
@@ -266,10 +259,22 @@ struct SensorItem {
     else
       return register_count * 2;
   }
+
+  ModbusRegisterType register_type;
+  SensorValueType sensor_value_type;
+  uint16_t start_address;
+  uint32_t bitmask;
+  uint8_t offset;
+  uint8_t register_count;
+  uint8_t skip_updates;
+  std::vector custom_data{};
+  bool force_new_range{false};
 };
 
-struct ModbusCommandItem {
+class ModbusCommandItem {
+ public:
   static const size_t MAX_PAYLOAD_BYTES = 240;
+  static const uint8_t MAX_SEND_REPEATS = 5;
   ModbusController *modbusdevice;
   uint16_t register_address;
   uint16_t register_count;
@@ -279,7 +284,9 @@ struct ModbusCommandItem {
       on_data_func;
   std::vector payload = {};
   bool send();
-
+  // wrong commands (esp. custom commands) can block the send queue
+  // limit the number of repeats
+  uint8_t send_countdown{MAX_SEND_REPEATS};
   /// factory methods
   /** Create modbus read command
    *  Function code 02-04
@@ -392,6 +399,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
  protected:
   /// parse sensormap_ and create range of sequential addresses
   size_t create_register_ranges_();
+  // find register in sensormap. Returns iterator with all registers having the same start address
+  std::map::iterator find_register_(ModbusRegisterType register_type, uint16_t start_address);
   /// submit the read command for the address range to the send queue
   void update_range_(RegisterRange &r);
   /// parse incoming modbus data
diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py
index 4de0ffbcea..3c5db9b9c8 100644
--- a/esphome/components/modbus_controller/number/__init__.py
+++ b/esphome/components/modbus_controller/number/__init__.py
@@ -4,29 +4,26 @@ from esphome.components import number
 from esphome.const import (
     CONF_ADDRESS,
     CONF_ID,
-    CONF_LAMBDA,
     CONF_MAX_VALUE,
     CONF_MIN_VALUE,
     CONF_MULTIPLY,
-    CONF_OFFSET,
     CONF_STEP,
 )
 
 from .. import (
+    add_modbus_base_properties,
     modbus_controller_ns,
-    ModbusController,
-    SENSOR_VALUE_TYPE,
+    modbus_calc_properties,
+    ModbusItemBaseSchema,
     SensorItem,
-    TYPE_REGISTER_MAP,
+    SENSOR_VALUE_TYPE,
 )
 
-
 from ..const import (
     CONF_BITMASK,
-    CONF_BYTE_OFFSET,
+    CONF_CUSTOM_COMMAND,
     CONF_FORCE_NEW_RANGE,
     CONF_MODBUS_CONTROLLER_ID,
-    CONF_REGISTER_COUNT,
     CONF_SKIP_UPDATES,
     CONF_VALUE_TYPE,
     CONF_WRITE_LAMBDA,
@@ -51,22 +48,21 @@ def validate_min_max(config):
     return config
 
 
+def validate_modbus_number(config):
+    if CONF_CUSTOM_COMMAND not in config and CONF_ADDRESS not in config:
+        raise cv.Invalid(
+            f" {CONF_ADDRESS} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used"
+        )
+    return config
+
+
 CONFIG_SCHEMA = cv.All(
-    number.NUMBER_SCHEMA.extend(
+    number.NUMBER_SCHEMA.extend(ModbusItemBaseSchema)
+    .extend(
         {
             cv.GenerateID(): cv.declare_id(ModbusNumber),
-            cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
-            cv.Required(CONF_ADDRESS): cv.positive_int,
-            cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
-            cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
-            cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t,
             cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
-            cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
-            cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
-            cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
-            cv.Optional(CONF_LAMBDA): cv.returning_lambda,
             cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
-            cv.GenerateID(): cv.declare_id(ModbusNumber),
             # 24 bits are the maximum value for fp32 before precison is lost
             # 0x00FFFFFF = 16777215
             cv.Optional(CONF_MAX_VALUE, default=16777215.0): cv.float_,
@@ -74,22 +70,15 @@ CONFIG_SCHEMA = cv.All(
             cv.Optional(CONF_STEP, default=1): cv.positive_float,
             cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_,
         }
-    ).extend(cv.polling_component_schema("60s")),
+    )
+    .extend(cv.polling_component_schema("60s")),
     validate_min_max,
+    validate_modbus_number,
 )
 
 
 async def to_code(config):
-    byte_offset = 0
-    if CONF_OFFSET in config:
-        byte_offset = config[CONF_OFFSET]
-    # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
-    if CONF_BYTE_OFFSET in config:
-        byte_offset = config[CONF_BYTE_OFFSET]
-    value_type = config[CONF_VALUE_TYPE]
-    reg_count = config[CONF_REGISTER_COUNT]
-    if reg_count == 0:
-        reg_count = TYPE_REGISTER_MAP[value_type]
+    byte_offset, reg_count = modbus_calc_properties(config)
     var = cg.new_Pvariable(
         config[CONF_ID],
         config[CONF_ADDRESS],
@@ -115,20 +104,7 @@ async def to_code(config):
 
     cg.add(var.set_parent(parent))
     cg.add(parent.add_sensor_item(var))
-    if CONF_LAMBDA in config:
-        template_ = await cg.process_lambda(
-            config[CONF_LAMBDA],
-            [
-                (ModbusNumber.operator("ptr"), "item"),
-                (cg.float_, "x"),
-                (
-                    cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
-                    "data",
-                ),
-            ],
-            return_type=cg.optional.template(float),
-        )
-        cg.add(var.set_template(template_))
+    await add_modbus_base_properties(var, config, ModbusNumber)
     if CONF_WRITE_LAMBDA in config:
         template_ = await cg.process_lambda(
             config[CONF_WRITE_LAMBDA],
diff --git a/esphome/components/modbus_controller/output/__init__.py b/esphome/components/modbus_controller/output/__init__.py
index 4aca4db64f..eacd96579f 100644
--- a/esphome/components/modbus_controller/output/__init__.py
+++ b/esphome/components/modbus_controller/output/__init__.py
@@ -6,24 +6,21 @@ from esphome.const import (
     CONF_ADDRESS,
     CONF_ID,
     CONF_MULTIPLY,
-    CONF_OFFSET,
 )
 
 from .. import (
-    SensorItem,
     modbus_controller_ns,
-    ModbusController,
-    TYPE_REGISTER_MAP,
+    modbus_calc_properties,
+    validate_modbus_register,
+    ModbusItemBaseSchema,
+    SensorItem,
 )
 
 from ..const import (
-    CONF_BYTE_OFFSET,
     CONF_MODBUS_CONTROLLER_ID,
-    CONF_REGISTER_COUNT,
     CONF_VALUE_TYPE,
     CONF_WRITE_LAMBDA,
 )
-from ..sensor import SENSOR_VALUE_TYPE
 
 DEPENDENCIES = ["modbus_controller"]
 CODEOWNERS = ["@martgras"]
@@ -34,38 +31,24 @@ ModbusOutput = modbus_controller_ns.class_(
 )
 
 CONFIG_SCHEMA = cv.All(
-    output.FLOAT_OUTPUT_SCHEMA.extend(
+    output.FLOAT_OUTPUT_SCHEMA.extend(ModbusItemBaseSchema).extend(
         {
-            cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
             cv.GenerateID(): cv.declare_id(ModbusOutput),
-            cv.Required(CONF_ADDRESS): cv.positive_int,
-            cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
-            cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
-            cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
-            cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
             cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
             cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_,
         }
     ),
+    validate_modbus_register,
 )
 
 
 async def to_code(config):
-    byte_offset = 0
-    if CONF_OFFSET in config:
-        byte_offset = config[CONF_OFFSET]
-    # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
-    if CONF_BYTE_OFFSET in config:
-        byte_offset = config[CONF_BYTE_OFFSET]
-    value_type = config[CONF_VALUE_TYPE]
-    reg_count = config[CONF_REGISTER_COUNT]
-    if reg_count == 0:
-        reg_count = TYPE_REGISTER_MAP[value_type]
+    byte_offset, reg_count = modbus_calc_properties(config)
     var = cg.new_Pvariable(
         config[CONF_ID],
         config[CONF_ADDRESS],
         byte_offset,
-        value_type,
+        config[CONF_VALUE_TYPE],
         reg_count,
     )
     await output.register_output(var, config)
diff --git a/esphome/components/modbus_controller/sensor/__init__.py b/esphome/components/modbus_controller/sensor/__init__.py
index 82acfe120b..da7b8928b4 100644
--- a/esphome/components/modbus_controller/sensor/__init__.py
+++ b/esphome/components/modbus_controller/sensor/__init__.py
@@ -2,18 +2,19 @@ from esphome.components import sensor
 import esphome.config_validation as cv
 import esphome.codegen as cg
 
-from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET
+from esphome.const import CONF_ID, CONF_ADDRESS
 from .. import (
-    SensorItem,
+    add_modbus_base_properties,
     modbus_controller_ns,
-    ModbusController,
+    modbus_calc_properties,
+    validate_modbus_register,
+    ModbusItemBaseSchema,
+    SensorItem,
     MODBUS_REGISTER_TYPE,
     SENSOR_VALUE_TYPE,
-    TYPE_REGISTER_MAP,
 )
 from ..const import (
     CONF_BITMASK,
-    CONF_BYTE_OFFSET,
     CONF_FORCE_NEW_RANGE,
     CONF_MODBUS_CONTROLLER_ID,
     CONF_REGISTER_COUNT,
@@ -31,43 +32,30 @@ ModbusSensor = modbus_controller_ns.class_(
 )
 
 CONFIG_SCHEMA = cv.All(
-    sensor.SENSOR_SCHEMA.extend(
+    sensor.SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA)
+    .extend(ModbusItemBaseSchema)
+    .extend(
         {
             cv.GenerateID(): cv.declare_id(ModbusSensor),
-            cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
-            cv.Required(CONF_ADDRESS): cv.positive_int,
-            cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
-            cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
-            cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
-            cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t,
+            cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
             cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
             cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
-            cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
-            cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
-            cv.Optional(CONF_LAMBDA): cv.returning_lambda,
         }
-    ).extend(cv.COMPONENT_SCHEMA),
+    ),
+    validate_modbus_register,
 )
 
 
 async def to_code(config):
-    byte_offset = 0
-    if CONF_OFFSET in config:
-        byte_offset = config[CONF_OFFSET]
-    # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
-    if CONF_BYTE_OFFSET in config:
-        byte_offset = config[CONF_BYTE_OFFSET]
+    byte_offset, reg_count = modbus_calc_properties(config)
     value_type = config[CONF_VALUE_TYPE]
-    reg_count = config[CONF_REGISTER_COUNT]
-    if reg_count == 0:
-        reg_count = TYPE_REGISTER_MAP[value_type]
     var = cg.new_Pvariable(
         config[CONF_ID],
         config[CONF_REGISTER_TYPE],
         config[CONF_ADDRESS],
         byte_offset,
         config[CONF_BITMASK],
-        config[CONF_VALUE_TYPE],
+        value_type,
         reg_count,
         config[CONF_SKIP_UPDATES],
         config[CONF_FORCE_NEW_RANGE],
@@ -77,17 +65,4 @@ async def to_code(config):
 
     paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
     cg.add(paren.add_sensor_item(var))
-    if CONF_LAMBDA in config:
-        template_ = await cg.process_lambda(
-            config[CONF_LAMBDA],
-            [
-                (ModbusSensor.operator("ptr"), "item"),
-                (cg.float_, "x"),
-                (
-                    cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
-                    "data",
-                ),
-            ],
-            return_type=cg.optional.template(float),
-        )
-        cg.add(var.set_template(template_))
+    await add_modbus_base_properties(var, config, ModbusSensor)
diff --git a/esphome/components/modbus_controller/sensor/modbus_sensor.h b/esphome/components/modbus_controller/sensor/modbus_sensor.h
index 4f48c2a4dd..37ea9d0dd0 100644
--- a/esphome/components/modbus_controller/sensor/modbus_sensor.h
+++ b/esphome/components/modbus_controller/sensor/modbus_sensor.h
@@ -25,6 +25,7 @@ class ModbusSensor : public Component, public sensor::Sensor, public SensorItem
   void parse_and_publish(const std::vector &data) override;
   void dump_config() override;
   using transform_func_t = std::function(ModbusSensor *, float, const std::vector &)>;
+
   void set_template(transform_func_t &&f) { this->transform_func_ = f; }
 
  protected:
diff --git a/esphome/components/modbus_controller/switch/__init__.py b/esphome/components/modbus_controller/switch/__init__.py
index e03b0d37be..df11b268ac 100644
--- a/esphome/components/modbus_controller/switch/__init__.py
+++ b/esphome/components/modbus_controller/switch/__init__.py
@@ -3,21 +3,25 @@ import esphome.config_validation as cv
 import esphome.codegen as cg
 
 
-from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET
+from esphome.const import CONF_ID, CONF_ADDRESS
 from .. import (
-    MODBUS_REGISTER_TYPE,
-    SensorItem,
+    add_modbus_base_properties,
     modbus_controller_ns,
-    ModbusController,
+    modbus_calc_properties,
+    validate_modbus_register,
+    ModbusItemBaseSchema,
+    SensorItem,
+    MODBUS_REGISTER_TYPE,
 )
 from ..const import (
     CONF_BITMASK,
-    CONF_BYTE_OFFSET,
     CONF_FORCE_NEW_RANGE,
     CONF_MODBUS_CONTROLLER_ID,
     CONF_REGISTER_TYPE,
+    CONF_WRITE_LAMBDA,
 )
 
+CONF_USE_WRITE_MULTIPLE = "use_write_multiple"
 DEPENDENCIES = ["modbus_controller"]
 CODEOWNERS = ["@martgras"]
 
@@ -26,31 +30,23 @@ ModbusSwitch = modbus_controller_ns.class_(
     "ModbusSwitch", cg.Component, switch.Switch, SensorItem
 )
 
-
 CONFIG_SCHEMA = cv.All(
-    switch.SWITCH_SCHEMA.extend(
+    switch.SWITCH_SCHEMA.extend(cv.COMPONENT_SCHEMA)
+    .extend(ModbusItemBaseSchema)
+    .extend(
         {
             cv.GenerateID(): cv.declare_id(ModbusSwitch),
-            cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
-            cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
-            cv.Required(CONF_ADDRESS): cv.positive_int,
-            cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
-            cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
-            cv.Optional(CONF_BITMASK, default=0x1): cv.hex_uint32_t,
-            cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
-            cv.Optional(CONF_LAMBDA): cv.returning_lambda,
+            cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
+            cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
+            cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
         }
-    ).extend(cv.COMPONENT_SCHEMA),
+    ),
+    validate_modbus_register,
 )
 
 
 async def to_code(config):
-    byte_offset = 0
-    if CONF_OFFSET in config:
-        byte_offset = config[CONF_OFFSET]
-    # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
-    if CONF_BYTE_OFFSET in config:
-        byte_offset = config[CONF_BYTE_OFFSET]
+    byte_offset, _ = modbus_calc_properties(config)
     var = cg.new_Pvariable(
         config[CONF_ID],
         config[CONF_REGISTER_TYPE],
@@ -63,19 +59,18 @@ async def to_code(config):
     await switch.register_switch(var, config)
 
     paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
-    cg.add(paren.add_sensor_item(var))
     cg.add(var.set_parent(paren))
-    if CONF_LAMBDA in config:
-        publish_template_ = await cg.process_lambda(
-            config[CONF_LAMBDA],
+    cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE]))
+    cg.add(paren.add_sensor_item(var))
+    if CONF_WRITE_LAMBDA in config:
+        template_ = await cg.process_lambda(
+            config[CONF_WRITE_LAMBDA],
             [
                 (ModbusSwitch.operator("ptr"), "item"),
-                (bool, "x"),
-                (
-                    cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
-                    "data",
-                ),
+                (cg.bool_, "x"),
+                (cg.std_vector.template(cg.uint8).operator("ref"), "payload"),
             ],
             return_type=cg.optional.template(bool),
         )
-        cg.add(var.set_template(publish_template_))
+        cg.add(var.set_write_template(template_))
+    await add_modbus_base_properties(var, config, ModbusSwitch, bool, bool)
diff --git a/esphome/components/modbus_controller/switch/modbus_switch.cpp b/esphome/components/modbus_controller/switch/modbus_switch.cpp
index ce9557e6c4..c7c3c419d4 100644
--- a/esphome/components/modbus_controller/switch/modbus_switch.cpp
+++ b/esphome/components/modbus_controller/switch/modbus_switch.cpp
@@ -45,22 +45,50 @@ void ModbusSwitch::parse_and_publish(const std::vector &data) {
 void ModbusSwitch::write_state(bool state) {
   // This will be called every time the user requests a state change.
   ModbusCommandItem cmd;
-  ESP_LOGV(TAG, "write_state '%s': new value = %s type = %d address = %X offset = %x", this->get_name().c_str(),
-           ONOFF(state), (int) this->register_type, this->start_address, this->offset);
-  switch (this->register_type) {
-    case ModbusRegisterType::COIL:
+  std::vector data;
+  // Is there are lambda configured?
+  if (this->write_transform_func_.has_value()) {
+    // data is passed by reference
+    // the lambda can fill the empty vector directly
+    // in that case the return value is ignored
+    auto val = (*this->write_transform_func_)(this, state, data);
+    if (val.has_value()) {
+      ESP_LOGV(TAG, "Value overwritten by lambda");
+      state = val.value();
+    } else {
+      ESP_LOGV(TAG, "Communication handled by lambda - exiting control");
+      return;
+    }
+  }
+  if (!data.empty()) {
+    ESP_LOGV(TAG, "Modbus Switch write raw: %s", hexencode(data).c_str());
+    cmd = ModbusCommandItem::create_custom_command(
+        this->parent_, data,
+        [this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) {
+          this->parent_->on_write_register_response(cmd.register_type, this->start_address, data);
+        });
+  } else {
+    ESP_LOGV(TAG, "write_state '%s': new value = %s type = %d address = %X offset = %x", this->get_name().c_str(),
+             ONOFF(state), (int) this->register_type, this->start_address, this->offset);
+    if (this->register_type == ModbusRegisterType::COIL) {
       // offset for coil and discrete inputs is the coil/register number not bytes
-      cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state);
-      break;
-    case ModbusRegisterType::DISCRETE_INPUT:
-      cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, state);
-      break;
-
-    default:
+      if (this->use_write_multiple_) {
+        std::vector states{state};
+        cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states);
+      } else {
+        cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state);
+      }
+    } else {
       // since offset is in bytes and a register is 16 bits we get the start by adding offset/2
-      cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2,
-                                                           state ? 0xFFFF & this->bitmask : 0);
-      break;
+      if (this->use_write_multiple_) {
+        std::vector bool_states(1, state ? (0xFFFF & this->bitmask) : 0);
+        cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, 1,
+                                                               bool_states);
+      } else {
+        cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2,
+                                                             state ? 0xFFFF & this->bitmask : 0u);
+      }
+    }
   }
   this->parent_->queue_command(cmd);
   publish_state(state);
diff --git a/esphome/components/modbus_controller/switch/modbus_switch.h b/esphome/components/modbus_controller/switch/modbus_switch.h
index a38668fabb..5ac2af01a1 100644
--- a/esphome/components/modbus_controller/switch/modbus_switch.h
+++ b/esphome/components/modbus_controller/switch/modbus_switch.h
@@ -33,11 +33,16 @@ class ModbusSwitch : public Component, public switch_::Switch, public SensorItem
   void set_parent(ModbusController *parent) { this->parent_ = parent; }
 
   using transform_func_t = std::function(ModbusSwitch *, bool, const std::vector &)>;
+  using write_transform_func_t = std::function(ModbusSwitch *, bool, std::vector &)>;
   void set_template(transform_func_t &&f) { this->publish_transform_func_ = f; }
+  void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
+  void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
 
  protected:
   ModbusController *parent_;
+  bool use_write_multiple_;
   optional publish_transform_func_{nullopt};
+  optional write_transform_func_{nullopt};
 };
 
 }  // namespace modbus_controller
diff --git a/esphome/components/modbus_controller/text_sensor/__init__.py b/esphome/components/modbus_controller/text_sensor/__init__.py
index 2c02c86795..5cc85af5bc 100644
--- a/esphome/components/modbus_controller/text_sensor/__init__.py
+++ b/esphome/components/modbus_controller/text_sensor/__init__.py
@@ -3,15 +3,17 @@ import esphome.config_validation as cv
 import esphome.codegen as cg
 
 
-from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET
+from esphome.const import CONF_ADDRESS, CONF_ID
 from .. import (
-    SensorItem,
+    add_modbus_base_properties,
     modbus_controller_ns,
-    ModbusController,
+    modbus_calc_properties,
+    validate_modbus_register,
+    ModbusItemBaseSchema,
+    SensorItem,
     MODBUS_REGISTER_TYPE,
 )
 from ..const import (
-    CONF_BYTE_OFFSET,
     CONF_FORCE_NEW_RANGE,
     CONF_MODBUS_CONTROLLER_ID,
     CONF_REGISTER_COUNT,
@@ -38,32 +40,23 @@ RAW_ENCODING = {
 }
 
 CONFIG_SCHEMA = cv.All(
-    text_sensor.TEXT_SENSOR_SCHEMA.extend(
+    text_sensor.TEXT_SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA)
+    .extend(ModbusItemBaseSchema)
+    .extend(
         {
             cv.GenerateID(): cv.declare_id(ModbusTextSensor),
-            cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
-            cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
-            cv.Required(CONF_ADDRESS): cv.positive_int,
-            cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
-            cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
+            cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
             cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
             cv.Optional(CONF_RESPONSE_SIZE, default=2): cv.positive_int,
             cv.Optional(CONF_RAW_ENCODE, default="NONE"): cv.enum(RAW_ENCODING),
-            cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
-            cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
-            cv.Optional(CONF_LAMBDA): cv.returning_lambda,
         }
-    ).extend(cv.COMPONENT_SCHEMA),
+    ),
+    validate_modbus_register,
 )
 
 
 async def to_code(config):
-    byte_offset = 0
-    if CONF_OFFSET in config:
-        byte_offset = config[CONF_OFFSET]
-    # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
-    if CONF_BYTE_OFFSET in config:
-        byte_offset = config[CONF_BYTE_OFFSET]
+    byte_offset, reg_count = modbus_calc_properties(config)
     response_size = config[CONF_RESPONSE_SIZE]
     reg_count = config[CONF_REGISTER_COUNT]
     if reg_count == 0:
@@ -85,17 +78,6 @@ async def to_code(config):
 
     paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
     cg.add(paren.add_sensor_item(var))
-    if CONF_LAMBDA in config:
-        template_ = await cg.process_lambda(
-            config[CONF_LAMBDA],
-            [
-                (ModbusTextSensor.operator("ptr"), "item"),
-                (cg.std_string.operator("const").operator("ref"), "x"),
-                (
-                    cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
-                    "data",
-                ),
-            ],
-            return_type=cg.optional.template(cg.std_string),
-        )
-        cg.add(var.set_template(template_))
+    await add_modbus_base_properties(
+        var, config, ModbusTextSensor, cg.std_string, cg.std_string
+    )

From 5946c379256a3b652eec9334f1401d5ceb96d955 Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Fri, 26 Nov 2021 09:16:39 +0100
Subject: [PATCH 386/549] Fix usage of deprecated climate method in anova
 (#2801)

---
 esphome/components/anova/anova.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/esphome/components/anova/anova.h b/esphome/components/anova/anova.h
index 2e6910f326..4f8f0d0ee2 100644
--- a/esphome/components/anova/anova.h
+++ b/esphome/components/anova/anova.h
@@ -30,7 +30,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode
   climate::ClimateTraits traits() override {
     auto traits = climate::ClimateTraits();
     traits.set_supports_current_temperature(true);
-    traits.set_supports_heat_mode(true);
+    traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::ClimateMode::CLIMATE_MODE_HEAT});
     traits.set_visual_min_temperature(25.0);
     traits.set_visual_max_temperature(100.0);
     traits.set_visual_temperature_step(0.1);

From 671d68bc2ceecb74756894cd634d1d3cf2653d6e Mon Sep 17 00:00:00 2001
From: Maurice Makaay 
Date: Fri, 26 Nov 2021 21:25:58 +0100
Subject: [PATCH 387/549] Add missing nvs_flash_init() to ESP32 preferences
 code (#2805)

Co-authored-by: Maurice Makaay 
---
 esphome/components/esp32/preferences.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp
index 96b7e7809e..8c2b67a942 100644
--- a/esphome/components/esp32/preferences.cpp
+++ b/esphome/components/esp32/preferences.cpp
@@ -76,6 +76,7 @@ class ESP32Preferences : public ESPPreferences {
   uint32_t current_offset = 0;
 
   void open() {
+    nvs_flash_init();
     esp_err_t err = nvs_open("esphome", NVS_READWRITE, &nvs_handle);
     if (err == 0)
       return;

From 7a564b222d8aa7a3ab1a29d10693eea0c85bf0b5 Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Sun, 28 Nov 2021 19:59:30 +0100
Subject: [PATCH 388/549] Make clang-tidy suggest stdint.h int types (#2820)

---
 .clang-tidy | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.clang-tidy b/.clang-tidy
index 79276f81c3..1c7e65b762 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -101,6 +101,8 @@ CheckOptions:
     value:           '10'
   - key:             google-readability-namespace-comments.SpacesBeforeComments
     value:           '2'
+  - key:             google-runtime-int.TypeSuffix
+    value:           '_t'
   - key:             modernize-loop-convert.MaxCopySize
     value:           '16'
   - key:             modernize-loop-convert.MinConfidence

From 10a2a7e0fc8e957010a32a3842b51547134300eb Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Sun, 28 Nov 2021 20:00:29 +0100
Subject: [PATCH 389/549] Fix parsing numbers in Anova (#2816)

---
 esphome/components/anova/anova_base.cpp | 6 +++---
 esphome/core/helpers.cpp                | 5 +++++
 esphome/core/helpers.h                  | 6 ++++++
 3 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp
index dcef75e483..cb877bef35 100644
--- a/esphome/components/anova/anova_base.cpp
+++ b/esphome/components/anova/anova_base.cpp
@@ -104,21 +104,21 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
       break;
     }
     case READ_TARGET_TEMPERATURE: {
-      this->target_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f);
+      this->target_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f);
       if (this->fahrenheit_)
         this->target_temp_ = ftoc(this->target_temp_);
       this->has_target_temp_ = true;
       break;
     }
     case SET_TARGET_TEMPERATURE: {
-      this->target_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f);
+      this->target_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f);
       if (this->fahrenheit_)
         this->target_temp_ = ftoc(this->target_temp_);
       this->has_target_temp_ = true;
       break;
     }
     case READ_CURRENT_TEMPERATURE: {
-      this->current_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f);
+      this->current_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f);
       if (this->fahrenheit_)
         this->current_temp_ = ftoc(this->current_temp_);
       this->has_current_temp_ = true;
diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp
index 37d9cdc24b..60849fcae7 100644
--- a/esphome/core/helpers.cpp
+++ b/esphome/core/helpers.cpp
@@ -448,6 +448,11 @@ IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); }
 std::string str_truncate(const std::string &str, size_t length) {
   return str.length() > length ? str.substr(0, length) : str;
 }
+std::string str_until(const char *str, char ch) {
+  char *pos = strchr(str, ch);
+  return pos == nullptr ? std::string(str) : std::string(str, pos - str);
+}
+std::string str_until(const std::string &str, char ch) { return str.substr(0, str.find(ch)); }
 std::string str_snake_case(const std::string &str) {
   std::string result;
   result.resize(str.length());
diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h
index 1faf1ac3aa..63aa4123ae 100644
--- a/esphome/core/helpers.h
+++ b/esphome/core/helpers.h
@@ -353,6 +353,12 @@ template::value, int> = 0> constexpr
 /// Truncate a string to a specific length.
 std::string str_truncate(const std::string &str, size_t length);
 
+/// Extract the part of the string until either the first occurence of the specified character, or the end (requires str
+/// to be null-terminated).
+std::string str_until(const char *str, char ch);
+/// Extract the part of the string until either the first occurence of the specified character, or the end.
+std::string str_until(const std::string &str, char ch);
+
 /// Convert the string to snake case (lowercase with underscores).
 std::string str_snake_case(const std::string &str);
 

From 2b504068569f599b0297536f7c7e287368c83f8d Mon Sep 17 00:00:00 2001
From: Oxan van Leeuwen 
Date: Sun, 28 Nov 2021 20:02:10 +0100
Subject: [PATCH 390/549] Fix parsing of multiple values in EZO sensor (#2814)

Co-authored-by: Lydia Sevelt 
---
 esphome/components/ezo/ezo.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp
index 426f2807c1..ca6f121dbb 100644
--- a/esphome/components/ezo/ezo.cpp
+++ b/esphome/components/ezo/ezo.cpp
@@ -74,6 +74,11 @@ void EZOSensor::loop() {
   if (buf[0] != 1)
     return;
 
+  // some sensors return multiple comma-separated values, terminate string after first one
+  for (int i = 1; i < sizeof(buf) - 1; i++)
+    if (buf[i] == ',')
+      buf[i] = '\0';
+
   float val = parse_number((char *) &buf[1], sizeof(buf) - 2).value_or(0);
   this->publish_state(val);
 }

From 7a5c3aa7edbac0e448977710ec958c5302eb840c Mon Sep 17 00:00:00 2001
From: Carlos Garcia Saura 
Date: Sun, 28 Nov 2021 20:06:53 +0100
Subject: [PATCH 391/549] Fix compilation error for WPA enterprise in ESP-IDF
 (#2815)

---
 esphome/components/wifi/wifi_component_esp_idf.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp
index 7f71b7078c..1d346c0a8e 100644
--- a/esphome/components/wifi/wifi_component_esp_idf.cpp
+++ b/esphome/components/wifi/wifi_component_esp_idf.cpp
@@ -375,8 +375,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
         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);
+    err = esp_wifi_sta_wpa2_ent_enable();
     if (err != ESP_OK) {
       ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_enable failed! %d", err);
     }

From 10f830c3efcddeb0a40348f100db1ef0344b4971 Mon Sep 17 00:00:00 2001
From: Dave T <17680170+davet2001@users.noreply.github.com>
Date: Sun, 28 Nov 2021 19:12:40 +0000
Subject: [PATCH 392/549] Correct bitmask for third color (blue) scaling.
 (#2817)

---
 esphome/components/display/display_color_utils.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/esphome/components/display/display_color_utils.h b/esphome/components/display/display_color_utils.h
index 8fc3b0adb9..202de912de 100644
--- a/esphome/components/display/display_color_utils.h
+++ b/esphome/components/display/display_color_utils.h
@@ -42,7 +42,7 @@ class ColorUtil {
                        ? esp_scale(((colorcode >> third_bits) & ((1 << second_bits) - 1)), ((1 << second_bits) - 1))
                        : esp_scale(((colorcode >> 8) & 0xFF), ((1 << second_bits) - 1));
 
-    third_color = (right_bit_aligned ? esp_scale(((colorcode >> 0) & 0xFF), ((1 << third_bits) - 1))
+    third_color = (right_bit_aligned ? esp_scale(((colorcode >> 0) & ((1 << third_bits) - 1)), ((1 << third_bits) - 1))
                                      : esp_scale(((colorcode >> 0) & 0xFF), (1 << third_bits) - 1));
 
     Color color_return;

From 7afcb0fb043945b1172cfdb45d33657d923100cf Mon Sep 17 00:00:00 2001
From: Conclusio 
Date: Sun, 28 Nov 2021 20:13:42 +0100
Subject: [PATCH 393/549] Add delay to improve stability (#2793)

---
 esphome/components/scd30/scd30.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp
index e6d6ec1c1a..272ee75e30 100644
--- a/esphome/components/scd30/scd30.cpp
+++ b/esphome/components/scd30/scd30.cpp
@@ -200,6 +200,7 @@ bool SCD30Component::is_data_ready_() {
   if (!this->write_command_(SCD30_CMD_GET_DATA_READY_STATUS)) {
     return false;
   }
+  delay(4);
   uint16_t is_data_ready;
   if (!this->read_data_(&is_data_ready, 1)) {
     return false;

From cae283dc8678789ae70b11fdab7ee9fca19964c8 Mon Sep 17 00:00:00 2001
From: anatoly-savchenkov
 <48646998+anatoly-savchenkov@users.noreply.github.com>
Date: Sun, 28 Nov 2021 22:31:15 +0300
Subject: [PATCH 394/549] Fixed data type inside fast_random_8() routine
 (#2818)

---
 esphome/core/helpers.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp
index 60849fcae7..6678eddbff 100644
--- a/esphome/core/helpers.cpp
+++ b/esphome/core/helpers.cpp
@@ -98,7 +98,7 @@ uint16_t fast_random_16() {
   return (rand32 & 0xFFFF) + (rand32 >> 16);
 }
 uint8_t fast_random_8() {
-  uint8_t rand32 = fast_random_32();
+  uint32_t rand32 = fast_random_32();
   return (rand32 & 0xFF) + ((rand32 >> 8) & 0xFF);
 }
 

From adf48246a9b73bd3abe78f9e41d00ea6f3f0b202 Mon Sep 17 00:00:00 2001
From: Maurice Makaay 
Date: Mon, 29 Nov 2021 16:40:53 +0100
Subject: [PATCH 395/549] Improve DSMR read timeout handling (#2699)

---
 esphome/components/dsmr/__init__.py       |  14 +-
 esphome/components/dsmr/dsmr.cpp          | 181 ++++++++++++----------
 esphome/components/dsmr/dsmr.h            |  30 ++--
 esphome/components/uart/uart_component.h  |   1 +
 esphome/components/uart/uart_debugger.cpp |   9 ++
 tests/test3.yaml                          |   1 +
 6 files changed, 136 insertions(+), 100 deletions(-)

diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py
index 06b022c513..7a7681082e 100644
--- a/esphome/components/dsmr/__init__.py
+++ b/esphome/components/dsmr/__init__.py
@@ -5,6 +5,7 @@ from esphome.components import uart
 from esphome.const import (
     CONF_ID,
     CONF_UART_ID,
+    CONF_RECEIVE_TIMEOUT,
 )
 
 CODEOWNERS = ["@glmnet", "@zuidwijk"]
@@ -52,7 +53,12 @@ CONFIG_SCHEMA = cv.All(
             cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
             cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_,
             cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema,
-            cv.Optional(CONF_REQUEST_INTERVAL): cv.positive_time_period_milliseconds,
+            cv.Optional(
+                CONF_REQUEST_INTERVAL, default="0ms"
+            ): cv.positive_time_period_milliseconds,
+            cv.Optional(
+                CONF_RECEIVE_TIMEOUT, default="200ms"
+            ): cv.positive_time_period_milliseconds,
         }
     ).extend(uart.UART_DEVICE_SCHEMA),
     cv.only_with_arduino,
@@ -70,10 +76,8 @@ async def to_code(config):
     if CONF_REQUEST_PIN in config:
         request_pin = await cg.gpio_pin_expression(config[CONF_REQUEST_PIN])
         cg.add(var.set_request_pin(request_pin))
-    if CONF_REQUEST_INTERVAL in config:
-        cg.add(
-            var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds)
-        )
+    cg.add(var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds))
+    cg.add(var.set_receive_timeout(config[CONF_RECEIVE_TIMEOUT].total_milliseconds))
 
     cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID])
 
diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp
index 03e418662a..7b339e5fe0 100644
--- a/esphome/components/dsmr/dsmr.cpp
+++ b/esphome/components/dsmr/dsmr.cpp
@@ -24,7 +24,7 @@ void Dsmr::loop() {
     if (this->decryption_key_.empty()) {
       this->receive_telegram_();
     } else {
-      this->receive_encrypted_();
+      this->receive_encrypted_telegram_();
     }
   }
 }
@@ -57,14 +57,42 @@ bool Dsmr::request_interval_reached_() {
   return millis() - this->last_request_time_ > this->request_interval_;
 }
 
+bool Dsmr::receive_timeout_reached_() { return millis() - this->last_read_time_ > this->receive_timeout_; }
+
 bool Dsmr::available_within_timeout_() {
-  uint8_t tries = READ_TIMEOUT_MS / 5;
-  while (tries--) {
-    delay(5);
-    if (this->available()) {
-      return true;
+  // Data are available for reading on the UART bus?
+  // Then we can start reading right away.
+  if (this->available()) {
+    this->last_read_time_ = millis();
+    return true;
+  }
+  // When we're not in the process of reading a telegram, then there is
+  // no need to actively wait for new data to come in.
+  if (!header_found_) {
+    return false;
+  }
+  // A telegram is being read. The smart meter might not deliver a telegram
+  // in one go, but instead send it in chunks with small pauses in between.
+  // When the UART RX buffer cannot hold a full telegram, then make sure
+  // that the UART read buffer does not overflow while other components
+  // perform their work in their loop. Do this by not returning control to
+  // the main loop, until the read timeout is reached.
+  if (this->parent_->get_rx_buffer_size() < this->max_telegram_len_) {
+    while (!this->receive_timeout_reached_()) {
+      delay(5);
+      if (this->available()) {
+        this->last_read_time_ = millis();
+        return true;
+      }
     }
   }
+  // No new data has come in during the read timeout? Then stop reading the
+  // telegram and start waiting for the next one to arrive.
+  if (this->receive_timeout_reached_()) {
+    ESP_LOGW(TAG, "Timeout while reading data for telegram");
+    this->reset_telegram_();
+  }
+
   return false;
 }
 
@@ -96,30 +124,31 @@ void Dsmr::stop_requesting_data_() {
   }
 }
 
-void Dsmr::receive_telegram_() {
-  while (true) {
-    if (!this->available()) {
-      if (!this->header_found_ || !this->available_within_timeout_()) {
-        return;
-      }
-    }
+void Dsmr::reset_telegram_() {
+  this->header_found_ = false;
+  this->footer_found_ = false;
+  this->bytes_read_ = 0;
+  this->crypt_bytes_read_ = 0;
+  this->crypt_telegram_len_ = 0;
+  this->last_read_time_ = 0;
+}
 
+void Dsmr::receive_telegram_() {
+  while (this->available_within_timeout_()) {
     const char c = this->read();
 
     // Find a new telegram header, i.e. forward slash.
     if (c == '/') {
       ESP_LOGV(TAG, "Header of telegram found");
+      this->reset_telegram_();
       this->header_found_ = true;
-      this->footer_found_ = false;
-      this->telegram_len_ = 0;
     }
     if (!this->header_found_)
       continue;
 
     // Check for buffer overflow.
-    if (this->telegram_len_ >= this->max_telegram_len_) {
-      this->header_found_ = false;
-      this->footer_found_ = false;
+    if (this->bytes_read_ >= this->max_telegram_len_) {
+      this->reset_telegram_();
       ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_);
       return;
     }
@@ -129,9 +158,9 @@ void Dsmr::receive_telegram_() {
     // proper parsing, remove these new line characters.
     if (c == '(') {
       while (true) {
-        auto previous_char = this->telegram_[this->telegram_len_ - 1];
+        auto previous_char = this->telegram_[this->bytes_read_ - 1];
         if (previous_char == '\n' || previous_char == '\r') {
-          this->telegram_len_--;
+          this->bytes_read_--;
         } else {
           break;
         }
@@ -139,8 +168,8 @@ void Dsmr::receive_telegram_() {
     }
 
     // Store the byte in the buffer.
-    this->telegram_[this->telegram_len_] = c;
-    this->telegram_len_++;
+    this->telegram_[this->bytes_read_] = c;
+    this->bytes_read_++;
 
     // Check for a footer, i.e. exlamation mark, followed by a hex checksum.
     if (c == '!') {
@@ -152,28 +181,14 @@ void Dsmr::receive_telegram_() {
     if (this->footer_found_ && c == '\n') {
       // Parse the telegram and publish sensor values.
       this->parse_telegram();
-
-      this->header_found_ = false;
+      this->reset_telegram_();
       return;
     }
   }
 }
 
-void Dsmr::receive_encrypted_() {
-  this->encrypted_telegram_len_ = 0;
-  size_t packet_size = 0;
-
-  while (true) {
-    if (!this->available()) {
-      if (!this->header_found_) {
-        return;
-      }
-      if (!this->available_within_timeout_()) {
-        ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram");
-        return;
-      }
-    }
-
+void Dsmr::receive_encrypted_telegram_() {
+  while (this->available_within_timeout_()) {
     const char c = this->read();
 
     // Find a new telegram start byte.
@@ -182,50 +197,58 @@ void Dsmr::receive_encrypted_() {
         continue;
       }
       ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found");
+      this->reset_telegram_();
       this->header_found_ = true;
     }
 
     // Check for buffer overflow.
-    if (this->encrypted_telegram_len_ >= this->max_telegram_len_) {
-      this->header_found_ = false;
+    if (this->crypt_bytes_read_ >= this->max_telegram_len_) {
+      this->reset_telegram_();
       ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_);
       return;
     }
 
-    this->encrypted_telegram_[this->encrypted_telegram_len_++] = c;
+    // Store the byte in the buffer.
+    this->crypt_telegram_[this->crypt_bytes_read_] = c;
+    this->crypt_bytes_read_++;
 
-    if (packet_size == 0 && this->encrypted_telegram_len_ > 20) {
+    // Read the length of the incoming encrypted telegram.
+    if (this->crypt_telegram_len_ == 0 && this->crypt_bytes_read_ > 20) {
       // Complete header + data bytes
-      packet_size = 13 + (this->encrypted_telegram_[11] << 8 | this->encrypted_telegram_[12]);
-      ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size);
+      this->crypt_telegram_len_ = 13 + (this->crypt_telegram_[11] << 8 | this->crypt_telegram_[12]);
+      ESP_LOGV(TAG, "Encrypted telegram length: %d bytes", this->crypt_telegram_len_);
     }
-    if (this->encrypted_telegram_len_ == packet_size && packet_size > 0) {
-      ESP_LOGV(TAG, "End of encrypted telegram found");
-      GCM *gcmaes128{new GCM()};
-      gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
-      // the iv is 8 bytes of the system title + 4 bytes frame counter
-      // system title is at byte 2 and frame counter at byte 15
-      for (int i = 10; i < 14; i++)
-        this->encrypted_telegram_[i] = this->encrypted_telegram_[i + 4];
-      constexpr uint16_t iv_size{12};
-      gcmaes128->setIV(&this->encrypted_telegram_[2], iv_size);
-      gcmaes128->decrypt(reinterpret_cast(this->telegram_),
-                         // the ciphertext start at byte 18
-                         &this->encrypted_telegram_[18],
-                         // cipher size
-                         this->encrypted_telegram_len_ - 17);
-      delete gcmaes128;  // NOLINT(cppcoreguidelines-owning-memory)
 
-      this->telegram_len_ = strnlen(this->telegram_, this->max_telegram_len_);
-      ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->telegram_len_);
-      ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
-
-      this->parse_telegram();
-
-      this->header_found_ = false;
-      this->telegram_len_ = 0;
-      return;
+    // Check for the end of the encrypted telegram.
+    if (this->crypt_telegram_len_ == 0 || this->crypt_bytes_read_ != this->crypt_telegram_len_) {
+      continue;
     }
+    ESP_LOGV(TAG, "End of encrypted telegram found");
+
+    // Decrypt the encrypted telegram.
+    GCM *gcmaes128{new GCM()};
+    gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
+    // the iv is 8 bytes of the system title + 4 bytes frame counter
+    // system title is at byte 2 and frame counter at byte 15
+    for (int i = 10; i < 14; i++)
+      this->crypt_telegram_[i] = this->crypt_telegram_[i + 4];
+    constexpr uint16_t iv_size{12};
+    gcmaes128->setIV(&this->crypt_telegram_[2], iv_size);
+    gcmaes128->decrypt(reinterpret_cast(this->telegram_),
+                       // the ciphertext start at byte 18
+                       &this->crypt_telegram_[18],
+                       // cipher size
+                       this->crypt_bytes_read_ - 17);
+    delete gcmaes128;  // NOLINT(cppcoreguidelines-owning-memory)
+
+    this->bytes_read_ = strnlen(this->telegram_, this->max_telegram_len_);
+    ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->bytes_read_);
+    ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
+
+    // Parse the decrypted telegram and publish sensor values.
+    this->parse_telegram();
+    this->reset_telegram_();
+    return;
   }
 }
 
@@ -234,11 +257,11 @@ bool Dsmr::parse_telegram() {
   ESP_LOGV(TAG, "Trying to parse telegram");
   this->stop_requesting_data_();
   ::dsmr::ParseResult res =
-      ::dsmr::P1Parser::parse(&data, this->telegram_, this->telegram_len_, false,
+      ::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, 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(this->telegram_, this->telegram_ + this->telegram_len_);
+    auto err_str = res.fullError(this->telegram_, this->telegram_ + this->bytes_read_);
     ESP_LOGE(TAG, "%s", err_str.c_str());
     return false;
   } else {
@@ -251,7 +274,7 @@ bool Dsmr::parse_telegram() {
 void Dsmr::dump_config() {
   ESP_LOGCONFIG(TAG, "DSMR:");
   ESP_LOGCONFIG(TAG, "  Max telegram length: %d", this->max_telegram_len_);
-
+  ESP_LOGCONFIG(TAG, "  Receive timeout: %.1fs", this->receive_timeout_ / 1e3f);
   if (this->request_pin_ != nullptr) {
     LOG_PIN("  Request Pin: ", this->request_pin_);
   }
@@ -270,9 +293,9 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
   if (decryption_key.length() == 0) {
     ESP_LOGI(TAG, "Disabling decryption");
     this->decryption_key_.clear();
-    if (this->encrypted_telegram_ != nullptr) {
-      delete[] this->encrypted_telegram_;
-      this->encrypted_telegram_ = nullptr;
+    if (this->crypt_telegram_ != nullptr) {
+      delete[] this->crypt_telegram_;
+      this->crypt_telegram_ = nullptr;
     }
     return;
   }
@@ -293,13 +316,11 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
     this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
   }
 
-  if (this->encrypted_telegram_ == nullptr) {
-    this->encrypted_telegram_ = new uint8_t[this->max_telegram_len_];  // NOLINT
+  if (this->crypt_telegram_ == nullptr) {
+    this->crypt_telegram_ = new uint8_t[this->max_telegram_len_];  // NOLINT
   }
 }
 
-void Dsmr::set_max_telegram_length(size_t length) { max_telegram_len_ = length; }
-
 }  // namespace dsmr
 }  // namespace esphome
 
diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h
index 0430eb93ed..db0bf95ca1 100644
--- a/esphome/components/dsmr/dsmr.h
+++ b/esphome/components/dsmr/dsmr.h
@@ -16,8 +16,6 @@
 namespace esphome {
 namespace dsmr {
 
-static constexpr uint32_t READ_TIMEOUT_MS = 200;
-
 using namespace ::dsmr::fields;
 
 // DSMR_**_LIST generated by ESPHome and written in esphome/core/defines
@@ -71,11 +69,10 @@ class Dsmr : public Component, public uart::UARTDevice {
   void dump_config() override;
 
   void set_decryption_key(const std::string &decryption_key);
-
-  void set_max_telegram_length(size_t length);
-
+  void set_max_telegram_length(size_t length) { this->max_telegram_len_ = length; }
   void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; }
   void set_request_interval(uint32_t interval) { this->request_interval_ = interval; }
+  void set_receive_timeout(uint32_t timeout) { this->receive_timeout_ = timeout; }
 
 // Sensor setters
 #define DSMR_SET_SENSOR(s) \
@@ -88,7 +85,8 @@ class Dsmr : public Component, public uart::UARTDevice {
 
  protected:
   void receive_telegram_();
-  void receive_encrypted_();
+  void receive_encrypted_telegram_();
+  void reset_telegram_();
 
   /// Wait for UART data to become available within the read timeout.
   ///
@@ -101,24 +99,26 @@ class Dsmr : public Component, public uart::UARTDevice {
   /// lost in the process.
   bool available_within_timeout_();
 
-  // Data request
+  // Request telegram
+  uint32_t request_interval_;
+  bool request_interval_reached_();
   GPIOPin *request_pin_{nullptr};
-  uint32_t request_interval_{0};
   uint32_t last_request_time_{0};
   bool requesting_data_{false};
   bool ready_to_request_data_();
-  bool request_interval_reached_();
   void start_requesting_data_();
   void stop_requesting_data_();
 
-  // Telegram buffer
+  // Read telegram
+  uint32_t receive_timeout_;
+  bool receive_timeout_reached_();
   size_t max_telegram_len_;
   char *telegram_{nullptr};
-  int telegram_len_{0};
-  uint8_t *encrypted_telegram_{nullptr};
-  int encrypted_telegram_len_{0};
-
-  // Serial parser
+  int bytes_read_{0};
+  uint8_t *crypt_telegram_{nullptr};
+  size_t crypt_telegram_len_{0};
+  int crypt_bytes_read_{0};
+  uint32_t last_read_time_{0};
   bool header_found_{false};
   bool footer_found_{false};
 
diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h
index 73694d3db7..42702cf5b8 100644
--- a/esphome/components/uart/uart_component.h
+++ b/esphome/components/uart/uart_component.h
@@ -52,6 +52,7 @@ class UARTComponent {
   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; }
+  size_t get_rx_buffer_size() { return this->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_; }
diff --git a/esphome/components/uart/uart_debugger.cpp b/esphome/components/uart/uart_debugger.cpp
index 9a535656a2..e2d92eac60 100644
--- a/esphome/components/uart/uart_debugger.cpp
+++ b/esphome/components/uart/uart_debugger.cpp
@@ -90,6 +90,11 @@ void UARTDummyReceiver::loop() {
   }
 }
 
+// In the upcoming log functions, a delay was added after all log calls.
+// This is done to allow the system to ship the log lines via the API
+// TCP connection(s). Without these delays, debug log lines could go
+// missing when UART devices block the main loop for too long.
+
 void UARTDebug::log_hex(UARTDirection direction, std::vector bytes, uint8_t separator) {
   std::string res;
   if (direction == UART_DIRECTION_RX) {
@@ -107,6 +112,7 @@ void UARTDebug::log_hex(UARTDirection direction, std::vector bytes, uin
     res += buf;
   }
   ESP_LOGD(TAG, "%s", res.c_str());
+  delay(10);
 }
 
 void UARTDebug::log_string(UARTDirection direction, std::vector bytes) {
@@ -150,6 +156,7 @@ void UARTDebug::log_string(UARTDirection direction, std::vector bytes)
   }
   res += '"';
   ESP_LOGD(TAG, "%s", res.c_str());
+  delay(10);
 }
 
 void UARTDebug::log_int(UARTDirection direction, std::vector bytes, uint8_t separator) {
@@ -167,6 +174,7 @@ void UARTDebug::log_int(UARTDirection direction, std::vector bytes, uin
     res += to_string(bytes[i]);
   }
   ESP_LOGD(TAG, "%s", res.c_str());
+  delay(10);
 }
 
 void UARTDebug::log_binary(UARTDirection direction, std::vector bytes, uint8_t separator) {
@@ -186,6 +194,7 @@ void UARTDebug::log_binary(UARTDirection direction, std::vector bytes,
     res += buf;
   }
   ESP_LOGD(TAG, "%s", res.c_str());
+  delay(10);
 }
 
 }  // namespace uart
diff --git a/tests/test3.yaml b/tests/test3.yaml
index 61c7a3dcad..8ae4a383e0 100644
--- a/tests/test3.yaml
+++ b/tests/test3.yaml
@@ -1313,6 +1313,7 @@ dsmr:
   max_telegram_length: 1000
   request_pin: D5
   request_interval: 20s
+  receive_timeout: 100ms
 
 daly_bms:
   update_interval: 20s

From 6f07421911bd1390cbf776956e3f4c4f45301f6b Mon Sep 17 00:00:00 2001
From: mechanarchy <1166756+mechanarchy@users.noreply.github.com>
Date: Tue, 30 Nov 2021 02:52:20 +1100
Subject: [PATCH 396/549] Optionally show internal components on the web server
 (#2627)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Oxan van Leeuwen 
---
 esphome/components/web_server/__init__.py    |   3 +
 esphome/components/web_server/web_server.cpp | 109 ++++++++-----------
 esphome/components/web_server/web_server.h   |   7 ++
 esphome/const.py                             |   1 +
 esphome/core/controller.cpp                  |  22 ++--
 esphome/core/controller.h                    |   2 +-
 tests/test4.yaml                             |   1 +
 7 files changed, 67 insertions(+), 78 deletions(-)

diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py
index dc652e0312..d9ff84d501 100644
--- a/esphome/components/web_server/__init__.py
+++ b/esphome/components/web_server/__init__.py
@@ -12,6 +12,7 @@ from esphome.const import (
     CONF_AUTH,
     CONF_USERNAME,
     CONF_PASSWORD,
+    CONF_INCLUDE_INTERNAL,
     CONF_OTA,
 )
 from esphome.core import CORE, coroutine_with_priority
@@ -42,6 +43,7 @@ CONFIG_SCHEMA = cv.Schema(
         cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
             web_server_base.WebServerBase
         ),
+        cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean,
         cv.Optional(CONF_OTA, default=True): cv.boolean,
     }
 ).extend(cv.COMPONENT_SCHEMA)
@@ -75,3 +77,4 @@ async def to_code(config):
         path = CORE.relative_config_path(config[CONF_JS_INCLUDE])
         with open(file=path, mode="r", encoding="utf-8") as myfile:
             cg.add(var.set_js_include(myfile.read()))
+    cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL]))
diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp
index 6f47f460af..3f74c2e8a1 100644
--- a/esphome/components/web_server/web_server.cpp
+++ b/esphome/components/web_server/web_server.cpp
@@ -31,10 +31,10 @@ static const char *const TAG = "web_server";
 
 void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action,
                const std::function &action_func = nullptr) {
-  if (obj->is_internal())
-    return;
   stream->print("print(klass.c_str());
+  if (obj->is_internal())
+    stream->print(" internal");
   stream->print("\" id=\"");
   stream->print(klass.c_str());
   stream->print("-");
@@ -83,7 +83,7 @@ void WebServer::set_js_include(const char *js_include) { this->js_include_ = js_
 
 void WebServer::setup() {
   ESP_LOGCONFIG(TAG, "Setting up web server...");
-  this->setup_controller();
+  this->setup_controller(this->include_internal_);
   this->base_->init();
 
   this->events_.onConnect([this](AsyncEventSourceClient *client) {
@@ -92,55 +92,55 @@ void WebServer::setup() {
 
 #ifdef USE_SENSOR
     for (auto *obj : App.get_sensors())
-      if (!obj->is_internal())
+      if (this->include_internal_ || !obj->is_internal())
         client->send(this->sensor_json(obj, obj->state).c_str(), "state");
 #endif
 
 #ifdef USE_SWITCH
     for (auto *obj : App.get_switches())
-      if (!obj->is_internal())
+      if (this->include_internal_ || !obj->is_internal())
         client->send(this->switch_json(obj, obj->state).c_str(), "state");
 #endif
 
 #ifdef USE_BINARY_SENSOR
     for (auto *obj : App.get_binary_sensors())
-      if (!obj->is_internal())
+      if (this->include_internal_ || !obj->is_internal())
         client->send(this->binary_sensor_json(obj, obj->state).c_str(), "state");
 #endif
 
 #ifdef USE_FAN
     for (auto *obj : App.get_fans())
-      if (!obj->is_internal())
+      if (this->include_internal_ || !obj->is_internal())
         client->send(this->fan_json(obj).c_str(), "state");
 #endif
 
 #ifdef USE_LIGHT
     for (auto *obj : App.get_lights())
-      if (!obj->is_internal())
+      if (this->include_internal_ || !obj->is_internal())
         client->send(this->light_json(obj).c_str(), "state");
 #endif
 
 #ifdef USE_TEXT_SENSOR
     for (auto *obj : App.get_text_sensors())
-      if (!obj->is_internal())
+      if (this->include_internal_ || !obj->is_internal())
         client->send(this->text_sensor_json(obj, obj->state).c_str(), "state");
 #endif
 
 #ifdef USE_COVER
     for (auto *obj : App.get_covers())
-      if (!obj->is_internal())
+      if (this->include_internal_ || !obj->is_internal())
         client->send(this->cover_json(obj).c_str(), "state");
 #endif
 
 #ifdef USE_NUMBER
     for (auto *obj : App.get_numbers())
-      if (!obj->is_internal())
+      if (this->include_internal_ || !obj->is_internal())
         client->send(this->number_json(obj, obj->state).c_str(), "state");
 #endif
 
 #ifdef USE_SELECT
     for (auto *obj : App.get_selects())
-      if (!obj->is_internal())
+      if (this->include_internal_ || !obj->is_internal())
         client->send(this->select_json(obj, obj->state).c_str(), "state");
 #endif
   });
@@ -188,57 +188,66 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
 
 #ifdef USE_SENSOR
   for (auto *obj : App.get_sensors())
-    write_row(stream, obj, "sensor", "");
+    if (this->include_internal_ || !obj->is_internal())
+      write_row(stream, obj, "sensor", "");
 #endif
 
 #ifdef USE_SWITCH
   for (auto *obj : App.get_switches())
-    write_row(stream, obj, "switch", "");
+    if (this->include_internal_ || !obj->is_internal())
+      write_row(stream, obj, "switch", "");
 #endif
 
 #ifdef USE_BINARY_SENSOR
   for (auto *obj : App.get_binary_sensors())
-    write_row(stream, obj, "binary_sensor", "");
+    if (this->include_internal_ || !obj->is_internal())
+      write_row(stream, obj, "binary_sensor", "");
 #endif
 
 #ifdef USE_FAN
   for (auto *obj : App.get_fans())
-    write_row(stream, obj, "fan", "");
+    if (this->include_internal_ || !obj->is_internal())
+      write_row(stream, obj, "fan", "");
 #endif
 
 #ifdef USE_LIGHT
   for (auto *obj : App.get_lights())
-    write_row(stream, obj, "light", "");
+    if (this->include_internal_ || !obj->is_internal())
+      write_row(stream, obj, "light", "");
 #endif
 
 #ifdef USE_TEXT_SENSOR
   for (auto *obj : App.get_text_sensors())
-    write_row(stream, obj, "text_sensor", "");
+    if (this->include_internal_ || !obj->is_internal())
+      write_row(stream, obj, "text_sensor", "");
 #endif
 
 #ifdef USE_COVER
   for (auto *obj : App.get_covers())
-    write_row(stream, obj, "cover", "");
+    if (this->include_internal_ || !obj->is_internal())
+      write_row(stream, obj, "cover", "");
 #endif
 
 #ifdef USE_NUMBER
   for (auto *obj : App.get_numbers())
-    write_row(stream, obj, "number", "");
+    if (this->include_internal_ || !obj->is_internal())
+      write_row(stream, obj, "number", "");
 #endif
 
 #ifdef USE_SELECT
   for (auto *obj : App.get_selects())
-    write_row(stream, obj, "select", "", [](AsyncResponseStream &stream, EntityBase *obj) {
-      select::Select *select = (select::Select *) obj;
-      stream.print("");
-    });
+    if (this->include_internal_ || !obj->is_internal())
+      write_row(stream, obj, "select", "", [](AsyncResponseStream &stream, EntityBase *obj) {
+        select::Select *select = (select::Select *) obj;
+        stream.print("");
+      });
 #endif
 
   stream->print(F("

See ESPHome Web API for " @@ -293,8 +302,6 @@ void WebServer::on_sensor_update(sensor::Sensor *obj, float state) { } void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (sensor::Sensor *obj : App.get_sensors()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; std::string data = this->sensor_json(obj, obj->state); @@ -321,8 +328,6 @@ void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::s } void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (text_sensor::TextSensor *obj : App.get_text_sensors()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; std::string data = this->text_sensor_json(obj, obj->state); @@ -353,8 +358,6 @@ std::string WebServer::switch_json(switch_::Switch *obj, bool value) { } void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (switch_::Switch *obj : App.get_switches()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; @@ -381,8 +384,6 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM #ifdef USE_BINARY_SENSOR void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { - if (obj->is_internal()) - return; this->events_.send(this->binary_sensor_json(obj, state).c_str(), "state"); } std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value) { @@ -394,8 +395,6 @@ std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool } void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; std::string data = this->binary_sensor_json(obj, obj->state); @@ -407,11 +406,7 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con #endif #ifdef USE_FAN -void WebServer::on_fan_update(fan::FanState *obj) { - if (obj->is_internal()) - return; - this->events_.send(this->fan_json(obj).c_str(), "state"); -} +void WebServer::on_fan_update(fan::FanState *obj) { this->events_.send(this->fan_json(obj).c_str(), "state"); } std::string WebServer::fan_json(fan::FanState *obj) { return json::build_json([obj](JsonObject &root) { root["id"] = "fan-" + obj->get_object_id(); @@ -442,8 +437,6 @@ std::string WebServer::fan_json(fan::FanState *obj) { } void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (fan::FanState *obj : App.get_fans()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; @@ -504,15 +497,9 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc #endif #ifdef USE_LIGHT -void WebServer::on_light_update(light::LightState *obj) { - if (obj->is_internal()) - return; - this->events_.send(this->light_json(obj).c_str(), "state"); -} +void WebServer::on_light_update(light::LightState *obj) { this->events_.send(this->light_json(obj).c_str(), "state"); } void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (light::LightState *obj : App.get_lights()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; @@ -579,15 +566,9 @@ std::string WebServer::light_json(light::LightState *obj) { #endif #ifdef USE_COVER -void WebServer::on_cover_update(cover::Cover *obj) { - if (obj->is_internal()) - return; - this->events_.send(this->cover_json(obj).c_str(), "state"); -} +void WebServer::on_cover_update(cover::Cover *obj) { this->events_.send(this->cover_json(obj).c_str(), "state"); } void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (cover::Cover *obj : App.get_covers()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; @@ -646,8 +627,6 @@ void WebServer::on_number_update(number::Number *obj, float state) { } void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_numbers()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; std::string data = this->number_json(obj, obj->state); @@ -673,8 +652,6 @@ void WebServer::on_select_update(select::Select *obj, const std::string &state) } void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_selects()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index cdfec51cf1..66fd082d19 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -58,6 +58,12 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { */ void set_js_include(const char *js_include); + /** Determine whether internal components should be displayed on the web server. + * Defaults to false. + * + * @param include_internal Whether internal components should be displayed. + */ + void set_include_internal(bool include_internal) { include_internal_ = include_internal; } /** Set whether or not the webserver should expose the OTA form and handler. * * @param allow_ota. @@ -188,6 +194,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { const char *css_include_{nullptr}; const char *js_url_{nullptr}; const char *js_include_{nullptr}; + bool include_internal_{false}; bool allow_ota_{true}; }; diff --git a/esphome/const.py b/esphome/const.py index f7beee8245..9006145dfe 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -286,6 +286,7 @@ CONF_ILLUMINANCE = "illuminance" CONF_IMPEDANCE = "impedance" CONF_IMPORT_ACTIVE_ENERGY = "import_active_energy" CONF_IMPORT_REACTIVE_ENERGY = "import_reactive_energy" +CONF_INCLUDE_INTERNAL = "include_internal" CONF_INCLUDES = "includes" CONF_INDEX = "index" CONF_INDOOR = "indoor" diff --git a/esphome/core/controller.cpp b/esphome/core/controller.cpp index 1d25be41f2..6d3a76a292 100644 --- a/esphome/core/controller.cpp +++ b/esphome/core/controller.cpp @@ -4,64 +4,64 @@ namespace esphome { -void Controller::setup_controller() { +void Controller::setup_controller(bool include_internal) { #ifdef USE_BINARY_SENSOR for (auto *obj : App.get_binary_sensors()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](bool state) { this->on_binary_sensor_update(obj, state); }); } #endif #ifdef USE_FAN for (auto *obj : App.get_fans()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj]() { this->on_fan_update(obj); }); } #endif #ifdef USE_LIGHT for (auto *obj : App.get_lights()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_new_remote_values_callback([this, obj]() { this->on_light_update(obj); }); } #endif #ifdef USE_SENSOR for (auto *obj : App.get_sensors()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](float state) { this->on_sensor_update(obj, state); }); } #endif #ifdef USE_SWITCH for (auto *obj : App.get_switches()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](bool state) { this->on_switch_update(obj, state); }); } #endif #ifdef USE_COVER for (auto *obj : App.get_covers()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj]() { this->on_cover_update(obj); }); } #endif #ifdef USE_TEXT_SENSOR for (auto *obj : App.get_text_sensors()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](const std::string &state) { this->on_text_sensor_update(obj, state); }); } #endif #ifdef USE_CLIMATE for (auto *obj : App.get_climates()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj]() { this->on_climate_update(obj); }); } #endif #ifdef USE_NUMBER for (auto *obj : App.get_numbers()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](float state) { this->on_number_update(obj, state); }); } #endif #ifdef USE_SELECT for (auto *obj : App.get_selects()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](const std::string &state) { this->on_select_update(obj, state); }); } #endif diff --git a/esphome/core/controller.h b/esphome/core/controller.h index 0de8f7ea19..f53924cd23 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -36,7 +36,7 @@ namespace esphome { class Controller { public: - void setup_controller(); + void setup_controller(bool include_internal = false); #ifdef USE_BINARY_SENSOR virtual void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state){}; #endif diff --git a/tests/test4.yaml b/tests/test4.yaml index 938145235a..ee88b422f2 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -49,6 +49,7 @@ web_server: auth: username: admin password: admin + include_internal: true time: - platform: sntp From f50e40e0b893618250e8537fa69cee4fad29e5f1 Mon Sep 17 00:00:00 2001 From: definitio <37266727+definitio@users.noreply.github.com> Date: Mon, 29 Nov 2021 20:09:09 +0300 Subject: [PATCH 397/549] Fix custom mode_state_topic (#2827) --- 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 b0f9146012..87b9a4b3e2 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -213,7 +213,7 @@ async def setup_climate_core_(var, config): if CONF_MODE_COMMAND_TOPIC in config: cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC])) if CONF_MODE_STATE_TOPIC in config: - cg.add(mqtt_.set_custom_state_topic(config[CONF_MODE_STATE_TOPIC])) + cg.add(mqtt_.set_custom_mode_state_topic(config[CONF_MODE_STATE_TOPIC])) if CONF_SWING_MODE_COMMAND_TOPIC in config: cg.add( From b5639a6472fdd06e2a6d72252a0f3097ab4b163a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 30 Nov 2021 08:00:51 +1300 Subject: [PATCH 398/549] Add support for button entities (#2824) --- CODEOWNERS | 1 + esphome/components/api/api.proto | 25 ++++ esphome/components/api/api_connection.cpp | 21 ++++ esphome/components/api/api_connection.h | 4 + esphome/components/api/api_pb2.cpp | 112 ++++++++++++++++++ esphome/components/api/api_pb2.h | 30 +++++ esphome/components/api/api_pb2_service.cpp | 34 ++++++ esphome/components/api/api_pb2_service.h | 12 ++ esphome/components/api/list_entities.cpp | 3 + esphome/components/api/list_entities.h | 3 + esphome/components/api/subscribe_state.h | 3 + esphome/components/api/util.cpp | 15 +++ esphome/components/api/util.h | 6 + esphome/components/button/__init__.py | 104 ++++++++++++++++ esphome/components/button/automation.h | 28 +++++ esphome/components/button/button.cpp | 21 ++++ esphome/components/button/button.h | 50 ++++++++ esphome/components/mqtt/__init__.py | 1 + esphome/components/mqtt/mqtt_button.cpp | 40 +++++++ esphome/components/mqtt/mqtt_button.h | 40 +++++++ esphome/components/restart/button/__init__.py | 23 ++++ .../restart/button/restart_button.cpp | 20 ++++ .../restart/button/restart_button.h | 18 +++ .../restart/{switch.py => switch/__init__.py} | 0 .../restart/{ => switch}/restart_switch.cpp | 0 .../restart/{ => switch}/restart_switch.h | 0 .../components/template/button/__init__.py | 13 ++ esphome/components/web_server/web_server.cpp | 37 ++++++ esphome/components/web_server/web_server.h | 5 + esphome/core/application.h | 19 +++ esphome/core/controller.h | 3 + esphome/core/defines.h | 1 + script/ci-custom.py | 1 + tests/test4.yaml | 4 + 34 files changed, 697 insertions(+) create mode 100644 esphome/components/button/__init__.py create mode 100644 esphome/components/button/automation.h create mode 100644 esphome/components/button/button.cpp create mode 100644 esphome/components/button/button.h create mode 100644 esphome/components/mqtt/mqtt_button.cpp create mode 100644 esphome/components/mqtt/mqtt_button.h create mode 100644 esphome/components/restart/button/__init__.py create mode 100644 esphome/components/restart/button/restart_button.cpp create mode 100644 esphome/components/restart/button/restart_button.h rename esphome/components/restart/{switch.py => switch/__init__.py} (100%) rename esphome/components/restart/{ => switch}/restart_switch.cpp (100%) rename esphome/components/restart/{ => switch}/restart_switch.h (100%) create mode 100644 esphome/components/template/button/__init__.py diff --git a/CODEOWNERS b/CODEOWNERS index 80c5dc34c1..687fad3948 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -30,6 +30,7 @@ esphome/components/bang_bang/* @OttoWinter esphome/components/binary_sensor/* @esphome/core esphome/components/ble_client/* @buxtronix esphome/components/bme680_bsec/* @trvrnrth +esphome/components/button/* @esphome/core esphome/components/canbus/* @danielschramm @mvturnho esphome/components/cap1188/* @MrEditor97 esphome/components/captive_portal/* @OttoWinter diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 0eb7ead735..eaad4b8d07 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -40,6 +40,7 @@ service APIConnection { rpc climate_command (ClimateCommandRequest) returns (void) {} rpc number_command (NumberCommandRequest) returns (void) {} rpc select_command (SelectCommandRequest) returns (void) {} + rpc button_command (ButtonCommandRequest) returns (void) {} } @@ -944,3 +945,27 @@ message SelectCommandRequest { fixed32 key = 1; string state = 2; } + +// ==================== BUTTON ==================== +message ListEntitiesButtonResponse { + option (id) = 61; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BUTTON"; + + string object_id = 1; + fixed32 key = 2; + string name = 3; + string unique_id = 4; + + string icon = 5; + bool disabled_by_default = 6; + EntityCategory entity_category = 7; +} +message ButtonCommandRequest { + option (id) = 62; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BUTTON"; + option (no_delay) = true; + + fixed32 key = 1; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 715a4f48c1..22b896a788 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -674,6 +674,27 @@ void APIConnection::select_command(const SelectCommandRequest &msg) { } #endif +#ifdef USE_BUTTON +bool APIConnection::send_button_info(button::Button *button) { + ListEntitiesButtonResponse msg; + msg.key = button->get_object_id_hash(); + msg.object_id = button->get_object_id(); + msg.name = button->get_name(); + msg.unique_id = get_default_unique_id("button", button); + msg.icon = button->get_icon(); + msg.disabled_by_default = button->is_disabled_by_default(); + msg.entity_category = static_cast(button->get_entity_category()); + return this->send_list_entities_button_response(msg); +} +void APIConnection::button_command(const ButtonCommandRequest &msg) { + button::Button *button = App.get_button_by_key(msg.key); + if (button == nullptr) + return; + + button->press(); +} +#endif + #ifdef USE_ESP32_CAMERA void APIConnection::send_camera_state(std::shared_ptr image) { if (!this->state_subscription_) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index a1f1769a19..72697b5911 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -73,6 +73,10 @@ class APIConnection : public APIServerConnection { bool send_select_state(select::Select *select, std::string state); bool send_select_info(select::Select *select); void select_command(const SelectCommandRequest &msg) override; +#endif +#ifdef USE_BUTTON + bool send_button_info(button::Button *button); + void button_command(const ButtonCommandRequest &msg) override; #endif bool send_log_message(int level, const char *tag, const char *line); void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 7bfa1e9edb..169349d995 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -4147,6 +4147,118 @@ void SelectCommandRequest::dump_to(std::string &out) const { out.append("}"); } #endif +bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 6: { + this->disabled_by_default = value.as_bool(); + return true; + } + case 7: { + this->entity_category = value.as_enum(); + return true; + } + default: + return false; + } +} +bool ListEntitiesButtonResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: { + this->object_id = value.as_string(); + return true; + } + case 3: { + this->name = value.as_string(); + return true; + } + case 4: { + this->unique_id = value.as_string(); + return true; + } + case 5: { + this->icon = value.as_string(); + return true; + } + default: + return false; + } +} +bool ListEntitiesButtonResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 2: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->object_id); + buffer.encode_fixed32(2, this->key); + buffer.encode_string(3, this->name); + buffer.encode_string(4, this->unique_id); + buffer.encode_string(5, this->icon); + buffer.encode_bool(6, this->disabled_by_default); + buffer.encode_enum(7, this->entity_category); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void ListEntitiesButtonResponse::dump_to(std::string &out) const { + char buffer[64]; + out.append("ListEntitiesButtonResponse {\n"); + out.append(" object_id: "); + out.append("'").append(this->object_id).append("'"); + out.append("\n"); + + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" name: "); + out.append("'").append(this->name).append("'"); + out.append("\n"); + + out.append(" unique_id: "); + out.append("'").append(this->unique_id).append("'"); + out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); + out.append("}"); +} +#endif +bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); } +#ifdef HAS_PROTO_MESSAGE_DUMP +void ButtonCommandRequest::dump_to(std::string &out) const { + char buffer[64]; + out.append("ButtonCommandRequest {\n"); + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index ec11732c7d..82fd8de687 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1041,6 +1041,36 @@ class SelectCommandRequest : public ProtoMessage { bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; }; +class ListEntitiesButtonResponse : public ProtoMessage { + public: + std::string object_id{}; + uint32_t key{0}; + std::string name{}; + std::string unique_id{}; + std::string icon{}; + bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class ButtonCommandRequest : public ProtoMessage { + public: + uint32_t key{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; +}; } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index ad2413ea57..567fbf02c9 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -282,6 +282,16 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon #endif #ifdef USE_SELECT #endif +#ifdef USE_BUTTON +bool APIServerConnectionBase::send_list_entities_button_response(const ListEntitiesButtonResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_list_entities_button_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 61); +} +#endif +#ifdef USE_BUTTON +#endif bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { switch (msg_type) { case 1: { @@ -513,6 +523,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str()); #endif this->on_select_command_request(msg); +#endif + break; + } + case 62: { +#ifdef USE_BUTTON + ButtonCommandRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str()); +#endif + this->on_button_command_request(msg); #endif break; } @@ -737,6 +758,19 @@ void APIServerConnection::on_select_command_request(const SelectCommandRequest & this->select_command(msg); } #endif +#ifdef USE_BUTTON +void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->button_command(msg); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 1b8d990b05..50b08d3ec4 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -129,6 +129,12 @@ class APIServerConnectionBase : public ProtoService { #endif #ifdef USE_SELECT virtual void on_select_command_request(const SelectCommandRequest &value){}; +#endif +#ifdef USE_BUTTON + bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg); +#endif +#ifdef USE_BUTTON + virtual void on_button_command_request(const ButtonCommandRequest &value){}; #endif protected: bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; @@ -171,6 +177,9 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_SELECT virtual void select_command(const SelectCommandRequest &msg) = 0; +#endif +#ifdef USE_BUTTON + virtual void button_command(const ButtonCommandRequest &msg) = 0; #endif protected: void on_hello_request(const HelloRequest &msg) override; @@ -209,6 +218,9 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_SELECT void on_select_command_request(const SelectCommandRequest &msg) override; #endif +#ifdef USE_BUTTON + void on_button_command_request(const ButtonCommandRequest &msg) override; +#endif }; } // namespace api diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index 745dd92c89..cb97df8ca1 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -27,6 +27,9 @@ bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->clie #ifdef USE_SWITCH bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); } #endif +#ifdef USE_BUTTON +bool ListEntitiesIterator::on_button(button::Button *button) { return this->client_->send_button_info(button); } +#endif #ifdef USE_TEXT_SENSOR bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { return this->client_->send_text_sensor_info(text_sensor); diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index c728fb0a97..714edaa91f 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -30,6 +30,9 @@ class ListEntitiesIterator : public ComponentIterator { #ifdef USE_SWITCH bool on_switch(switch_::Switch *a_switch) override; #endif +#ifdef USE_BUTTON + bool on_button(button::Button *button) override; +#endif #ifdef USE_TEXT_SENSOR bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; #endif diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index beb9b947d4..d3f2d3aa45 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -31,6 +31,9 @@ class InitialStateIterator : public ComponentIterator { #ifdef USE_SWITCH bool on_switch(switch_::Switch *a_switch) override; #endif +#ifdef USE_BUTTON + bool on_button(button::Button *button) override { return true; }; +#endif #ifdef USE_TEXT_SENSOR bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; #endif diff --git a/esphome/components/api/util.cpp b/esphome/components/api/util.cpp index 5085994607..f5fd752101 100644 --- a/esphome/components/api/util.cpp +++ b/esphome/components/api/util.cpp @@ -116,6 +116,21 @@ void ComponentIterator::advance() { } break; #endif +#ifdef USE_BUTTON + case IteratorState::BUTTON: + if (this->at_ >= App.get_buttons().size()) { + advance_platform = true; + } else { + auto *button = App.get_buttons()[this->at_]; + if (button->is_internal()) { + success = true; + break; + } else { + success = this->on_button(button); + } + } + break; +#endif #ifdef USE_TEXT_SENSOR case IteratorState::TEXT_SENSOR: if (this->at_ >= App.get_text_sensors().size()) { diff --git a/esphome/components/api/util.h b/esphome/components/api/util.h index e404a95619..7849b3e028 100644 --- a/esphome/components/api/util.h +++ b/esphome/components/api/util.h @@ -38,6 +38,9 @@ class ComponentIterator { #ifdef USE_SWITCH virtual bool on_switch(switch_::Switch *a_switch) = 0; #endif +#ifdef USE_BUTTON + virtual bool on_button(button::Button *button) = 0; +#endif #ifdef USE_TEXT_SENSOR virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0; #endif @@ -78,6 +81,9 @@ class ComponentIterator { #ifdef USE_SWITCH SWITCH, #endif +#ifdef USE_BUTTON + BUTTON, +#endif #ifdef USE_TEXT_SENSOR TEXT_SENSOR, #endif diff --git a/esphome/components/button/__init__.py b/esphome/components/button/__init__.py new file mode 100644 index 0000000000..495a85b6b4 --- /dev/null +++ b/esphome/components/button/__init__.py @@ -0,0 +1,104 @@ +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 mqtt +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_ON_PRESS, + CONF_TRIGGER_ID, + CONF_MQTT_ID, +) +from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity + +CODEOWNERS = ["@esphome/core"] +IS_PLATFORM_COMPONENT = True + +button_ns = cg.esphome_ns.namespace("button") +Button = button_ns.class_("Button", cg.EntityBase) +ButtonPtr = Button.operator("ptr") + +PressAction = button_ns.class_("PressAction", automation.Action) + +ButtonPressTrigger = button_ns.class_( + "ButtonPressTrigger", automation.Trigger.template() +) + + +BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent), + cv.Optional(CONF_ON_PRESS): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger), + } + ), + } +) + +_UNDEF = object() + + +def button_schema( + icon: str = _UNDEF, + entity_category: str = _UNDEF, +) -> cv.Schema: + schema = BUTTON_SCHEMA + if icon is not _UNDEF: + schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon}) + if entity_category is not _UNDEF: + schema = schema.extend( + { + cv.Optional( + CONF_ENTITY_CATEGORY, default=entity_category + ): cv.entity_category + } + ) + return schema + + +async def setup_button_core_(var, config): + await setup_entity(var, config) + + for conf in config.get(CONF_ON_PRESS, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + if CONF_MQTT_ID in config: + mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) + await mqtt.register_mqtt_component(mqtt_, config) + + +async def register_button(var, config): + if not CORE.has_id(config[CONF_ID]): + var = cg.Pvariable(config[CONF_ID], var) + cg.add(cg.App.register_button(var)) + await setup_button_core_(var, config) + + +async def new_button(config): + var = cg.new_Pvariable(config[CONF_ID]) + await register_button(var, config) + return var + + +BUTTON_PRESS_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(Button), + } +) + + +@automation.register_action("button.press", PressAction, BUTTON_PRESS_SCHEMA) +async def button_press_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) + + +@coroutine_with_priority(100.0) +async def to_code(config): + cg.add_global(button_ns.using) + cg.add_define("USE_BUTTON") diff --git a/esphome/components/button/automation.h b/esphome/components/button/automation.h new file mode 100644 index 0000000000..a5fb9f35b7 --- /dev/null +++ b/esphome/components/button/automation.h @@ -0,0 +1,28 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace button { + +template class PressAction : public Action { + public: + explicit PressAction(Button *button) : button_(button) {} + + void play(Ts... x) override { this->button_->press(); } + + protected: + Button *button_; +}; + +class ButtonPressTrigger : public Trigger<> { + public: + ButtonPressTrigger(Button *button) { + button->add_on_press_callback([this]() { this->trigger(); }); + } +}; + +} // namespace button +} // namespace esphome diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp new file mode 100644 index 0000000000..fe39b1e458 --- /dev/null +++ b/esphome/components/button/button.cpp @@ -0,0 +1,21 @@ +#include "button.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace button { + +static const char *const TAG = "button"; + +Button::Button(const std::string &name) : EntityBase(name) {} +Button::Button() : Button("") {} + +void Button::press() { + ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str()); + this->press_action(); + this->press_callback_.call(); +} +void Button::add_on_press_callback(std::function &&callback) { this->press_callback_.add(std::move(callback)); } +uint32_t Button::hash_base() { return 1495763804UL; } + +} // namespace button +} // namespace esphome diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h new file mode 100644 index 0000000000..954afa0ab9 --- /dev/null +++ b/esphome/components/button/button.h @@ -0,0 +1,50 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/entity_base.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace button { + +#define LOG_BUTTON(prefix, type, obj) \ + if ((obj) != nullptr) { \ + 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()); \ + } \ + } + +/** Base class for all buttons. + * + * A button is just a momentary switch that does not have a state, only a trigger. + */ +class Button : public EntityBase { + public: + explicit Button(); + explicit Button(const std::string &name); + + /** Press this button. This is called by the front-end. + * + * For implementing buttons, please override press_action. + */ + void press(); + + /** Set callback for state changes. + * + * @param callback The void() callback. + */ + void add_on_press_callback(std::function &&callback); + + protected: + /** You should implement this virtual method if you want to create your own button. + */ + virtual void press_action(){}; + + uint32_t hash_base() override; + + CallbackManager press_callback_{}; +}; + +} // namespace button +} // namespace esphome diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 73af0bad90..d677d54d23 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -95,6 +95,7 @@ MQTTSwitchComponent = mqtt_ns.class_("MQTTSwitchComponent", MQTTComponent) MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent) MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent) MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent) +MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent) MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator") MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = { diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp new file mode 100644 index 0000000000..5a0d14b648 --- /dev/null +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -0,0 +1,40 @@ +#include "mqtt_button.h" +#include "esphome/core/log.h" + +#include "mqtt_const.h" + +#ifdef USE_MQTT +#ifdef USE_BUTTON + +namespace esphome { +namespace mqtt { + +static const char *const TAG = "mqtt.button"; + +using namespace esphome::button; + +MQTTButtonComponent::MQTTButtonComponent(button::Button *button) : MQTTComponent(), button_(button) {} + +void MQTTButtonComponent::setup() { + this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { + if (payload == "press") { + this->button_->press(); + } else { + ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str()); + this->status_momentary_warning("state", 5000); + } + }); +} +void MQTTButtonComponent::dump_config() { + ESP_LOGCONFIG(TAG, "MQTT Button '%s': ", this->button_->get_name().c_str()); + LOG_MQTT_COMPONENT(true, true); +} + +std::string MQTTButtonComponent::component_type() const { return "button"; } +const EntityBase *MQTTButtonComponent::get_entity() const { return this->button_; } + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_button.h b/esphome/components/mqtt/mqtt_button.h new file mode 100644 index 0000000000..a7e60db380 --- /dev/null +++ b/esphome/components/mqtt/mqtt_button.h @@ -0,0 +1,40 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_MQTT +#ifdef USE_BUTTON + +#include "esphome/components/button/button.h" +#include "mqtt_component.h" + +namespace esphome { +namespace mqtt { + +class MQTTButtonComponent : public mqtt::MQTTComponent { + public: + explicit MQTTButtonComponent(button::Button *button); + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + void setup() override; + void dump_config() override; + + /// Buttons do not send a state so just return true. + bool send_initial_state() override { return true; } + + void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override {} + + protected: + /// "button" component type. + std::string component_type() const override; + const EntityBase *get_entity() const override; + + button::Button *button_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/restart/button/__init__.py b/esphome/components/restart/button/__init__.py new file mode 100644 index 0000000000..257a8e35f7 --- /dev/null +++ b/esphome/components/restart/button/__init__.py @@ -0,0 +1,23 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button +from esphome.const import ( + CONF_ID, + ENTITY_CATEGORY_CONFIG, + ICON_RESTART, +) + +restart_ns = cg.esphome_ns.namespace("restart") +RestartButton = restart_ns.class_("RestartButton", button.Button, cg.Component) + +CONFIG_SCHEMA = ( + button.button_schema(icon=ICON_RESTART, entity_category=ENTITY_CATEGORY_CONFIG) + .extend({cv.GenerateID(): cv.declare_id(RestartButton)}) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await button.register_button(var, config) diff --git a/esphome/components/restart/button/restart_button.cpp b/esphome/components/restart/button/restart_button.cpp new file mode 100644 index 0000000000..d8ff061355 --- /dev/null +++ b/esphome/components/restart/button/restart_button.cpp @@ -0,0 +1,20 @@ +#include "restart_button.h" +#include "esphome/core/application.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace restart { + +static const char *const TAG = "restart.button"; + +void RestartButton::press_action() { + ESP_LOGI(TAG, "Restarting device..."); + // Let MQTT settle a bit + delay(100); // NOLINT + App.safe_reboot(); +} +void RestartButton::dump_config() { LOG_BUTTON("", "Restart Button", this); } + +} // namespace restart +} // namespace esphome diff --git a/esphome/components/restart/button/restart_button.h b/esphome/components/restart/button/restart_button.h new file mode 100644 index 0000000000..db18f1dadc --- /dev/null +++ b/esphome/components/restart/button/restart_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace restart { + +class RestartButton : public button::Button, public Component { + public: + void dump_config() override; + + protected: + void press_action() override; +}; + +} // namespace restart +} // namespace esphome diff --git a/esphome/components/restart/switch.py b/esphome/components/restart/switch/__init__.py similarity index 100% rename from esphome/components/restart/switch.py rename to esphome/components/restart/switch/__init__.py diff --git a/esphome/components/restart/restart_switch.cpp b/esphome/components/restart/switch/restart_switch.cpp similarity index 100% rename from esphome/components/restart/restart_switch.cpp rename to esphome/components/restart/switch/restart_switch.cpp diff --git a/esphome/components/restart/restart_switch.h b/esphome/components/restart/switch/restart_switch.h similarity index 100% rename from esphome/components/restart/restart_switch.h rename to esphome/components/restart/switch/restart_switch.h diff --git a/esphome/components/template/button/__init__.py b/esphome/components/template/button/__init__.py new file mode 100644 index 0000000000..aa192d118e --- /dev/null +++ b/esphome/components/template/button/__init__.py @@ -0,0 +1,13 @@ +import esphome.config_validation as cv +from esphome.components import button + + +CONFIG_SCHEMA = button.BUTTON_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(button.Button), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + await button.new_button(config) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 3f74c2e8a1..29cb4827bd 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -198,6 +198,11 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { write_row(stream, obj, "switch", ""); #endif +#ifdef USE_BUTTON + for (auto *obj : App.get_buttons()) + write_row(stream, obj, "button", ""); +#endif + #ifdef USE_BINARY_SENSOR for (auto *obj : App.get_binary_sensors()) if (this->include_internal_ || !obj->is_internal()) @@ -382,6 +387,26 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM } #endif +#ifdef USE_BUTTON +void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) { + for (button::Button *obj : App.get_buttons()) { + if (obj->is_internal()) + continue; + if (obj->get_object_id() != match.id) + continue; + + if (request->method() == HTTP_POST && match.method == "press") { + this->defer([obj]() { obj->press(); }); + request->send(200); + } else { + request->send(404); + } + return; + } + request->send(404); +} +#endif + #ifdef USE_BINARY_SENSOR void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { this->events_.send(this->binary_sensor_json(obj, state).c_str(), "state"); @@ -715,6 +740,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return true; #endif +#ifdef USE_BUTTON + if (request->method() == HTTP_POST && match.domain == "button") + return true; +#endif + #ifdef USE_BINARY_SENSOR if (request->method() == HTTP_GET && match.domain == "binary_sensor") return true; @@ -787,6 +817,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { } #endif +#ifdef USE_BUTTON + if (match.domain == "button") { + this->handle_button_request(request, match); + return; + } +#endif + #ifdef USE_BINARY_SENSOR if (match.domain == "binary_sensor") { this->handle_binary_sensor_request(request, match); diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 66fd082d19..8edb4237a2 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -112,6 +112,11 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { std::string switch_json(switch_::Switch *obj, bool value); #endif +#ifdef USE_BUTTON + /// Handle a button request under '/button//press'. + void handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match); +#endif + #ifdef USE_BINARY_SENSOR void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override; diff --git a/esphome/core/application.h b/esphome/core/application.h index 5c1483d301..ace0c9ad6d 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -17,6 +17,9 @@ #ifdef USE_SWITCH #include "esphome/components/switch/switch.h" #endif +#ifdef USE_BUTTON +#include "esphome/components/button/button.h" +#endif #ifdef USE_TEXT_SENSOR #include "esphome/components/text_sensor/text_sensor.h" #endif @@ -67,6 +70,10 @@ class Application { void register_switch(switch_::Switch *a_switch) { this->switches_.push_back(a_switch); } #endif +#ifdef USE_BUTTON + void register_button(button::Button *button) { this->buttons_.push_back(button); } +#endif + #ifdef USE_TEXT_SENSOR void register_text_sensor(text_sensor::TextSensor *sensor) { this->text_sensors_.push_back(sensor); } #endif @@ -167,6 +174,15 @@ class Application { return nullptr; } #endif +#ifdef USE_BUTTON + const std::vector &get_buttons() { return this->buttons_; } + button::Button *get_button_by_key(uint32_t key, bool include_internal = false) { + for (auto *obj : this->buttons_) + if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) + return obj; + return nullptr; + } +#endif #ifdef USE_SENSOR const std::vector &get_sensors() { return this->sensors_; } sensor::Sensor *get_sensor_by_key(uint32_t key, bool include_internal = false) { @@ -260,6 +276,9 @@ class Application { #ifdef USE_SWITCH std::vector switches_{}; #endif +#ifdef USE_BUTTON + std::vector buttons_{}; +#endif #ifdef USE_SENSOR std::vector sensors_{}; #endif diff --git a/esphome/core/controller.h b/esphome/core/controller.h index f53924cd23..0c3722855c 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -22,6 +22,9 @@ #ifdef USE_SWITCH #include "esphome/components/switch/switch.h" #endif +#ifdef USE_BUTTON +#include "esphome/components/button/button.h" +#endif #ifdef USE_CLIMATE #include "esphome/components/climate/climate.h" #endif diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 8dcae9fa31..a74755f651 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -16,6 +16,7 @@ #define USE_API_NOISE #define USE_API_PLAINTEXT #define USE_BINARY_SENSOR +#define USE_BUTTON #define USE_CLIMATE #define USE_COVER #define USE_DEEP_SLEEP diff --git a/script/ci-custom.py b/script/ci-custom.py index 3f01fb81bf..de2dfda44d 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -603,6 +603,7 @@ def lint_inclusive_language(fname, match): "esphome/components/switch/switch.h", "esphome/components/text_sensor/text_sensor.h", "esphome/components/climate/climate.h", + "esphome/components/button/button.h", "esphome/core/component.h", "esphome/core/gpio.h", "esphome/core/log.h", diff --git a/tests/test4.yaml b/tests/test4.yaml index ee88b422f2..b4708acf65 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -520,3 +520,7 @@ xpt2046: id(touchscreen).y_raw, id(touchscreen).z_raw ); + +button: + - platform: restart + name: Restart Button From 939fb313df622d3fabc14ea837e296c7aa5ec0ec Mon Sep 17 00:00:00 2001 From: dentra Date: Mon, 29 Nov 2021 22:08:52 +0300 Subject: [PATCH 399/549] Tuya text_sensor and raw data usage (#1812) --- CODEOWNERS | 1 + esphome/components/tuya/__init__.py | 89 ++++++++++++++++++- esphome/components/tuya/automation.cpp | 66 ++++++++++++++ esphome/components/tuya/automation.h | 53 +++++++++++ .../components/tuya/binary_sensor/__init__.py | 4 +- esphome/components/tuya/sensor/__init__.py | 4 +- .../components/tuya/text_sensor/__init__.py | 29 ++++++ .../tuya/text_sensor/tuya_text_sensor.cpp | 35 ++++++++ .../tuya/text_sensor/tuya_text_sensor.h | 24 +++++ esphome/const.py | 1 + 10 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 esphome/components/tuya/automation.cpp create mode 100644 esphome/components/tuya/automation.h create mode 100644 esphome/components/tuya/text_sensor/__init__.py create mode 100644 esphome/components/tuya/text_sensor/tuya_text_sensor.cpp create mode 100644 esphome/components/tuya/text_sensor/tuya_text_sensor.h diff --git a/CODEOWNERS b/CODEOWNERS index 687fad3948..6dbdef12ec 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -181,6 +181,7 @@ esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/climate/* @jesserockz esphome/components/tuya/sensor/* @jesserockz esphome/components/tuya/switch/* @jesserockz +esphome/components/tuya/text_sensor/* @dentra esphome/components/uart/* @esphome/core esphome/components/ultrasonic/* @OttoWinter esphome/components/version/* @esphome/core diff --git a/esphome/components/tuya/__init__.py b/esphome/components/tuya/__init__.py index 436759979a..965893e012 100644 --- a/esphome/components/tuya/__init__.py +++ b/esphome/components/tuya/__init__.py @@ -1,16 +1,84 @@ from esphome.components import time +from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import uart -from esphome.const import CONF_ID, CONF_TIME_ID +from esphome.const import CONF_ID, CONF_TIME_ID, CONF_TRIGGER_ID, CONF_SENSOR_DATAPOINT DEPENDENCIES = ["uart"] CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS = "ignore_mcu_update_on_datapoints" +CONF_ON_DATAPOINT_UPDATE = "on_datapoint_update" +CONF_DATAPOINT_TYPE = "datapoint_type" + tuya_ns = cg.esphome_ns.namespace("tuya") Tuya = tuya_ns.class_("Tuya", cg.Component, uart.UARTDevice) +DPTYPE_ANY = "any" +DPTYPE_RAW = "raw" +DPTYPE_BOOL = "bool" +DPTYPE_INT = "int" +DPTYPE_UINT = "uint" +DPTYPE_STRING = "string" +DPTYPE_ENUM = "enum" +DPTYPE_BITMASK = "bitmask" + +DATAPOINT_TYPES = { + DPTYPE_ANY: tuya_ns.struct("TuyaDatapoint"), + DPTYPE_RAW: cg.std_vector.template(cg.uint8), + DPTYPE_BOOL: cg.bool_, + DPTYPE_INT: cg.int_, + DPTYPE_UINT: cg.uint32, + DPTYPE_STRING: cg.std_string, + DPTYPE_ENUM: cg.uint8, + DPTYPE_BITMASK: cg.uint32, +} + +DATAPOINT_TRIGGERS = { + DPTYPE_ANY: tuya_ns.class_( + "TuyaDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_ANY]), + ), + DPTYPE_RAW: tuya_ns.class_( + "TuyaRawDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_RAW]), + ), + DPTYPE_BOOL: tuya_ns.class_( + "TuyaBoolDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_BOOL]), + ), + DPTYPE_INT: tuya_ns.class_( + "TuyaIntDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_INT]), + ), + DPTYPE_UINT: tuya_ns.class_( + "TuyaUIntDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_UINT]), + ), + DPTYPE_STRING: tuya_ns.class_( + "TuyaStringDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_STRING]), + ), + DPTYPE_ENUM: tuya_ns.class_( + "TuyaEnumDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_ENUM]), + ), + DPTYPE_BITMASK: tuya_ns.class_( + "TuyaBitmaskDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_BITMASK]), + ), +} + + +def assign_declare_id(value): + value = value.copy() + value[CONF_TRIGGER_ID] = cv.declare_id( + DATAPOINT_TRIGGERS[value[CONF_DATAPOINT_TYPE]] + )(value[CONF_TRIGGER_ID].id) + return value + + CONF_TUYA_ID = "tuya_id" CONFIG_SCHEMA = ( cv.Schema( @@ -20,6 +88,18 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS): cv.ensure_list( cv.uint8_t ), + cv.Optional(CONF_ON_DATAPOINT_UPDATE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + DATAPOINT_TRIGGERS[DPTYPE_ANY] + ), + cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_DATAPOINT_TYPE, default=DPTYPE_ANY): cv.one_of( + *DATAPOINT_TRIGGERS, lower=True + ), + }, + extra_validators=assign_declare_id, + ), } ) .extend(cv.COMPONENT_SCHEMA) @@ -37,3 +117,10 @@ async def to_code(config): if CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS in config: for dp in config[CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS]: cg.add(var.add_ignore_mcu_update_on_datapoints(dp)) + for conf in config.get(CONF_ON_DATAPOINT_UPDATE, []): + trigger = cg.new_Pvariable( + conf[CONF_TRIGGER_ID], var, conf[CONF_SENSOR_DATAPOINT] + ) + await automation.build_automation( + trigger, [(DATAPOINT_TYPES[conf[CONF_DATAPOINT_TYPE]], "x")], conf + ) diff --git a/esphome/components/tuya/automation.cpp b/esphome/components/tuya/automation.cpp new file mode 100644 index 0000000000..eb1c170083 --- /dev/null +++ b/esphome/components/tuya/automation.cpp @@ -0,0 +1,66 @@ +#include "esphome/core/log.h" + +#include "automation.h" + +static const char *const TAG = "tuya.automation"; + +namespace esphome { +namespace tuya { + +void check_expected_datapoint(const TuyaDatapoint &dp, TuyaDatapointType expected) { + if (dp.type != expected) { + ESP_LOGW(TAG, "Tuya sensor %u expected datapoint type %#02hhX but got %#02hhX", dp.id, expected, dp.type); + } +} + +TuyaRawDatapointUpdateTrigger::TuyaRawDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::RAW); + this->trigger(dp.value_raw); + }); +} + +TuyaBoolDatapointUpdateTrigger::TuyaBoolDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::BOOLEAN); + this->trigger(dp.value_bool); + }); +} + +TuyaIntDatapointUpdateTrigger::TuyaIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::INTEGER); + this->trigger(dp.value_int); + }); +} + +TuyaUIntDatapointUpdateTrigger::TuyaUIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::INTEGER); + this->trigger(dp.value_uint); + }); +} + +TuyaStringDatapointUpdateTrigger::TuyaStringDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::STRING); + this->trigger(dp.value_string); + }); +} + +TuyaEnumDatapointUpdateTrigger::TuyaEnumDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::ENUM); + this->trigger(dp.value_enum); + }); +} + +TuyaBitmaskDatapointUpdateTrigger::TuyaBitmaskDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::BITMASK); + this->trigger(dp.value_bitmask); + }); +} + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/automation.h b/esphome/components/tuya/automation.h new file mode 100644 index 0000000000..d7706e1d60 --- /dev/null +++ b/esphome/components/tuya/automation.h @@ -0,0 +1,53 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "tuya.h" + +namespace esphome { +namespace tuya { + +class TuyaDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { this->trigger(dp); }); + } +}; + +class TuyaRawDatapointUpdateTrigger : public Trigger> { + public: + explicit TuyaRawDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaBoolDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaBoolDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaIntDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaUIntDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaUIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaStringDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaStringDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaEnumDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaEnumDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaBitmaskDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaBitmaskDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/binary_sensor/__init__.py b/esphome/components/tuya/binary_sensor/__init__.py index 65f13ea422..cd4a2db89f 100644 --- a/esphome/components/tuya/binary_sensor/__init__.py +++ b/esphome/components/tuya/binary_sensor/__init__.py @@ -1,14 +1,12 @@ from esphome.components import binary_sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] CODEOWNERS = ["@jesserockz"] -CONF_SENSOR_DATAPOINT = "sensor_datapoint" - TuyaBinarySensor = tuya_ns.class_( "TuyaBinarySensor", binary_sensor.BinarySensor, cg.Component ) diff --git a/esphome/components/tuya/sensor/__init__.py b/esphome/components/tuya/sensor/__init__.py index d87a2e7ce4..441400fa43 100644 --- a/esphome/components/tuya/sensor/__init__.py +++ b/esphome/components/tuya/sensor/__init__.py @@ -1,14 +1,12 @@ from esphome.components import sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] CODEOWNERS = ["@jesserockz"] -CONF_SENSOR_DATAPOINT = "sensor_datapoint" - TuyaSensor = tuya_ns.class_("TuyaSensor", sensor.Sensor, cg.Component) CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( diff --git a/esphome/components/tuya/text_sensor/__init__.py b/esphome/components/tuya/text_sensor/__init__.py new file mode 100644 index 0000000000..1989ca10e3 --- /dev/null +++ b/esphome/components/tuya/text_sensor/__init__.py @@ -0,0 +1,29 @@ +from esphome.components import text_sensor +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT +from .. import tuya_ns, CONF_TUYA_ID, Tuya + +DEPENDENCIES = ["tuya"] +CODEOWNERS = ["@dentra"] + +TuyaTextSensor = tuya_ns.class_("TuyaTextSensor", text_sensor.TextSensor, cg.Component) + +CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TuyaTextSensor), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await text_sensor.register_text_sensor(var, config) + + paren = await cg.get_variable(config[CONF_TUYA_ID]) + cg.add(var.set_tuya_parent(paren)) + + cg.add(var.set_sensor_id(config[CONF_SENSOR_DATAPOINT])) diff --git a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp new file mode 100644 index 0000000000..e939225453 --- /dev/null +++ b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp @@ -0,0 +1,35 @@ +#include "esphome/core/log.h" +#include "tuya_text_sensor.h" + +namespace esphome { +namespace tuya { + +static const char *const TAG = "tuya.text_sensor"; + +void TuyaTextSensor::setup() { + this->parent_->register_listener(this->sensor_id_, [this](const TuyaDatapoint &datapoint) { + switch (datapoint.type) { + case TuyaDatapointType::STRING: + ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, datapoint.value_string.c_str()); + this->publish_state(datapoint.value_string); + break; + case TuyaDatapointType::RAW: { + std::string data = hexencode(datapoint.value_raw); + ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, data.c_str()); + this->publish_state(data); + break; + } + default: + ESP_LOGW(TAG, "Unsupported data type for tuya text sensor %u: %#02hhX", datapoint.id, datapoint.type); + break; + } + }); +} + +void TuyaTextSensor::dump_config() { + ESP_LOGCONFIG(TAG, "Tuya Text Sensor:"); + ESP_LOGCONFIG(TAG, " Text Sensor has datapoint ID %u", this->sensor_id_); +} + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/text_sensor/tuya_text_sensor.h b/esphome/components/tuya/text_sensor/tuya_text_sensor.h new file mode 100644 index 0000000000..502ae5e8c7 --- /dev/null +++ b/esphome/components/tuya/text_sensor/tuya_text_sensor.h @@ -0,0 +1,24 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/tuya/tuya.h" +#include "esphome/components/text_sensor/text_sensor.h" + +namespace esphome { +namespace tuya { + +class TuyaTextSensor : public text_sensor::TextSensor, public Component { + public: + void setup() override; + void dump_config() override; + void set_sensor_id(uint8_t sensor_id) { this->sensor_id_ = sensor_id; } + + void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } + + protected: + Tuya *parent_; + uint8_t sensor_id_{0}; +}; + +} // namespace tuya +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 9006145dfe..3510e500f5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -579,6 +579,7 @@ CONF_SEND_EVERY = "send_every" CONF_SEND_FIRST_AT = "send_first_at" CONF_SENSING_PIN = "sensing_pin" CONF_SENSOR = "sensor" +CONF_SENSOR_DATAPOINT = "sensor_datapoint" CONF_SENSOR_ID = "sensor_id" CONF_SENSORS = "sensors" CONF_SEQUENCE = "sequence" From 556d071e7fa4ac62082aeecfe039f5875dec464c Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Tue, 30 Nov 2021 00:30:45 -0600 Subject: [PATCH 400/549] Fix 8266 SPI Clock Polarity Setting (#2836) --- esphome/components/spi/spi.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 601a5c5a7e..6c3fd17e56 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -195,7 +195,14 @@ class SPIComponent : public Component { void enable(GPIOPin *cs) { #ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { - uint8_t data_mode = (uint8_t(CLOCK_POLARITY) << 1) | uint8_t(CLOCK_PHASE); + uint8_t data_mode = SPI_MODE0; + if (!CLOCK_POLARITY && CLOCK_PHASE) { + data_mode = SPI_MODE1; + } else if (CLOCK_POLARITY && !CLOCK_PHASE) { + data_mode = SPI_MODE2; + } else if (CLOCK_POLARITY && CLOCK_PHASE) { + data_mode = SPI_MODE3; + } SPISettings settings(DATA_RATE, BIT_ORDER, data_mode); this->hw_spi_->beginTransaction(settings); } else { From ab027a6ae2df3fabb851686efe82b7ea2f350fd9 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 30 Nov 2021 09:35:52 +0100 Subject: [PATCH 401/549] Fix too-broad matcher for custom CI script (#2829) --- .github/workflows/matchers/ci-custom.json | 2 +- script/ci-custom.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/matchers/ci-custom.json b/.github/workflows/matchers/ci-custom.json index 9888dbb440..1d5f2551cd 100644 --- a/.github/workflows/matchers/ci-custom.json +++ b/.github/workflows/matchers/ci-custom.json @@ -4,7 +4,7 @@ "owner": "ci-custom", "pattern": [ { - "regexp": "^(.*):(\\d+):(\\d+):\\s+(.*)$", + "regexp": "^(.*):(\\d+):(\\d+):\\s+lint:\\s+(.*)$", "file": 1, "line": 2, "column": 3, diff --git a/script/ci-custom.py b/script/ci-custom.py index de2dfda44d..9acfbcdc23 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -660,7 +660,9 @@ for fname in files: run_checks(LINT_POST_CHECKS, "POST") for f, errs in sorted(errors.items()): - err_str = (f"{styled(colorama.Style.BRIGHT, f'{f}:{lineno}:{col}:')} {msg}\n" for lineno, col, msg in errs) + bold = functools.partial(styled, colorama.Style.BRIGHT) + bold_red = functools.partial(styled, (colorama.Style.BRIGHT, colorama.Fore.RED)) + err_str = (f"{bold(f'{f}:{lineno}:{col}:')} {bold_red('lint:')} {msg}\n" for lineno, col, msg in errs) print_error_for_file(f, "\n".join(err_str)) if args.print_slowest: From 24dfecb6f03e66fa4e88c462d6092c5ba7e61372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Panella?= Date: Tue, 30 Nov 2021 09:08:00 -0600 Subject: [PATCH 402/549] cse7766: add energy sensor (#2822) --- esphome/components/cse7766/cse7766.cpp | 16 ++++++++++++++++ esphome/components/cse7766/cse7766.h | 4 ++++ esphome/components/cse7766/sensor.py | 14 ++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index 87bc4c4bdf..55e1ec82cf 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -90,6 +90,7 @@ void CSE7766Component::parse_data_() { uint32_t power_cycle = this->get_24_bit_uint_(17); uint8_t adj = this->raw_data_[20]; + uint32_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22]; bool power_ok = true; bool voltage_ok = true; @@ -127,6 +128,18 @@ void CSE7766Component::parse_data_() { power = power_calib / float(power_cycle); this->power_acc_ += power; this->power_counts_ += 1; + + uint32_t difference; + if (this->cf_pulses_last_ == 0) + this->cf_pulses_last_ = cf_pulses; + + if (cf_pulses < this->cf_pulses_last_) { + difference = cf_pulses + (0x10000 - this->cf_pulses_last_); + } else { + difference = cf_pulses - this->cf_pulses_last_; + } + this->cf_pulses_last_ = cf_pulses; + this->energy_total_ += difference * float(power_calib) / 1000000.0 / 3600.0; } if ((adj & 0x20) == 0x20 && current_ok && voltage_ok && power != 0.0) { @@ -152,6 +165,8 @@ void CSE7766Component::update() { this->current_sensor_->publish_state(current); if (this->power_sensor_ != nullptr) this->power_sensor_->publish_state(power); + if (this->energy_sensor_ != nullptr) + this->energy_sensor_->publish_state(this->energy_total_); this->voltage_acc_ = 0.0f; this->current_acc_ = 0.0f; @@ -172,6 +187,7 @@ void CSE7766Component::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_); this->check_uart_settings(4800); } diff --git a/esphome/components/cse7766/cse7766.h b/esphome/components/cse7766/cse7766.h index 6cacfee072..d6062c251c 100644 --- a/esphome/components/cse7766/cse7766.h +++ b/esphome/components/cse7766/cse7766.h @@ -12,6 +12,7 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice { 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; } + void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } void loop() override; float get_setup_priority() const override; @@ -29,9 +30,12 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice { sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr}; sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; float voltage_acc_{0.0f}; float current_acc_{0.0f}; float power_acc_{0.0f}; + float energy_total_{0.0f}; + uint32_t cf_pulses_last_{0}; uint32_t voltage_counts_{0}; uint32_t current_counts_{0}; uint32_t power_counts_{0}; diff --git a/esphome/components/cse7766/sensor.py b/esphome/components/cse7766/sensor.py index 1c8efc4f72..2f48aff0aa 100644 --- a/esphome/components/cse7766/sensor.py +++ b/esphome/components/cse7766/sensor.py @@ -3,16 +3,20 @@ import esphome.config_validation as cv from esphome.components import sensor, uart from esphome.const import ( CONF_CURRENT, + CONF_ENERGY, CONF_ID, CONF_POWER, CONF_VOLTAGE, DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, + UNIT_WATT_HOURS, ) DEPENDENCIES = ["uart"] @@ -44,6 +48,12 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_ENERGY): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), } ) .extend(cv.polling_component_schema("60s")) @@ -71,3 +81,7 @@ async def to_code(config): conf = config[CONF_POWER] sens = await sensor.new_sensor(conf) cg.add(var.set_power_sensor(sens)) + if CONF_ENERGY in config: + conf = config[CONF_ENERGY] + sens = await sensor.new_sensor(conf) + cg.add(var.set_energy_sensor(sens)) From cd018ad3a59e941a2bc45d2757c10dd886187d01 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Tue, 30 Nov 2021 16:12:52 +0100 Subject: [PATCH 403/549] Burst read for BME280, to reduce spurious spikes (#2809) --- esphome/components/bme280/bme280.cpp | 33 ++++++++++++++-------------- esphome/components/bme280/bme280.h | 6 ++--- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/esphome/components/bme280/bme280.cpp b/esphome/components/bme280/bme280.cpp index 18386430a2..627072443e 100644 --- a/esphome/components/bme280/bme280.cpp +++ b/esphome/components/bme280/bme280.cpp @@ -33,6 +33,7 @@ static const uint8_t BME280_REGISTER_CONTROLHUMID = 0xF2; static const uint8_t BME280_REGISTER_STATUS = 0xF3; static const uint8_t BME280_REGISTER_CONTROL = 0xF4; static const uint8_t BME280_REGISTER_CONFIG = 0xF5; +static const uint8_t BME280_REGISTER_MEASUREMENTS = 0xF7; static const uint8_t BME280_REGISTER_PRESSUREDATA = 0xF7; static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA; static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD; @@ -178,21 +179,27 @@ void BME280Component::update() { return; } - float meas_time = 1.5; + float meas_time = 1.5f; meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_); meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f; meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f; this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { + uint8_t data[8]; + if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) { + ESP_LOGW(TAG, "Error reading registers."); + this->status_set_warning(); + return; + } int32_t t_fine = 0; - float temperature = this->read_temperature_(&t_fine); + float temperature = this->read_temperature_(data, &t_fine); if (std::isnan(temperature)) { ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values."); this->status_set_warning(); return; } - float pressure = this->read_pressure_(t_fine); - float humidity = this->read_humidity_(t_fine); + float pressure = this->read_pressure_(data, t_fine); + float humidity = this->read_humidity_(data, t_fine); ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity); if (this->temperature_sensor_ != nullptr) @@ -204,11 +211,8 @@ void BME280Component::update() { this->status_clear_warning(); }); } -float BME280Component::read_temperature_(int32_t *t_fine) { - uint8_t data[3]; - if (!this->read_bytes(BME280_REGISTER_TEMPDATA, data, 3)) - return NAN; - int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); +float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) { + int32_t adc = ((data[3] & 0xFF) << 16) | ((data[4] & 0xFF) << 8) | (data[5] & 0xFF); adc >>= 4; if (adc == 0x80000) // temperature was disabled @@ -226,10 +230,7 @@ float BME280Component::read_temperature_(int32_t *t_fine) { return temperature / 100.0f; } -float BME280Component::read_pressure_(int32_t t_fine) { - uint8_t data[3]; - if (!this->read_bytes(BME280_REGISTER_PRESSUREDATA, data, 3)) - return NAN; +float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) { int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); adc >>= 4; if (adc == 0x80000) @@ -265,9 +266,9 @@ float BME280Component::read_pressure_(int32_t t_fine) { return (p / 256.0f) / 100.0f; } -float BME280Component::read_humidity_(int32_t t_fine) { - uint16_t raw_adc; - if (!this->read_byte_16(BME280_REGISTER_HUMIDDATA, &raw_adc) || raw_adc == 0x8000) +float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) { + uint16_t raw_adc = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF); + if (raw_adc == 0x8000) return NAN; int32_t adc = raw_adc; diff --git a/esphome/components/bme280/bme280.h b/esphome/components/bme280/bme280.h index 82724d6887..8511f73382 100644 --- a/esphome/components/bme280/bme280.h +++ b/esphome/components/bme280/bme280.h @@ -82,11 +82,11 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice { protected: /// Read the temperature value and store the calculated ambient temperature in t_fine. - float read_temperature_(int32_t *t_fine); + float read_temperature_(const uint8_t *data, int32_t *t_fine); /// Read the pressure value in hPa using the provided t_fine value. - float read_pressure_(int32_t t_fine); + float read_pressure_(const uint8_t *data, int32_t t_fine); /// Read the humidity value in % using the provided t_fine value. - float read_humidity_(int32_t t_fine); + float read_humidity_(const uint8_t *data, int32_t t_fine); uint8_t read_u8_(uint8_t a_register); uint16_t read_u16_le_(uint8_t a_register); int16_t read_s16_le_(uint8_t a_register); From 0f47ffd9085ee7655d49b4f567e2d9702337b32f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Nov 2021 16:17:48 +0100 Subject: [PATCH 404/549] Bump aioesphomeapi from 10.2.0 to 10.6.0 (#2840) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c4b211283d..c99c5efad9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 esphome-dashboard==20211021.1 -aioesphomeapi==10.2.0 +aioesphomeapi==10.6.0 zeroconf==0.36.13 # esp-idf requires this, but doesn't bundle it by default From b32b918936cc1cdc796de27f2e022b743fa6c421 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 1 Dec 2021 04:18:21 +1300 Subject: [PATCH 405/549] Button device class (#2835) --- esphome/components/api/api.proto | 1 + esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 9 ++++++++ esphome/components/api/api_pb2.h | 1 + esphome/components/button/__init__.py | 23 +++++++++++++++++++ esphome/components/button/button.cpp | 7 ++++++ esphome/components/button/button.h | 7 ++++++ esphome/components/mqtt/mqtt_button.cpp | 5 ++++ esphome/components/mqtt/mqtt_button.h | 2 +- esphome/components/restart/button/__init__.py | 6 +++-- esphome/const.py | 6 ++++- 11 files changed, 64 insertions(+), 4 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index eaad4b8d07..3e2c806135 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -960,6 +960,7 @@ message ListEntitiesButtonResponse { string icon = 5; bool disabled_by_default = 6; EntityCategory entity_category = 7; + string device_class = 8; } message ButtonCommandRequest { option (id) = 62; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 22b896a788..8367afc042 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -684,6 +684,7 @@ bool APIConnection::send_button_info(button::Button *button) { msg.icon = button->get_icon(); msg.disabled_by_default = button->is_disabled_by_default(); msg.entity_category = static_cast(button->get_entity_category()); + msg.device_class = button->get_device_class(); return this->send_list_entities_button_response(msg); } void APIConnection::button_command(const ButtonCommandRequest &msg) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 169349d995..b6974de08e 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -4179,6 +4179,10 @@ bool ListEntitiesButtonResponse::decode_length(uint32_t field_id, ProtoLengthDel this->icon = value.as_string(); return true; } + case 8: { + this->device_class = value.as_string(); + return true; + } default: return false; } @@ -4201,6 +4205,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); + buffer.encode_string(8, this->device_class); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesButtonResponse::dump_to(std::string &out) const { @@ -4234,6 +4239,10 @@ void ListEntitiesButtonResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" device_class: "); + out.append("'").append(this->device_class).append("'"); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 82fd8de687..4d1f658910 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1050,6 +1050,7 @@ class ListEntitiesButtonResponse : public ProtoMessage { std::string icon{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; + std::string device_class{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/button/__init__.py b/esphome/components/button/__init__.py index 495a85b6b4..1e248ddf07 100644 --- a/esphome/components/button/__init__.py +++ b/esphome/components/button/__init__.py @@ -4,12 +4,15 @@ from esphome import automation from esphome.automation import maybe_simple_id from esphome.components import mqtt from esphome.const import ( + CONF_DEVICE_CLASS, CONF_ENTITY_CATEGORY, CONF_ICON, CONF_ID, CONF_ON_PRESS, CONF_TRIGGER_ID, CONF_MQTT_ID, + DEVICE_CLASS_RESTART, + DEVICE_CLASS_UPDATE, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_helpers import setup_entity @@ -17,6 +20,11 @@ from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True +DEVICE_CLASSES = [ + DEVICE_CLASS_RESTART, + DEVICE_CLASS_UPDATE, +] + button_ns = cg.esphome_ns.namespace("button") Button = button_ns.class_("Button", cg.EntityBase) ButtonPtr = Button.operator("ptr") @@ -27,10 +35,13 @@ ButtonPressTrigger = button_ns.class_( "ButtonPressTrigger", automation.Trigger.template() ) +validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") + BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent), + cv.Optional(CONF_DEVICE_CLASS): validate_device_class, cv.Optional(CONF_ON_PRESS): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger), @@ -45,6 +56,7 @@ _UNDEF = object() def button_schema( icon: str = _UNDEF, entity_category: str = _UNDEF, + device_class: str = _UNDEF, ) -> cv.Schema: schema = BUTTON_SCHEMA if icon is not _UNDEF: @@ -57,6 +69,14 @@ def button_schema( ): cv.entity_category } ) + if device_class is not _UNDEF: + schema = schema.extend( + { + cv.Optional( + CONF_DEVICE_CLASS, default=device_class + ): validate_device_class + } + ) return schema @@ -67,6 +87,9 @@ async def setup_button_core_(var, config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + if CONF_DEVICE_CLASS in config: + cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) + if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp index fe39b1e458..d57b46e9aa 100644 --- a/esphome/components/button/button.cpp +++ b/esphome/components/button/button.cpp @@ -17,5 +17,12 @@ void Button::press() { void Button::add_on_press_callback(std::function &&callback) { this->press_callback_.add(std::move(callback)); } uint32_t Button::hash_base() { return 1495763804UL; } +void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } +std::string Button::get_device_class() { + if (this->device_class_.has_value()) + return *this->device_class_; + return ""; +} + } // namespace button } // namespace esphome diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h index 954afa0ab9..b21a96b8e1 100644 --- a/esphome/components/button/button.h +++ b/esphome/components/button/button.h @@ -36,6 +36,12 @@ class Button : public EntityBase { */ void add_on_press_callback(std::function &&callback); + /// Set the Home Assistant device class (see button::device_class). + void set_device_class(const std::string &device_class); + + /// Get the device class for this button. + std::string get_device_class(); + protected: /** You should implement this virtual method if you want to create your own button. */ @@ -44,6 +50,7 @@ class Button : public EntityBase { uint32_t hash_base() override; CallbackManager press_callback_{}; + optional device_class_{}; }; } // namespace button diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index 5a0d14b648..25ff327cf9 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -30,6 +30,11 @@ void MQTTButtonComponent::dump_config() { LOG_MQTT_COMPONENT(true, true); } +void MQTTButtonComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { + if (!this->button_->get_device_class().empty()) + root[MQTT_DEVICE_CLASS] = this->button_->get_device_class(); +} + std::string MQTTButtonComponent::component_type() const { return "button"; } const EntityBase *MQTTButtonComponent::get_entity() const { return this->button_; } diff --git a/esphome/components/mqtt/mqtt_button.h b/esphome/components/mqtt/mqtt_button.h index a7e60db380..66e4b2609f 100644 --- a/esphome/components/mqtt/mqtt_button.h +++ b/esphome/components/mqtt/mqtt_button.h @@ -23,7 +23,7 @@ class MQTTButtonComponent : public mqtt::MQTTComponent { /// Buttons do not send a state so just return true. bool send_initial_state() override { return true; } - void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override {} + void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; protected: /// "button" component type. diff --git a/esphome/components/restart/button/__init__.py b/esphome/components/restart/button/__init__.py index 257a8e35f7..1a0e9cdc3d 100644 --- a/esphome/components/restart/button/__init__.py +++ b/esphome/components/restart/button/__init__.py @@ -3,15 +3,17 @@ import esphome.config_validation as cv from esphome.components import button from esphome.const import ( CONF_ID, + DEVICE_CLASS_RESTART, ENTITY_CATEGORY_CONFIG, - ICON_RESTART, ) restart_ns = cg.esphome_ns.namespace("restart") RestartButton = restart_ns.class_("RestartButton", button.Button, cg.Component) CONFIG_SCHEMA = ( - button.button_schema(icon=ICON_RESTART, entity_category=ENTITY_CATEGORY_CONFIG) + button.button_schema( + device_class=DEVICE_CLASS_RESTART, entity_category=ENTITY_CATEGORY_CONFIG + ) .extend({cv.GenerateID(): cv.declare_id(RestartButton)}) .extend(cv.COMPONENT_SCHEMA) ) diff --git a/esphome/const.py b/esphome/const.py index 3510e500f5..740e38cf44 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -865,7 +865,6 @@ DEVICE_CLASS_SAFETY = "safety" DEVICE_CLASS_SMOKE = "smoke" DEVICE_CLASS_SOUND = "sound" DEVICE_CLASS_TAMPER = "tamper" -DEVICE_CLASS_UPDATE = "update" DEVICE_CLASS_VIBRATION = "vibration" DEVICE_CLASS_WINDOW = "window" # device classes of both binary_sensor and sensor component @@ -897,6 +896,11 @@ DEVICE_CLASS_TEMPERATURE = "temperature" DEVICE_CLASS_TIMESTAMP = "timestamp" DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds" DEVICE_CLASS_VOLTAGE = "voltage" +# device classes of both binary_sensor and button component +DEVICE_CLASS_UPDATE = "update" +# device classes of button component +DEVICE_CLASS_RESTART = "restart" + # state classes STATE_CLASS_NONE = "" From b5a0e8b2c0d016ea384f4f962e0f0e4c61a8dc85 Mon Sep 17 00:00:00 2001 From: puuu Date: Wed, 1 Dec 2021 00:20:59 +0900 Subject: [PATCH 406/549] Implement unit_of_measurement for number component (#2804) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/api/api.proto | 1 + esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 9 +++++++++ esphome/components/api/api_pb2.h | 1 + esphome/components/mqtt/mqtt_number.cpp | 2 ++ esphome/components/number/__init__.py | 4 ++++ esphome/components/number/number.cpp | 9 +++++++++ esphome/components/number/number.h | 9 +++++++++ tests/test5.yaml | 1 + 9 files changed, 37 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 3e2c806135..0f7a5839ab 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -885,6 +885,7 @@ message ListEntitiesNumberResponse { float step = 8; bool disabled_by_default = 9; EntityCategory entity_category = 10; + string unit_of_measurement = 11; } message NumberStateResponse { option (id) = 50; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 8367afc042..b41a7633a8 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -619,6 +619,7 @@ bool APIConnection::send_number_info(number::Number *number) { msg.icon = number->get_icon(); msg.disabled_by_default = number->is_disabled_by_default(); msg.entity_category = static_cast(number->get_entity_category()); + msg.unit_of_measurement = number->traits.get_unit_of_measurement(); msg.min_value = number->traits.get_min_value(); msg.max_value = number->traits.get_max_value(); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index b6974de08e..62cecb7818 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -3780,6 +3780,10 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel this->icon = value.as_string(); return true; } + case 11: { + this->unit_of_measurement = value.as_string(); + return true; + } default: return false; } @@ -3817,6 +3821,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(8, this->step); buffer.encode_bool(9, this->disabled_by_default); buffer.encode_enum(10, this->entity_category); + buffer.encode_string(11, this->unit_of_measurement); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { @@ -3865,6 +3870,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" unit_of_measurement: "); + out.append("'").append(this->unit_of_measurement).append("'"); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 4d1f658910..4866f50c9b 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -957,6 +957,7 @@ class ListEntitiesNumberResponse : public ProtoMessage { float step{0.0f}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; + std::string unit_of_measurement{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 337013055a..bf8b6b39c5 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -43,6 +43,8 @@ void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo root[MQTT_MIN] = traits.get_min_value(); root[MQTT_MAX] = traits.get_max_value(); root[MQTT_STEP] = traits.get_step(); + if (!this->number_->traits.get_unit_of_measurement().empty()) + root[MQTT_UNIT_OF_MEASUREMENT] = this->number_->traits.get_unit_of_measurement(); config.command_topic = true; } diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 1da25caafe..ae15704a91 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_ON_VALUE, CONF_ON_VALUE_RANGE, CONF_TRIGGER_ID, + CONF_UNIT_OF_MEASUREMENT, CONF_MQTT_ID, CONF_VALUE, ) @@ -58,6 +59,7 @@ NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e }, cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), ), + cv.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string_strict, } ) @@ -86,6 +88,8 @@ async def setup_number_core_( cg.add(trigger.set_max(template_)) await automation.build_automation(trigger, [(float, "x")], conf) + if CONF_UNIT_OF_MEASUREMENT in config: + cg.add(var.traits.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT])) if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index 57a5c7c4bd..99a2c04a22 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -41,6 +41,15 @@ void Number::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } +std::string NumberTraits::get_unit_of_measurement() { + if (this->unit_of_measurement_.has_value()) + return *this->unit_of_measurement_; + return ""; +} +void NumberTraits::set_unit_of_measurement(const std::string &unit_of_measurement) { + this->unit_of_measurement_ = unit_of_measurement; +} + uint32_t Number::hash_base() { return 2282307003UL; } } // namespace number diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index ed104fb477..b3214913d9 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -13,6 +13,9 @@ namespace number { if (!(obj)->get_icon().empty()) { \ ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ } \ + if (!(obj)->traits.get_unit_of_measurement().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->traits.get_unit_of_measurement().c_str()); \ + } \ } class Number; @@ -42,10 +45,16 @@ class NumberTraits { void set_step(float step) { step_ = step; } float get_step() const { return step_; } + /// 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); + protected: float min_value_ = NAN; float max_value_ = NAN; float step_ = NAN; + optional unit_of_measurement_; ///< Unit of measurement override }; /** Base-class for all numbers. diff --git a/tests/test5.yaml b/tests/test5.yaml index f1fb786fe5..708db55044 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -97,6 +97,7 @@ number: max_value: 100 min_value: 0 step: 5 + unit_of_measurement: '%' select: - platform: template From d9513e5ff2905d654a35249722df4528158a6bc3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 1 Dec 2021 08:11:38 +1300 Subject: [PATCH 407/549] Number mode (#2838) --- esphome/components/api/api.proto | 6 ++++++ esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 21 +++++++++++++++++++++ esphome/components/api/api_pb2.h | 6 ++++++ esphome/components/mqtt/mqtt_const.h | 1 + esphome/components/mqtt/mqtt_number.cpp | 10 ++++++++++ esphome/components/number/__init__.py | 12 ++++++++++++ esphome/components/number/number.h | 11 +++++++++++ tests/test5.yaml | 3 ++- 9 files changed, 70 insertions(+), 1 deletion(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 0f7a5839ab..dca722dca5 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -869,6 +869,11 @@ message ClimateCommandRequest { } // ==================== NUMBER ==================== +enum NumberMode { + NUMBER_MODE_AUTO = 0; + NUMBER_MODE_BOX = 1; + NUMBER_MODE_SLIDER = 2; +} message ListEntitiesNumberResponse { option (id) = 49; option (source) = SOURCE_SERVER; @@ -886,6 +891,7 @@ message ListEntitiesNumberResponse { bool disabled_by_default = 9; EntityCategory entity_category = 10; string unit_of_measurement = 11; + NumberMode mode = 12; } message NumberStateResponse { option (id) = 50; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index b41a7633a8..92699df0da 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -620,6 +620,7 @@ bool APIConnection::send_number_info(number::Number *number) { msg.disabled_by_default = number->is_disabled_by_default(); msg.entity_category = static_cast(number->get_entity_category()); msg.unit_of_measurement = number->traits.get_unit_of_measurement(); + msg.mode = static_cast(number->traits.get_mode()); msg.min_value = number->traits.get_min_value(); msg.max_value = number->traits.get_max_value(); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 62cecb7818..9228be0860 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -266,6 +266,18 @@ template<> const char *proto_enum_to_string(enums::Climate return "UNKNOWN"; } } +template<> const char *proto_enum_to_string(enums::NumberMode value) { + switch (value) { + case enums::NUMBER_MODE_AUTO: + return "NUMBER_MODE_AUTO"; + case enums::NUMBER_MODE_BOX: + return "NUMBER_MODE_BOX"; + case enums::NUMBER_MODE_SLIDER: + return "NUMBER_MODE_SLIDER"; + default: + return "UNKNOWN"; + } +} bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -3758,6 +3770,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->entity_category = value.as_enum(); return true; } + case 12: { + this->mode = value.as_enum(); + return true; + } default: return false; } @@ -3822,6 +3838,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(9, this->disabled_by_default); buffer.encode_enum(10, this->entity_category); buffer.encode_string(11, this->unit_of_measurement); + buffer.encode_enum(12, this->mode); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { @@ -3874,6 +3891,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append(" unit_of_measurement: "); out.append("'").append(this->unit_of_measurement).append("'"); out.append("\n"); + + out.append(" mode: "); + out.append(proto_enum_to_string(this->mode)); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 4866f50c9b..e92b2fa4b6 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -123,6 +123,11 @@ enum ClimatePreset : uint32_t { CLIMATE_PRESET_SLEEP = 6, CLIMATE_PRESET_ACTIVITY = 7, }; +enum NumberMode : uint32_t { + NUMBER_MODE_AUTO = 0, + NUMBER_MODE_BOX = 1, + NUMBER_MODE_SLIDER = 2, +}; } // namespace enums @@ -958,6 +963,7 @@ class ListEntitiesNumberResponse : public ProtoMessage { bool disabled_by_default{false}; enums::EntityCategory entity_category{}; std::string unit_of_measurement{}; + enums::NumberMode mode{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h index 1d5e22efde..8134a6b53e 100644 --- a/esphome/components/mqtt/mqtt_const.h +++ b/esphome/components/mqtt/mqtt_const.h @@ -514,6 +514,7 @@ constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area"; // Additional MQTT fields where no abbreviation is defined in HA source constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category"; +constexpr const char *const MQTT_MODE = "mode"; } // namespace mqtt } // namespace esphome diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index bf8b6b39c5..18e3a61417 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -45,6 +45,16 @@ void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo root[MQTT_STEP] = traits.get_step(); if (!this->number_->traits.get_unit_of_measurement().empty()) root[MQTT_UNIT_OF_MEASUREMENT] = this->number_->traits.get_unit_of_measurement(); + switch (this->number_->traits.get_mode()) { + case NUMBER_MODE_AUTO: + break; + case NUMBER_MODE_BOX: + root[MQTT_MODE] = "box"; + break; + case NUMBER_MODE_SLIDER: + root[MQTT_MODE] = "slider"; + break; + } config.command_topic = true; } diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index ae15704a91..71e288a4cc 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -7,6 +7,7 @@ from esphome.const import ( CONF_ABOVE, CONF_BELOW, CONF_ID, + CONF_MODE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, CONF_TRIGGER_ID, @@ -40,6 +41,14 @@ NumberInRangeCondition = number_ns.class_( "NumberInRangeCondition", automation.Condition ) +NumberMode = number_ns.enum("NumberMode") + +NUMBER_MODES = { + "AUTO": NumberMode.NUMBER_MODE_AUTO, + "BOX": NumberMode.NUMBER_MODE_BOX, + "SLIDER": NumberMode.NUMBER_MODE_SLIDER, +} + icon = cv.icon NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( @@ -60,6 +69,7 @@ NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), ), cv.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string_strict, + cv.Optional(CONF_MODE, default="AUTO"): cv.enum(NUMBER_MODES, upper=True), } ) @@ -74,6 +84,8 @@ async def setup_number_core_( if step is not None: cg.add(var.traits.set_step(step)) + cg.add(var.traits.set_mode(config[CONF_MODE])) + for conf in config.get(CONF_ON_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [(float, "x")], conf) diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index b3214913d9..40fdfceec1 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -36,6 +36,12 @@ class NumberCall { optional value_; }; +enum NumberMode : uint8_t { + NUMBER_MODE_AUTO = 0, + NUMBER_MODE_BOX = 1, + NUMBER_MODE_SLIDER = 2, +}; + class NumberTraits { public: void set_min_value(float min_value) { min_value_ = min_value; } @@ -50,11 +56,16 @@ class NumberTraits { /// Manually set the unit of measurement. void set_unit_of_measurement(const std::string &unit_of_measurement); + // Get/set the frontend mode. + NumberMode get_mode() const { return this->mode_; } + void set_mode(NumberMode mode) { this->mode_ = mode; } + protected: float min_value_ = NAN; float max_value_ = NAN; float step_ = NAN; optional unit_of_measurement_; ///< Unit of measurement override + NumberMode mode_{NUMBER_MODE_AUTO}; }; /** Base-class for all numbers. diff --git a/tests/test5.yaml b/tests/test5.yaml index 708db55044..aa3d057252 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -10,7 +10,7 @@ esp32: framework: type: esp-idf advanced: - ignore_efuse_mac_crc: true + ignore_efuse_mac_crc: true wifi: networks: @@ -98,6 +98,7 @@ number: min_value: 0 step: 5 unit_of_measurement: '%' + mode: slider select: - platform: template From 5719cc1a248f8be62fbfa3a4551a18b4194e52fe Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 1 Dec 2021 16:54:30 +1300 Subject: [PATCH 408/549] Bump esphome-dashboard to 20211201.0 (#2842) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c99c5efad9..6061476802 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20211021.1 +esphome-dashboard==20211201.0 aioesphomeapi==10.6.0 zeroconf==0.36.13 From 08cbb97ec9bfd7b5dec872b3cb25bc7c2b9684ca Mon Sep 17 00:00:00 2001 From: mechanarchy <1166756+mechanarchy@users.noreply.github.com> Date: Wed, 1 Dec 2021 15:10:25 +1100 Subject: [PATCH 409/549] Allow Git credentials to be loaded from secrets (#2825) --- .../components/external_components/__init__.py | 6 ++++++ esphome/components/packages/__init__.py | 6 ++++++ esphome/git.py | 15 ++++++++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index e0548e8981..d0153f6104 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -12,6 +12,8 @@ from esphome.const import ( CONF_TYPE, CONF_EXTERNAL_COMPONENTS, CONF_PATH, + CONF_USERNAME, + CONF_PASSWORD, ) from esphome.core import CORE from esphome import git, loader @@ -27,6 +29,8 @@ TYPE_LOCAL = "local" GIT_SCHEMA = { cv.Required(CONF_URL): cv.url, cv.Optional(CONF_REF): cv.git_ref, + cv.Optional(CONF_USERNAME): cv.string, + cv.Optional(CONF_PASSWORD): cv.string, } LOCAL_SCHEMA = { cv.Required(CONF_PATH): cv.directory, @@ -99,6 +103,8 @@ def _process_git_config(config: dict, refresh) -> str: ref=config.get(CONF_REF), refresh=refresh, domain=DOMAIN, + username=config.get(CONF_USERNAME), + password=config.get(CONF_PASSWORD), ) if (repo_dir / "esphome" / "components").is_dir(): diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index df0f0de13d..7483d65b9d 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -10,6 +10,8 @@ from esphome.const import ( CONF_REF, CONF_REFRESH, CONF_URL, + CONF_USERNAME, + CONF_PASSWORD, ) import esphome.config_validation as cv @@ -93,6 +95,8 @@ BASE_SCHEMA = cv.All( cv.Schema( { cv.Required(CONF_URL): cv.url, + cv.Optional(CONF_USERNAME): cv.string, + cv.Optional(CONF_PASSWORD): cv.string, cv.Exclusive(CONF_FILE, "files"): validate_yaml_filename, cv.Exclusive(CONF_FILES, "files"): cv.All( cv.ensure_list(validate_yaml_filename), @@ -124,6 +128,8 @@ def _process_base_package(config: dict) -> dict: ref=config.get(CONF_REF), refresh=config[CONF_REFRESH], domain=DOMAIN, + username=config.get(CONF_USERNAME), + password=config.get(CONF_PASSWORD), ) files: str = config[CONF_FILES] diff --git a/esphome/git.py b/esphome/git.py index 25d893b2f5..64c8d6a6b7 100644 --- a/esphome/git.py +++ b/esphome/git.py @@ -2,6 +2,7 @@ from pathlib import Path import subprocess import hashlib import logging +import urllib.parse from datetime import datetime @@ -36,9 +37,21 @@ def _compute_destination_path(key: str, domain: str) -> Path: def clone_or_update( - *, url: str, ref: str = None, refresh: TimePeriodSeconds, domain: str + *, + url: str, + ref: str = None, + refresh: TimePeriodSeconds, + domain: str, + username: str = None, + password: str = None, ) -> Path: key = f"{url}@{ref}" + + if username is not None and password is not None: + url = url.replace( + "://", f"://{urllib.parse.quote(username)}:{urllib.parse.quote(password)}@" + ) + repo_dir = _compute_destination_path(key, domain) fetch_pr_branch = ref is not None and ref.startswith("pull/") if not repo_dir.is_dir(): From cbc1334b8dd5e541fa7f934cc613675625c90934 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 05:11:21 +0100 Subject: [PATCH 410/549] Fix compile warning in Tuya automations (#2837) --- esphome/components/tuya/automation.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/tuya/automation.cpp b/esphome/components/tuya/automation.cpp index eb1c170083..a8cfd098f1 100644 --- a/esphome/components/tuya/automation.cpp +++ b/esphome/components/tuya/automation.cpp @@ -9,7 +9,8 @@ namespace tuya { void check_expected_datapoint(const TuyaDatapoint &dp, TuyaDatapointType expected) { if (dp.type != expected) { - ESP_LOGW(TAG, "Tuya sensor %u expected datapoint type %#02hhX but got %#02hhX", dp.id, expected, dp.type); + ESP_LOGW(TAG, "Tuya sensor %u expected datapoint type %#02hhX but got %#02hhX", dp.id, + static_cast(expected), static_cast(dp.type)); } } From bfeb0b36397661e72c5441c1a399357f590ad2ca Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 05:12:14 +0100 Subject: [PATCH 411/549] Add problem matcher for Python formatting errors (#2833) --- .github/workflows/matchers/lint-python.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/matchers/lint-python.json b/.github/workflows/matchers/lint-python.json index e88f74f6f6..6a09f04770 100644 --- a/.github/workflows/matchers/lint-python.json +++ b/.github/workflows/matchers/lint-python.json @@ -1,5 +1,16 @@ { "problemMatcher": [ + { + "owner": "black", + "severity": "error", + "pattern": [ + { + "regexp": "^(.*): (Please format this file with the black formatter)", + "file": 1, + "message": 2 + } + ] + }, { "owner": "flake8", "severity": "error", From c9190574a95de84bfca91e017944c4ccd8dd2001 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 05:14:25 +0100 Subject: [PATCH 412/549] Fix CI check for Windows line endings (#2831) --- script/ci-custom.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/script/ci-custom.py b/script/ci-custom.py index 9acfbcdc23..52ac4025ca 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -20,7 +20,7 @@ def find_all(a_str, sub): # Optimization: If str is not in whole text, then do not try # on each line return - for i, line in enumerate(a_str.splitlines()): + for i, line in enumerate(a_str.split('\n')): column = 0 while True: column = line.find(sub, column) @@ -172,7 +172,7 @@ def lint_re_check(regex, **kwargs): return decorator -def lint_content_find_check(find, **kwargs): +def lint_content_find_check(find, only_first=False, **kwargs): decor = lint_content_check(**kwargs) def decorator(func): @@ -185,6 +185,8 @@ def lint_content_find_check(find, **kwargs): for line, col in find_all(content, find_): err = func(fname) errors.append((line + 1, col + 1, err)) + if only_first: + break return errors return decor(new_func) @@ -234,6 +236,7 @@ def lint_executable_bit(fname): @lint_content_find_check( "\t", + only_first=True, exclude=[ "esphome/dashboard/static/ace.js", "esphome/dashboard/static/ext-searchbox.js", @@ -243,9 +246,9 @@ def lint_tabs(fname): return "File contains tab character. Please convert tabs to spaces." -@lint_content_find_check("\r") +@lint_content_find_check("\r", only_first=True) def lint_newline(fname): - return "File contains windows newline. Please set your editor to unix newline mode." + return "File contains Windows newline. Please set your editor to Unix newline mode." @lint_content_check(exclude=["*.svg"]) From ca8db7696e2965c6ffffad1c462891ad1ca4e0ab Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 05:21:19 +0100 Subject: [PATCH 413/549] Don't enable namespace comment clang-tidy check twice (#2830) --- .clang-tidy | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 1c7e65b762..05858c8e52 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -51,6 +51,7 @@ Checks: >- -google-explicit-constructor, -google-readability-braces-around-statements, -google-readability-casting, + -google-readability-namespace-comments, -google-readability-todo, -google-runtime-references, -hicpp-*, @@ -97,12 +98,12 @@ CheckOptions: value: '1' - key: google-readability-function-size.StatementThreshold value: '800' - - key: google-readability-namespace-comments.ShortNamespaceLines - value: '10' - - key: google-readability-namespace-comments.SpacesBeforeComments - value: '2' - key: google-runtime-int.TypeSuffix value: '_t' + - key: llvm-namespace-comment.ShortNamespaceLines + value: '10' + - key: llvm-namespace-comment.SpacesBeforeComments + value: '2' - key: modernize-loop-convert.MaxCopySize value: '16' - key: modernize-loop-convert.MinConfidence From 1ec3140759f2a6c124b88a6a7318ad750f47e56c Mon Sep 17 00:00:00 2001 From: Yuval Brik Date: Wed, 1 Dec 2021 10:38:58 +0200 Subject: [PATCH 414/549] ESP32 Deep Sleep: correct level value (#2812) Upon registering for ESP32 deep sleep, DeepSleepComponent::begin_sleep calculates the level value to wake up on. As part of PR #2303, the level was changed to be based on `inverted` instead of `!inverted`: Before: https://github.com/esphome/esphome/blob/1e8e471dec19ceafba1997b1d9663f7912f244a2/esphome/components/deep_sleep/deep_sleep_component.cpp#L76 After: https://github.com/esphome/esphome/blob/2b04152482da3e9faaa4f6d0fd3370134d792fd1/esphome/components/deep_sleep/deep_sleep_component.cpp#L80 The level argument to `esp_sleep_enable_ext0_wakeup(pin, level)` [0] should be 0 when the inverted property is true (low triggers wakeup), and 1 when inverted property is false (high triggers wakeup). Also revert the changes of #2644. [0] https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/sleep_modes.html#_CPPv428esp_sleep_enable_ext0_wakeup10gpio_num_ti --- esphome/components/deep_sleep/deep_sleep_component.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 0998a57af3..c854b6da6e 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -77,8 +77,8 @@ void DeepSleepComponent::begin_sleep(bool manual) { if (this->sleep_duration_.has_value()) esp_sleep_enable_timer_wakeup(*this->sleep_duration_); 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()) { + 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); From 24a5325db35a49d0aa3c4a9a8b0258da12c4688d Mon Sep 17 00:00:00 2001 From: Mark Dietzer Date: Wed, 1 Dec 2021 01:01:15 -0800 Subject: [PATCH 415/549] Declare arch_get_cpu_cycle_count for esp8266 as IRAM (#2843) --- esphome/components/esp8266/core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp index 51f3ca50ec..137d4382b4 100644 --- a/esphome/components/esp8266/core.cpp +++ b/esphome/components/esp8266/core.cpp @@ -27,7 +27,7 @@ void IRAM_ATTR HOT arch_feed_wdt() { uint8_t progmem_read_byte(const uint8_t *addr) { return pgm_read_byte(addr); // NOLINT } -uint32_t arch_get_cpu_cycle_count() { +uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { return ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) } uint32_t arch_get_cpu_freq_hz() { return F_CPU; } From fbe1bca1b9896ba8c8b754c5a4faf790bffd887b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 17:37:24 +0100 Subject: [PATCH 416/549] Fix compilation using subprocesses (#2834) --- esphome/util.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/esphome/util.py b/esphome/util.py index 937635fa43..b2ba0c22c3 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -219,24 +219,23 @@ def run_external_process(*cmd, **kwargs): capture_stdout = kwargs.get("capture_stdout", False) if capture_stdout: - sub_stdout = io.BytesIO() + sub_stdout = subprocess.PIPE else: sub_stdout = RedirectText(sys.stdout, filter_lines=filter_lines) sub_stderr = RedirectText(sys.stderr, filter_lines=filter_lines) try: - return subprocess.call(cmd, stdout=sub_stdout, stderr=sub_stderr) + proc = subprocess.run( + cmd, stdout=sub_stdout, stderr=sub_stderr, encoding="utf-8", check=False + ) + return proc.stdout if capture_stdout else proc.returncode except KeyboardInterrupt: # pylint: disable=try-except-raise raise except Exception as err: # pylint: disable=broad-except _LOGGER.error("Running command failed: %s", err) _LOGGER.error("Please try running %s locally.", full_cmd) return 1 - finally: - if capture_stdout: - # pylint: disable=lost-exception - return sub_stdout.getvalue() def is_dev_esphome_version(): From 11330af05f0b5da70333d299b2f080518e7ae0ab Mon Sep 17 00:00:00 2001 From: Leon Loopik <489021+Lewn@users.noreply.github.com> Date: Wed, 1 Dec 2021 20:31:04 +0100 Subject: [PATCH 417/549] Expand uart invert feature to ESP8266 (#1727) --- esphome/components/uart/__init__.py | 15 +++++++++++++++ .../components/uart/uart_component_esp8266.cpp | 5 +++++ tests/test3.yaml | 4 +++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 61b54044d7..a63b220fc7 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -14,6 +14,7 @@ from esphome.const import ( CONF_UART_ID, CONF_DATA, CONF_RX_BUFFER_SIZE, + CONF_INVERTED, CONF_INVERT, CONF_TRIGGER_ID, CONF_SEQUENCE, @@ -67,6 +68,19 @@ def validate_rx_pin(value): return value +def validate_invert_esp32(config): + if ( + CORE.is_esp32 + and CONF_TX_PIN in config + and CONF_RX_PIN in config + and config[CONF_TX_PIN][CONF_INVERTED] != config[CONF_RX_PIN][CONF_INVERTED] + ): + raise cv.Invalid( + "Different invert values for TX and RX pin are not (yet) supported for ESP32." + ) + return config + + def _uart_declare_type(value): if CORE.is_esp8266: return cv.declare_id(ESP8266UartComponent)(value) @@ -162,6 +176,7 @@ CONFIG_SCHEMA = cv.All( } ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN), + validate_invert_esp32, ) diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index c367de05bb..408c83a0db 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -45,6 +45,11 @@ uint32_t ESP8266UartComponent::get_config() { else config |= UART_NB_STOP_BIT_2; + if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) + config |= BIT(22); + if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) + config |= BIT(19); + return config; } diff --git a/tests/test3.yaml b/tests/test3.yaml index 8ae4a383e0..50cd6d6cf6 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -227,7 +227,9 @@ spi: uart: - id: uart1 - tx_pin: GPIO1 + tx_pin: + number: GPIO1 + inverted: yes rx_pin: GPIO3 baud_rate: 115200 - id: uart2 From f58828cb8219d0e4e17e2fc66786eedd987ff37b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 20:55:27 +0100 Subject: [PATCH 418/549] Support setting manual_ip under networks option (#2839) --- esphome/components/wifi/__init__.py | 19 ++++++++++++++++--- tests/test5.yaml | 4 ++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 7a9319f5e0..a24791b458 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -221,10 +221,22 @@ def _validate(config): raise cv.Invalid("Fast connect can only be used with one network!") if CONF_USE_ADDRESS not in config: + use_address = CORE.name + config[CONF_DOMAIN] if CONF_MANUAL_IP in config: use_address = str(config[CONF_MANUAL_IP][CONF_STATIC_IP]) - else: - use_address = CORE.name + config[CONF_DOMAIN] + elif CONF_NETWORKS in config: + ips = set( + str(net[CONF_MANUAL_IP][CONF_STATIC_IP]) + for net in config[CONF_NETWORKS] + if CONF_MANUAL_IP in net + ) + if len(ips) > 1: + raise cv.Invalid( + "Must specify use_address when using multiple static IP addresses." + ) + if len(ips) == 1: + use_address = next(iter(ips)) + config[CONF_USE_ADDRESS] = use_address return config @@ -334,7 +346,8 @@ async def to_code(config): cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) for network in config.get(CONF_NETWORKS, []): - cg.add(var.add_sta(wifi_network(network, config.get(CONF_MANUAL_IP)))) + ip_config = network.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP)) + cg.add(var.add_sta(wifi_network(network, ip_config))) if CONF_AP in config: conf = config[CONF_AP] diff --git a/tests/test5.yaml b/tests/test5.yaml index aa3d057252..37e65e7da2 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -16,6 +16,10 @@ wifi: networks: - ssid: 'MySSID' password: 'password1' + manual_ip: + static_ip: 192.168.1.23 + gateway: 192.168.1.1 + subnet: 255.255.255.0 api: From 607601b3a4be5f8e5941d59744f438177c015815 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 21:03:51 +0100 Subject: [PATCH 419/549] Enable a bunch of clang-tidy checks (#2149) --- .clang-tidy | 8 +- .../adalight/adalight_light_effect.cpp | 2 +- .../adalight/adalight_light_effect.h | 2 +- esphome/components/aht10/aht10.cpp | 4 +- esphome/components/am2320/am2320.cpp | 4 +- esphome/components/anova/anova_base.cpp | 8 +- esphome/components/api/api_connection.cpp | 2 +- esphome/components/api/api_frame_helper.cpp | 22 +---- esphome/components/api/api_pb2.cpp | 92 +++++++++---------- .../captive_portal/captive_portal.cpp | 9 +- esphome/components/cs5460a/cs5460a.cpp | 2 - esphome/components/cse7766/cse7766.cpp | 6 +- esphome/components/daikin/daikin.cpp | 2 +- esphome/components/display/display_buffer.cpp | 6 +- esphome/components/dsmr/dsmr.h | 4 +- esphome/components/esp8266/gpio.cpp | 2 +- esphome/components/esp8266/preferences.cpp | 4 +- esphome/components/ezo/ezo.cpp | 2 +- esphome/components/graph/graph.cpp | 16 ++-- .../hitachi_ac344/hitachi_ac344.cpp | 4 +- .../hitachi_ac424/hitachi_ac424.cpp | 4 +- .../components/ili9341/ili9341_display.cpp | 4 +- .../improv_serial/improv_serial_component.cpp | 2 +- .../components/lcd_gpio/gpio_lcd_display.cpp | 2 +- .../light/addressable_light_effect.h | 4 +- esphome/components/light/light_call.cpp | 2 +- esphome/components/ltr390/ltr390.cpp | 2 +- esphome/components/max31865/max31865.cpp | 12 +-- .../components/max7219digit/max7219digit.cpp | 6 +- esphome/components/mcp23s08/mcp23s08.cpp | 1 - esphome/components/mcp2515/mcp2515.cpp | 3 - .../binary_sensor/modbus_binarysensor.cpp | 2 - .../modbus_controller/modbus_controller.h | 4 +- .../number/modbus_number.cpp | 11 --- .../output/modbus_output.cpp | 5 - .../sensor/modbus_sensor.cpp | 5 - esphome/components/nextion/nextion.cpp | 24 ++--- esphome/components/nextion/nextion_upload.cpp | 4 +- .../nextion/sensor/nextion_sensor.cpp | 2 +- esphome/components/nfc/ndef_message.cpp | 2 +- esphome/components/nfc/nfc.cpp | 4 +- esphome/components/ota/ota_component.cpp | 28 +++--- esphome/components/pid/pid_autotuner.cpp | 2 +- esphome/components/pipsolar/pipsolar.cpp | 6 +- esphome/components/pn532/pn532.cpp | 4 +- .../pulse_meter/pulse_meter_sensor.cpp | 2 +- esphome/components/rc522/rc522.cpp | 4 +- .../remote_base/pronto_protocol.cpp | 2 +- .../components/remote_base/remote_base.cpp | 2 +- .../remote_transmitter_esp32.cpp | 2 +- esphome/components/rtttl/rtttl.cpp | 2 +- esphome/components/sgp30/sgp30.cpp | 4 +- .../sgp40/sensirion_voc_algorithm.cpp | 2 +- esphome/components/sgp40/sgp40.cpp | 4 +- esphome/components/sgp40/sgp40.h | 2 +- esphome/components/sm300d2/sm300d2.cpp | 8 +- .../components/socket/lwip_raw_tcp_impl.cpp | 4 +- esphome/components/spi/spi.cpp | 8 +- .../components/ssd1306_base/ssd1306_base.cpp | 2 +- .../components/ssd1306_i2c/ssd1306_i2c.cpp | 4 +- .../components/ssd1306_spi/ssd1306_spi.cpp | 4 +- esphome/components/st7735/st7735.cpp | 4 +- esphome/components/st7920/st7920.cpp | 2 +- esphome/components/teleinfo/teleinfo.cpp | 2 +- esphome/components/text_sensor/filter.cpp | 2 +- .../components/tof10120/tof10120_sensor.cpp | 2 +- esphome/components/toshiba/toshiba.cpp | 4 +- esphome/components/tuya/tuya.cpp | 4 +- esphome/components/tx20/tx20.cpp | 2 +- .../uart/uart_component_esp8266.cpp | 4 +- .../waveshare_epaper/waveshare_epaper.cpp | 41 ++++----- .../waveshare_epaper/waveshare_epaper.h | 6 +- .../wifi/wifi_component_esp_idf.cpp | 10 +- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 6 +- esphome/core/application.cpp | 2 +- esphome/core/application.h | 2 +- esphome/core/component.cpp | 2 +- platformio.ini | 3 +- script/api_protobuf/api_protobuf.py | 2 +- 79 files changed, 206 insertions(+), 296 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 05858c8e52..b40e606121 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -5,11 +5,8 @@ Checks: >- -altera-*, -android-*, -boost-*, - -bugprone-branch-clone, - -bugprone-easily-swappable-parameters, -bugprone-narrowing-conversions, -bugprone-signed-char-misuse, - -bugprone-too-small-loop-variable, -cert-dcl50-cpp, -cert-err58-cpp, -cert-oop57-cpp, @@ -19,12 +16,10 @@ Checks: >- -clang-diagnostic-delete-abstract-non-virtual-dtor, -clang-diagnostic-delete-non-abstract-non-virtual-dtor, -clang-diagnostic-shadow-field, - -clang-diagnostic-sign-compare, - -clang-diagnostic-unused-variable, -clang-diagnostic-unused-const-variable, + -clang-diagnostic-unused-parameter, -concurrency-*, -cppcoreguidelines-avoid-c-arrays, - -cppcoreguidelines-avoid-goto, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-init-variables, -cppcoreguidelines-macro-usage, @@ -41,7 +36,6 @@ Checks: >- -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-special-member-functions, - -fuchsia-default-arguments, -fuchsia-multiple-inheritance, -fuchsia-overloaded-operator, -fuchsia-statically-constructed-objects, diff --git a/esphome/components/adalight/adalight_light_effect.cpp b/esphome/components/adalight/adalight_light_effect.cpp index d9c2892d21..35e98d7360 100644 --- a/esphome/components/adalight/adalight_light_effect.cpp +++ b/esphome/components/adalight/adalight_light_effect.cpp @@ -25,7 +25,7 @@ void AdalightLightEffect::stop() { AddressableLightEffect::stop(); } -int AdalightLightEffect::get_frame_size_(int led_count) const { +unsigned int AdalightLightEffect::get_frame_size_(int led_count) const { // 3 bytes: Ada // 2 bytes: LED count // 1 byte: checksum diff --git a/esphome/components/adalight/adalight_light_effect.h b/esphome/components/adalight/adalight_light_effect.h index c1df55659b..b757191864 100644 --- a/esphome/components/adalight/adalight_light_effect.h +++ b/esphome/components/adalight/adalight_light_effect.h @@ -25,7 +25,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U CONSUMED, }; - int get_frame_size_(int led_count) const; + unsigned int get_frame_size_(int led_count) const; void reset_frame_(light::AddressableLight &it); void blank_all_leds_(light::AddressableLight &it); Frame parse_frame_(light::AddressableLight &it); diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp index 78f98cb14f..3c690c39b5 100644 --- a/esphome/components/aht10/aht10.cpp +++ b/esphome/components/aht10/aht10.cpp @@ -110,12 +110,12 @@ void AHT10Component::update() { uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4; - float temperature = ((200.0 * (float) raw_temperature) / 1048576.0) - 50.0; + float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f; float humidity; if (raw_humidity == 0) { // unrealistic value humidity = NAN; } else { - humidity = (float) raw_humidity * 100.0 / 1048576.0; + humidity = (float) raw_humidity * 100.0f / 1048576.0f; } if (this->temperature_sensor_ != nullptr) { diff --git a/esphome/components/am2320/am2320.cpp b/esphome/components/am2320/am2320.cpp index b53eb69464..c06a2a34d7 100644 --- a/esphome/components/am2320/am2320.cpp +++ b/esphome/components/am2320/am2320.cpp @@ -38,9 +38,9 @@ void AM2320Component::update() { return; } - float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0; + float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0f; temperature = (data[4] & 0x80) ? -temperature : temperature; - float humidity = ((data[2] << 8) + data[3]) / 10.0; + float humidity = ((data[2] << 8) + data[3]) / 10.0f; ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity); if (this->temperature_sensor_ != nullptr) diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp index cb877bef35..ce4febbe37 100644 --- a/esphome/components/anova/anova_base.cpp +++ b/esphome/components/anova/anova_base.cpp @@ -103,13 +103,7 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) { } break; } - case READ_TARGET_TEMPERATURE: { - this->target_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f); - if (this->fahrenheit_) - this->target_temp_ = ftoc(this->target_temp_); - this->has_target_temp_ = true; - break; - } + case READ_TARGET_TEMPERATURE: case SET_TARGET_TEMPERATURE: { this->target_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f); if (this->fahrenheit_) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 92699df0da..f615815023 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -132,7 +132,7 @@ void APIConnection::loop() { if (state_subs_at_ != -1) { const auto &subs = this->parent_->get_state_subs(); - if (state_subs_at_ >= subs.size()) { + if (state_subs_at_ >= (int) subs.size()) { state_subs_at_ = -1; } else { auto &it = subs[state_subs_at_]; diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index c0e37ec90d..23766ec1b1 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -174,9 +174,6 @@ APIError APINoiseFrameHelper::loop() { * 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; @@ -200,7 +197,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { return APIError::CONNECTION_CLOSED; } rx_header_buf_len_ += received; - if (received != to_read) { + if ((size_t) received != to_read) { // not a full read return APIError::WOULD_BLOCK; } @@ -247,7 +244,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { return APIError::CONNECTION_CLOSED; } rx_buf_len_ += received; - if (received != to_read) { + if ((size_t) received != to_read) { // not all read return APIError::WOULD_BLOCK; } @@ -544,7 +541,6 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() { APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { if (iovcnt == 0) return APIError::OK; - int err; APIError aerr; size_t total_write_len = 0; @@ -584,7 +580,7 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); return APIError::SOCKET_WRITE_FAILED; - } else if (sent != total_write_len) { + } else if ((size_t) sent != total_write_len) { // partially sent, add end to tx_buf size_t to_consume = sent; for (int i = 0; i < iovcnt; i++) { @@ -778,9 +774,6 @@ APIError APIPlaintextFrameHelper::loop() { * 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; @@ -854,7 +847,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { return APIError::CONNECTION_CLOSED; } rx_buf_len_ += received; - if (received != to_read) { + if ((size_t) received != to_read) { // not all read return APIError::WOULD_BLOCK; } @@ -874,7 +867,6 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { } APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { - int err; APIError aerr; if (state_ != State::DATA) { @@ -894,9 +886,6 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { } 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; } @@ -940,7 +929,6 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() { APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { if (iovcnt == 0) return APIError::OK; - int err; APIError aerr; size_t total_write_len = 0; @@ -980,7 +968,7 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); return APIError::SOCKET_WRITE_FAILED; - } else if (sent != total_write_len) { + } else if ((size_t) sent != total_write_len) { // partially sent, add end to tx_buf size_t to_consume = sent; for (int i = 0; i < iovcnt; i++) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 9228be0860..5b6853c276 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -291,7 +291,7 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); } #ifdef HAS_PROTO_MESSAGE_DUMP void HelloRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HelloRequest {\n"); out.append(" client_info: "); out.append("'").append(this->client_info).append("'"); @@ -330,7 +330,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void HelloResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HelloResponse {\n"); out.append(" api_version_major: "); sprintf(buffer, "%u", this->api_version_major); @@ -361,7 +361,7 @@ bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); } #ifdef HAS_PROTO_MESSAGE_DUMP void ConnectRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ConnectRequest {\n"); out.append(" password: "); out.append("'").append(this->password).append("'"); @@ -382,7 +382,7 @@ bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); } #ifdef HAS_PROTO_MESSAGE_DUMP void ConnectResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ConnectResponse {\n"); out.append(" invalid_password: "); out.append(YESNO(this->invalid_password)); @@ -476,7 +476,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("DeviceInfoResponse {\n"); out.append(" uses_password: "); out.append(YESNO(this->uses_password)); @@ -600,7 +600,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesBinarySensorResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -672,7 +672,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void BinarySensorStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("BinarySensorStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -766,7 +766,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesCoverResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -856,7 +856,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void CoverStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("CoverStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -939,7 +939,7 @@ void CoverCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void CoverCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("CoverCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1055,7 +1055,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesFanResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesFanResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -1151,7 +1151,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void FanStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("FanStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1252,7 +1252,7 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void FanCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("FanCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1403,7 +1403,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLightResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesLightResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -1561,7 +1561,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void LightStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("LightStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1784,7 +1784,7 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void LightCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("LightCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1995,7 +1995,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSensorResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesSensorResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -2084,7 +2084,7 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SensorStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SensorStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -2164,7 +2164,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSwitchResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesSwitchResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -2227,7 +2227,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SwitchStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SwitchStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -2266,7 +2266,7 @@ void SwitchCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SwitchCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SwitchCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -2336,7 +2336,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesTextSensorResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -2406,7 +2406,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void TextSensorStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("TextSensorStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -2443,7 +2443,7 @@ void SubscribeLogsRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeLogsRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SubscribeLogsRequest {\n"); out.append(" level: "); out.append(proto_enum_to_string(this->level)); @@ -2486,7 +2486,7 @@ void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeLogsResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SubscribeLogsResponse {\n"); out.append(" level: "); out.append(proto_enum_to_string(this->level)); @@ -2528,7 +2528,7 @@ void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void HomeassistantServiceMap::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HomeassistantServiceMap {\n"); out.append(" key: "); out.append("'").append(this->key).append("'"); @@ -2587,7 +2587,7 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void HomeassistantServiceResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HomeassistantServiceResponse {\n"); out.append(" service: "); out.append("'").append(this->service).append("'"); @@ -2643,7 +2643,7 @@ void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const } #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SubscribeHomeAssistantStateResponse {\n"); out.append(" entity_id: "); out.append("'").append(this->entity_id).append("'"); @@ -2680,7 +2680,7 @@ void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void HomeAssistantStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HomeAssistantStateResponse {\n"); out.append(" entity_id: "); out.append("'").append(this->entity_id).append("'"); @@ -2713,7 +2713,7 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); } #ifdef HAS_PROTO_MESSAGE_DUMP void GetTimeResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("GetTimeResponse {\n"); out.append(" epoch_seconds: "); sprintf(buffer, "%u", this->epoch_seconds); @@ -2748,7 +2748,7 @@ void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesServicesArgument::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesServicesArgument {\n"); out.append(" name: "); out.append("'").append(this->name).append("'"); @@ -2793,7 +2793,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesServicesResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesServicesResponse {\n"); out.append(" name: "); out.append("'").append(this->name).append("'"); @@ -2887,7 +2887,7 @@ void ExecuteServiceArgument::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ExecuteServiceArgument::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ExecuteServiceArgument {\n"); out.append(" bool_: "); out.append(YESNO(this->bool_)); @@ -2968,7 +2968,7 @@ void ExecuteServiceRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ExecuteServiceRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ExecuteServiceRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3040,7 +3040,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCameraResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesCameraResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -3110,7 +3110,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void CameraImageResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("CameraImageResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3147,7 +3147,7 @@ void CameraImageRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void CameraImageRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("CameraImageRequest {\n"); out.append(" single: "); out.append(YESNO(this->single)); @@ -3293,7 +3293,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesClimateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesClimateResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -3480,7 +3480,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ClimateStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ClimateStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3668,7 +3668,7 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ClimateCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ClimateCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3842,7 +3842,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesNumberResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -3929,7 +3929,7 @@ void NumberStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void NumberStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("NumberStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3967,7 +3967,7 @@ void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void NumberCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("NumberCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -4045,7 +4045,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSelectResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesSelectResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -4121,7 +4121,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SelectStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SelectStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -4164,7 +4164,7 @@ void SelectCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SelectCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SelectCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index ad4c32bb1f..d4e37f62f2 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -85,14 +85,7 @@ void CaptivePortal::start() { 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().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) { - not_found = true; - } - - if (not_found) { + if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) { req->send(404, "text/html", "File not found"); return; } diff --git a/esphome/components/cs5460a/cs5460a.cpp b/esphome/components/cs5460a/cs5460a.cpp index a172bcdf56..b0c0531936 100644 --- a/esphome/components/cs5460a/cs5460a.cpp +++ b/esphome/components/cs5460a/cs5460a.cpp @@ -102,8 +102,6 @@ void CS5460AComponent::hw_init_() { /* Doesn't reset the register values etc., just restarts the "computation cycle" */ void CS5460AComponent::restart_() { - int cnt; - this->enable(); /* Stop running conversion, wake up if needed */ this->write_byte(CMD_POWER_UP); diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index 55e1ec82cf..25d75da3e6 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -149,9 +149,9 @@ void CSE7766Component::parse_data_() { } } void CSE7766Component::update() { - float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0; - float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0; - float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0; + float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0f; + float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0f; + float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0f; ESP_LOGV(TAG, "Got voltage_acc=%.2f current_acc=%.2f power_acc=%.2f", this->voltage_acc_, this->current_acc_, this->power_acc_); diff --git a/esphome/components/daikin/daikin.cpp b/esphome/components/daikin/daikin.cpp index 5f8d0288e2..83d0253691 100644 --- a/esphome/components/daikin/daikin.cpp +++ b/esphome/components/daikin/daikin.cpp @@ -231,7 +231,7 @@ bool DaikinClimate::on_receive(remote_base::RemoteReceiveData data) { // frame header if (byte != 0x27) return false; - } else if (pos == 3) { + } else if (pos == 3) { // NOLINT(bugprone-branch-clone) // frame header if (byte != 0x00) return false; diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index ac806611b5..1458629acd 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -496,7 +496,7 @@ bool Animation::get_pixel(int x, int y) const { return false; const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; const uint32_t frame_index = this->height_ * width_8 * this->current_frame_; - if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) + if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_)) return false; const uint32_t pos = x + y * width_8 + frame_index; return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); @@ -505,7 +505,7 @@ Color Animation::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 frame_index = this->width_ * this->height_ * this->current_frame_; - if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) + if (frame_index >= (uint32_t)(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 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) | @@ -517,7 +517,7 @@ Color Animation::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 frame_index = this->width_ * this->height_ * this->current_frame_; - if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) + if (frame_index >= (uint32_t)(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 = progmem_read_byte(this->data_start_ + pos); diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index db0bf95ca1..76f79ee55c 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -114,10 +114,10 @@ class Dsmr : public Component, public uart::UARTDevice { bool receive_timeout_reached_(); size_t max_telegram_len_; char *telegram_{nullptr}; - int bytes_read_{0}; + size_t bytes_read_{0}; uint8_t *crypt_telegram_{nullptr}; size_t crypt_telegram_len_{0}; - int crypt_bytes_read_{0}; + size_t crypt_bytes_read_{0}; uint32_t last_read_time_{0}; bool header_found_{false}; bool footer_found_{false}; diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index 2660318182..a24f217756 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -9,7 +9,7 @@ namespace esp8266 { static const char *const TAG = "esp8266"; static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) { - if (flags == gpio::FLAG_INPUT) { + if (flags == gpio::FLAG_INPUT) { // NOLINT(bugprone-branch-clone) return INPUT; } else if (flags == gpio::FLAG_OUTPUT) { return OUTPUT; diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index 041736943b..a8f8bd0d41 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -55,7 +55,7 @@ static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) { extern "C" uint32_t _SPIFFS_end; // NOLINT -static const uint32_t get_esp8266_flash_sector() { +static uint32_t get_esp8266_flash_sector() { union { uint32_t *ptr; uint32_t uint; @@ -63,7 +63,7 @@ static const uint32_t get_esp8266_flash_sector() { 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; } +static 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; diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index ca6f121dbb..3c1b6e33e8 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -75,7 +75,7 @@ void EZOSensor::loop() { return; // some sensors return multiple comma-separated values, terminate string after first one - for (int i = 1; i < sizeof(buf) - 1; i++) + for (size_t i = 1; i < sizeof(buf) - 1; i++) if (buf[i] == ',') buf[i] = '\0'; diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index a9daad4ab9..daff89e0a6 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -86,7 +86,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo // 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 (uint32_t i = 0; i < this->width_; i++) { for (auto *trace : traces_) { float v = trace->get_tracedata()->get_value(i); if (!std::isnan(v)) { @@ -132,7 +132,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo 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) { + for (uint32_t x = 0; x < this->width_; x += 2) { buff->draw_pixel_at(x_offset + x, y_offset + py, color); } } @@ -147,7 +147,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo 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) { + for (uint32_t y = 0; y < this->height_; y += 2) { buff->draw_pixel_at(x_offset + i * (this->width_ - 1) / n, y_offset + y, color); } } @@ -158,14 +158,14 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo 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++) { + for (uint32_t i = 0; i < this->width_; i++) { float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange; 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)) { int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2; - for (int16_t t = 0; t < thick; t++) { + for (uint16_t t = 0; t < thick; t++) { buff->draw_pixel_at(x_offset + x, y_offset + y + t, c); } } @@ -179,8 +179,8 @@ 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 txtw = 0, txth = 0; + int valw = 0, valh = 0; int lt = 0; for (auto *trace : g->traces_) { std::string txtstr = trace->get_name(); @@ -320,7 +320,7 @@ void Graph::draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_ if (legend_->lines_) { uint16_t thick = trace->get_line_thickness(); - for (int16_t i = 0; i < legend_->x0_ * 4 / 3; i++) { + for (int 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, diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.cpp b/esphome/components/hitachi_ac344/hitachi_ac344.cpp index 067ea39d07..7702baf312 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.cpp +++ b/esphome/components/hitachi_ac344/hitachi_ac344.cpp @@ -299,9 +299,7 @@ bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) { GETBITS8(remote_state[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE); ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC344_SWINGH_BYTE], swing_modeh); - if ((swing_modeh & 0x7) == 0x0) { - this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; - } else if ((swing_modeh & 0x3) == 0x3) { + if ((swing_modeh & 0x3) == 0x3) { this->swing_mode = climate::CLIMATE_SWING_OFF; } else { this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.cpp b/esphome/components/hitachi_ac424/hitachi_ac424.cpp index 2e5423a37a..713bc0be25 100644 --- a/esphome/components/hitachi_ac424/hitachi_ac424.cpp +++ b/esphome/components/hitachi_ac424/hitachi_ac424.cpp @@ -300,9 +300,7 @@ bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) { HITACHI_AC424_SWINGH_SIZE); ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC424_SWINGH_BYTE], swing_modeh); - if ((swing_modeh & 0x7) == 0x0) { - this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; - } else if ((swing_modeh & 0x3) == 0x3) { + if ((swing_modeh & 0x3) == 0x3) { this->swing_mode = climate::CLIMATE_SWING_OFF; } else { this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; diff --git a/esphome/components/ili9341/ili9341_display.cpp b/esphome/components/ili9341/ili9341_display.cpp index ab5586fa28..a24f0bbb64 100644 --- a/esphome/components/ili9341/ili9341_display.cpp +++ b/esphome/components/ili9341/ili9341_display.cpp @@ -86,8 +86,8 @@ void ILI9341Display::update() { void ILI9341Display::display_() { // we will only update the changed window to the display - int w = this->x_high_ - this->x_low_ + 1; - int h = this->y_high_ - this->y_low_ + 1; + uint16_t w = this->x_high_ - this->x_low_ + 1; + uint16_t h = this->y_high_ - this->y_low_ + 1; set_addr_window_(this->x_low_, this->y_low_, w, h); this->start_data_(); diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index a9a7467125..b4d1d88370 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -145,7 +145,7 @@ bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) { if (at == 8 + data_len + 1) { uint8_t checksum = 0x00; - for (uint8_t i = 0; i < at; i++) + for (size_t i = 0; i < at; i++) checksum += raw[i]; if (checksum != byte) { diff --git a/esphome/components/lcd_gpio/gpio_lcd_display.cpp b/esphome/components/lcd_gpio/gpio_lcd_display.cpp index b0344d313c..94ddc34051 100644 --- a/esphome/components/lcd_gpio/gpio_lcd_display.cpp +++ b/esphome/components/lcd_gpio/gpio_lcd_display.cpp @@ -17,7 +17,7 @@ void GPIOLCDDisplay::setup() { this->enable_pin_->setup(); // OUTPUT this->enable_pin_->digital_write(false); - for (uint8_t i = 0; i < (this->is_four_bit_mode() ? 4 : 8); i++) { + for (uint8_t i = 0; i < (uint8_t)(this->is_four_bit_mode() ? 4u : 8u); i++) { this->data_pins_[i]->setup(); // OUTPUT this->data_pins_[i]->digital_write(false); } diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index 358fe69c23..5091bae2d5 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -167,7 +167,7 @@ class AddressableScanEffect : public AddressableLightEffect { this->last_move_ = now; it.all() = Color::BLACK; - for (auto i = 0; i < this->scan_width_; i++) { + for (uint32_t i = 0; i < this->scan_width_; i++) { it[this->at_led_ + i] = current_color; } @@ -178,7 +178,7 @@ class AddressableScanEffect : public AddressableLightEffect { uint32_t move_interval_{}; uint32_t scan_width_{1}; uint32_t last_move_{0}; - int at_led_{0}; + uint32_t at_led_{0}; bool direction_{true}; }; diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 9858590850..3f1b8aef30 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -98,7 +98,7 @@ void LightCall::perform() { // EFFECT auto effect = this->effect_; const char *effect_s; - if (effect == 0) + if (effect == 0u) effect_s = "None"; else effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str(); diff --git a/esphome/components/ltr390/ltr390.cpp b/esphome/components/ltr390/ltr390.cpp index 36f3835724..959af68235 100644 --- a/esphome/components/ltr390/ltr390.cpp +++ b/esphome/components/ltr390/ltr390.cpp @@ -97,7 +97,7 @@ void LTR390Component::read_mode_(int mode_index) { // If there are more modes to read then begin the next // otherwise stop - if (mode_index + 1 < this->mode_funcs_.size()) { + if (mode_index + 1 < (int) this->mode_funcs_.size()) { this->read_mode_(mode_index + 1); } else { this->reading_ = false; diff --git a/esphome/components/max31865/max31865.cpp b/esphome/components/max31865/max31865.cpp index 91946cde2c..126915dc15 100644 --- a/esphome/components/max31865/max31865.cpp +++ b/esphome/components/max31865/max31865.cpp @@ -203,16 +203,16 @@ float MAX31865Sensor::calc_temperature_(float rtd_ratio) { rtd_resistance *= 100; } float rpoly = rtd_resistance; - float neg_temp = -242.02; - neg_temp += 2.2228 * rpoly; + float neg_temp = -242.02f; + neg_temp += 2.2228f * rpoly; rpoly *= rtd_resistance; // square - neg_temp += 2.5859e-3 * rpoly; + neg_temp += 2.5859e-3f * rpoly; rpoly *= rtd_resistance; // ^3 - neg_temp -= 4.8260e-6 * rpoly; + neg_temp -= 4.8260e-6f * rpoly; rpoly *= rtd_resistance; // ^4 - neg_temp -= 2.8183e-8 * rpoly; + neg_temp -= 2.8183e-8f * rpoly; rpoly *= rtd_resistance; // ^5 - neg_temp += 1.5243e-10 * rpoly; + neg_temp += 1.5243e-10f * rpoly; return neg_temp; } diff --git a/esphome/components/max7219digit/max7219digit.cpp b/esphome/components/max7219digit/max7219digit.cpp index 0f86ac635c..2368c17448 100644 --- a/esphome/components/max7219digit/max7219digit.cpp +++ b/esphome/components/max7219digit/max7219digit.cpp @@ -76,7 +76,7 @@ void MAX7219Component::loop() { this->stepsleft_ = 0; // Return if there is no need to scroll or scroll is off - if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= get_width_internal())) { + if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= (size_t) get_width_internal())) { this->display(); return; } @@ -88,7 +88,7 @@ void MAX7219Component::loop() { // Dwell time at end of string in case of stop at end if (this->scroll_mode_ == ScrollMode::STOP) { - if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - get_width_internal() + 1) { + if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - (size_t) get_width_internal() + 1) { if (now - this->last_scroll_ >= this->scroll_dwell_) { this->stepsleft_ = 0; this->last_scroll_ = now; @@ -155,7 +155,7 @@ int MAX7219Component::get_height_internal() { int MAX7219Component::get_width_internal() { return this->num_chips_ / this->num_chip_lines_ * 8; } void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) { - if (x + 1 > this->max_displaybuffer_[0].size()) { // Extend the display buffer in case required + if (x + 1 > (int) this->max_displaybuffer_[0].size()) { // Extend the display buffer in case required for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { this->max_displaybuffer_[chip_line].resize(x + 1, this->bckgrnd_); } diff --git a/esphome/components/mcp23s08/mcp23s08.cpp b/esphome/components/mcp23s08/mcp23s08.cpp index b7adeb94d2..af834b4c40 100644 --- a/esphome/components/mcp23s08/mcp23s08.cpp +++ b/esphome/components/mcp23s08/mcp23s08.cpp @@ -35,7 +35,6 @@ void MCP23S08::dump_config() { } bool MCP23S08::read_reg(uint8_t reg, uint8_t *value) { - uint8_t data; this->enable(); this->transfer_byte(this->device_opcode_ | 1); this->transfer_byte(reg); diff --git a/esphome/components/mcp2515/mcp2515.cpp b/esphome/components/mcp2515/mcp2515.cpp index ce451cbb33..e845c79a64 100644 --- a/esphome/components/mcp2515/mcp2515.cpp +++ b/esphome/components/mcp2515/mcp2515.cpp @@ -127,9 +127,6 @@ canbus::Error MCP2515::set_mode_(const CanctrlReqopMode mode) { } canbus::Error MCP2515::set_clk_out_(const CanClkOut divisor) { - canbus::Error res; - uint8_t cfg3; - if (divisor == CLKOUT_DISABLE) { /* Turn off CLKEN */ modify_register_(MCP_CANCTRL, CANCTRL_CLKEN, 0x00); diff --git a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp index 81066b3f5c..c3eb3d4411 100644 --- a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp +++ b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp @@ -13,8 +13,6 @@ void ModbusBinarySensor::parse_and_publish(const std::vector &data) { switch (this->register_type) { case ModbusRegisterType::DISCRETE_INPUT: - value = coil_from_vector(this->offset, data); - break; case ModbusRegisterType::COIL: // offset for coil is the actual number of the coil not the byte offset value = coil_from_vector(this->offset, data); diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 39c0d8026f..045075f5e0 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -102,8 +102,6 @@ inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_ return ModbusFunctionCode::READ_WRITE_MULTIPLE_REGISTERS; break; case ModbusRegisterType::READ: - return ModbusFunctionCode::CUSTOM; - break; default: return ModbusFunctionCode::CUSTOM; break; @@ -221,7 +219,7 @@ template N mask_and_shift_by_rightbit(N data, uint32_t mask) { if (result == 0) { return result; } - for (int pos = 0; pos < sizeof(N) << 3; pos++) { + for (size_t pos = 0; pos < sizeof(N) << 3; pos++) { if ((mask & (1 << pos)) != 0) return result >> pos; } diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index 95c6ac6f6a..ba2ffdd09f 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -8,11 +8,6 @@ namespace modbus_controller { static const char *const TAG = "modbus.number"; void ModbusNumber::parse_and_publish(const std::vector &data) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - float result = payload_to_float(data, *this); // Is there a lambda registered @@ -31,13 +26,7 @@ void ModbusNumber::parse_and_publish(const std::vector &data) { } void ModbusNumber::control(float value) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - std::vector data; - auto original_value = value; // Is there are lambda configured? if (this->write_transform_func_.has_value()) { // data is passed by reference diff --git a/esphome/components/modbus_controller/output/modbus_output.cpp b/esphome/components/modbus_controller/output/modbus_output.cpp index f7d7c42342..d2b5d02bda 100644 --- a/esphome/components/modbus_controller/output/modbus_output.cpp +++ b/esphome/components/modbus_controller/output/modbus_output.cpp @@ -13,11 +13,6 @@ void ModbusOutput::setup() {} * */ void ModbusOutput::write_state(float value) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - std::vector data; auto original_value = value; // Is there are lambda configured? diff --git a/esphome/components/modbus_controller/sensor/modbus_sensor.cpp b/esphome/components/modbus_controller/sensor/modbus_sensor.cpp index dbd0525347..a21fd91032 100644 --- a/esphome/components/modbus_controller/sensor/modbus_sensor.cpp +++ b/esphome/components/modbus_controller/sensor/modbus_sensor.cpp @@ -10,11 +10,6 @@ static const char *const TAG = "modbus_controller.sensor"; void ModbusSensor::dump_config() { LOG_SENSOR(TAG, "Modbus Controller Sensor", this); } void ModbusSensor::parse_and_publish(const std::vector &data) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - float result = payload_to_float(data, *this); // Is there a lambda registered diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index f23f55c9bb..494765db4d 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -64,7 +64,7 @@ bool Nextion::check_connect_() { 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++) { + for (size_t i = 0; i < response.length(); i++) { ESP_LOGN(TAG, "response %s %d %d %c", response.c_str(), i, response[i], response[i]); } #endif @@ -563,11 +563,10 @@ void Nextion::process_nextion_commands_() { // 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) { + auto index = to_process.find('\0'); + if (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; @@ -591,10 +590,9 @@ void Nextion::process_nextion_commands_() { // 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) { + auto index = to_process.find('\0'); + if (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; @@ -626,11 +624,10 @@ void Nextion::process_nextion_commands_() { 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) { + auto index = to_process.find('\0'); + if (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; @@ -660,11 +657,10 @@ void Nextion::process_nextion_commands_() { // 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) { + auto index = to_process.find('\0'); + if (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; @@ -736,7 +732,7 @@ void Nextion::process_nextion_commands_() { 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++) { + for (size_t 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) diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index cd1c073320..b16f2fe7eb 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -95,7 +95,7 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { } http->end(); ESP_LOGN(TAG, "this->content_length_ %d sent %d", this->content_length_, sent); - for (uint32_t i = 0; i < range; i += 4096) { + for (int 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, @@ -238,7 +238,7 @@ void Nextion::upload_tft() { // 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++) { + for (size_t i = 0; i < response.length(); i++) { ESP_LOGD(TAG, "Available %d : 0x%02X", i, response[i]); } diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp index 4b7532d32d..32bfccf9f8 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.cpp +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -24,7 +24,7 @@ void NextionSensor::add_to_wave_buffer(float state) { wave_buffer_.push_back(wave_state); - if (this->wave_buffer_.size() > this->wave_max_length_) { + if (this->wave_buffer_.size() > (size_t) this->wave_max_length_) { this->wave_buffer_.erase(this->wave_buffer_.begin()); } } diff --git a/esphome/components/nfc/ndef_message.cpp b/esphome/components/nfc/ndef_message.cpp index d8c940254e..d7d134aedb 100644 --- a/esphome/components/nfc/ndef_message.cpp +++ b/esphome/components/nfc/ndef_message.cpp @@ -93,7 +93,7 @@ bool NdefMessage::add_uri_record(const std::string &uri) { return this->add_reco std::vector NdefMessage::encode() { std::vector data; - for (uint8_t i = 0; i < this->records_.size(); i++) { + for (size_t i = 0; i < this->records_.size(); i++) { auto encoded_record = this->records_[i]->encode(i == 0, (i + 1) == this->records_.size()); data.insert(data.end(), encoded_record.begin(), encoded_record.end()); } diff --git a/esphome/components/nfc/nfc.cpp b/esphome/components/nfc/nfc.cpp index 706c09a5aa..09dbdcfe94 100644 --- a/esphome/components/nfc/nfc.cpp +++ b/esphome/components/nfc/nfc.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "nfc"; std::string format_uid(std::vector &uid) { char buf[(uid.size() * 2) + uid.size() - 1]; int offset = 0; - for (uint8_t i = 0; i < uid.size(); i++) { + for (size_t i = 0; i < uid.size(); i++) { const char *format = "%02X"; if (i + 1 < uid.size()) format = "%02X-"; @@ -22,7 +22,7 @@ std::string format_uid(std::vector &uid) { std::string format_bytes(std::vector &bytes) { char buf[(bytes.size() * 2) + bytes.size() - 1]; int offset = 0; - for (uint8_t i = 0; i < bytes.size(); i++) { + for (size_t i = 0; i < bytes.size(); i++) { const char *format = "%02X"; if (i + 1 < bytes.size()) format = "%02X "; diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index e49c108320..79edd91173 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -141,14 +141,14 @@ void OTAComponent::handle_() { if (!this->readall_(buf, 5)) { ESP_LOGW(TAG, "Reading magic bytes failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // 0x6C, 0x26, 0xF7, 0x5C, 0x45 if (buf[0] != 0x6C || buf[1] != 0x26 || buf[2] != 0xF7 || buf[3] != 0x5C || buf[4] != 0x45) { ESP_LOGW(TAG, "Magic bytes do not match! 0x%02X-0x%02X-0x%02X-0x%02X-0x%02X", buf[0], buf[1], buf[2], buf[3], buf[4]); error_code = OTA_RESPONSE_ERROR_MAGIC; - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // Send OK and version - 2 bytes @@ -161,7 +161,7 @@ void OTAComponent::handle_() { // Read features - 1 byte if (!this->readall_(buf, 1)) { ESP_LOGW(TAG, "Reading features failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } ota_features = buf[0]; // NOLINT ESP_LOGV(TAG, "OTA features is 0x%02X", ota_features); @@ -189,7 +189,7 @@ void OTAComponent::handle_() { // Send nonce, 32 bytes hex MD5 if (!this->writeall_(reinterpret_cast(sbuf), 32)) { ESP_LOGW(TAG, "Auth: Writing nonce failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // prepare challenge @@ -201,7 +201,7 @@ void OTAComponent::handle_() { // Receive cnonce, 32 bytes hex MD5 if (!this->readall_(buf, 32)) { ESP_LOGW(TAG, "Auth: Reading cnonce failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } sbuf[32] = '\0'; ESP_LOGV(TAG, "Auth: CNonce is %s", sbuf); @@ -216,7 +216,7 @@ void OTAComponent::handle_() { // Receive result, 32 bytes hex MD5 if (!this->readall_(buf + 64, 32)) { ESP_LOGW(TAG, "Auth: Reading response failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } sbuf[64 + 32] = '\0'; ESP_LOGV(TAG, "Auth: Response is %s", sbuf + 64); @@ -228,7 +228,7 @@ void OTAComponent::handle_() { if (!matches) { ESP_LOGW(TAG, "Auth failed! Passwords do not match!"); error_code = OTA_RESPONSE_ERROR_AUTH_INVALID; - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } } #endif // USE_OTA_PASSWORD @@ -240,7 +240,7 @@ void OTAComponent::handle_() { // Read size, 4 bytes MSB first if (!this->readall_(buf, 4)) { ESP_LOGW(TAG, "Reading size failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } ota_size = 0; for (uint8_t i = 0; i < 4; i++) { @@ -251,7 +251,7 @@ void OTAComponent::handle_() { error_code = backend->begin(ota_size); if (error_code != OTA_RESPONSE_OK) - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) update_started = true; // Acknowledge prepare OK - 1 byte @@ -261,7 +261,7 @@ void OTAComponent::handle_() { // Read binary MD5, 32 bytes if (!this->readall_(buf, 32)) { ESP_LOGW(TAG, "Reading binary MD5 checksum failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } sbuf[32] = '\0'; ESP_LOGV(TAG, "Update: Binary MD5 is %s", sbuf); @@ -281,19 +281,19 @@ void OTAComponent::handle_() { continue; } ESP_LOGW(TAG, "Error receiving data for update, errno: %d", errno); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } else if (read == 0) { // $ man recv // "When a stream socket peer has performed an orderly shutdown, the return value will // be 0 (the traditional "end-of-file" return)." ESP_LOGW(TAG, "Remote end closed connection"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } error_code = backend->write(buf, read); if (error_code != OTA_RESPONSE_OK) { ESP_LOGW(TAG, "Error writing binary data to flash!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } total += read; @@ -317,7 +317,7 @@ void OTAComponent::handle_() { error_code = backend->end(); if (error_code != OTA_RESPONSE_OK) { ESP_LOGW(TAG, "Error ending OTA!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // Acknowledge Update end OK - 1 byte diff --git a/esphome/components/pid/pid_autotuner.cpp b/esphome/components/pid/pid_autotuner.cpp index 15c1c5f076..fc012aaa39 100644 --- a/esphome/components/pid/pid_autotuner.cpp +++ b/esphome/components/pid/pid_autotuner.cpp @@ -330,7 +330,7 @@ bool PIDAutotuner::OscillationAmplitudeDetector::has_enough_data() const { float PIDAutotuner::OscillationAmplitudeDetector::get_mean_oscillation_amplitude() const { float total_amplitudes = 0; size_t total_amplitudes_n = 0; - for (int i = 1; i < std::min(phase_mins.size(), phase_maxs.size()) - 1; i++) { + for (size_t i = 1; i < std::min(phase_mins.size(), phase_maxs.size()) - 1; i++) { total_amplitudes += std::abs(phase_maxs[i] - phase_mins[i + 1]); total_amplitudes_n++; } diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index 9f8b57003a..13a08bbd16 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -413,8 +413,6 @@ void Pipsolar::loop() { this->state_ = STATE_IDLE; break; case POLLING_QT: - this->state_ = STATE_IDLE; - break; case POLLING_QMN: this->state_ = STATE_IDLE; break; @@ -481,7 +479,7 @@ void Pipsolar::loop() { 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++) { + for (size_t i = 1; i < strlen(tmp); i++) { switch (tmp[i]) { case 'E': enabled = true; @@ -530,7 +528,7 @@ void Pipsolar::loop() { this->value_warnings_present_ = false; this->value_faults_present_ = true; - for (int i = 1; i < strlen(tmp); i++) { + for (size_t i = 1; i < strlen(tmp); i++) { enabled = tmp[i] == '1'; switch (i) { case 1: diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index ed2a2c1e35..0c46ff8a57 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -145,7 +145,7 @@ void PN532::loop() { if (nfcid.size() == this->current_uid_.size()) { bool same_uid = false; - for (uint8_t i = 0; i < nfcid.size(); i++) + for (size_t i = 0; i < nfcid.size(); i++) same_uid |= nfcid[i] == this->current_uid_[i]; if (same_uid) return; @@ -367,7 +367,7 @@ bool PN532BinarySensor::process(std::vector &data) { if (data.size() != this->uid_.size()) return false; - for (uint8_t i = 0; i < data.size(); i++) { + for (size_t i = 0; i < data.size(); i++) { if (data[i] != this->uid_[i]) return false; } diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.cpp b/esphome/components/pulse_meter/pulse_meter_sensor.cpp index fd1403b4fd..7d526b241b 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.cpp +++ b/esphome/components/pulse_meter/pulse_meter_sensor.cpp @@ -35,7 +35,7 @@ void PulseMeterSensor::loop() { this->publish_state(0); } else { // Calculate pulses/min from the pulse width in ms - this->publish_state((60.0 * 1000.0) / pulse_width_ms); + this->publish_state((60.0f * 1000.0f) / pulse_width_ms); } } diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp index 385641fea0..d203b3ce8f 100644 --- a/esphome/components/rc522/rc522.cpp +++ b/esphome/components/rc522/rc522.cpp @@ -28,7 +28,7 @@ std::string format_buffer(uint8_t *b, uint8_t len) { std::string format_uid(std::vector &uid) { char buf[32]; int offset = 0; - for (uint8_t i = 0; i < uid.size(); i++) { + for (size_t i = 0; i < uid.size(); i++) { const char *format = "%02X"; if (i + 1 < uid.size()) format = "%02X-"; @@ -479,7 +479,7 @@ bool RC522BinarySensor::process(std::vector &data) { if (data.size() != this->uid_.size()) result = false; else { - for (uint8_t i = 0; i < data.size(); i++) { + for (size_t i = 0; i < data.size(); i++) { if (data[i] != this->uid_[i]) { result = false; break; diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp index 11aebb6c5d..4f6ace720c 100644 --- a/esphome/components/remote_base/pronto_protocol.cpp +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -113,7 +113,7 @@ void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &st const char *p = str.c_str(); char *endptr[1]; - for (uint16_t i = 0; i < len; i++) { + for (size_t i = 0; i < len; i++) { uint16_t x = strtol(p, endptr, 16); if (x == 0 && i >= NUMBERS_IN_PREAMBLE) { // Alignment error?, bail immediately (often right result). diff --git a/esphome/components/remote_base/remote_base.cpp b/esphome/components/remote_base/remote_base.cpp index a853c9849e..97ee027b84 100644 --- a/esphome/components/remote_base/remote_base.cpp +++ b/esphome/components/remote_base/remote_base.cpp @@ -33,7 +33,7 @@ void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) { uint32_t buffer_offset = 0; buffer_offset += sprintf(buffer, "Sending times=%u wait=%ums: ", send_times, send_wait); - for (int32_t i = 0; i < vec.size(); i++) { + for (size_t i = 0; i < vec.size(); i++) { const int32_t value = vec[i]; const uint32_t remaining_length = sizeof(buffer) - buffer_offset; int written; diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index c3b61b72c2..368b21f892 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -113,7 +113,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen this->rmt_temp_.push_back(rmt_item); } - for (uint16_t i = 0; i < send_times; i++) { + for (uint32_t i = 0; i < send_times; i++) { esp_err_t error = rmt_write_items(this->channel_, this->rmt_temp_.data(), this->rmt_temp_.size(), true); if (error != ESP_OK) { ESP_LOGW(TAG, "rmt_write_items failed: %s", esp_err_to_name(error)); diff --git a/esphome/components/rtttl/rtttl.cpp b/esphome/components/rtttl/rtttl.cpp index d571c2f287..c76d4a89b0 100644 --- a/esphome/components/rtttl/rtttl.cpp +++ b/esphome/components/rtttl/rtttl.cpp @@ -159,7 +159,7 @@ void Rtttl::loop() { // Now play the note if (note) { auto note_index = (scale - 4) * 12 + note; - if (note_index < 0 || note_index >= sizeof(NOTES)) { + if (note_index < 0 || note_index >= (int) sizeof(NOTES)) { ESP_LOGE(TAG, "Note out of valid range"); return; } diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 87cf0fa61a..4157fd55cf 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -147,8 +147,8 @@ void SGP30Component::read_iaq_baseline_() { // much if (this->store_baseline_ && (this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL || - abs(this->baselines_storage_.eco2 - this->eco2_baseline_) > MAXIMUM_STORAGE_DIFF || - abs(this->baselines_storage_.tvoc - this->tvoc_baseline_) > MAXIMUM_STORAGE_DIFF)) { + (uint32_t) abs(this->baselines_storage_.eco2 - this->eco2_baseline_) > MAXIMUM_STORAGE_DIFF || + (uint32_t) abs(this->baselines_storage_.tvoc - this->tvoc_baseline_) > MAXIMUM_STORAGE_DIFF)) { this->seconds_since_last_store_ = 0; this->baselines_storage_.eco2 = this->eco2_baseline_; this->baselines_storage_.tvoc = this->tvoc_baseline_; diff --git a/esphome/components/sgp40/sensirion_voc_algorithm.cpp b/esphome/components/sgp40/sensirion_voc_algorithm.cpp index f3cdeee35b..d76b776641 100644 --- a/esphome/components/sgp40/sensirion_voc_algorithm.cpp +++ b/esphome/components/sgp40/sensirion_voc_algorithm.cpp @@ -149,7 +149,7 @@ static fix16_t fix16_div(fix16_t a, fix16_t b) { /* Figure out the sign of result */ if ((a ^ b) & 0x80000000) { #ifndef FIXMATH_NO_OVERFLOW - if (result == FIX16_MINIMUM) + if (result == FIX16_MINIMUM) // NOLINT(clang-diagnostic-sign-compare) return FIX16_OVERFLOW; #endif diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index a3d2c74eb7..9561efcde2 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -144,8 +144,8 @@ int32_t SGP40Component::measure_voc_index_() { // much if (this->store_baseline_ && this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL) { voc_algorithm_get_states(&voc_algorithm_params_, &this->state0_, &this->state1_); - if (abs(this->baselines_storage_.state0 - this->state0_) > MAXIMUM_STORAGE_DIFF || - abs(this->baselines_storage_.state1 - this->state1_) > MAXIMUM_STORAGE_DIFF) { + if ((uint32_t) abs(this->baselines_storage_.state0 - this->state0_) > MAXIMUM_STORAGE_DIFF || + (uint32_t) abs(this->baselines_storage_.state1 - this->state1_) > MAXIMUM_STORAGE_DIFF) { this->seconds_since_last_store_ = 0; this->baselines_storage_.state0 = this->state0_; this->baselines_storage_.state1 = this->state1_; diff --git a/esphome/components/sgp40/sgp40.h b/esphome/components/sgp40/sgp40.h index bb68a1ffcf..c854b21060 100644 --- a/esphome/components/sgp40/sgp40.h +++ b/esphome/components/sgp40/sgp40.h @@ -66,7 +66,7 @@ class SGP40Component : public PollingComponent, public sensor::Sensor, public i2 uint8_t generate_crc_(const uint8_t *data, uint8_t datalen); uint16_t measure_raw_(); ESPPreferenceObject pref_; - int32_t seconds_since_last_store_; + uint32_t seconds_since_last_store_; SGP40Baselines baselines_storage_; VocAlgorithmParams voc_algorithm_params_; bool self_test_complete_; diff --git a/esphome/components/sm300d2/sm300d2.cpp b/esphome/components/sm300d2/sm300d2.cpp index e41a4855db..c726faec48 100644 --- a/esphome/components/sm300d2/sm300d2.cpp +++ b/esphome/components/sm300d2/sm300d2.cpp @@ -50,10 +50,10 @@ void SM300D2Sensor::update() { const uint16_t pm_2_5 = (response[8] * 256) + response[9]; const uint16_t pm_10_0 = (response[10] * 256) + response[11]; // 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); + const float temperature = ((response[12] + (response[13] * 0.1f)) > 128) + ? (((response[12] + (response[13] * 0.1f)) - 128) * -1) + : response[12] + (response[13] * 0.1f); + const float humidity = response[14] + (response[15] * 0.1f); ESP_LOGD(TAG, "Received CO₂: %u ppm", co2); if (this->co2_sensor_ != nullptr) diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 922d895ff4..d57413c739 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -383,7 +383,7 @@ class LWIPRawImpl : public Socket { return err; } ret += err; - if (err != iov[i].iov_len) + if ((size_t) err != iov[i].iov_len) break; } return ret; @@ -462,7 +462,7 @@ class LWIPRawImpl : public Socket { return err; } written += err; - if (err != iov[i].iov_len) + if ((size_t) err != iov[i].iov_len) break; } if (written == 0) diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index d883142c81..d427e2c91b 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -55,13 +55,9 @@ void SPIComponent::setup() { } } #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)) { - // pass - } else { + if (!(clk_pin == 6 && miso_pin == 7 && mosi_pin == 8) && + !(clk_pin == 14 && (!has_miso || miso_pin == 12) && (!has_mosi || mosi_pin == 13))) use_hw_spi = false; - } if (use_hw_spi) { this->hw_spi_ = &SPI; diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index 2537133605..4b9feb10ce 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -292,7 +292,7 @@ const char *SSD1306::model_str_() { case SSD1305_MODEL_128_32: return "SSD1305 128x32"; case SSD1305_MODEL_128_64: - return "SSD1305 128x32"; + return "SSD1305 128x64"; default: return "Unknown"; } diff --git a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp index fddea25fc8..64b09c0672 100644 --- a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp +++ b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp @@ -40,12 +40,12 @@ void I2CSSD1306::command(uint8_t value) { this->write_byte(0x00, value); } void HOT I2CSSD1306::write_display_data() { if (this->is_sh1106_()) { uint32_t i = 0; - for (uint8_t page = 0; page < this->get_height_internal() / 8; page++) { + for (uint8_t page = 0; page < (uint8_t) this->get_height_internal() / 8; page++) { this->command(0xB0 + page); // row this->command(0x02); // lower column this->command(0x10); // higher column - for (uint8_t x = 0; x < this->get_width_internal() / 16; x++) { + for (uint8_t x = 0; x < (uint8_t) this->get_width_internal() / 16; x++) { uint8_t data[16]; for (uint8_t &j : data) j = this->buffer_[i++]; diff --git a/esphome/components/ssd1306_spi/ssd1306_spi.cpp b/esphome/components/ssd1306_spi/ssd1306_spi.cpp index 33d474a8ee..7f025d77cd 100644 --- a/esphome/components/ssd1306_spi/ssd1306_spi.cpp +++ b/esphome/components/ssd1306_spi/ssd1306_spi.cpp @@ -37,12 +37,12 @@ void SPISSD1306::command(uint8_t value) { } void HOT SPISSD1306::write_display_data() { if (this->is_sh1106_()) { - for (uint8_t y = 0; y < this->get_height_internal() / 8; y++) { + for (uint8_t y = 0; y < (uint8_t) this->get_height_internal() / 8; y++) { this->command(0xB0 + y); this->command(0x02); this->command(0x10); this->dc_pin_->digital_write(true); - for (uint8_t x = 0; x < this->get_width_internal(); x++) { + for (uint8_t x = 0; x < (uint8_t) this->get_width_internal(); x++) { this->enable(); this->write_byte(this->buffer_[x + y * this->get_width_internal()]); this->disable(); diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index 8490aa1fe4..c5178986f3 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -275,7 +275,7 @@ void ST7735::setup() { uint8_t data = 0; if (this->model_ != INITR_HALLOWING) { - uint8_t data = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY; + data = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY; } if (this->usebgr_) { data = data | ST7735_MADCTL_BGR; @@ -446,7 +446,7 @@ void HOT ST7735::write_display_data_() { this->dc_pin_->digital_write(true); if (this->eightbitcolor_) { - for (int line = 0; line < this->get_buffer_length(); line = line + this->get_width_internal()) { + for (size_t line = 0; line < this->get_buffer_length(); line = line + this->get_width_internal()) { for (int index = 0; index < this->get_width_internal(); ++index) { auto color332 = display::ColorUtil::to_color(this->buffer_[index + line], display::ColorOrder::COLOR_ORDER_RGB, display::ColorBitness::COLOR_BITNESS_332, true); diff --git a/esphome/components/st7920/st7920.cpp b/esphome/components/st7920/st7920.cpp index d985b0a426..63fa0ba72f 100644 --- a/esphome/components/st7920/st7920.cpp +++ b/esphome/components/st7920/st7920.cpp @@ -74,7 +74,7 @@ void ST7920::goto_xy_(uint16_t x, uint16_t y) { void HOT ST7920::write_display_data() { uint8_t i, j, b; - for (j = 0; j < this->get_height_internal() / 2; j++) { + for (j = 0; j < (uint8_t)(this->get_height_internal() / 2); j++) { this->goto_xy_(0, j); this->enable(); for (i = 0; i < 16; i++) { // 16 bytes from line #0+ diff --git a/esphome/components/teleinfo/teleinfo.cpp b/esphome/components/teleinfo/teleinfo.cpp index 5a1e44ac8b..d9f80134f4 100644 --- a/esphome/components/teleinfo/teleinfo.cpp +++ b/esphome/components/teleinfo/teleinfo.cpp @@ -118,7 +118,7 @@ void TeleInfo::loop() { * */ while ((buf_finger = static_cast(memchr(buf_finger, (int) 0xa, buf_index_ - 1))) && - ((buf_finger - buf_) < buf_index_)) { + ((buf_finger - buf_) < buf_index_)) { // NOLINT(clang-diagnostic-sign-compare) /* * Make sure timesamp is nullified between each tag as some tags don't * have a timestamp diff --git a/esphome/components/text_sensor/filter.cpp b/esphome/components/text_sensor/filter.cpp index a692378e8b..c6cbcd7c1e 100644 --- a/esphome/components/text_sensor/filter.cpp +++ b/esphome/components/text_sensor/filter.cpp @@ -64,7 +64,7 @@ optional PrependFilter::new_value(std::string value) { return this- // Substitute optional SubstituteFilter::new_value(std::string value) { std::size_t pos; - for (int i = 0; i < this->from_strings_.size(); i++) + for (size_t i = 0; i < this->from_strings_.size(); i++) while ((pos = value.find(this->from_strings_[i])) != std::string::npos) value.replace(pos, this->from_strings_[i].size(), this->to_strings_[i]); return value; diff --git a/esphome/components/tof10120/tof10120_sensor.cpp b/esphome/components/tof10120/tof10120_sensor.cpp index 4ba591f9c4..5cd086938e 100644 --- a/esphome/components/tof10120/tof10120_sensor.cpp +++ b/esphome/components/tof10120/tof10120_sensor.cpp @@ -50,7 +50,7 @@ void TOF10120Sensor::update() { ESP_LOGW(TAG, "Distance measurement out of range"); this->publish_state(NAN); } else { - this->publish_state(distance_mm / 1000.0); + this->publish_state(distance_mm / 1000.0f); } this->status_clear_warning(); } diff --git a/esphome/components/toshiba/toshiba.cpp b/esphome/components/toshiba/toshiba.cpp index 25528abbe1..975a149b52 100644 --- a/esphome/components/toshiba/toshiba.cpp +++ b/esphome/components/toshiba/toshiba.cpp @@ -580,13 +580,13 @@ bool ToshibaClimate::on_receive(remote_base::RemoteReceiveData data) { temperature_code = (message[4] >> 4) | (message[14] & RAC_PT1411HWRU_FLAG_FRAC) | (message[15] & RAC_PT1411HWRU_FLAG_NEG); if (message[15] & RAC_PT1411HWRU_FLAG_FAH) { - for (uint8_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_F.size(); i++) { + for (size_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_F.size(); i++) { if (RAC_PT1411HWRU_TEMPERATURE_F[i] == temperature_code) { this->target_temperature = static_cast((i + TOSHIBA_RAC_PT1411HWRU_TEMP_F_MIN - 32) * 5) / 9; } } } else { - for (uint8_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_C.size(); i++) { + for (size_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_C.size(); i++) { if (RAC_PT1411HWRU_TEMPERATURE_C[i] == temperature_code) { this->target_temperature = i + TOSHIBA_RAC_PT1411HWRU_TEMP_C_MIN; } diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 4f65fa7118..2677731224 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -143,7 +143,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff case TuyaCommandType::PRODUCT_QUERY: { // check it is a valid string made up of printable characters bool valid = true; - for (int i = 0; i < len; i++) { + for (size_t i = 0; i < len; i++) { if (!std::isprint(buffer[i])) { valid = false; break; @@ -339,8 +339,6 @@ void Tuya::send_raw_command_(TuyaCommand command) { 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; diff --git a/esphome/components/tx20/tx20.cpp b/esphome/components/tx20/tx20.cpp index 6e0b6343d1..fefcc8f4d5 100644 --- a/esphome/components/tx20/tx20.cpp +++ b/esphome/components/tx20/tx20.cpp @@ -113,7 +113,7 @@ void Tx20Component::decode_and_publish_() { if (tx20_sa == 4) { if (chk == tx20_sd) { if (tx20_sf == tx20_sc) { - tx20_wind_speed_kmh = float(tx20_sc) * 0.36; + tx20_wind_speed_kmh = float(tx20_sc) * 0.36f; ESP_LOGV(TAG, "WindSpeed %f", tx20_wind_speed_kmh); if (this->wind_speed_sensor_ != nullptr) this->wind_speed_sensor_->publish_state(tx20_wind_speed_kmh); diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index 408c83a0db..370adad779 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -214,9 +214,7 @@ void IRAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) { /* If parity is enabled, just read it and ignore it. */ /* TODO: Should we check parity? Or is it too slow for nothing added..*/ - if (arg->parity_ == UART_CONFIG_PARITY_EVEN) - arg->read_bit_(&wait, start); - else if (arg->parity_ == UART_CONFIG_PARITY_ODD) + if (arg->parity_ == UART_CONFIG_PARITY_EVEN || arg->parity_ == UART_CONFIG_PARITY_ODD) arg->read_bit_(&wait, start); // Stop bit diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index ee3fb2fe47..322c375f0e 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -360,15 +360,18 @@ void HOT WaveshareEPaperTypeA::display() { // COMMAND DISPLAY UPDATE CONTROL 2 this->command(0x22); - 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); - } else if (this->model_ == TTGO_EPAPER_2_13_IN_B74) { - // this->data(0xC7); - this->data(full_update ? 0xF7 : 0xFF); - } else { - this->data(0xC4); + switch (this->model_) { + case WAVESHARE_EPAPER_2_9_IN_V2: + case WAVESHARE_EPAPER_1_54_IN_V2: + case TTGO_EPAPER_2_13_IN_B74: + this->data(full_update ? 0xF7 : 0xFF); + break; + case TTGO_EPAPER_2_13_IN_B73: + this->data(0xC7); + break; + default: + this->data(0xC4); + break; } // COMMAND MASTER ACTIVATION @@ -381,20 +384,14 @@ void HOT WaveshareEPaperTypeA::display() { 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: - return 128; case TTGO_EPAPER_2_13_IN_B73: case TTGO_EPAPER_2_13_IN_B74: - return 128; case TTGO_EPAPER_2_13_IN_B1: - return 128; case WAVESHARE_EPAPER_2_9_IN: - return 128; case WAVESHARE_EPAPER_2_9_IN_V2: return 128; } @@ -403,20 +400,15 @@ int WaveshareEPaperTypeA::get_width_internal() { 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: - return 250; case TTGO_EPAPER_2_13_IN_B73: case TTGO_EPAPER_2_13_IN_B74: - return 250; case TTGO_EPAPER_2_13_IN_B1: return 250; case WAVESHARE_EPAPER_2_9_IN: - return 296; case WAVESHARE_EPAPER_2_9_IN_V2: return 296; } @@ -433,11 +425,10 @@ void WaveshareEPaperTypeA::set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; } -int WaveshareEPaperTypeA::idle_timeout_() { +uint32_t WaveshareEPaperTypeA::idle_timeout_() { switch (this->model_) { case TTGO_EPAPER_2_13_IN_B1: return 2500; - break; default: return WaveshareEPaper::idle_timeout_(); } @@ -646,7 +637,7 @@ void HOT WaveshareEPaper2P9InB::display() { this->command(0x13); delay(2); this->start_data_(); - for (int i = 0; i < this->get_buffer_length_(); i++) + for (size_t i = 0; i < this->get_buffer_length_(); i++) this->write_byte(0x00); this->end_data_(); delay(2); @@ -825,7 +816,7 @@ void HOT WaveshareEPaper4P2InBV2::display() { // COMMAND DATA START TRANSMISSION 2 (RED data) this->command(0x13); this->start_data_(); - for (int i = 0; i < this->get_buffer_length_(); i++) + for (size_t i = 0; i < this->get_buffer_length_(); i++) this->write_byte(0xFF); this->end_data_(); delay(2); @@ -1293,7 +1284,7 @@ void HOT WaveshareEPaper2P13InDKE::display() { int WaveshareEPaper2P13InDKE::get_width_internal() { return 128; } int WaveshareEPaper2P13InDKE::get_height_internal() { return 250; } -int WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; } +uint32_t WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; } void WaveshareEPaper2P13InDKE::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this); ESP_LOGCONFIG(TAG, " Model: 2.13inDKE"); diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index b50596643d..a1e2f6037a 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -61,7 +61,7 @@ class WaveshareEPaper : public PollingComponent, GPIOPin *reset_pin_{nullptr}; GPIOPin *dc_pin_; GPIOPin *busy_pin_{nullptr}; - virtual int idle_timeout_() { return 1000; } // NOLINT(readability-identifier-naming) + virtual uint32_t idle_timeout_() { return 1000u; } // NOLINT(readability-identifier-naming) }; enum WaveshareEPaperTypeAModel { @@ -110,7 +110,7 @@ class WaveshareEPaperTypeA : public WaveshareEPaper { uint32_t full_update_every_{30}; uint32_t at_update_{0}; WaveshareEPaperTypeAModel model_; - int idle_timeout_() override; + uint32_t idle_timeout_() override; }; enum WaveshareEPaperTypeBModel { @@ -346,7 +346,7 @@ class WaveshareEPaper2P13InDKE : public WaveshareEPaper { int get_height_internal() override; - int idle_timeout_() override; + uint32_t idle_timeout_() override; uint32_t full_update_every_{30}; uint32_t at_update_{0}; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 1d346c0a8e..d2217eb88a 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -67,9 +67,9 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi 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) { + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { // NOLINT(bugprone-branch-clone) // no data - } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_STOP) { + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_STOP) { // NOLINT(bugprone-branch-clone) // 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)); @@ -79,13 +79,13 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi 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) { + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP) { // NOLINT(bugprone-branch-clone) // 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) { + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_START) { // NOLINT(bugprone-branch-clone) // no data - } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STOP) { + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STOP) { // NOLINT(bugprone-branch-clone) // 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)); diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 884969f793..7588198c70 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -171,10 +171,8 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service result.type = XiaomiParseResult::TYPE_MUE4094RT; result.name = "MUE4094RT"; result.raw_offset -= 6; - } else if ((raw[2] == 0x47) && (raw[3] == 0x03)) { // ClearGrass-branded, round body, e-ink display - result.type = XiaomiParseResult::TYPE_CGG1; - result.name = "CGG1"; - } else if ((raw[2] == 0x48) && (raw[3] == 0x0B)) { // Qingping-branded, round body, e-ink display — with bindkeys + } else if ((raw[2] == 0x47 && raw[3] == 0x03) || // ClearGrass-branded, round body, e-ink display + (raw[2] == 0x48 && raw[3] == 0x0B)) { // Qingping-branded, round body, e-ink display — with bindkeys result.type = XiaomiParseResult::TYPE_CGG1; result.name = "CGG1"; } else if ((raw[2] == 0xbc) && (raw[3] == 0x03)) { // VegTrug Grow Care Garden diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index f67fc826cf..1bef99e868 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -94,7 +94,7 @@ void Application::loop() { } this->last_loop_ = now; - if (this->dump_config_at_ >= 0 && this->dump_config_at_ < this->components_.size()) { + if (this->dump_config_at_ < this->components_.size()) { if (this->dump_config_at_ == 0) { ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", this->compilation_time_.c_str()); #ifdef ESPHOME_PROJECT_NAME diff --git a/esphome/core/application.h b/esphome/core/application.h index ace0c9ad6d..f4fe571490 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -309,7 +309,7 @@ class Application { bool name_add_mac_suffix_; uint32_t last_loop_{0}; uint32_t loop_interval_{16}; - int dump_config_at_{-1}; + size_t dump_config_at_{SIZE_MAX}; uint32_t app_state_{0}; }; diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index f97818cfb1..591c9943b5 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -96,7 +96,7 @@ void Component::call() { // State loop: Call loop this->call_loop(); break; - case COMPONENT_STATE_FAILED: + case COMPONENT_STATE_FAILED: // NOLINT(bugprone-branch-clone) // State failed: Do nothing break; default: diff --git a/platformio.ini b/platformio.ini index 0f80d6d8d3..3c0b725d65 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,7 +11,6 @@ include_dir = [runtime] ; This are the flags as set by the runtime. build_flags = - -Wno-unused-variable -Wno-unused-but-set-variable -Wno-sign-compare @@ -19,10 +18,12 @@ build_flags = ; This are the flags for clang-tidy. build_flags = -Wall + -Wextra -Wunreachable-code -Wfor-loop-analysis -Wshadow-field -Wshadow-field-in-constructor + -Wshadow-uncaptured-local [common] lib_deps = diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 7ccdc5a24e..016a0995b9 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -649,7 +649,7 @@ def build_message_type(desc): o += f" {dump[0]} " else: o += "\n" - o += f" char buffer[64];\n" + o += f" __attribute__((unused)) char buffer[64];\n" o += f' out.append("{desc.name} {{\\n");\n' o += indent("\n".join(dump)) + "\n" o += f' out.append("}}");\n' From 54106179a11ec9ff4586f73f8246507165ba9390 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 21:05:42 +0100 Subject: [PATCH 420/549] Set ESP32 watchdog to loop task (#2846) --- esphome/components/esp32/__init__.py | 7 ++++++ esphome/components/esp32/core.cpp | 35 ++++++++++++++++------------ esphome/components/esp8266/core.cpp | 1 + esphome/core/application.h | 2 ++ esphome/core/hal.h | 1 + sdkconfig.defaults | 4 ++++ 6 files changed, 35 insertions(+), 15 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 1c249476e7..d6f1180aa7 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -311,9 +311,16 @@ async def to_code(config): ) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True) + # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000) + # Setup watchdog + add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True) + add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True) + add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0", False) + add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1", False) + cg.add_platformio_option("board_build.partitions", "partitions.csv") for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 359999120f..a9756b41cd 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -6,12 +6,17 @@ #include #include #include +#include #include #if ESP_IDF_VERSION_MAJOR >= 4 #include #endif +#ifdef USE_ARDUINO +#include +#endif + void setup(); void loop(); @@ -29,24 +34,24 @@ void arch_restart() { 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); +void arch_init() { + // Enable the task watchdog only on the loop task (from which we're currently running) +#if defined(USE_ESP_IDF) + esp_task_wdt_add(nullptr); + // Idle task watchdog is disabled on ESP-IDF +#elif defined(USE_ARDUINO) + enableLoopWDT(); + // Disable idle task watchdog on the core we're using (Arduino pins the process to a core) +#if CONFIG_ARDUINO_RUNNING_CORE == 0 + disableCore0WDT(); +#endif +#if CONFIG_ARDUINO_RUNNING_CORE == 1 + disableCore1WDT(); +#endif #endif -#endif // USE_ESP_IDF } +void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); } uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } uint32_t arch_get_cpu_cycle_count() { diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp index 137d4382b4..828d71a3bd 100644 --- a/esphome/components/esp8266/core.cpp +++ b/esphome/components/esp8266/core.cpp @@ -20,6 +20,7 @@ void arch_restart() { yield(); } } +void arch_init() {} void IRAM_ATTR HOT arch_feed_wdt() { ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance) } diff --git a/esphome/core/application.h b/esphome/core/application.h index f4fe571490..2a20793c19 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -5,6 +5,7 @@ #include "esphome/core/defines.h" #include "esphome/core/preferences.h" #include "esphome/core/component.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/scheduler.h" @@ -47,6 +48,7 @@ namespace esphome { class Application { public: void pre_setup(const std::string &name, const char *compilation_time, bool name_add_mac_suffix) { + arch_init(); this->name_add_mac_suffix_ = name_add_mac_suffix; if (name_add_mac_suffix) { this->name_ = name + "-" + get_mac_address().substr(6); diff --git a/esphome/core/hal.h b/esphome/core/hal.h index a86dbf2534..034f9d692f 100644 --- a/esphome/core/hal.h +++ b/esphome/core/hal.h @@ -39,6 +39,7 @@ uint32_t micros(); void delay(uint32_t ms); void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming) void __attribute__((noreturn)) arch_restart(); +void arch_init(); void arch_feed_wdt(); uint32_t arch_get_cpu_cycle_count(); uint32_t arch_get_cpu_freq_hz(); diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 26db4705b8..72ca3f6e9c 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -9,6 +9,10 @@ CONFIG_PARTITION_TABLE_CUSTOM=y #CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_SINGLE_APP=n CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT=y +CONFIG_ESP_TASK_WDT_PANIC=y +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n # esp32_ble CONFIG_BT_ENABLED=y From caf352ff066275c0952b7dbf96a7b73ef9e6b5b7 Mon Sep 17 00:00:00 2001 From: Paul Nicholls Date: Thu, 2 Dec 2021 15:26:56 +1300 Subject: [PATCH 421/549] Tuya Cover improvements (#2637) --- esphome/components/tuya/cover/__init__.py | 24 +++++ esphome/components/tuya/cover/tuya_cover.cpp | 108 ++++++++++++++++--- esphome/components/tuya/cover/tuya_cover.h | 17 ++- esphome/components/tuya/tuya.cpp | 103 ++++++++++++------ esphome/components/tuya/tuya.h | 16 ++- 5 files changed, 215 insertions(+), 53 deletions(-) diff --git a/esphome/components/tuya/cover/__init__.py b/esphome/components/tuya/cover/__init__.py index 5a654841f7..f886c7030f 100644 --- a/esphome/components/tuya/cover/__init__.py +++ b/esphome/components/tuya/cover/__init__.py @@ -5,16 +5,27 @@ from esphome.const import ( CONF_OUTPUT_ID, CONF_MIN_VALUE, CONF_MAX_VALUE, + CONF_RESTORE_MODE, ) from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] +CONF_CONTROL_DATAPOINT = "control_datapoint" +CONF_DIRECTION_DATAPOINT = "direction_datapoint" CONF_POSITION_DATAPOINT = "position_datapoint" +CONF_POSITION_REPORT_DATAPOINT = "position_report_datapoint" CONF_INVERT_POSITION = "invert_position" TuyaCover = tuya_ns.class_("TuyaCover", cover.Cover, cg.Component) +TuyaCoverRestoreMode = tuya_ns.enum("TuyaCoverRestoreMode") +RESTORE_MODES = { + "NO_RESTORE": TuyaCoverRestoreMode.COVER_NO_RESTORE, + "RESTORE": TuyaCoverRestoreMode.COVER_RESTORE, + "RESTORE_AND_CALL": TuyaCoverRestoreMode.COVER_RESTORE_AND_CALL, +} + def validate_range(config): if config[CONF_MIN_VALUE] > config[CONF_MAX_VALUE]: @@ -29,10 +40,16 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaCover), cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Optional(CONF_CONTROL_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_DIRECTION_DATAPOINT): cv.uint8_t, cv.Required(CONF_POSITION_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_POSITION_REPORT_DATAPOINT): cv.uint8_t, cv.Optional(CONF_MIN_VALUE, default=0): cv.int_, cv.Optional(CONF_MAX_VALUE, default=100): cv.int_, cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, + cv.Optional(CONF_RESTORE_MODE, default="RESTORE"): cv.enum( + RESTORE_MODES, upper=True + ), }, ).extend(cv.COMPONENT_SCHEMA), validate_range, @@ -44,9 +61,16 @@ async def to_code(config): await cg.register_component(var, config) await cover.register_cover(var, config) + if CONF_CONTROL_DATAPOINT in config: + cg.add(var.set_control_id(config[CONF_CONTROL_DATAPOINT])) + if CONF_DIRECTION_DATAPOINT in config: + cg.add(var.set_direction_id(config[CONF_DIRECTION_DATAPOINT])) cg.add(var.set_position_id(config[CONF_POSITION_DATAPOINT])) + if CONF_POSITION_REPORT_DATAPOINT in config: + cg.add(var.set_position_report_id(config[CONF_POSITION_REPORT_DATAPOINT])) cg.add(var.set_min_value(config[CONF_MIN_VALUE])) cg.add(var.set_max_value(config[CONF_MAX_VALUE])) cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) + cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) paren = await cg.get_variable(config[CONF_TUYA_ID]) cg.add(var.set_tuya_parent(paren)) diff --git a/esphome/components/tuya/cover/tuya_cover.cpp b/esphome/components/tuya/cover/tuya_cover.cpp index 7da1312938..b63eb9109d 100644 --- a/esphome/components/tuya/cover/tuya_cover.cpp +++ b/esphome/components/tuya/cover/tuya_cover.cpp @@ -4,48 +4,122 @@ namespace esphome { namespace tuya { +const uint8_t COMMAND_OPEN = 0x00; +const uint8_t COMMAND_CLOSE = 0x02; +const uint8_t COMMAND_STOP = 0x01; + +using namespace esphome::cover; + static const char *const TAG = "tuya.cover"; void TuyaCover::setup() { this->value_range_ = this->max_value_ - this->min_value_; - if (this->position_id_.has_value()) { - this->parent_->register_listener(*this->position_id_, [this](const TuyaDatapoint &datapoint) { - auto pos = float(datapoint.value_uint - this->min_value_) / this->value_range_; - if (this->invert_position_) - pos = 1.0f - pos; - this->position = pos; - this->publish_state(); - }); + + this->parent_->add_on_initialized_callback([this]() { + // Set the direction (if configured/supported). + this->set_direction_(this->invert_position_); + + // Handle configured restore mode. + switch (this->restore_mode_) { + case COVER_NO_RESTORE: + break; + case COVER_RESTORE: { + auto restore = this->restore_state_(); + if (restore.has_value()) + restore->apply(this); + break; + } + case COVER_RESTORE_AND_CALL: { + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->to_call(this).perform(); + } + break; + } + } + }); + + uint8_t report_id = *this->position_id_; + if (this->position_report_id_.has_value()) { + // A position report datapoint is configured; listen to that instead. + report_id = *this->position_report_id_; } + + this->parent_->register_listener(report_id, [this](const TuyaDatapoint &datapoint) { + if (datapoint.value_int == 123) { + ESP_LOGD(TAG, "Ignoring MCU position report - not calibrated"); + return; + } + auto pos = float(datapoint.value_uint - this->min_value_) / this->value_range_; + this->position = 1.0f - pos; + this->publish_state(); + }); } void TuyaCover::control(const cover::CoverCall &call) { if (call.get_stop()) { - auto pos = this->position; - if (this->invert_position_) + if (this->control_id_.has_value()) { + this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_STOP); + } else { + auto pos = this->position; pos = 1.0f - pos; - auto position_int = static_cast(pos * this->value_range_); - position_int = position_int + this->min_value_; + auto position_int = static_cast(pos * this->value_range_); + position_int = position_int + this->min_value_; - parent_->set_integer_datapoint_value(*this->position_id_, position_int); + parent_->set_integer_datapoint_value(*this->position_id_, position_int); + } } if (call.get_position().has_value()) { auto pos = *call.get_position(); - if (this->invert_position_) + if (this->control_id_.has_value() && (pos == COVER_OPEN || pos == COVER_CLOSED)) { + if (pos == COVER_OPEN) { + this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_OPEN); + } else { + this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_CLOSE); + } + } else { pos = 1.0f - pos; - auto position_int = static_cast(pos * this->value_range_); - position_int = position_int + this->min_value_; + auto position_int = static_cast(pos * this->value_range_); + position_int = position_int + this->min_value_; - parent_->set_integer_datapoint_value(*this->position_id_, position_int); + parent_->set_integer_datapoint_value(*this->position_id_, position_int); + } } this->publish_state(); } +void TuyaCover::set_direction_(bool inverted) { + if (!this->direction_id_.has_value()) { + return; + } + + if (inverted) { + ESP_LOGD(TAG, "Setting direction: inverted"); + } else { + ESP_LOGD(TAG, "Setting direction: normal"); + } + + this->parent_->set_boolean_datapoint_value(*this->direction_id_, inverted); +} + void TuyaCover::dump_config() { ESP_LOGCONFIG(TAG, "Tuya Cover:"); + if (this->invert_position_) { + if (this->direction_id_.has_value()) { + ESP_LOGCONFIG(TAG, " Inverted"); + } else { + ESP_LOGCONFIG(TAG, " Configured as Inverted, but direction_datapoint isn't configured"); + } + } + if (this->control_id_.has_value()) + ESP_LOGCONFIG(TAG, " Control has datapoint ID %u", *this->control_id_); + if (this->direction_id_.has_value()) + ESP_LOGCONFIG(TAG, " Direction has datapoint ID %u", *this->direction_id_); if (this->position_id_.has_value()) ESP_LOGCONFIG(TAG, " Position has datapoint ID %u", *this->position_id_); + if (this->position_report_id_.has_value()) + ESP_LOGCONFIG(TAG, " Position Report has datapoint ID %u", *this->position_report_id_); } cover::CoverTraits TuyaCover::get_traits() { diff --git a/esphome/components/tuya/cover/tuya_cover.h b/esphome/components/tuya/cover/tuya_cover.h index c3b0c3e069..87c72b0e66 100644 --- a/esphome/components/tuya/cover/tuya_cover.h +++ b/esphome/components/tuya/cover/tuya_cover.h @@ -7,22 +7,37 @@ namespace esphome { namespace tuya { +enum TuyaCoverRestoreMode { + COVER_NO_RESTORE, + COVER_RESTORE, + COVER_RESTORE_AND_CALL, +}; + class TuyaCover : public cover::Cover, public Component { public: void setup() override; void dump_config() override; - void set_position_id(uint8_t dimmer_id) { this->position_id_ = dimmer_id; } + void set_control_id(uint8_t control_id) { this->control_id_ = control_id; } + void set_direction_id(uint8_t direction_id) { this->direction_id_ = direction_id; } + void set_position_id(uint8_t position_id) { this->position_id_ = position_id; } + void set_position_report_id(uint8_t position_report_id) { this->position_report_id_ = position_report_id; } 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; } void set_invert_position(bool invert_position) { invert_position_ = invert_position; } + void set_restore_mode(TuyaCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; } protected: void control(const cover::CoverCall &call) override; + void set_direction_(bool inverted); cover::CoverTraits get_traits() override; Tuya *parent_; + TuyaCoverRestoreMode restore_mode_{}; + optional control_id_{}; + optional direction_id_{}; optional position_id_{}; + optional position_report_id_{}; uint32_t min_value_; uint32_t max_value_; uint32_t value_range_; diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 2677731224..404a70a80e 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -195,6 +195,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff if (this->init_state_ == TuyaInitState::INIT_DATAPOINT) { this->init_state_ = TuyaInitState::INIT_DONE; this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); }); + this->initialized_callback_.call(); } this->handle_datapoint_(buffer, len); break; @@ -439,53 +440,51 @@ void Tuya::send_local_time_() { #endif 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_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; - } else if (datapoint->value_raw == value) { - ESP_LOGV(TAG, "Not sending unchanged value"); - return; - } - this->send_datapoint_command_(datapoint_id, TuyaDatapointType::RAW, value); + this->set_raw_datapoint_value_(datapoint_id, value, false); } void Tuya::set_boolean_datapoint_value(uint8_t datapoint_id, bool value) { - this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1); + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1, false); } void Tuya::set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) { - this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4); + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4, false); } 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_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; - } - std::vector data; - for (char const &c : value) { - data.push_back(c); - } - this->send_datapoint_command_(datapoint_id, TuyaDatapointType::STRING, data); + this->set_string_datapoint_value_(datapoint_id, value, false); } void Tuya::set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) { - this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1); + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1, false); } 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); + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length, false); +} + +void Tuya::force_set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value) { + this->set_raw_datapoint_value_(datapoint_id, value, true); +} + +void Tuya::force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1, true); +} + +void Tuya::force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4, true); +} + +void Tuya::force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) { + this->set_string_datapoint_value_(datapoint_id, value, true); +} + +void Tuya::force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1, true); +} + +void Tuya::force_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, true); } optional Tuya::get_datapoint_(uint8_t datapoint_id) { @@ -496,7 +495,7 @@ optional Tuya::get_datapoint_(uint8_t datapoint_id) { } void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, const uint32_t value, - uint8_t length) { + uint8_t length, bool forced) { ESP_LOGD(TAG, "Setting datapoint %u to %u", datapoint_id, value); optional datapoint = this->get_datapoint_(datapoint_id); if (!datapoint.has_value()) { @@ -504,7 +503,7 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType } 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) { + } else if (!forced && datapoint->value_uint == value) { ESP_LOGV(TAG, "Not sending unchanged value"); return; } @@ -526,6 +525,40 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType this->send_datapoint_command_(datapoint_id, datapoint_type, data); } +void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector &value, bool forced) { + 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_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; + } else if (!forced && datapoint->value_raw == value) { + ESP_LOGV(TAG, "Not sending unchanged value"); + return; + } + this->send_datapoint_command_(datapoint_id, TuyaDatapointType::RAW, value); +} + +void Tuya::set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced) { + 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_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 (!forced && datapoint->value_string == value) { + ESP_LOGV(TAG, "Not sending unchanged value"); + return; + } + std::vector data; + for (char const &c : value) { + data.push_back(c); + } + this->send_datapoint_command_(datapoint_id, TuyaDatapointType::STRING, data); +} + void Tuya::send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector data) { std::vector buffer; buffer.push_back(datapoint_id); @@ -550,5 +583,7 @@ void Tuya::register_listener(uint8_t datapoint_id, const std::functioninit_state_; } + } // namespace tuya } // namespace esphome diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 785399502b..c46d61119e 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" #include "esphome/components/uart/uart.h" #ifdef USE_TIME @@ -81,12 +82,22 @@ class Tuya : public Component, public uart::UARTDevice { 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); + void force_set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value); + void force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value); + void force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value); + void force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value); + void force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value); + void force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length); + TuyaInitState get_init_state(); #ifdef USE_TIME void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; } #endif void add_ignore_mcu_update_on_datapoints(uint8_t ignore_mcu_update_on_datapoints) { this->ignore_mcu_update_on_datapoints_.push_back(ignore_mcu_update_on_datapoints); } + void add_on_initialized_callback(std::function callback) { + this->initialized_callback_.add(std::move(callback)); + } protected: void handle_char_(uint8_t c); @@ -100,7 +111,9 @@ class Tuya : public Component, public uart::UARTDevice { 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); + uint8_t length, bool forced); + void set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced); + void set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector &value, bool forced); void send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector data); void send_wifi_status_(); @@ -122,6 +135,7 @@ class Tuya : public Component, public uart::UARTDevice { std::vector command_queue_; optional expected_response_{}; uint8_t wifi_status_ = -1; + CallbackManager initialized_callback_{}; }; } // namespace tuya From 1b88b7a1664396cabb3a339eecbf12f1c0fe2c29 Mon Sep 17 00:00:00 2001 From: Alexandre-Jacques St-Jacques Date: Wed, 1 Dec 2021 21:33:48 -0500 Subject: [PATCH 422/549] Fix wifi not working with manual_ip using esp-idf (#2849) --- esphome/components/wifi/wifi_component_esp_idf.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index d2217eb88a..5a81fd0a39 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -430,7 +430,7 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { info.netmask.addr = static_cast(manual_ip->subnet); err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); - if (err != ESP_OK) { + if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) { ESP_LOGV(TAG, "tcpip_adapter_dhcpc_stop failed: %s", esp_err_to_name(err)); return false; } From 9ca4e8f32a78d9ec0bbb247ba67504682092c371 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Thu, 2 Dec 2021 03:45:11 +0100 Subject: [PATCH 423/549] modbus_controller: bugfix: enable overriding calculated register size (#2845) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/modbus_controller/__init__.py | 5 +++++ esphome/components/modbus_controller/modbus_controller.h | 8 +++++--- .../modbus_controller/text_sensor/modbus_textsensor.cpp | 2 +- .../modbus_controller/text_sensor/modbus_textsensor.h | 3 +-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 825b91280e..b927faf9a7 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -13,6 +13,7 @@ from .const import ( CONF_MODBUS_CONTROLLER_ID, CONF_REGISTER_COUNT, CONF_REGISTER_TYPE, + CONF_RESPONSE_SIZE, CONF_SKIP_UPDATES, CONF_VALUE_TYPE, ) @@ -125,6 +126,7 @@ ModbusItemBaseSchema = cv.Schema( cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_RESPONSE_SIZE, default=0): cv.positive_int, }, ) @@ -180,6 +182,9 @@ async def add_modbus_base_properties( if CONF_CUSTOM_COMMAND in config: cg.add(var.set_custom_data(config[CONF_CUSTOM_COMMAND])) + if config[CONF_RESPONSE_SIZE] > 0: + cg.add(var.set_register_size(config[CONF_RESPONSE_SIZE])) + if CONF_LAMBDA in config: template_ = await cg.process_lambda( config[CONF_LAMBDA], diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 045075f5e0..f4948e6ff9 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -254,16 +254,18 @@ class SensorItem { size_t virtual get_register_size() const { if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) return 1; - else - return register_count * 2; + else // if CONF_RESPONSE_BYTES is used override the default + return response_bytes > 0 ? response_bytes : register_count * 2; } - + // Override register size for modbus devices not using 1 register for one dword + void set_register_size(uint8_t register_size) { response_bytes = register_size; } ModbusRegisterType register_type; SensorValueType sensor_value_type; uint16_t start_address; uint32_t bitmask; uint8_t offset; uint8_t register_count; + uint8_t response_bytes{0}; uint8_t skip_updates; std::vector custom_data{}; bool force_new_range{false}; diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp index a06d44e90b..25b79474e8 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp @@ -13,7 +13,7 @@ void ModbusTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Modbus Controller Te void ModbusTextSensor::parse_and_publish(const std::vector &data) { std::ostringstream output; - uint8_t max_items = this->response_bytes_; + uint8_t max_items = this->response_bytes; char buffer[4]; bool add_comma = false; for (auto b : data) { diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h index 77b5b9363a..3db4d94a45 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h @@ -17,7 +17,7 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi this->register_type = register_type; this->start_address = start_address; this->offset = offset; - this->response_bytes_ = response_bytes; + this->response_bytes = response_bytes; this->register_count = register_count; this->encode_ = encode; this->skip_updates = skip_updates; @@ -38,7 +38,6 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi protected: RawEncoding encode_; - uint16_t response_bytes_; }; } // namespace modbus_controller From 6a0b34328909d30ea23cb49e3c5295e4e5f652c1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 2 Dec 2021 19:38:49 +1300 Subject: [PATCH 424/549] Bump version to 2022.1.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 740e38cf44..df86216447 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.12.0-dev" +__version__ = "2022.1.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From f0bcf81a98efa57f721bbc6648d48189fe830126 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 2 Dec 2021 21:23:11 +1300 Subject: [PATCH 425/549] Add a simple helper to remap values (#2850) --- esphome/core/helpers.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 63aa4123ae..8b61e6aa38 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -415,4 +415,14 @@ optional parse_number(const std::string &str) { ///@} +/// @name Number manipulation +///@{ + +/// Remap a number from one range to another. +template T remap(U value, U min, U max, T min_out, T max_out) { + return (value - min) * (max_out - min_out) / (max - min) + min_out; +} + +///@} + } // namespace esphome From 40c017fd5461287116909c8d003869d5117f9544 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 2 Dec 2021 19:52:56 +0100 Subject: [PATCH 426/549] Update ota_component.cpp (#2852) --- esphome/components/ota/ota_component.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 79edd91173..0cf5ea242c 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -277,6 +277,7 @@ void OTAComponent::handle_() { ssize_t read = this->client_->read(buf, requested); if (read == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { + App.feed_wdt(); delay(1); continue; } @@ -305,8 +306,9 @@ void OTAComponent::handle_() { #ifdef USE_OTA_STATE_CALLBACK this->state_callback_.call(OTA_IN_PROGRESS, percentage, 0); #endif - // slow down OTA update to avoid getting killed by task watchdog (task_wdt) - delay(10); + // feed watchdog and give other tasks a chance to run + App.feed_wdt(); + yield(); } } From 06da540ab03f9b25df876d004b02116fd0fb2674 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Dec 2021 13:29:08 +0100 Subject: [PATCH 427/549] Bump pylint from 2.12.1 to 2.12.2 (#2858) 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 b916e8bb1b..abfd07f022 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.12.1 +pylint==2.12.2 flake8==4.0.1 black==21.11b1 pre-commit From ef44acbf10322e8d3ed1013ee050aa57befd6003 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Dec 2021 19:42:49 +1300 Subject: [PATCH 428/549] Bump esphome-dashboard to 20211206.0 (#2870) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6061476802..22cfeecd5d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20211201.0 +esphome-dashboard==20211206.0 aioesphomeapi==10.6.0 zeroconf==0.36.13 From 10e89a7dbbcb1b8a7894333c35cfc49acbc3ffce Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 6 Dec 2021 07:54:46 +0100 Subject: [PATCH 429/549] tlc59208f : fix compilation error (#2867) --- esphome/components/tlc59208f/tlc59208f_output.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/tlc59208f/tlc59208f_output.cpp b/esphome/components/tlc59208f/tlc59208f_output.cpp index 59fb9f98ed..bd62f8de6d 100644 --- a/esphome/components/tlc59208f/tlc59208f_output.cpp +++ b/esphome/components/tlc59208f/tlc59208f_output.cpp @@ -1,6 +1,7 @@ #include "tlc59208f_output.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace tlc59208f { From c84efe64d3f66d3a6eb63871c56ae7ba49b8210e Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Mon, 6 Dec 2021 07:56:53 +0100 Subject: [PATCH 430/549] ADC: Turn verbose the debugging "got voltage" (#2863) --- esphome/components/adc/adc_sensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index c8242ce008..0a439f8b8d 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -91,7 +91,7 @@ void ADCSensor::dump_config() { float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } void ADCSensor::update() { float value_v = this->sample(); - ESP_LOGD(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); + ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); this->publish_state(value_v); } From 14f6ae75ea0efdd7118d82286b591ba7f8d746d0 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 6 Dec 2021 07:58:26 +0100 Subject: [PATCH 431/549] SPS30 : fix i2c read size (#2866) --- esphome/components/sps30/sps30.cpp | 26 ++++++++++++++++---------- esphome/components/sps30/sps30.h | 1 + 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/esphome/components/sps30/sps30.cpp b/esphome/components/sps30/sps30.cpp index 472b7606ed..6160120564 100644 --- a/esphome/components/sps30/sps30.cpp +++ b/esphome/components/sps30/sps30.cpp @@ -32,14 +32,11 @@ void SPS30Component::setup() { return; } - uint16_t raw_firmware_version[4]; - if (!this->read_data_(raw_firmware_version, 4)) { + if (!this->read_data_(&raw_firmware_version_, 1)) { this->error_code_ = FIRMWARE_VERSION_READ_FAILED; this->mark_failed(); return; } - ESP_LOGD(TAG, " Firmware version v%0d.%02d", (raw_firmware_version[0] >> 8), - uint16_t(raw_firmware_version[0] & 0xFF)); /// Serial number identification if (!this->write_command_(SPS30_CMD_GET_SERIAL_NUMBER)) { this->error_code_ = SERIAL_NUMBER_REQUEST_FAILED; @@ -59,6 +56,8 @@ void SPS30Component::setup() { this->serial_number_[i * 2 + 1] = uint16_t(uint16_t(raw_serial_number[i] & 0xFF)); } ESP_LOGD(TAG, " Serial Number: '%s'", this->serial_number_); + this->status_clear_warning(); + this->skipped_data_read_cycles_ = 0; this->start_continuous_measurement_(); }); } @@ -93,10 +92,17 @@ void SPS30Component::dump_config() { } LOG_UPDATE_INTERVAL(this); ESP_LOGCONFIG(TAG, " Serial Number: '%s'", this->serial_number_); - LOG_SENSOR(" ", "PM1.0", this->pm_1_0_sensor_); - LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_); - LOG_SENSOR(" ", "PM4", this->pm_4_0_sensor_); - LOG_SENSOR(" ", "PM10", this->pm_10_0_sensor_); + ESP_LOGCONFIG(TAG, " Firmware version v%0d.%0d", (raw_firmware_version_ >> 8), + uint16_t(raw_firmware_version_ & 0xFF)); + LOG_SENSOR(" ", "PM1.0 Weight Concentration", this->pm_1_0_sensor_); + LOG_SENSOR(" ", "PM2.5 Weight Concentration", this->pm_2_5_sensor_); + LOG_SENSOR(" ", "PM4 Weight Concentration", this->pm_4_0_sensor_); + LOG_SENSOR(" ", "PM10 Weight Concentration", this->pm_10_0_sensor_); + LOG_SENSOR(" ", "PM1.0 Number Concentration", this->pmc_1_0_sensor_); + LOG_SENSOR(" ", "PM2.5 Number Concentration", this->pmc_2_5_sensor_); + LOG_SENSOR(" ", "PM4 Number Concentration", this->pmc_4_0_sensor_); + LOG_SENSOR(" ", "PM10 Number Concentration", this->pmc_10_0_sensor_); + LOG_SENSOR(" ", "PM typical size", this->pm_size_sensor_); } void SPS30Component::update() { @@ -123,8 +129,8 @@ void SPS30Component::update() { return; } - uint16_t raw_read_status[1]; - if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) { + uint16_t raw_read_status; + if (!this->read_data_(&raw_read_status, 1) || raw_read_status == 0x00) { ESP_LOGD(TAG, "Sensor measurement not ready yet."); this->skipped_data_read_cycles_++; /// The following logic is required to address the cases when a sensor is quickly replaced before it's marked diff --git a/esphome/components/sps30/sps30.h b/esphome/components/sps30/sps30.h index 2f977252a5..bae33a46e1 100644 --- a/esphome/components/sps30/sps30.h +++ b/esphome/components/sps30/sps30.h @@ -33,6 +33,7 @@ class SPS30Component : public PollingComponent, public i2c::I2CDevice { bool read_data_(uint16_t *data, uint8_t len); uint8_t sht_crc_(uint8_t data1, uint8_t data2); char serial_number_[17] = {0}; /// Terminating NULL character + uint16_t raw_firmware_version_; bool start_continuous_measurement_(); uint8_t skipped_data_read_cycles_ = 0; From d3e48e296f81f47c65de8d005023e272e64510af Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 6 Dec 2021 07:59:50 +0100 Subject: [PATCH 432/549] Fix MCP23x17 not disabling pullup after config change (#2855) --- esphome/components/mcp23x17_base/mcp23x17_base.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/mcp23x17_base/mcp23x17_base.cpp b/esphome/components/mcp23x17_base/mcp23x17_base.cpp index e975670faa..744f2fbe9c 100644 --- a/esphome/components/mcp23x17_base/mcp23x17_base.cpp +++ b/esphome/components/mcp23x17_base/mcp23x17_base.cpp @@ -24,6 +24,7 @@ void MCP23X17Base::pin_mode(uint8_t pin, gpio::Flags flags) { uint8_t gppu = pin < 8 ? mcp23x17_base::MCP23X17_GPPUA : mcp23x17_base::MCP23X17_GPPUB; if (flags == gpio::FLAG_INPUT) { this->update_reg(pin, true, iodir); + this->update_reg(pin, false, gppu); } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { this->update_reg(pin, true, iodir); this->update_reg(pin, true, gppu); From ffc112c9d04e9ffd642ac2cf7284fc302aaa207c Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 6 Dec 2021 08:01:14 +0100 Subject: [PATCH 433/549] Don't disable idle task WDT when it's not enabled (#2856) --- esphome/components/esp32/core.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index a9756b41cd..6123d83a34 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -42,11 +42,11 @@ void arch_init() { // Idle task watchdog is disabled on ESP-IDF #elif defined(USE_ARDUINO) enableLoopWDT(); - // Disable idle task watchdog on the core we're using (Arduino pins the process to a core) -#if CONFIG_ARDUINO_RUNNING_CORE == 0 + // Disable idle task watchdog on the core we're using (Arduino pins the task to a core) +#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0) && CONFIG_ARDUINO_RUNNING_CORE == 0 disableCore0WDT(); #endif -#if CONFIG_ARDUINO_RUNNING_CORE == 1 +#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1) && CONFIG_ARDUINO_RUNNING_CORE == 1 disableCore1WDT(); #endif #endif From 71fe2f7ed334c84d209fd641ec99bd090e1b79da Mon Sep 17 00:00:00 2001 From: Massimiliano Ravelli Date: Mon, 6 Dec 2021 08:01:50 +0100 Subject: [PATCH 434/549] Ignore already stopped dhcp for ethernet (#2862) Co-authored-by: Oxan van Leeuwen Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/ethernet/ethernet_component.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 00f68df2b4..384a31ed2f 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -184,7 +184,9 @@ void EthernetComponent::start_connect_() { } err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_ETH); - ESPHL_ERROR_CHECK(err, "DHCPC stop error"); + if (err != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) { + ESPHL_ERROR_CHECK(err, "DHCPC stop error"); + } err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_ETH, &info); ESPHL_ERROR_CHECK(err, "DHCPC set IP info error"); From 55db1908758ff3b5585b01b8563c2549d9784eb9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Dec 2021 20:15:34 +1300 Subject: [PATCH 435/549] Add endpoint to fetch secrets keys (#2873) --- esphome/dashboard/dashboard.py | 25 ++++++++++++++++++++++++- esphome/yaml_util.py | 7 ++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index c98047d9e5..5e5cc4ecd2 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -27,7 +27,7 @@ import tornado.process import tornado.web import tornado.websocket -from esphome import const, platformio_api, util +from esphome import const, platformio_api, util, yaml_util from esphome.helpers import mkdir_p, get_bool_env, run_system_command from esphome.storage_json import ( EsphomeStorageJSON, @@ -836,6 +836,28 @@ class LogoutHandler(BaseHandler): self.redirect("./login") +class SecretKeysRequestHandler(BaseHandler): + @authenticated + def get(self): + + filename = None + + for secret_filename in const.SECRETS_FILES: + relative_filename = settings.rel_path(secret_filename) + if os.path.isfile(relative_filename): + filename = relative_filename + break + + if filename is None: + self.send_error(404) + return + + secret_keys = list(yaml_util.load_yaml(filename, clear_secrets=False)) + + self.set_header("content-type", "application/json") + self.write(json.dumps(secret_keys)) + + def get_base_frontend_path(): if ENV_DEV not in os.environ: import esphome_dashboard @@ -939,6 +961,7 @@ def make_app(debug=get_bool_env(ENV_DEV)): (f"{rel}static/(.*)", StaticFileHandler, {"path": get_static_path()}), (f"{rel}devices", ListDevicesHandler), (f"{rel}import", ImportRequestHandler), + (f"{rel}secret_keys", SecretKeysRequestHandler), ], **app_settings, ) diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index bdadbbd43a..57009be57e 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -329,9 +329,10 @@ ESPHomeLoader.add_constructor("!lambda", ESPHomeLoader.construct_lambda) ESPHomeLoader.add_constructor("!force", ESPHomeLoader.construct_force) -def load_yaml(fname): - _SECRET_VALUES.clear() - _SECRET_CACHE.clear() +def load_yaml(fname, clear_secrets=True): + if clear_secrets: + _SECRET_VALUES.clear() + _SECRET_CACHE.clear() return _load_yaml_internal(fname) From 49932747b31c8fb8fdc3b7650abb4911a026f308 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Dec 2021 20:57:56 +1300 Subject: [PATCH 436/549] Adopt using wifi secrets that should exist at this point (#2874) --- esphome/components/dashboard_import/__init__.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/esphome/components/dashboard_import/__init__.py b/esphome/components/dashboard_import/__init__.py index d483c77c61..4c47c32ccc 100644 --- a/esphome/components/dashboard_import/__init__.py +++ b/esphome/components/dashboard_import/__init__.py @@ -29,12 +29,11 @@ CONFIG_SCHEMA = cv.Schema( } ) -WIFI_MESSAGE = """ +WIFI_CONFIG = """ -# Do not forget to add your own wifi configuration before installing this configuration -# wifi: -# ssid: !secret wifi_ssid -# password: !secret wifi_password +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password """ @@ -55,6 +54,6 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N "esphome": {"name_add_mac_suffix": False}, } p.write_text( - dump(config) + WIFI_MESSAGE, + dump(config) + WIFI_CONFIG, encoding="utf8", ) From 1db7043a4de5491a8fd68be2f9a24e50de11337e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Dec 2021 20:58:51 +1300 Subject: [PATCH 437/549] Allow wizard to specify secrets (#2875) --- esphome/wizard.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/esphome/wizard.py b/esphome/wizard.py index 6c87b66453..5f4f347ba7 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -86,12 +86,11 @@ def wizard_file(**kwargs): config += "\n\nwifi:\n" if "ssid" in kwargs: - # pylint: disable=consider-using-f-string - config += """ ssid: "{ssid}" - password: "{psk}" -""".format( - **kwargs - ) + if kwargs["ssid"].startswith("!secret"): + template = " ssid: {ssid}\n password: {psk}\n" + else: + template = """ ssid: "{ssid}"\n password: "{psk}"\n""" + config += template.format(**kwargs) else: config += """ # ssid: "My SSID" # password: "mypassword" From 12467a18e63f38ec600f97d4dfe7e76baa916aec Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 6 Dec 2021 19:24:20 +0100 Subject: [PATCH 438/549] Feed watchdog when no component loops (#2857) --- esphome/core/application.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 1bef99e868..a423397453 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -37,6 +37,7 @@ void Application::setup() { component->call(); this->scheduler.process_to_add(); + this->feed_wdt(); if (component->can_proceed()) continue; @@ -46,14 +47,15 @@ void Application::setup() { do { uint32_t new_app_state = STATUS_LED_WARNING; this->scheduler.call(); + this->feed_wdt(); for (uint32_t j = 0; j <= i; j++) { this->components_[j]->call(); new_app_state |= this->components_[j]->get_component_state(); this->app_state_ |= new_app_state; + this->feed_wdt(); } this->app_state_ = new_app_state; yield(); - this->feed_wdt(); } while (!component->can_proceed()); } @@ -65,6 +67,7 @@ void Application::loop() { uint32_t new_app_state = 0; this->scheduler.call(); + this->feed_wdt(); for (Component *component : this->looping_components_) { { WarnIfComponentBlockingGuard guard{component}; From 5404617d435a3e95c8e7e69a9d16039fa1d12094 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 7 Dec 2021 07:41:40 +1300 Subject: [PATCH 439/549] Bump esphome-dashboard to 20211207.0 (#2877) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 22cfeecd5d..e27ae0f625 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20211206.0 +esphome-dashboard==20211207.0 aioesphomeapi==10.6.0 zeroconf==0.36.13 From e5cc19de43a94bcfb413106b4f2d26f7777bf8a1 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Mon, 6 Dec 2021 23:26:06 +0100 Subject: [PATCH 440/549] Feed watchdog while setting up OTA (#2876) --- esphome/components/ota/ota_component.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 0cf5ea242c..92256eb1b6 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -372,6 +372,7 @@ bool OTAComponent::readall_(uint8_t *buf, size_t len) { ssize_t read = this->client_->read(buf + at, len - at); if (read == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { + App.feed_wdt(); delay(1); continue; } @@ -383,6 +384,7 @@ bool OTAComponent::readall_(uint8_t *buf, size_t len) { } else { at += read; } + App.feed_wdt(); delay(1); } @@ -401,6 +403,7 @@ bool OTAComponent::writeall_(const uint8_t *buf, size_t len) { ssize_t written = this->client_->write(buf + at, len - at); if (written == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { + App.feed_wdt(); delay(1); continue; } @@ -409,6 +412,7 @@ bool OTAComponent::writeall_(const uint8_t *buf, size_t len) { } else { at += written; } + App.feed_wdt(); delay(1); } return true; From 2253d4bc165ec8fa46795202c028a89f22d8c6d2 Mon Sep 17 00:00:00 2001 From: Yuval Brik Date: Tue, 7 Dec 2021 00:30:27 +0200 Subject: [PATCH 441/549] Support different run duration for non-timer wakeup (#2861) --- esphome/components/deep_sleep/__init__.py | 40 +++++++++++++- .../deep_sleep/deep_sleep_component.cpp | 35 ++++++++++++- .../deep_sleep/deep_sleep_component.h | 19 +++++++ .../deep_sleep/test_deep_sleep.py | 52 +++++++++++++++++++ .../deep_sleep/test_deep_sleep1.yaml | 9 ++++ .../deep_sleep/test_deep_sleep2.yaml | 11 ++++ tests/test2.yaml | 5 +- 7 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 tests/component_tests/deep_sleep/test_deep_sleep.py create mode 100644 tests/component_tests/deep_sleep/test_deep_sleep1.yaml create mode 100644 tests/component_tests/deep_sleep/test_deep_sleep2.yaml diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index f47888b8eb..ba4c2c0d7e 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -41,15 +41,30 @@ EXT1_WAKEUP_MODES = { "ALL_LOW": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ALL_LOW, "ANY_HIGH": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_HIGH, } +WakeupCauseToRunDuration = deep_sleep_ns.struct("WakeupCauseToRunDuration") CONF_WAKEUP_PIN_MODE = "wakeup_pin_mode" CONF_ESP32_EXT1_WAKEUP = "esp32_ext1_wakeup" CONF_TOUCH_WAKEUP = "touch_wakeup" +CONF_DEFAULT = "default" +CONF_GPIO_WAKEUP_REASON = "gpio_wakeup_reason" +CONF_TOUCH_WAKEUP_REASON = "touch_wakeup_reason" + +WAKEUP_CAUSES_SCHEMA = cv.Schema( + { + cv.Required(CONF_DEFAULT): cv.positive_time_period_milliseconds, + cv.Optional(CONF_TOUCH_WAKEUP_REASON): cv.positive_time_period_milliseconds, + cv.Optional(CONF_GPIO_WAKEUP_REASON): cv.positive_time_period_milliseconds, + } +) CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(DeepSleepComponent), - cv.Optional(CONF_RUN_DURATION): cv.positive_time_period_milliseconds, + cv.Optional(CONF_RUN_DURATION): cv.Any( + cv.All(cv.only_on_esp32, WAKEUP_CAUSES_SCHEMA), + cv.positive_time_period_milliseconds, + ), cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds, cv.Optional(CONF_WAKEUP_PIN): cv.All( cv.only_on_esp32, pins.internal_gpio_input_pin_schema, validate_pin_number @@ -85,7 +100,28 @@ async def to_code(config): if CONF_WAKEUP_PIN_MODE in config: cg.add(var.set_wakeup_pin_mode(config[CONF_WAKEUP_PIN_MODE])) if CONF_RUN_DURATION in config: - cg.add(var.set_run_duration(config[CONF_RUN_DURATION])) + run_duration_config = config[CONF_RUN_DURATION] + if not isinstance(run_duration_config, dict): + cg.add(var.set_run_duration(config[CONF_RUN_DURATION])) + else: + default_run_duration = run_duration_config[CONF_DEFAULT] + wakeup_cause_to_run_duration = cg.StructInitializer( + WakeupCauseToRunDuration, + ("default_cause", default_run_duration), + ( + "touch_cause", + run_duration_config.get( + CONF_TOUCH_WAKEUP_REASON, default_run_duration + ), + ), + ( + "gpio_cause", + run_duration_config.get( + CONF_GPIO_WAKEUP_REASON, default_run_duration + ), + ), + ) + cg.add(var.set_run_duration(wakeup_cause_to_run_duration)) if CONF_ESP32_EXT1_WAKEUP in config: conf = config[CONF_ESP32_EXT1_WAKEUP] diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index c854b6da6e..7774014d3d 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -13,12 +13,35 @@ static const char *const TAG = "deep_sleep"; bool global_has_deep_sleep = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +optional DeepSleepComponent::get_run_duration_() const { +#ifdef USE_ESP32 + if (this->wakeup_cause_to_run_duration_.has_value()) { + esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause(); + switch (wakeup_cause) { + case ESP_SLEEP_WAKEUP_EXT0: + case ESP_SLEEP_WAKEUP_EXT1: + return this->wakeup_cause_to_run_duration_->gpio_cause; + case ESP_SLEEP_WAKEUP_TOUCHPAD: + return this->wakeup_cause_to_run_duration_->touch_cause; + default: + return this->wakeup_cause_to_run_duration_->default_cause; + } + } +#endif + return this->run_duration_; +} + void DeepSleepComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up Deep Sleep..."); global_has_deep_sleep = true; - if (this->run_duration_.has_value()) - this->set_timeout(*this->run_duration_, [this]() { this->begin_sleep(); }); + const optional run_duration = get_run_duration_(); + if (run_duration.has_value()) { + ESP_LOGI(TAG, "Scheduling Deep Sleep to start in %u ms", *run_duration); + this->set_timeout(*run_duration, [this]() { this->begin_sleep(); }); + } else { + ESP_LOGD(TAG, "Not scheduling Deep Sleep, as no run duration is configured."); + } } void DeepSleepComponent::dump_config() { ESP_LOGCONFIG(TAG, "Setting up Deep Sleep..."); @@ -33,6 +56,11 @@ void DeepSleepComponent::dump_config() { if (wakeup_pin_ != nullptr) { LOG_PIN(" Wakeup Pin: ", this->wakeup_pin_); } + if (this->wakeup_cause_to_run_duration_.has_value()) { + ESP_LOGCONFIG(TAG, " Default Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->default_cause); + ESP_LOGCONFIG(TAG, " Touch Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->touch_cause); + ESP_LOGCONFIG(TAG, " GPIO Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->gpio_cause); + } #endif } void DeepSleepComponent::loop() { @@ -49,6 +77,9 @@ void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode 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; } +void DeepSleepComponent::set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration) { + wakeup_cause_to_run_duration_ = wakeup_cause_to_run_duration; +} #endif void DeepSleepComponent::set_run_duration(uint32_t time_ms) { this->run_duration_ = time_ms; } void DeepSleepComponent::begin_sleep(bool manual) { diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index d7969ba999..59df199a9f 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -32,6 +32,15 @@ struct Ext1Wakeup { esp_sleep_ext1_wakeup_mode_t wakeup_mode; }; +struct WakeupCauseToRunDuration { + // Run duration if woken up by timer or any other reason besides those below. + uint32_t default_cause; + // Run duration if woken up by touch pads. + uint32_t touch_cause; + // Run duration if woken up by GPIO pins. + uint32_t gpio_cause; +}; + #endif template class EnterDeepSleepAction; @@ -59,6 +68,11 @@ class DeepSleepComponent : public Component { void set_ext1_wakeup(Ext1Wakeup ext1_wakeup); void set_touch_wakeup(bool touch_wakeup); + + // Set the duration in ms for how long the code should run before entering + // deep sleep mode, according to the cause the ESP32 has woken. + void set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration); + #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); @@ -75,12 +89,17 @@ class DeepSleepComponent : public Component { void prevent_deep_sleep(); protected: + // Returns nullopt if no run duration is set. Otherwise, returns the run + // duration before entering deep sleep. + optional get_run_duration_() const; + optional sleep_duration_; #ifdef USE_ESP32 InternalGPIOPin *wakeup_pin_; WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE}; optional ext1_wakeup_; optional touch_wakeup_; + optional wakeup_cause_to_run_duration_; #endif optional run_duration_; bool next_enter_deep_sleep_{false}; diff --git a/tests/component_tests/deep_sleep/test_deep_sleep.py b/tests/component_tests/deep_sleep/test_deep_sleep.py new file mode 100644 index 0000000000..690d323a50 --- /dev/null +++ b/tests/component_tests/deep_sleep/test_deep_sleep.py @@ -0,0 +1,52 @@ +"""Tests for the deep sleep component.""" + + +def test_deep_sleep_setup(generate_main): + """ + When the deep sleep is set in the yaml file, it should be registered in main + """ + main_cpp = generate_main( + "tests/component_tests/deep_sleep/test_deep_sleep1.yaml" + ) + + assert "deepsleep = new deep_sleep::DeepSleepComponent();" in main_cpp + assert "App.register_component(deepsleep);" in main_cpp + + +def test_deep_sleep_sleep_duration(generate_main): + """ + When deep sleep is configured with sleep duration, it should be set. + """ + main_cpp = generate_main( + "tests/component_tests/deep_sleep/test_deep_sleep1.yaml" + ) + + assert "deepsleep->set_sleep_duration(60000);" in main_cpp + + +def test_deep_sleep_run_duration_simple(generate_main): + """ + When deep sleep is configured with run duration, it should be set. + """ + main_cpp = generate_main( + "tests/component_tests/deep_sleep/test_deep_sleep1.yaml" + ) + + assert "deepsleep->set_run_duration(10000);" in main_cpp + + +def test_deep_sleep_run_duration_dictionary(generate_main): + """ + When deep sleep is configured with dictionary run duration, it should be set. + """ + main_cpp = generate_main( + "tests/component_tests/deep_sleep/test_deep_sleep2.yaml" + ) + + assert ( + "deepsleep->set_run_duration(deep_sleep::WakeupCauseToRunDuration{\n" + " .default_cause = 10000,\n" + " .touch_cause = 10000,\n" + " .gpio_cause = 30000,\n" + "});" + ) in main_cpp diff --git a/tests/component_tests/deep_sleep/test_deep_sleep1.yaml b/tests/component_tests/deep_sleep/test_deep_sleep1.yaml new file mode 100644 index 0000000000..18a425df58 --- /dev/null +++ b/tests/component_tests/deep_sleep/test_deep_sleep1.yaml @@ -0,0 +1,9 @@ +esphome: + name: test + platform: ESP32 + board: nodemcu-32s + +deep_sleep: + id: deepsleep + sleep_duration: 1min + run_duration: 10s diff --git a/tests/component_tests/deep_sleep/test_deep_sleep2.yaml b/tests/component_tests/deep_sleep/test_deep_sleep2.yaml new file mode 100644 index 0000000000..49a7f510f2 --- /dev/null +++ b/tests/component_tests/deep_sleep/test_deep_sleep2.yaml @@ -0,0 +1,11 @@ +esphome: + name: test + platform: ESP32 + board: nodemcu-32s + +deep_sleep: + id: deepsleep + sleep_duration: 1min + run_duration: + default: 10s + gpio_wakeup_reason: 30s diff --git a/tests/test2.yaml b/tests/test2.yaml index 50743dc643..67b819a4d3 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -55,7 +55,10 @@ logger: level: DEBUG deep_sleep: - run_duration: 20s + run_duration: + default: 20s + gpio_wakeup_reason: 10s + touch_wakeup_reason: 15s sleep_duration: 50s wakeup_pin: GPIO39 wakeup_pin_mode: INVERT_WAKEUP From 6fe4ff7f85c625e90f28b31f43bdf241c303f2e8 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 7 Dec 2021 20:46:25 +0100 Subject: [PATCH 442/549] Drop len parameter from parse_number() (#2883) --- esphome/components/ezo/ezo.cpp | 2 +- esphome/core/helpers.h | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index 3c1b6e33e8..2ee5782ff6 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -79,7 +79,7 @@ void EZOSensor::loop() { if (buf[i] == ',') buf[i] = '\0'; - float val = parse_number((char *) &buf[1], sizeof(buf) - 2).value_or(0); + float val = parse_number((char *) &buf[1]).value_or(0); this->publish_state(val); } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 8b61e6aa38..208c555afd 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -370,9 +370,9 @@ std::string str_sanitize(const std::string &str); /// @name Parsing & formatting ///@{ -/// Parse an unsigned decimal number (requires null-terminated string). +/// Parse an unsigned decimal number from a null-terminated string. template::value && std::is_unsigned::value), int> = 0> -optional parse_number(const char *str, size_t len) { +optional parse_number(const char *str) { char *end = nullptr; unsigned long value = ::strtoul(str, &end, 10); // NOLINT(google-runtime-int) if (end == str || *end != '\0' || value > std::numeric_limits::max()) @@ -382,11 +382,11 @@ optional parse_number(const char *str, size_t len) { /// Parse an unsigned decimal number. template::value && std::is_unsigned::value), int> = 0> optional parse_number(const std::string &str) { - return parse_number(str.c_str(), str.length() + 1); + return parse_number(str.c_str()); } -/// Parse a signed decimal number (requires null-terminated string). +/// Parse a signed decimal number from a null-terminated string. template::value && std::is_signed::value), int> = 0> -optional parse_number(const char *str, size_t len) { +optional parse_number(const char *str) { char *end = nullptr; signed long value = ::strtol(str, &end, 10); // NOLINT(google-runtime-int) if (end == str || *end != '\0' || value < std::numeric_limits::min() || value > std::numeric_limits::max()) @@ -396,11 +396,10 @@ optional parse_number(const char *str, size_t len) { /// Parse a signed decimal number. template::value && std::is_signed::value), int> = 0> optional parse_number(const std::string &str) { - return parse_number(str.c_str(), str.length() + 1); + return parse_number(str.c_str()); } -/// Parse a decimal floating-point number (requires null-terminated string). -template::value), int> = 0> -optional parse_number(const char *str, size_t len) { +/// Parse a decimal floating-point number from a null-terminated string. +template::value), int> = 0> optional parse_number(const char *str) { char *end = nullptr; float value = ::strtof(str, &end); if (end == str || *end != '\0' || value == HUGE_VALF) @@ -410,7 +409,7 @@ optional parse_number(const char *str, size_t len) { /// Parse a decimal floating-point number. template::value), int> = 0> optional parse_number(const std::string &str) { - return parse_number(str.c_str(), str.length() + 1); + return parse_number(str.c_str()); } ///@} From 3d51ac8df0279dc714527304011ac8c405ae0b15 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:22:03 +1300 Subject: [PATCH 443/549] Use new platform component config blocks for wizard (#2885) --- esphome/wizard.py | 24 ++++++++++++++++++++++-- tests/unit_tests/test_wizard.py | 9 +++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/esphome/wizard.py b/esphome/wizard.py index 5f4f347ba7..f2632caf71 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -45,9 +45,9 @@ OTA_BIG = r""" ____ _______ BASE_CONFIG = """esphome: name: {name} - platform: {platform} - board: {board} +""" +LOGGER_API_CONFIG = """ # Enable logging logger: @@ -55,6 +55,18 @@ logger: api: """ +ESP8266_CONFIG = """ +esp8266: + board: {board} +""" + +ESP32_CONFIG = """ +esp32: + board: {board} + framework: + type: arduino +""" + def sanitize_double_quotes(value): return value.replace("\\", "\\\\").replace('"', '\\"') @@ -71,6 +83,14 @@ def wizard_file(**kwargs): config = BASE_CONFIG.format(**kwargs) + config += ( + ESP8266_CONFIG.format(**kwargs) + if kwargs["platform"] == "ESP8266" + else ESP32_CONFIG.format(**kwargs) + ) + + config += LOGGER_API_CONFIG + # Configure API if "password" in kwargs: config += f" password: \"{kwargs['password']}\"\n" diff --git a/tests/unit_tests/test_wizard.py b/tests/unit_tests/test_wizard.py index 18e040b0a6..59fcfbff60 100644 --- a/tests/unit_tests/test_wizard.py +++ b/tests/unit_tests/test_wizard.py @@ -11,7 +11,7 @@ def default_config(): return { "name": "test-name", "platform": "test_platform", - "board": "test_board", + "board": "esp01_1m", "ssid": "test_ssid", "psk": "test_psk", "password": "", @@ -105,6 +105,7 @@ def test_wizard_write_sets_platform(default_config, tmp_path, monkeypatch): If the platform is not explicitly set, use "ESP8266" if the board is one of the ESP8266 boards """ # Given + del default_config["platform"] monkeypatch.setattr(wz, "write_file", MagicMock()) # When @@ -112,7 +113,7 @@ def test_wizard_write_sets_platform(default_config, tmp_path, monkeypatch): # Then generated_config = wz.write_file.call_args.args[1] - assert f"platform: {default_config['platform']}" in generated_config + assert "esp8266:" in generated_config def test_wizard_write_defaults_platform_from_board_esp8266( @@ -132,7 +133,7 @@ def test_wizard_write_defaults_platform_from_board_esp8266( # Then generated_config = wz.write_file.call_args.args[1] - assert "platform: ESP8266" in generated_config + assert "esp8266:" in generated_config def test_wizard_write_defaults_platform_from_board_esp32( @@ -152,7 +153,7 @@ def test_wizard_write_defaults_platform_from_board_esp32( # Then generated_config = wz.write_file.call_args.args[1] - assert "platform: ESP32" in generated_config + assert "esp32:" in generated_config def test_safe_print_step_prints_step_number_and_description(monkeypatch): From 58fb7a02f6c0ad402cf6a2036668fd07a8452e67 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 8 Dec 2021 12:42:50 +1300 Subject: [PATCH 444/549] Bump esphome-dashboard to 20211208.0 (#2887) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e27ae0f625..f7b1a6a1fe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20211207.0 +esphome-dashboard==20211208.0 aioesphomeapi==10.6.0 zeroconf==0.36.13 From 6df1d5222d23e0af7bce2d30c248046717e8c043 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 8 Dec 2021 00:46:36 +0100 Subject: [PATCH 445/549] Drop unused xSemaphoreWait define (#2888) --- esphome/core/helpers.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 208c555afd..db507f824d 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -19,10 +19,6 @@ #define ALWAYS_INLINE __attribute__((always_inline)) #define PACKED __attribute__((packed)) -#define xSemaphoreWait(semaphore, wait_time) \ - xSemaphoreTake(semaphore, wait_time); \ - xSemaphoreGive(semaphore); - namespace esphome { /// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes). From 24ec5a6e9d6e379f0f571eaac724a76656fa2d82 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 10 Dec 2021 09:32:34 +1300 Subject: [PATCH 446/549] Fix published state for modbus number (#2894) --- .../modbus_controller/number/modbus_number.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index ba2ffdd09f..70be9eaa1f 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -27,6 +27,7 @@ void ModbusNumber::parse_and_publish(const std::vector &data) { void ModbusNumber::control(float value) { std::vector data; + float write_value = value; // Is there are lambda configured? if (this->write_transform_func_.has_value()) { // data is passed by reference @@ -35,23 +36,23 @@ void ModbusNumber::control(float value) { auto val = (*this->write_transform_func_)(this, value, data); if (val.has_value()) { ESP_LOGV(TAG, "Value overwritten by lambda"); - value = val.value(); + write_value = val.value(); } else { ESP_LOGV(TAG, "Communication handled by lambda - exiting control"); return; } } else { - value = multiply_by_ * value; + write_value = multiply_by_ * write_value; } // lambda didn't set payload if (data.empty()) { - data = float_to_payload(value, this->sensor_value_type); + data = float_to_payload(write_value, this->sensor_value_type); } ESP_LOGD(TAG, "Updating register: connected Sensor=%s start address=0x%X register count=%d new value=%.02f (val=%.02f)", - this->get_name().c_str(), this->start_address, this->register_count, value, value); + this->get_name().c_str(), this->start_address, this->register_count, write_value, write_value); // Create and send the write command auto write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, From c490388e8014ec895a6a7182ee98987c0665185a Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Thu, 9 Dec 2021 17:44:43 -0300 Subject: [PATCH 447/549] Modbus number/output use write single (#2896) Co-authored-by: Martin <25747549+martgras@users.noreply.github.com> --- esphome/components/modbus_controller/const.py | 1 + .../components/modbus_controller/number/__init__.py | 3 +++ .../modbus_controller/number/modbus_number.cpp | 10 +++++++--- .../modbus_controller/number/modbus_number.h | 2 ++ .../components/modbus_controller/output/__init__.py | 3 +++ .../modbus_controller/output/modbus_output.cpp | 10 ++++++++-- .../modbus_controller/output/modbus_output.h | 2 ++ .../components/modbus_controller/switch/__init__.py | 2 +- 8 files changed, 27 insertions(+), 6 deletions(-) diff --git a/esphome/components/modbus_controller/const.py b/esphome/components/modbus_controller/const.py index 8d1676dd38..baf72efb94 100644 --- a/esphome/components/modbus_controller/const.py +++ b/esphome/components/modbus_controller/const.py @@ -10,5 +10,6 @@ CONF_REGISTER_COUNT = "register_count" CONF_REGISTER_TYPE = "register_type" CONF_RESPONSE_SIZE = "response_size" CONF_SKIP_UPDATES = "skip_updates" +CONF_USE_WRITE_MULTIPLE = "use_write_multiple" CONF_VALUE_TYPE = "value_type" CONF_WRITE_LAMBDA = "write_lambda" diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py index 3c5db9b9c8..4ad6601fee 100644 --- a/esphome/components/modbus_controller/number/__init__.py +++ b/esphome/components/modbus_controller/number/__init__.py @@ -25,6 +25,7 @@ from ..const import ( CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, CONF_SKIP_UPDATES, + CONF_USE_WRITE_MULTIPLE, CONF_VALUE_TYPE, CONF_WRITE_LAMBDA, ) @@ -69,6 +70,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_MIN_VALUE, default=-16777215.0): cv.float_, cv.Optional(CONF_STEP, default=1): cv.positive_float, cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, + cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, } ) .extend(cv.polling_component_schema("60s")), @@ -105,6 +107,7 @@ async def to_code(config): cg.add(var.set_parent(parent)) cg.add(parent.add_sensor_item(var)) await add_modbus_base_properties(var, config, ModbusNumber) + cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE])) if CONF_WRITE_LAMBDA in config: template_ = await cg.process_lambda( config[CONF_WRITE_LAMBDA], diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index 70be9eaa1f..e5afd0c611 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -55,9 +55,13 @@ void ModbusNumber::control(float value) { this->get_name().c_str(), this->start_address, this->register_count, write_value, write_value); // Create and send the write command - auto write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, - this->register_count, data); - + ModbusCommandItem write_cmd; + if (this->register_count == 1 && !this->use_write_multiple_) { + write_cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, data[0]); + } else { + write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, + this->register_count, data); + } // publish new value write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { diff --git a/esphome/components/modbus_controller/number/modbus_number.h b/esphome/components/modbus_controller/number/modbus_number.h index 271bbfac50..c678cd00cc 100644 --- a/esphome/components/modbus_controller/number/modbus_number.h +++ b/esphome/components/modbus_controller/number/modbus_number.h @@ -35,6 +35,7 @@ class ModbusNumber : public number::Number, public Component, public SensorItem using write_transform_func_t = std::function(ModbusNumber *, float, std::vector &)>; void set_template(transform_func_t &&f) { this->transform_func_ = f; } void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } protected: void control(float value) override; @@ -42,6 +43,7 @@ class ModbusNumber : public number::Number, public Component, public SensorItem optional write_transform_func_; ModbusController *parent_; float multiply_by_{1.0}; + bool use_write_multiple_{false}; }; } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/output/__init__.py b/esphome/components/modbus_controller/output/__init__.py index eacd96579f..a26d05a18b 100644 --- a/esphome/components/modbus_controller/output/__init__.py +++ b/esphome/components/modbus_controller/output/__init__.py @@ -18,6 +18,7 @@ from .. import ( from ..const import ( CONF_MODBUS_CONTROLLER_ID, + CONF_USE_WRITE_MULTIPLE, CONF_VALUE_TYPE, CONF_WRITE_LAMBDA, ) @@ -36,6 +37,7 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(): cv.declare_id(ModbusOutput), cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, + cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, } ), validate_modbus_register, @@ -54,6 +56,7 @@ async def to_code(config): await output.register_output(var, config) cg.add(var.set_write_multiply(config[CONF_MULTIPLY])) parent = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) + cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE])) cg.add(var.set_parent(parent)) if CONF_WRITE_LAMBDA in config: template_ = await cg.process_lambda( diff --git a/esphome/components/modbus_controller/output/modbus_output.cpp b/esphome/components/modbus_controller/output/modbus_output.cpp index d2b5d02bda..4c2e5775b9 100644 --- a/esphome/components/modbus_controller/output/modbus_output.cpp +++ b/esphome/components/modbus_controller/output/modbus_output.cpp @@ -40,8 +40,14 @@ void ModbusOutput::write_state(float value) { this->start_address, this->register_count, value, original_value); // Create and send the write command - auto write_cmd = - ModbusCommandItem::create_write_multiple_command(parent_, this->start_address, this->register_count, data); + // Create and send the write command + ModbusCommandItem write_cmd; + if (this->register_count == 1 && !this->use_write_multiple_) { + write_cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, data[0]); + } else { + write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, + this->register_count, data); + } parent_->queue_command(write_cmd); } diff --git a/esphome/components/modbus_controller/output/modbus_output.h b/esphome/components/modbus_controller/output/modbus_output.h index 6e8521854b..78d3474ad6 100644 --- a/esphome/components/modbus_controller/output/modbus_output.h +++ b/esphome/components/modbus_controller/output/modbus_output.h @@ -33,6 +33,7 @@ class ModbusOutput : public output::FloatOutput, public Component, public Sensor using write_transform_func_t = std::function(ModbusOutput *, float, std::vector &)>; void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } protected: void write_state(float value) override; @@ -40,6 +41,7 @@ class ModbusOutput : public output::FloatOutput, public Component, public Sensor ModbusController *parent_; float multiply_by_{1.0}; + bool use_write_multiple_; }; } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/switch/__init__.py b/esphome/components/modbus_controller/switch/__init__.py index df11b268ac..9858d45617 100644 --- a/esphome/components/modbus_controller/switch/__init__.py +++ b/esphome/components/modbus_controller/switch/__init__.py @@ -18,10 +18,10 @@ from ..const import ( CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, CONF_REGISTER_TYPE, + CONF_USE_WRITE_MULTIPLE, CONF_WRITE_LAMBDA, ) -CONF_USE_WRITE_MULTIPLE = "use_write_multiple" DEPENDENCIES = ["modbus_controller"] CODEOWNERS = ["@martgras"] From cf5193d3e54e59525be78dded20fa142d5ff7d88 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sat, 11 Dec 2021 01:03:22 -0600 Subject: [PATCH 448/549] Fix for two points setting when fan_only_cooling is disabled (#2903) Co-authored-by: Paulus Schoutsen Co-authored-by: Keith Burzinski --- esphome/components/thermostat/climate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index 7b5ee7c624..20565e811c 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -431,7 +431,8 @@ async def to_code(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 + CONF_COOL_ACTION in config + or (config[CONF_FAN_ONLY_COOLING] and CONF_FAN_ONLY_ACTION in config) ) sens = await cg.get_variable(config[CONF_SENSOR]) From 8375e1d64df7e7477f185b7021626517fed10b20 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 11 Dec 2021 21:03:41 +1300 Subject: [PATCH 449/549] Bump esphome-dashboard to 20211211.0 (#2904) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f7b1a6a1fe..c45797a71f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20211208.0 +esphome-dashboard==20211211.0 aioesphomeapi==10.6.0 zeroconf==0.36.13 From b2f05faee0bc41ac878f936fec19aab9b2fc87a5 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Sun, 12 Dec 2021 21:12:50 +0100 Subject: [PATCH 450/549] Move i2c scan to setup (#2869) Co-authored-by: Oxan van Leeuwen --- esphome/components/i2c/i2c_bus.h | 16 +++++++++++++ esphome/components/i2c/i2c_bus_arduino.cpp | 27 +++++++++++---------- esphome/components/i2c/i2c_bus_arduino.h | 1 - esphome/components/i2c/i2c_bus_esp_idf.cpp | 28 ++++++++++++---------- esphome/components/i2c/i2c_bus_esp_idf.h | 1 - 5 files changed, 46 insertions(+), 27 deletions(-) diff --git a/esphome/components/i2c/i2c_bus.h b/esphome/components/i2c/i2c_bus.h index cb00260f43..71f6b1d15b 100644 --- a/esphome/components/i2c/i2c_bus.h +++ b/esphome/components/i2c/i2c_bus.h @@ -1,6 +1,8 @@ #pragma once #include #include +#include +#include namespace esphome { namespace i2c { @@ -40,6 +42,20 @@ class I2CBus { return writev(address, &buf, 1); } virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) = 0; + + protected: + void i2c_scan_() { + for (uint8_t address = 8; address < 120; address++) { + auto err = writev(address, nullptr, 0); + if (err == ERROR_OK) { + scan_results_.emplace_back(address, true); + } else if (err == ERROR_UNKNOWN) { + scan_results_.emplace_back(address, false); + } + } + } + std::vector> scan_results_; + bool scan_{false}; }; } // namespace i2c diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 4afabbfa53..eac2a47524 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -2,6 +2,7 @@ #include "i2c_bus_arduino.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" #include #include @@ -27,6 +28,10 @@ void ArduinoI2CBus::setup() { wire_->begin(sda_pin_, scl_pin_); wire_->setClock(frequency_); initialized_ = true; + if (this->scan_) { + ESP_LOGV(TAG, "Scanning i2c bus for active devices..."); + this->i2c_scan_(); + } } void ArduinoI2CBus::dump_config() { ESP_LOGCONFIG(TAG, "I2C Bus:"); @@ -45,22 +50,20 @@ void ArduinoI2CBus::dump_config() { break; } if (this->scan_) { - ESP_LOGI(TAG, "Scanning i2c bus for active devices..."); - uint8_t found = 0; - for (uint8_t address = 8; address < 120; address++) { - auto err = writev(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, "Results from i2c bus scan:"); + if (scan_results_.empty()) { ESP_LOGI(TAG, "Found no i2c devices!"); + } else { + for (const auto &s : scan_results_) { + if (s.second) + ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first); + else + ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first); + } } } } + ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { // logging is only enabled with vv level, if warnings are shown the caller // should log them diff --git a/esphome/components/i2c/i2c_bus_arduino.h b/esphome/components/i2c/i2c_bus_arduino.h index 82f043ef7d..f4151e4f37 100644 --- a/esphome/components/i2c/i2c_bus_arduino.h +++ b/esphome/components/i2c/i2c_bus_arduino.h @@ -34,7 +34,6 @@ class ArduinoI2CBus : public I2CBus, public Component { protected: TwoWire *wire_; - bool scan_; uint8_t sda_pin_; uint8_t scl_pin_; uint32_t frequency_; diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index f7ecfe5f7c..109c3f890d 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -3,6 +3,7 @@ #include "i2c_bus_esp_idf.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" #include namespace esphome { @@ -37,6 +38,10 @@ void IDFI2CBus::setup() { return; } initialized_ = true; + if (this->scan_) { + ESP_LOGV(TAG, "Scanning i2c bus for active devices..."); + this->i2c_scan_(); + } } void IDFI2CBus::dump_config() { ESP_LOGCONFIG(TAG, "I2C Bus:"); @@ -55,23 +60,20 @@ void IDFI2CBus::dump_config() { break; } if (this->scan_) { - ESP_LOGI(TAG, "Scanning i2c bus for active devices..."); - uint8_t found = 0; - for (uint8_t address = 8; address < 120; address++) { - auto err = writev(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, "Results from i2c bus scan:"); + if (scan_results_.empty()) { ESP_LOGI(TAG, "Found no i2c devices!"); + } else { + for (const auto &s : scan_results_) { + if (s.second) + ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first); + else + ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first); + } } } } + ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { // logging is only enabled with vv level, if warnings are shown the caller // should log them diff --git a/esphome/components/i2c/i2c_bus_esp_idf.h b/esphome/components/i2c/i2c_bus_esp_idf.h index 13d996dbd8..d4b0626467 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.h +++ b/esphome/components/i2c/i2c_bus_esp_idf.h @@ -36,7 +36,6 @@ class IDFI2CBus : public I2CBus, public Component { protected: i2c_port_t port_; - bool scan_; uint8_t sda_pin_; bool sda_pullup_enabled_; uint8_t scl_pin_; From beeb0c7c5af85fa9d05a5338a36b76a3d95084cf Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 12 Dec 2021 21:15:23 +0100 Subject: [PATCH 451/549] Introduce hex parsing & formatting helper functions (#2882) --- esphome/components/api/api_frame_helper.cpp | 10 +- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 6 +- .../esp32_improv/esp32_improv_component.cpp | 2 +- esphome/components/md5/md5.cpp | 18 +--- esphome/components/md5/md5.h | 2 +- esphome/components/modbus/modbus.cpp | 4 +- .../switch/modbus_switch.cpp | 2 +- esphome/components/pn532_spi/pn532_spi.cpp | 8 +- .../components/remote_base/midea_protocol.h | 2 +- esphome/components/tuya/light/tuya_light.cpp | 12 +-- .../tuya/text_sensor/tuya_text_sensor.cpp | 2 +- esphome/components/tuya/tuya.cpp | 10 +- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 16 +-- .../components/xiaomi_cgd1/xiaomi_cgd1.cpp | 2 +- .../components/xiaomi_cgdk2/xiaomi_cgdk2.cpp | 2 +- .../components/xiaomi_cgg1/xiaomi_cgg1.cpp | 2 +- .../xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp | 2 +- .../xiaomi_mhoc401/xiaomi_mhoc401.cpp | 2 +- esphome/core/helpers.cpp | 99 ++++++++++--------- esphome/core/helpers.h | 92 +++++++++++++++-- 20 files changed, 186 insertions(+), 109 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 23766ec1b1..151c2512de 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -252,7 +252,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { // uncomment for even more debugging #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); + ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str()); #endif frame->msg = std::move(rx_buf_); // consume msg @@ -546,7 +546,8 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { size_t total_write_len = 0; for (int i = 0; i < iovcnt; i++) { #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); + ESP_LOGVV(TAG, "Sending raw: %s", + format_hex_pretty(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); #endif total_write_len += iov[i].iov_len; } @@ -855,7 +856,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { // uncomment for even more debugging #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); + ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str()); #endif frame->msg = std::move(rx_buf_); // consume msg @@ -934,7 +935,8 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt size_t total_write_len = 0; for (int i = 0; i < iovcnt; i++) { #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); + ESP_LOGVV(TAG, "Sending raw: %s", + format_hex_pretty(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); #endif total_write_len += iov[i].iov_len; } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 303cb34aa7..e3de02e2d7 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -524,7 +524,7 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str()); } for (auto &data : this->manufacturer_datas_) { - ESP_LOGVV(TAG, " Manufacturer data: %s", hexencode(data.data).c_str()); + ESP_LOGVV(TAG, " Manufacturer data: %s", format_hex_pretty(data.data).c_str()); if (this->get_ibeacon().has_value()) { auto ibeacon = this->get_ibeacon().value(); ESP_LOGVV(TAG, " iBeacon data:"); @@ -537,10 +537,10 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e for (auto &data : this->service_datas_) { ESP_LOGVV(TAG, " Service data:"); ESP_LOGVV(TAG, " UUID: %s", data.uuid.to_string().c_str()); - ESP_LOGVV(TAG, " Data: %s", hexencode(data.data).c_str()); + ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str()); } - ESP_LOGVV(TAG, "Adv data: %s", hexencode(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str()); + ESP_LOGVV(TAG, "Adv data: %s", format_hex_pretty(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str()); #endif } void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 22bebdfe98..788e7a9460 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -219,7 +219,7 @@ void ESP32ImprovComponent::dump_config() { void ESP32ImprovComponent::process_incoming_data_() { uint8_t length = this->incoming_data_[1]; - ESP_LOGD(TAG, "Processing bytes - %s", hexencode(this->incoming_data_).c_str()); + ESP_LOGD(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str()); if (this->incoming_data_.size() - 3 == length) { this->set_error_(improv::ERROR_NONE); improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_); diff --git a/esphome/components/md5/md5.cpp b/esphome/components/md5/md5.cpp index c6ff783439..0528a87d0e 100644 --- a/esphome/components/md5/md5.cpp +++ b/esphome/components/md5/md5.cpp @@ -23,7 +23,7 @@ void MD5Digest::get_hex(char *output) { } } -bool MD5Digest::equals_bytes(const char *expected) { +bool MD5Digest::equals_bytes(const uint8_t *expected) { for (size_t i = 0; i < 16; i++) { if (expected[i] != this->digest_[i]) { return false; @@ -33,18 +33,10 @@ bool MD5Digest::equals_bytes(const char *expected) { } bool MD5Digest::equals_hex(const char *expected) { - for (size_t i = 0; i < 16; i++) { - auto high = parse_hex(expected[i * 2]); - auto low = parse_hex(expected[i * 2 + 1]); - if (!high.has_value() || !low.has_value()) { - return false; - } - auto value = (*high << 4) | *low; - if (value != this->digest_[i]) { - return false; - } - } - return true; + uint8_t parsed[16]; + if (!parse_hex(expected, parsed, 16)) + return false; + return equals_bytes(parsed); } } // namespace md5 diff --git a/esphome/components/md5/md5.h b/esphome/components/md5/md5.h index e40f419347..1c15c9e57d 100644 --- a/esphome/components/md5/md5.h +++ b/esphome/components/md5/md5.h @@ -44,7 +44,7 @@ class MD5Digest { void get_hex(char *output); /// Compare the digest against a provided byte-encoded digest (16 bytes). - bool equals_bytes(const char *expected); + bool equals_bytes(const uint8_t *expected); /// Compare the digest against a provided hex-encoded digest (32 bytes). bool equals_hex(const char *expected); diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 9524f9daf4..9ee3137be9 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -181,7 +181,7 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address this->flow_control_pin_->digital_write(false); waiting_for_response = address; last_send_ = millis(); - ESP_LOGV(TAG, "Modbus write: %s", hexencode(data).c_str()); + ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty(data).c_str()); } // Helper function for lambdas @@ -202,7 +202,7 @@ void Modbus::send_raw(const std::vector &payload) { if (this->flow_control_pin_ != nullptr) this->flow_control_pin_->digital_write(false); waiting_for_response = payload[0]; - ESP_LOGV(TAG, "Modbus write raw: %s", hexencode(payload).c_str()); + ESP_LOGV(TAG, "Modbus write raw: %s", format_hex_pretty(payload).c_str()); last_send_ = millis(); } diff --git a/esphome/components/modbus_controller/switch/modbus_switch.cpp b/esphome/components/modbus_controller/switch/modbus_switch.cpp index c7c3c419d4..ca8d0be720 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.cpp +++ b/esphome/components/modbus_controller/switch/modbus_switch.cpp @@ -61,7 +61,7 @@ void ModbusSwitch::write_state(bool state) { } } if (!data.empty()) { - ESP_LOGV(TAG, "Modbus Switch write raw: %s", hexencode(data).c_str()); + ESP_LOGV(TAG, "Modbus Switch write raw: %s", format_hex_pretty(data).c_str()); cmd = ModbusCommandItem::create_custom_command( this->parent_, data, [this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { diff --git a/esphome/components/pn532_spi/pn532_spi.cpp b/esphome/components/pn532_spi/pn532_spi.cpp index ec32e45b3d..be58f265b9 100644 --- a/esphome/components/pn532_spi/pn532_spi.cpp +++ b/esphome/components/pn532_spi/pn532_spi.cpp @@ -26,7 +26,7 @@ bool PN532Spi::write_data(const std::vector &data) { delay(2); // First byte, communication mode: Write data this->write_byte(0x01); - ESP_LOGV(TAG, "Writing data: %s", hexencode(data).c_str()); + ESP_LOGV(TAG, "Writing data: %s", format_hex_pretty(data).c_str()); this->write_array(data.data(), data.size()); this->disable(); @@ -65,7 +65,7 @@ bool PN532Spi::read_data(std::vector &data, uint8_t len) { this->read_array(data.data(), len); this->disable(); data.insert(data.begin(), 0x01); - ESP_LOGV(TAG, "Read data: %s", hexencode(data).c_str()); + ESP_LOGV(TAG, "Read data: %s", format_hex_pretty(data).c_str()); return true; } @@ -97,7 +97,7 @@ bool PN532Spi::read_response(uint8_t command, std::vector &data) { std::vector header(7); this->read_array(header.data(), 7); - ESP_LOGV(TAG, "Header data: %s", hexencode(header).c_str()); + ESP_LOGV(TAG, "Header data: %s", format_hex_pretty(header).c_str()); if (header[0] != 0x00 && header[1] != 0x00 && header[2] != 0xFF) { // invalid packet @@ -127,7 +127,7 @@ bool PN532Spi::read_response(uint8_t command, std::vector &data) { this->read_array(data.data(), len + 1); this->disable(); - ESP_LOGV(TAG, "Response data: %s", hexencode(data).c_str()); + ESP_LOGV(TAG, "Response data: %s", format_hex_pretty(data).c_str()); uint8_t checksum = header[5] + header[6]; // TFI + Command response code for (int i = 0; i < len - 1; i++) { diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h index 35ea23acfb..61e511601b 100644 --- a/esphome/components/remote_base/midea_protocol.h +++ b/esphome/components/remote_base/midea_protocol.h @@ -26,7 +26,7 @@ class MideaData { 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); } + std::string to_string() const { return format_hex_pretty(this->data_, sizeof(this->data_)); } // compare only 40-bits bool operator==(const MideaData &rhs) const { return !memcmp(this->data_, rhs.data_, OFFSET_CS); } enum MideaDataType : uint8_t { diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index 133ee1e557..ecd3802839 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -37,9 +37,9 @@ void TuyaLight::setup() { } if (rgb_id_.has_value()) { this->parent_->register_listener(*this->rgb_id_, [this](const TuyaDatapoint &datapoint) { - auto red = parse_hex(datapoint.value_string, 0, 2); - auto green = parse_hex(datapoint.value_string, 2, 2); - auto blue = parse_hex(datapoint.value_string, 4, 2); + auto red = parse_hex(datapoint.value_string.substr(0, 2)); + auto green = parse_hex(datapoint.value_string.substr(2, 2)); + auto blue = parse_hex(datapoint.value_string.substr(4, 2)); if (red.has_value() && green.has_value() && blue.has_value()) { auto call = this->state_->make_call(); call.set_rgb(float(*red) / 255, float(*green) / 255, float(*blue) / 255); @@ -48,9 +48,9 @@ void TuyaLight::setup() { }); } else if (hsv_id_.has_value()) { this->parent_->register_listener(*this->hsv_id_, [this](const TuyaDatapoint &datapoint) { - auto hue = parse_hex(datapoint.value_string, 0, 4); - auto saturation = parse_hex(datapoint.value_string, 4, 4); - auto value = parse_hex(datapoint.value_string, 8, 4); + auto hue = parse_hex(datapoint.value_string.substr(0, 4)); + auto saturation = parse_hex(datapoint.value_string.substr(4, 4)); + auto value = parse_hex(datapoint.value_string.substr(8, 4)); if (hue.has_value() && saturation.has_value() && value.has_value()) { float red, green, blue; hsv_to_rgb(*hue, float(*saturation) / 1000, float(*value) / 1000, red, green, blue); diff --git a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp index e939225453..0b51ba90c4 100644 --- a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp +++ b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp @@ -14,7 +14,7 @@ void TuyaTextSensor::setup() { this->publish_state(datapoint.value_string); break; case TuyaDatapointType::RAW: { - std::string data = hexencode(datapoint.value_raw); + std::string data = format_hex_pretty(datapoint.value_raw); ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, data.c_str()); this->publish_state(data); break; diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 404a70a80e..7ff8c66c44 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -34,7 +34,7 @@ void Tuya::dump_config() { } for (auto &info : this->datapoints_) { if (info.type == TuyaDatapointType::RAW) - ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, hexencode(info.value_raw).c_str()); + ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, format_hex_pretty(info.value_raw).c_str()); else if (info.type == TuyaDatapointType::BOOLEAN) ESP_LOGCONFIG(TAG, " Datapoint %u: switch (value: %s)", info.id, ONOFF(info.value_bool)); else if (info.type == TuyaDatapointType::INTEGER) @@ -104,7 +104,7 @@ bool Tuya::validate_message_() { // valid message const uint8_t *message_data = data + 6; ESP_LOGV(TAG, "Received Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version, - hexencode(message_data, length).c_str(), static_cast(this->init_state_)); + format_hex_pretty(message_data, length).c_str(), static_cast(this->init_state_)); this->handle_command_(command, version, message_data, length); // return false to reset rx buffer @@ -253,7 +253,7 @@ void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) { switch (datapoint.type) { case TuyaDatapointType::RAW: datapoint.value_raw = std::vector(data, data + data_len); - ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, hexencode(datapoint.value_raw).c_str()); + ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str()); break; case TuyaDatapointType::BOOLEAN: if (data_len != 1) { @@ -348,7 +348,7 @@ void Tuya::send_raw_command_(TuyaCommand command) { } 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_)); + version, format_hex_pretty(command.payload).c_str(), static_cast(this->init_state_)); this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo}); if (!command.payload.empty()) @@ -526,7 +526,7 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType } void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector &value, bool forced) { - ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, hexencode(value).c_str()); + ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty(value).c_str()); optional datapoint = this->get_datapoint_(datapoint_id); if (!datapoint.has_value()) { ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 7588198c70..583b68a77b 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -217,7 +217,7 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, const uint64_t &address) { if (!((raw.size() == 19) || ((raw.size() >= 22) && (raw.size() <= 24)))) { ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): data packet has wrong size (%d)!", raw.size()); - ESP_LOGVV(TAG, " Packet : %s", hexencode(raw.data(), raw.size()).c_str()); + ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str()); return false; } @@ -274,12 +274,12 @@ bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, c memcpy(mac_address + 4, mac_reverse + 1, 1); memcpy(mac_address + 5, mac_reverse, 1); ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption failed."); - ESP_LOGVV(TAG, " MAC address : %s", hexencode(mac_address, 6).c_str()); - ESP_LOGVV(TAG, " Packet : %s", hexencode(raw.data(), raw.size()).c_str()); - ESP_LOGVV(TAG, " Key : %s", hexencode(vector.key, vector.keysize).c_str()); - ESP_LOGVV(TAG, " Iv : %s", hexencode(vector.iv, vector.ivsize).c_str()); - ESP_LOGVV(TAG, " Cipher : %s", hexencode(vector.ciphertext, vector.datasize).c_str()); - ESP_LOGVV(TAG, " Tag : %s", hexencode(vector.tag, vector.tagsize).c_str()); + ESP_LOGVV(TAG, " MAC address : %s", format_hex_pretty(mac_address, 6).c_str()); + ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str()); + ESP_LOGVV(TAG, " Key : %s", format_hex_pretty(vector.key, vector.keysize).c_str()); + ESP_LOGVV(TAG, " Iv : %s", format_hex_pretty(vector.iv, vector.ivsize).c_str()); + ESP_LOGVV(TAG, " Cipher : %s", format_hex_pretty(vector.ciphertext, vector.datasize).c_str()); + ESP_LOGVV(TAG, " Tag : %s", format_hex_pretty(vector.tag, vector.tagsize).c_str()); mbedtls_ccm_free(&ctx); return false; } @@ -295,7 +295,7 @@ bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, c raw[0] &= ~0x08; ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption passed."); - ESP_LOGVV(TAG, " Plaintext : %s, Packet : %d", hexencode(raw.data() + cipher_pos, vector.datasize).c_str(), + ESP_LOGVV(TAG, " Plaintext : %s, Packet : %d", format_hex_pretty(raw.data() + cipher_pos, vector.datasize).c_str(), static_cast(raw[4])); mbedtls_ccm_free(&ctx); diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp index 97bbd6e6d6..baf9cb8075 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_cgd1"; void XiaomiCGD1::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi CGD1"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp index a97ca93206..c74794f4f4 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_cgdk2"; void XiaomiCGDK2::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi CGDK2"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp index e1f83e4ddd..c20c7578d0 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_cgg1"; void XiaomiCGG1::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi CGG1"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp index 547cc7c114..d0319c9474 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_lywsd03mmc"; void XiaomiLYWSD03MMC::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi LYWSD03MMC"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp index 0cad5c67b2..9ec2b10e12 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_mhoc401"; void XiaomiMHOC401::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi MHOC401"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 6678eddbff..b82a2666e7 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -244,38 +244,6 @@ std::string to_string(long double val) { return buf; } -optional parse_hex(const char chr) { - int out = chr; - if (out >= '0' && out <= '9') - return (out - '0'); - if (out >= 'A' && out <= 'F') - return (10 + (out - 'A')); - if (out >= 'a' && out <= 'f') - return (10 + (out - 'a')); - return {}; -} - -optional parse_hex(const std::string &str, size_t start, size_t length) { - if (str.length() < start) { - return {}; - } - size_t end = start + length; - if (str.length() < end) { - return {}; - } - int out = 0; - for (size_t i = start; i < end; i++) { - char chr = str[i]; - auto digit = parse_hex(chr); - if (!digit.has_value()) { - ESP_LOGW(TAG, "Can't convert '%s' to number, invalid character %c!", str.substr(start, length).c_str(), chr); - return {}; - } - out = (out << 4) | *digit; - } - return out; -} - uint32_t fnv1_hash(const std::string &str) { uint32_t hash = 2166136261UL; for (char c : str) { @@ -355,22 +323,6 @@ std::string str_sprintf(const char *fmt, ...) { return str; } -std::string hexencode(const uint8_t *data, uint32_t len) { - char buf[20]; - std::string res; - for (size_t i = 0; i < len; i++) { - if (i + 1 != len) { - sprintf(buf, "%02X.", data[i]); - } else { - sprintf(buf, "%02X ", data[i]); - } - res += buf; - } - sprintf(buf, "(%u)", len); - res += buf; - return res; -} - void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value) { float max_color_value = std::max(std::max(red, green), blue); float min_color_value = std::min(std::min(red, green), blue); @@ -445,6 +397,8 @@ IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } // --------------------------------------------------------------------------------------------------------------------- +// Strings + std::string str_truncate(const std::string &str, size_t length) { return str.length() > length ? str.substr(0, length) : str; } @@ -468,4 +422,53 @@ std::string str_sanitize(const std::string &str) { return out; } +// Parsing & formatting + +size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) { + uint8_t val; + size_t chars = std::min(length, 2 * count); + for (size_t i = 2 * count - chars; i < 2 * count; i++, str++) { + if (*str >= '0' && *str <= '9') + val = *str - '0'; + else if (*str >= 'A' && *str <= 'F') + val = 10 + (*str - 'A'); + else if (*str >= 'a' && *str <= 'f') + val = 10 + (*str - 'a'); + else + return 0; + data[i >> 1] = !(i & 1) ? val << 4 : data[i >> 1] | val; + } + return chars; +} + +static char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; } +std::string format_hex(const uint8_t *data, size_t length) { + std::string ret; + ret.resize(length * 2); + for (size_t i = 0; i < length; i++) { + ret[2 * i] = format_hex_char((data[i] & 0xF0) >> 4); + ret[2 * i + 1] = format_hex_char(data[i] & 0x0F); + } + return ret; +} +std::string format_hex(std::vector data) { return format_hex(data.data(), data.size()); } + +static char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; } +std::string format_hex_pretty(const uint8_t *data, size_t length) { + if (length == 0) + return ""; + std::string ret; + ret.resize(3 * length - 1); + for (size_t i = 0; i < length; i++) { + ret[3 * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4); + ret[3 * i + 1] = format_hex_pretty_char(data[i] & 0x0F); + if (i != length - 1) + ret[3 * i + 2] = '.'; + } + if (length > 4) + return ret + " (" + to_string(length) + ")"; + return ret; +} +std::string format_hex_pretty(std::vector data) { return format_hex_pretty(data.data(), data.size()); } + } // namespace esphome diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index db507f824d..90f35ee4ca 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -45,8 +46,6 @@ std::string to_string(unsigned long long val); // NOLINT std::string to_string(float val); std::string to_string(double val); std::string to_string(long double val); -optional parse_hex(const std::string &str, size_t start, size_t length); -optional parse_hex(char chr); /// Compare string a to string b (ignoring case) and return whether they are equal. bool str_equals_case_insensitive(const std::string &a, const std::string &b); @@ -186,10 +185,6 @@ enum ParseOnOffState { ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const char *off = nullptr); -// Encode raw data to a human-readable string (for debugging) -std::string hexencode(const uint8_t *data, uint32_t len); -template std::string hexencode(const T &data) { return hexencode(data.data(), data.size()); } - // https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/7858971#7858971 template struct seq {}; // NOLINT template struct gens : gens {}; // NOLINT @@ -408,6 +403,77 @@ optional parse_number(const std::string &str) { return parse_number(str.c_str()); } +/** Parse bytes from a hex-encoded string into a byte array. + * + * When \p len is less than \p 2*count, the result is written to the back of \p data (i.e. this function treats \p str + * as if it were padded with zeros at the front). + * + * @param str String to read from. + * @param len Length of \p str (excluding optional null-terminator), is a limit on the number of characters parsed. + * @param data Byte array to write to. + * @param count Length of \p data. + * @return The number of characters parsed from \p str. + */ +size_t parse_hex(const char *str, size_t len, uint8_t *data, size_t count); +/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into array \p data. +inline bool parse_hex(const char *str, uint8_t *data, size_t count) { + return parse_hex(str, strlen(str), data, count) == 2 * count; +} +/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into array \p data. +inline bool parse_hex(const std::string &str, uint8_t *data, size_t count) { + return parse_hex(str.c_str(), str.length(), data, count) == 2 * count; +} +/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into vector \p data. +inline bool parse_hex(const char *str, std::vector &data, size_t count) { + data.resize(count); + return parse_hex(str, strlen(str), data.data(), count) == 2 * count; +} +/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into vector \p data. +inline bool parse_hex(const std::string &str, std::vector &data, size_t count) { + data.resize(count); + return parse_hex(str.c_str(), str.length(), data.data(), count) == 2 * count; +} +/** Parse a hex-encoded string into an unsigned integer. + * + * @param str String to read from, starting with the most significant byte. + * @param len Length of \p str (excluding optional null-terminator), is a limit on the number of characters parsed. + */ +template::value, int> = 0> +optional parse_hex(const char *str, size_t len) { + T val = 0; + if (len > 2 * sizeof(T) || parse_hex(str, len, reinterpret_cast(&val), sizeof(T)) == 0) + return {}; + return convert_big_endian(val); +} +/// Parse a hex-encoded null-terminated string (starting with the most significant byte) into an unsigned integer. +template::value, int> = 0> optional parse_hex(const char *str) { + return parse_hex(str, strlen(str)); +} +/// Parse a hex-encoded null-terminated string (starting with the most significant byte) into an unsigned integer. +template::value, int> = 0> optional parse_hex(const std::string &str) { + return parse_hex(str.c_str(), str.length()); +} + +/// Format the byte array \p data of length \p len in lowercased hex. +std::string format_hex(const uint8_t *data, size_t length); +/// Format the vector \p data in lowercased hex. +std::string format_hex(std::vector data); +/// Format an unsigned integer in lowercased hex, starting with the most significant byte. +template::value, int> = 0> std::string format_hex(T val) { + val = convert_big_endian(val); + return format_hex(reinterpret_cast(&val), sizeof(T)); +} + +/// Format the byte array \p data of length \p len in pretty-printed, human-readable hex. +std::string format_hex_pretty(const uint8_t *data, size_t length); +/// Format the vector \p data in pretty-printed, human-readable hex. +std::string format_hex_pretty(std::vector data); +/// Format an unsigned integer in pretty-printed, human-readable hex, starting with the most significant byte. +template::value, int> = 0> std::string format_hex_pretty(T val) { + val = convert_big_endian(val); + return format_hex_pretty(reinterpret_cast(&val), sizeof(T)); +} + ///@} /// @name Number manipulation @@ -420,4 +486,18 @@ template T remap(U value, U min, U max, T min_out, T max ///@} +/// @name Deprecated functions +///@{ + +ESPDEPRECATED("hexencode() is deprecated, use format_hex_pretty() instead.", "2022.1") +inline std::string hexencode(const uint8_t *data, uint32_t len) { return format_hex_pretty(data, len); } + +template +ESPDEPRECATED("hexencode() is deprecated, use format_hex_pretty() instead.", "2022.1") +std::string hexencode(const T &data) { + return hexencode(data.data(), data.size()); +} + +///@} + } // namespace esphome From 9c0506592b946c70670fdcfcf14f3729458ecec3 Mon Sep 17 00:00:00 2001 From: tony <42725386+tony-fav@users.noreply.github.com> Date: Sun, 12 Dec 2021 15:19:57 -0500 Subject: [PATCH 452/549] Add light.on_state trigger (#2868) --- esphome/components/light/__init__.py | 10 ++++++++++ esphome/components/light/automation.h | 7 +++++++ esphome/components/light/types.py | 1 + 3 files changed, 18 insertions(+) diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index 03224d4c10..fe8a90b8db 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -14,6 +14,7 @@ from esphome.const import ( CONF_RESTORE_MODE, CONF_ON_TURN_OFF, CONF_ON_TURN_ON, + CONF_ON_STATE, CONF_TRIGGER_ID, CONF_COLD_WHITE_COLOR_TEMPERATURE, CONF_WARM_WHITE_COLOR_TEMPERATURE, @@ -37,6 +38,7 @@ from .types import ( # noqa AddressableLight, LightTurnOnTrigger, LightTurnOffTrigger, + LightStateTrigger, ) CODEOWNERS = ["@esphome/core"] @@ -69,6 +71,11 @@ LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ex cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOffTrigger), } ), + cv.Optional(CONF_ON_STATE): auto.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightStateTrigger), + } + ), } ) @@ -151,6 +158,9 @@ async def setup_light_core_(light_var, output_var, config): for conf in config.get(CONF_ON_TURN_OFF, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], light_var) await auto.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_STATE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], light_var) + await auto.build_automation(trigger, [], conf) if CONF_COLOR_CORRECT in config: cg.add(output_var.set_correction(*config[CONF_COLOR_CORRECT])) diff --git a/esphome/components/light/automation.h b/esphome/components/light/automation.h index 5ec2cb626a..b63fc93dc5 100644 --- a/esphome/components/light/automation.h +++ b/esphome/components/light/automation.h @@ -141,6 +141,13 @@ class LightTurnOffTrigger : public Trigger<> { } }; +class LightStateTrigger : public Trigger<> { + public: + LightStateTrigger(LightState *a_light) { + a_light->add_new_remote_values_callback([this]() { this->trigger(); }); + } +}; + // This is slightly ugly, but we can't log in headers, and can't make this a static method on AddressableSet // due to the template. It's just a temporary warning anyway. void addressableset_warn_about_scale(const char *field); diff --git a/esphome/components/light/types.py b/esphome/components/light/types.py index bc20cd5555..a453debd94 100644 --- a/esphome/components/light/types.py +++ b/esphome/components/light/types.py @@ -41,6 +41,7 @@ LightTurnOnTrigger = light_ns.class_( LightTurnOffTrigger = light_ns.class_( "LightTurnOffTrigger", automation.Trigger.template() ) +LightStateTrigger = light_ns.class_("LightStateTrigger", automation.Trigger.template()) # Effects LightEffect = light_ns.class_("LightEffect") From 31a61b598be920abc1e35b430591e76fadededb6 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Sun, 12 Dec 2021 21:30:47 +0100 Subject: [PATCH 453/549] Reduce timing noise in duty_cycle (#2881) --- esphome/components/duty_cycle/duty_cycle_sensor.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.cpp b/esphome/components/duty_cycle/duty_cycle_sensor.cpp index aed22312a7..9a881c81f0 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.cpp +++ b/esphome/components/duty_cycle/duty_cycle_sensor.cpp @@ -23,22 +23,24 @@ void DutyCycleSensor::dump_config() { } void DutyCycleSensor::update() { const uint32_t now = micros(); + const uint32_t last_interrupt = this->store_.last_interrupt; // Read the measurement taken by the interrupt + uint32_t on_time = this->store_.on_time; + + this->store_.on_time = 0; // Start new measurement, exactly aligned with the micros() reading + this->store_.last_interrupt = now; + if (this->last_update_ != 0) { const bool level = this->store_.last_level; - const uint32_t last_interrupt = this->store_.last_interrupt; - uint32_t on_time = this->store_.on_time; if (level) on_time += now - last_interrupt; const float total_time = float(now - this->last_update_); - const float value = (on_time / total_time) * 100.0f; + const float value = (on_time * 100.0f) / total_time; ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value); this->publish_state(value); } - this->store_.on_time = 0; - this->store_.last_interrupt = now; this->last_update_ = now; } From da45923d05b3836be5d03fc290f17aed30baae49 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Sun, 12 Dec 2021 21:32:37 +0100 Subject: [PATCH 454/549] Turn verbose a debug statement in bme280 (#2906) --- esphome/components/bme280/bme280.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/bme280/bme280.cpp b/esphome/components/bme280/bme280.cpp index 627072443e..d3a228328b 100644 --- a/esphome/components/bme280/bme280.cpp +++ b/esphome/components/bme280/bme280.cpp @@ -201,7 +201,7 @@ void BME280Component::update() { float pressure = this->read_pressure_(data, t_fine); float humidity = this->read_humidity_(data, t_fine); - ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity); + ESP_LOGV(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity); if (this->temperature_sensor_ != nullptr) this->temperature_sensor_->publish_state(temperature); if (this->pressure_sensor_ != nullptr) From cec4a81e14322b970134a1ffff87cb4cdf42db85 Mon Sep 17 00:00:00 2001 From: Ben Owen Date: Sun, 12 Dec 2021 16:27:11 -0500 Subject: [PATCH 455/549] Add reset_duration option for waveshare epaper HAT rev 2.1 (#1481) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/waveshare_epaper/display.py | 9 ++++++++- esphome/components/waveshare_epaper/waveshare_epaper.h | 4 +++- esphome/const.py | 1 + tests/test4.yaml | 1 + 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 64f5597a65..1d1644dc25 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome import pins +from esphome import core, pins from esphome.components import display, spi from esphome.const import ( CONF_BUSY_PIN, @@ -10,6 +10,7 @@ from esphome.const import ( CONF_LAMBDA, CONF_MODEL, CONF_PAGES, + CONF_RESET_DURATION, CONF_RESET_PIN, ) @@ -95,6 +96,10 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema, cv.Optional(CONF_FULL_UPDATE_EVERY): cv.uint32_t, + cv.Optional(CONF_RESET_DURATION): cv.All( + cv.positive_time_period_milliseconds, + cv.Range(max=core.TimePeriod(milliseconds=500)), + ), } ) .extend(cv.polling_component_schema("1s")) @@ -135,3 +140,5 @@ async def to_code(config): cg.add(var.set_busy_pin(reset)) if CONF_FULL_UPDATE_EVERY in config: cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY])) + if CONF_RESET_DURATION in config: + cg.add(var.set_reset_duration(config[CONF_RESET_DURATION])) diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index a1e2f6037a..4de2ac7d97 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -16,6 +16,7 @@ class WaveshareEPaper : public PollingComponent, float get_setup_priority() const override; void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; } + void set_reset_duration(uint32_t reset_duration) { this->reset_duration_ = reset_duration; } void command(uint8_t value); void data(uint8_t value); @@ -45,13 +46,14 @@ class WaveshareEPaper : public PollingComponent, void reset_() { if (this->reset_pin_ != nullptr) { this->reset_pin_->digital_write(false); - delay(200); // NOLINT + delay(reset_duration_); // NOLINT this->reset_pin_->digital_write(true); delay(200); // NOLINT } } uint32_t get_buffer_length_(); + uint32_t reset_duration_{200}; void start_command_(); void end_command_(); diff --git a/esphome/const.py b/esphome/const.py index df86216447..baaebbf90f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -539,6 +539,7 @@ CONF_REFERENCE_TEMPERATURE = "reference_temperature" CONF_REFRESH = "refresh" CONF_REPEAT = "repeat" CONF_REPOSITORY = "repository" +CONF_RESET_DURATION = "reset_duration" CONF_RESET_PIN = "reset_pin" CONF_RESIZE = "resize" CONF_RESOLUTION = "resolution" diff --git a/tests/test4.yaml b/tests/test4.yaml index b4708acf65..bb8f0f15c6 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -413,6 +413,7 @@ display: reset_pin: GPIO23 model: 2.90in full_update_every: 30 + reset_duration: 200ms lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: waveshare_epaper From 4e108813310c33a1584bbec7c8150e1fcde4b522 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 13 Dec 2021 10:28:19 +1300 Subject: [PATCH 456/549] Log the actual value in modbus number (#2901) --- esphome/components/modbus_controller/number/modbus_number.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index e5afd0c611..74dd615e61 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -52,7 +52,7 @@ void ModbusNumber::control(float value) { ESP_LOGD(TAG, "Updating register: connected Sensor=%s start address=0x%X register count=%d new value=%.02f (val=%.02f)", - this->get_name().c_str(), this->start_address, this->register_count, write_value, write_value); + this->get_name().c_str(), this->start_address, this->register_count, value, write_value); // Create and send the write command ModbusCommandItem write_cmd; From 4bb58b2de9eaf7e4c835b051ebf1918792637e5b Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Sun, 12 Dec 2021 23:03:08 +0100 Subject: [PATCH 457/549] Add gpio 12 to strapping pin list (#2902) --- esphome/components/esp32/gpio_esp32.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32/gpio_esp32.py b/esphome/components/esp32/gpio_esp32.py index 425d77b343..dbafb73dba 100644 --- a/esphome/components/esp32/gpio_esp32.py +++ b/esphome/components/esp32/gpio_esp32.py @@ -18,7 +18,7 @@ _ESP_SDIO_PINS = { 11: "Flash Command", } -_ESP32_STRAPPING_PINS = {0, 2, 4, 15} +_ESP32_STRAPPING_PINS = {0, 2, 4, 12, 15} _LOGGER = logging.getLogger(__name__) From a79c6aa9e0d8831349fbb22e7158a29191654631 Mon Sep 17 00:00:00 2001 From: myhomeiot <70070601+myhomeiot@users.noreply.github.com> Date: Mon, 13 Dec 2021 02:08:18 +0200 Subject: [PATCH 458/549] Added access to ble_scan_result_evt_param as get_scan_result (#2854) --- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp | 1 + esphome/components/esp32_ble_tracker/esp32_ble_tracker.h | 3 +++ 2 files changed, 4 insertions(+) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index e3de02e2d7..084dab4c84 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -483,6 +483,7 @@ optional ESPBLEiBeacon::from_manufacturer_data(const ServiceData } void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { + this->scan_result_ = param; for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++) this->address_[i] = param.bda[i]; this->address_type_ = param.ble_addr_type; diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 02e102f06c..9ff2a5a861 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -97,6 +97,8 @@ class ESPBTDevice { const std::vector &get_service_datas() const { return service_datas_; } + const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &get_scan_result() const { return scan_result_; } + optional get_ibeacon() const { for (auto &it : this->manufacturer_datas_) { auto res = ESPBLEiBeacon::from_manufacturer_data(it); @@ -121,6 +123,7 @@ class ESPBTDevice { std::vector service_uuids_; std::vector manufacturer_datas_{}; std::vector service_datas_{}; + esp_ble_gap_cb_param_t::ble_scan_result_evt_param scan_result_{}; }; class ESP32BLETracker; From b3fb35783e052fc6e151b29b4e47a030585de441 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Dec 2021 03:21:09 +0100 Subject: [PATCH 459/549] Set text sensor state property to filter output (#2893) --- esphome/components/text_sensor/text_sensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 0bcab90843..5d47e7465a 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -62,7 +62,7 @@ void TextSensor::add_on_raw_state_callback(std::function call std::string TextSensor::get_state() const { return this->state; } std::string TextSensor::get_raw_state() const { return this->raw_state; } void TextSensor::internal_send_state_to_frontend(const std::string &state) { - this->state = this->raw_state; + this->state = state; this->has_state_ = true; ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str()); this->callback_.call(state); From 16e7bd038833e2c1e149598697e566488b4006c0 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 13 Dec 2021 19:15:22 +0100 Subject: [PATCH 460/549] fix multi-line comment warning/error (#2891) --- esphome/__main__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/__main__.py b/esphome/__main__.py index 2f06a71b5f..6f57791480 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -145,6 +145,8 @@ def wrap_to_code(name, comp): if comp.config_schema is not None: conf_str = yaml_util.dump(conf) conf_str = conf_str.replace("//", "") + # remove tailing \ to avoid multi-line comment warning + conf_str = conf_str.replace("\\\n", "\n") cg.add(cg.LineComment(indent(conf_str))) await coro(conf) From 80e2bfada33a5a9848bf4c3a7492f006c47f0833 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Dec 2021 19:15:49 +0100 Subject: [PATCH 461/549] Bump black from 21.11b1 to 21.12b0 (#2879) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen --- esphome/wizard.py | 1 - requirements_test.txt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/esphome/wizard.py b/esphome/wizard.py index f2632caf71..c64ad3a583 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -160,7 +160,6 @@ if get_bool_env(ENV_QUICKWIZARD): def sleep(time): pass - else: from time import sleep diff --git a/requirements_test.txt b/requirements_test.txt index abfd07f022..4d5c40296f 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==2.12.2 flake8==4.0.1 -black==21.11b1 +black==21.12b0 pre-commit # Unit tests From 45e346cf1b9d18189af861dfeff354e60ea6825a Mon Sep 17 00:00:00 2001 From: wilberforce Date: Tue, 14 Dec 2021 15:08:01 +1300 Subject: [PATCH 462/549] Allow button POST on press from web server (#2913) --- esphome/components/web_server/web_server.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 29cb4827bd..1e7696edfb 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -390,8 +390,6 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM #ifdef USE_BUTTON void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (button::Button *obj : App.get_buttons()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; From a7b05db2a160e91c1e22be1f368bcb24b1224c7e Mon Sep 17 00:00:00 2001 From: jddonovan Date: Tue, 14 Dec 2021 20:39:50 +0200 Subject: [PATCH 463/549] Adding Pascal unit to constants (#2914) --- esphome/const.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/const.py b/esphome/const.py index baaebbf90f..36d2257c30 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -832,6 +832,7 @@ UNIT_MINUTE = "min" UNIT_OHM = "Ω" UNIT_PARTS_PER_BILLION = "ppb" UNIT_PARTS_PER_MILLION = "ppm" +UNIT_PASCAL = "Pa" UNIT_PERCENT = "%" UNIT_PULSES = "pulses" UNIT_PULSES_PER_MINUTE = "pulses/min" From 5d70ff702b40722e6a843926f1cf920445790dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Vran=C3=ADk?= Date: Tue, 14 Dec 2021 19:43:42 +0100 Subject: [PATCH 464/549] quantile filter support (#2900) Co-authored-by: Oxan van Leeuwen Co-authored-by: pvranik --- esphome/components/sensor/__init__.py | 26 +++++++++++++++++ esphome/components/sensor/filter.cpp | 40 +++++++++++++++++++++++++-- esphome/components/sensor/filter.h | 31 +++++++++++++++++++++ esphome/const.py | 1 + tests/test3.yaml | 5 ++++ 5 files changed, 101 insertions(+), 2 deletions(-) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index d9d226aab6..14a15da2f1 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -19,6 +19,7 @@ from esphome.const import ( CONF_ON_RAW_VALUE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, + CONF_QUANTILE, CONF_SEND_EVERY, CONF_SEND_FIRST_AT, CONF_STATE_CLASS, @@ -151,6 +152,7 @@ SensorPublishAction = sensor_ns.class_("SensorPublishAction", automation.Action) # Filters Filter = sensor_ns.class_("Filter") +QuantileFilter = sensor_ns.class_("QuantileFilter", Filter) MedianFilter = sensor_ns.class_("MedianFilter", Filter) MinFilter = sensor_ns.class_("MinFilter", Filter) MaxFilter = sensor_ns.class_("MaxFilter", Filter) @@ -285,6 +287,30 @@ async def filter_out_filter_to_code(config, filter_id): return cg.new_Pvariable(filter_id, config) +QUANTILE_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_WINDOW_SIZE, default=5): cv.positive_not_null_int, + cv.Optional(CONF_SEND_EVERY, default=5): cv.positive_not_null_int, + cv.Optional(CONF_SEND_FIRST_AT, default=1): cv.positive_not_null_int, + cv.Optional(CONF_QUANTILE, default=0.9): cv.zero_to_one_float, + } + ), + validate_send_first_at, +) + + +@FILTER_REGISTRY.register("quantile", QuantileFilter, QUANTILE_SCHEMA) +async def quantile_filter_to_code(config, filter_id): + return cg.new_Pvariable( + filter_id, + config[CONF_WINDOW_SIZE], + config[CONF_SEND_EVERY], + config[CONF_SEND_FIRST_AT], + config[CONF_QUANTILE], + ) + + MEDIAN_SCHEMA = cv.All( cv.Schema( { diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index 321e3a4a4f..7a8a557273 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -1,7 +1,8 @@ #include "filter.h" -#include "sensor.h" -#include "esphome/core/log.h" #include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "sensor.h" +#include namespace esphome { namespace sensor { @@ -66,6 +67,41 @@ optional MedianFilter::new_value(float value) { return {}; } +// QuantileFilter +QuantileFilter::QuantileFilter(size_t window_size, size_t send_every, size_t send_first_at, float quantile) + : send_every_(send_every), send_at_(send_every - send_first_at), window_size_(window_size), quantile_(quantile) {} +void QuantileFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; } +void QuantileFilter::set_window_size(size_t window_size) { this->window_size_ = window_size; } +void QuantileFilter::set_quantile(float quantile) { this->quantile_ = quantile; } +optional QuantileFilter::new_value(float value) { + if (!std::isnan(value)) { + while (this->queue_.size() >= this->window_size_) { + this->queue_.pop_front(); + } + this->queue_.push_back(value); + ESP_LOGVV(TAG, "QuantileFilter(%p)::new_value(%f), quantile:%f", this, value, this->quantile_); + } + + if (++this->send_at_ >= this->send_every_) { + this->send_at_ = 0; + + float result = 0.0f; + if (!this->queue_.empty()) { + std::deque quantile_queue = this->queue_; + sort(quantile_queue.begin(), quantile_queue.end()); + + size_t queue_size = quantile_queue.size(); + size_t position = ceilf(queue_size * this->quantile_) - 1; + ESP_LOGVV(TAG, "QuantileFilter(%p)::position: %d/%d", this, position, queue_size); + result = quantile_queue[position]; + } + + ESP_LOGVV(TAG, "QuantileFilter(%p)::new_value(%f) SENDING", this, result); + return result; + } + return {}; +} + // 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) {} diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index d595e419a6..0ed7ce4801 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -42,6 +42,37 @@ class Filter { Sensor *parent_{nullptr}; }; +/** Simple quantile filter. + * + * Takes the quantile of the last values and pushes it out every . + */ +class QuantileFilter : public Filter { + public: + /** Construct a QuantileFilter. + * + * @param window_size The number of values that should be used in quantile calculation. + * @param send_every After how many sensor values should a new one be pushed out. + * @param send_first_at After how many values to forward the very first value. Defaults to the first value + * on startup being published on the first *raw* value, so with no filter applied. Must be less than or equal to + * send_every. + * @param quantile float 0..1 to pick the requested quantile. Defaults to 0.9. + */ + explicit QuantileFilter(size_t window_size, size_t send_every, size_t send_first_at, float quantile); + + optional new_value(float value) override; + + void set_send_every(size_t send_every); + void set_window_size(size_t window_size); + void set_quantile(float quantile); + + protected: + std::deque queue_; + size_t send_every_; + size_t send_at_; + size_t window_size_; + float quantile_; +}; + /** Simple median filter. * * Takes the median of the last values and pushes it out every . diff --git a/esphome/const.py b/esphome/const.py index 36d2257c30..28648412a5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -518,6 +518,7 @@ CONF_PULLDOWN = "pulldown" CONF_PULLUP = "pullup" CONF_PULSE_LENGTH = "pulse_length" CONF_QOS = "qos" +CONF_QUANTILE = "quantile" CONF_RADON = "radon" CONF_RADON_LONG_TERM = "radon_long_term" CONF_RANDOM = "random" diff --git a/tests/test3.yaml b/tests/test3.yaml index 50cd6d6cf6..61d68d824b 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -358,6 +358,11 @@ sensor: - filter_out: NAN - sliding_window_moving_average: - exponential_moving_average: + - quantile: + window_size: 5 + send_every: 5 + send_first_at: 3 + quantile: .8 - lambda: 'return 0;' - delta: 100 - throttle: 100ms From 192eb495898da6c630818580d797b9dc5d3c1335 Mon Sep 17 00:00:00 2001 From: sveip Date: Tue, 14 Dec 2021 19:46:43 +0100 Subject: [PATCH 465/549] ESP32 CAM add Automatic Exposure Control option (#2892) Co-authored-by: Peter Co-authored-by: Carlos Garcia Saura --- esphome/components/esp32_camera/__init__.py | 9 +++++++++ esphome/components/esp32_camera/esp32_camera.cpp | 12 +++++++++--- esphome/components/esp32_camera/esp32_camera.h | 6 ++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 2b1890267f..1135b31798 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -57,6 +57,9 @@ CONF_IDLE_FRAMERATE = "idle_framerate" CONF_JPEG_QUALITY = "jpeg_quality" CONF_VERTICAL_FLIP = "vertical_flip" CONF_HORIZONTAL_MIRROR = "horizontal_mirror" +CONF_AEC2 = "aec2" +CONF_AE_LEVEL = "ae_level" +CONF_AEC_VALUE = "aec_value" CONF_SATURATION = "saturation" CONF_TEST_PATTERN = "test_pattern" @@ -102,6 +105,9 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( cv.Optional(CONF_SATURATION, default=0): camera_range_param, cv.Optional(CONF_VERTICAL_FLIP, default=True): cv.boolean, cv.Optional(CONF_HORIZONTAL_MIRROR, default=True): cv.boolean, + cv.Optional(CONF_AEC2, default=False): cv.boolean, + cv.Optional(CONF_AE_LEVEL, default=0): camera_range_param, + cv.Optional(CONF_AEC_VALUE, default=300): cv.int_range(min=0, max=1200), cv.Optional(CONF_TEST_PATTERN, default=False): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA) @@ -116,6 +122,9 @@ SETTERS = { CONF_JPEG_QUALITY: "set_jpeg_quality", CONF_VERTICAL_FLIP: "set_vertical_flip", CONF_HORIZONTAL_MIRROR: "set_horizontal_mirror", + CONF_AEC2: "set_aec2", + CONF_AE_LEVEL: "set_ae_level", + CONF_AEC_VALUE: "set_aec_value", CONF_CONTRAST: "set_contrast", CONF_BRIGHTNESS: "set_brightness", CONF_SATURATION: "set_saturation", diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 6f93532f47..a6d4a30c27 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -26,6 +26,9 @@ void ESP32Camera::setup() { sensor_t *s = esp_camera_sensor_get(); s->set_vflip(s, this->vertical_flip_); s->set_hmirror(s, this->horizontal_mirror_); + s->set_aec2(s, this->aec2_); // 0 = disable , 1 = enable + s->set_ae_level(s, this->ae_level_); // -2 to 2 + s->set_aec_value(s, this->aec_value_); // 0 to 1200 s->set_contrast(s, this->contrast_); s->set_brightness(s, this->brightness_); s->set_saturation(s, this->saturation_); @@ -111,9 +114,9 @@ void ESP32Camera::dump_config() { // ESP_LOGCONFIG(TAG, " Auto White Balance: %u", st.awb); // ESP_LOGCONFIG(TAG, " Auto White Balance Gain: %u", st.awb_gain); // ESP_LOGCONFIG(TAG, " Auto Exposure Control: %u", st.aec); - // ESP_LOGCONFIG(TAG, " Auto Exposure Control 2: %u", st.aec2); - // ESP_LOGCONFIG(TAG, " Auto Exposure Level: %d", st.ae_level); - // ESP_LOGCONFIG(TAG, " Auto Exposure Value: %u", st.aec_value); + ESP_LOGCONFIG(TAG, " Auto Exposure Control 2: %u", st.aec2); + ESP_LOGCONFIG(TAG, " Auto Exposure Level: %d", st.ae_level); + ESP_LOGCONFIG(TAG, " Auto Exposure Value: %u", st.aec_value); // ESP_LOGCONFIG(TAG, " AGC: %u", st.agc); // ESP_LOGCONFIG(TAG, " AGC Gain: %u", st.agc_gain); // ESP_LOGCONFIG(TAG, " Gain Ceiling: %u", st.gainceiling); @@ -250,6 +253,9 @@ void ESP32Camera::add_image_callback(std::functionvertical_flip_ = vertical_flip; } void ESP32Camera::set_horizontal_mirror(bool horizontal_mirror) { this->horizontal_mirror_ = horizontal_mirror; } +void ESP32Camera::set_aec2(bool aec2) { this->aec2_ = aec2; } +void ESP32Camera::set_ae_level(int ae_level) { this->ae_level_ = ae_level; } +void ESP32Camera::set_aec_value(uint32_t aec_value) { this->aec_value_ = aec_value; } void ESP32Camera::set_contrast(int contrast) { this->contrast_ = contrast; } void ESP32Camera::set_brightness(int brightness) { this->brightness_ = brightness; } void ESP32Camera::set_saturation(int saturation) { this->saturation_ = saturation; } diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 84f8d9cbea..b20485a0f7 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -67,6 +67,9 @@ class ESP32Camera : public Component, public EntityBase { void set_power_down_pin(uint8_t pin); void set_vertical_flip(bool vertical_flip); void set_horizontal_mirror(bool horizontal_mirror); + void set_aec2(bool aec2); + void set_ae_level(int ae_level); + void set_aec_value(uint32_t aec_value); void set_contrast(int contrast); void set_brightness(int brightness); void set_saturation(int saturation); @@ -91,6 +94,9 @@ class ESP32Camera : public Component, public EntityBase { camera_config_t config_{}; bool vertical_flip_{true}; bool horizontal_mirror_{true}; + bool aec2_{false}; + int ae_level_{0}; + uint32_t aec_value_{300}; int contrast_{0}; int brightness_{0}; int saturation_{0}; From 1fb0a7109d8f08e3e8bb7e2e8761085506193def Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Wed, 15 Dec 2021 10:38:23 +0100 Subject: [PATCH 466/549] Modbus: use multiply for publishing number (#2916) --- esphome/components/modbus_controller/number/modbus_number.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index 74dd615e61..5e977f5df4 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -8,7 +8,7 @@ namespace modbus_controller { static const char *const TAG = "modbus.number"; void ModbusNumber::parse_and_publish(const std::vector &data) { - float result = payload_to_float(data, *this); + float result = payload_to_float(data, *this) / multiply_by_; // Is there a lambda registered // call it with the pre converted value and the raw data array From 66e0ff839278bb80f9bdae14d16007105a0355aa Mon Sep 17 00:00:00 2001 From: Benny de Leeuw Date: Mon, 20 Dec 2021 02:30:23 +0100 Subject: [PATCH 467/549] Add growatt modbus sensor (#2922) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/growatt_solar/__init__.py | 0 .../growatt_solar/growatt_solar.cpp | 69 ++++++ .../components/growatt_solar/growatt_solar.h | 73 +++++++ esphome/components/growatt_solar/sensor.py | 201 ++++++++++++++++++ 5 files changed, 344 insertions(+) create mode 100644 esphome/components/growatt_solar/__init__.py create mode 100644 esphome/components/growatt_solar/growatt_solar.cpp create mode 100644 esphome/components/growatt_solar/growatt_solar.h create mode 100644 esphome/components/growatt_solar/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 6dbdef12ec..2f2260e0e4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -65,6 +65,7 @@ esphome/components/globals/* @esphome/core esphome/components/gpio/* @esphome/core esphome/components/gps/* @coogle esphome/components/graph/* @synco +esphome/components/growatt_solar/* @leeuwte esphome/components/havells_solar/* @sourabhjaiswal esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/light/* @DotNetDann diff --git a/esphome/components/growatt_solar/__init__.py b/esphome/components/growatt_solar/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/growatt_solar/growatt_solar.cpp b/esphome/components/growatt_solar/growatt_solar.cpp new file mode 100644 index 0000000000..ed7240ab6c --- /dev/null +++ b/esphome/components/growatt_solar/growatt_solar.cpp @@ -0,0 +1,69 @@ +#include "growatt_solar.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace growatt_solar { + +static const char *const TAG = "growatt_solar"; + +static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x04; +static const uint8_t MODBUS_REGISTER_COUNT = 33; + +void GrowattSolar::update() { this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT); } + +void GrowattSolar::on_modbus_data(const std::vector &data) { + auto publish_1_reg_sensor_state = [&](sensor::Sensor *sensor, size_t i, float unit) -> void { + if (sensor == nullptr) + return; + float value = encode_uint16(data[i * 2], data[i * 2 + 1]) * unit; + sensor->publish_state(value); + }; + + auto publish_2_reg_sensor_state = [&](sensor::Sensor *sensor, size_t reg1, size_t reg2, float unit) -> void { + float value = ((encode_uint16(data[reg1 * 2], data[reg1 * 2 + 1]) << 16) + + encode_uint16(data[reg2 * 2], data[reg2 * 2 + 1])) * + unit; + if (sensor != nullptr) + sensor->publish_state(value); + }; + + publish_1_reg_sensor_state(this->inverter_status_, 0, 1); + + publish_2_reg_sensor_state(this->pv_active_power_sensor_, 1, 2, ONE_DEC_UNIT); + + publish_1_reg_sensor_state(this->pvs_[0].voltage_sensor_, 3, ONE_DEC_UNIT); + publish_1_reg_sensor_state(this->pvs_[0].current_sensor_, 4, ONE_DEC_UNIT); + publish_2_reg_sensor_state(this->pvs_[0].active_power_sensor_, 5, 6, ONE_DEC_UNIT); + + publish_1_reg_sensor_state(this->pvs_[1].voltage_sensor_, 7, ONE_DEC_UNIT); + publish_1_reg_sensor_state(this->pvs_[1].current_sensor_, 8, ONE_DEC_UNIT); + publish_2_reg_sensor_state(this->pvs_[1].active_power_sensor_, 9, 10, ONE_DEC_UNIT); + + publish_2_reg_sensor_state(this->grid_active_power_sensor_, 11, 12, ONE_DEC_UNIT); + publish_1_reg_sensor_state(this->grid_frequency_sensor_, 13, TWO_DEC_UNIT); + + publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 14, ONE_DEC_UNIT); + publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 15, ONE_DEC_UNIT); + publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 16, 17, ONE_DEC_UNIT); + + publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 18, ONE_DEC_UNIT); + publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 19, ONE_DEC_UNIT); + publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 20, 21, ONE_DEC_UNIT); + + publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 22, ONE_DEC_UNIT); + publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 23, ONE_DEC_UNIT); + publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 24, 25, ONE_DEC_UNIT); + + publish_2_reg_sensor_state(this->today_production_, 26, 27, ONE_DEC_UNIT); + publish_2_reg_sensor_state(this->total_energy_production_, 28, 29, ONE_DEC_UNIT); + + publish_1_reg_sensor_state(this->inverter_module_temp_, 32, ONE_DEC_UNIT); +} + +void GrowattSolar::dump_config() { + ESP_LOGCONFIG(TAG, "GROWATT Solar:"); + ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); +} + +} // namespace growatt_solar +} // namespace esphome diff --git a/esphome/components/growatt_solar/growatt_solar.h b/esphome/components/growatt_solar/growatt_solar.h new file mode 100644 index 0000000000..5356ac907a --- /dev/null +++ b/esphome/components/growatt_solar/growatt_solar.h @@ -0,0 +1,73 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/modbus/modbus.h" + +namespace esphome { +namespace growatt_solar { + +static const float TWO_DEC_UNIT = 0.01; +static const float ONE_DEC_UNIT = 0.1; + +class GrowattSolar : public PollingComponent, public modbus::ModbusDevice { + public: + void update() override; + void on_modbus_data(const std::vector &data) override; + void dump_config() override; + + void set_inverter_status_sensor(sensor::Sensor *sensor) { this->inverter_status_ = sensor; } + + void set_grid_frequency_sensor(sensor::Sensor *sensor) { this->grid_frequency_sensor_ = sensor; } + void set_grid_active_power_sensor(sensor::Sensor *sensor) { this->grid_active_power_sensor_ = sensor; } + void set_pv_active_power_sensor(sensor::Sensor *sensor) { this->pv_active_power_sensor_ = sensor; } + + void set_today_production_sensor(sensor::Sensor *sensor) { this->today_production_ = sensor; } + void set_total_energy_production_sensor(sensor::Sensor *sensor) { this->total_energy_production_ = sensor; } + void set_inverter_module_temp_sensor(sensor::Sensor *sensor) { this->inverter_module_temp_ = sensor; } + + void set_voltage_sensor(uint8_t phase, sensor::Sensor *voltage_sensor) { + this->phases_[phase].voltage_sensor_ = voltage_sensor; + } + void set_current_sensor(uint8_t phase, sensor::Sensor *current_sensor) { + this->phases_[phase].current_sensor_ = current_sensor; + } + void set_active_power_sensor(uint8_t phase, sensor::Sensor *active_power_sensor) { + this->phases_[phase].active_power_sensor_ = active_power_sensor; + } + void set_voltage_sensor_pv(uint8_t pv, sensor::Sensor *voltage_sensor) { + this->pvs_[pv].voltage_sensor_ = voltage_sensor; + } + void set_current_sensor_pv(uint8_t pv, sensor::Sensor *current_sensor) { + this->pvs_[pv].current_sensor_ = current_sensor; + } + void set_active_power_sensor_pv(uint8_t pv, sensor::Sensor *active_power_sensor) { + this->pvs_[pv].active_power_sensor_ = active_power_sensor; + } + + protected: + struct GrowattPhase { + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *active_power_sensor_{nullptr}; + } phases_[3]; + struct GrowattPV { + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *active_power_sensor_{nullptr}; + } pvs_[2]; + + sensor::Sensor *inverter_status_{nullptr}; + + sensor::Sensor *grid_frequency_sensor_{nullptr}; + sensor::Sensor *grid_active_power_sensor_{nullptr}; + + sensor::Sensor *pv_active_power_sensor_{nullptr}; + + sensor::Sensor *today_production_{nullptr}; + sensor::Sensor *total_energy_production_{nullptr}; + sensor::Sensor *inverter_module_temp_{nullptr}; +}; + +} // namespace growatt_solar +} // namespace esphome diff --git a/esphome/components/growatt_solar/sensor.py b/esphome/components/growatt_solar/sensor.py new file mode 100644 index 0000000000..99936c33ee --- /dev/null +++ b/esphome/components/growatt_solar/sensor.py @@ -0,0 +1,201 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, modbus +from esphome.const import ( + CONF_ACTIVE_POWER, + CONF_CURRENT, + CONF_FREQUENCY, + CONF_ID, + CONF_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + ICON_CURRENT_AC, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, + UNIT_AMPERE, + UNIT_CELSIUS, + UNIT_HERTZ, + UNIT_VOLT, + UNIT_WATT, +) + +CONF_PHASE_A = "phase_a" +CONF_PHASE_B = "phase_b" +CONF_PHASE_C = "phase_c" + +CONF_ENERGY_PRODUCTION_DAY = "energy_production_day" +CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production" +CONF_TOTAL_GENERATION_TIME = "total_generation_time" +CONF_TODAY_GENERATION_TIME = "today_generation_time" +CONF_PV1 = "pv1" +CONF_PV2 = "pv2" +UNIT_KILOWATT_HOURS = "kWh" +UNIT_HOURS = "h" +UNIT_KOHM = "kΩ" +UNIT_MILLIAMPERE = "mA" + +CONF_INVERTER_STATUS = "inverter_status" +CONF_PV_ACTIVE_POWER = "pv_active_power" +CONF_INVERTER_MODULE_TEMP = "inverter_module_temp" + + +AUTO_LOAD = ["modbus"] +CODEOWNERS = ["@leeuwte"] + +growatt_solar_ns = cg.esphome_ns.namespace("growatt_solar") +GrowattSolar = growatt_solar_ns.class_( + "GrowattSolar", cg.PollingComponent, modbus.ModbusDevice +) + +PHASE_SENSORS = { + CONF_VOLTAGE: sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + ), + CONF_CURRENT: sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + CONF_ACTIVE_POWER: sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), +} +PV_SENSORS = { + CONF_VOLTAGE: sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + ), + CONF_CURRENT: sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + CONF_ACTIVE_POWER: sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), +} + +PHASE_SCHEMA = cv.Schema( + {cv.Optional(sensor): schema for sensor, schema in PHASE_SENSORS.items()} +) +PV_SCHEMA = cv.Schema( + {cv.Optional(sensor): schema for sensor, schema in PV_SENSORS.items()} +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(GrowattSolar), + cv.Optional(CONF_PHASE_A): PHASE_SCHEMA, + cv.Optional(CONF_PHASE_B): PHASE_SCHEMA, + cv.Optional(CONF_PHASE_C): PHASE_SCHEMA, + cv.Optional(CONF_PV1): PV_SCHEMA, + cv.Optional(CONF_PV2): PV_SCHEMA, + cv.Optional(CONF_INVERTER_STATUS): sensor.sensor_schema(), + cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( + unit_of_measurement=UNIT_HERTZ, + icon=ICON_CURRENT_AC, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ACTIVE_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PV_ACTIVE_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ENERGY_PRODUCTION_DAY): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + 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_TOTAL_INCREASING, + ), + cv.Optional(CONF_INVERTER_MODULE_TEMP): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("10s")) + .extend(modbus.modbus_device_schema(0x01)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await modbus.register_modbus_device(var, config) + + if CONF_INVERTER_STATUS in config: + sens = await sensor.new_sensor(config[CONF_INVERTER_STATUS]) + cg.add(var.set_inverter_status_sensor(sens)) + + if CONF_FREQUENCY in config: + sens = await sensor.new_sensor(config[CONF_FREQUENCY]) + cg.add(var.set_grid_frequency_sensor(sens)) + + if CONF_ACTIVE_POWER in config: + sens = await sensor.new_sensor(config[CONF_ACTIVE_POWER]) + cg.add(var.set_grid_active_power_sensor(sens)) + + if CONF_PV_ACTIVE_POWER in config: + sens = await sensor.new_sensor(config[CONF_PV_ACTIVE_POWER]) + cg.add(var.set_pv_active_power_sensor(sens)) + + if CONF_ENERGY_PRODUCTION_DAY in config: + sens = await sensor.new_sensor(config[CONF_ENERGY_PRODUCTION_DAY]) + cg.add(var.set_today_production_sensor(sens)) + + if CONF_TOTAL_ENERGY_PRODUCTION in config: + sens = await sensor.new_sensor(config[CONF_TOTAL_ENERGY_PRODUCTION]) + cg.add(var.set_total_energy_production_sensor(sens)) + + if CONF_INVERTER_MODULE_TEMP in config: + sens = await sensor.new_sensor(config[CONF_INVERTER_MODULE_TEMP]) + cg.add(var.set_inverter_module_temp_sensor(sens)) + + for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]): + if phase not in config: + continue + + phase_config = config[phase] + for sensor_type in PHASE_SENSORS: + if sensor_type in phase_config: + sens = await sensor.new_sensor(phase_config[sensor_type]) + cg.add(getattr(var, f"set_{sensor_type}_sensor")(i, sens)) + + for i, pv in enumerate([CONF_PV1, CONF_PV2]): + if pv not in config: + continue + + pv_config = config[pv] + for sensor_type in pv_config: + if sensor_type in pv_config: + sens = await sensor.new_sensor(pv_config[sensor_type]) + cg.add(getattr(var, f"set_{sensor_type}_sensor_pv")(i, sens)) From 6ec9cfb0447a4e140e2071fe4947e5f43bf68f0a Mon Sep 17 00:00:00 2001 From: Frank Langtind Date: Mon, 20 Dec 2021 02:35:10 +0100 Subject: [PATCH 468/549] Add Tuya Number support (#2765) --- CODEOWNERS | 1 + esphome/components/tuya/number/__init__.py | 54 +++++++++++++++++++ .../components/tuya/number/tuya_number.cpp | 38 +++++++++++++ esphome/components/tuya/number/tuya_number.h | 27 ++++++++++ esphome/const.py | 1 + tests/test4.yaml | 8 ++- 6 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 esphome/components/tuya/number/__init__.py create mode 100644 esphome/components/tuya/number/tuya_number.cpp create mode 100644 esphome/components/tuya/number/tuya_number.h diff --git a/CODEOWNERS b/CODEOWNERS index 2f2260e0e4..fed6815a66 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -180,6 +180,7 @@ esphome/components/toshiba/* @kbx81 esphome/components/tsl2591/* @wjcarpenter esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/climate/* @jesserockz +esphome/components/tuya/number/* @frankiboy1 esphome/components/tuya/sensor/* @jesserockz esphome/components/tuya/switch/* @jesserockz esphome/components/tuya/text_sensor/* @dentra diff --git a/esphome/components/tuya/number/__init__.py b/esphome/components/tuya/number/__init__.py new file mode 100644 index 0000000000..12c0c0f6e5 --- /dev/null +++ b/esphome/components/tuya/number/__init__.py @@ -0,0 +1,54 @@ +from esphome.components import number +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import ( + CONF_ID, + CONF_NUMBER_DATAPOINT, + CONF_MAX_VALUE, + CONF_MIN_VALUE, + CONF_STEP, +) +from .. import tuya_ns, CONF_TUYA_ID, Tuya + +DEPENDENCIES = ["tuya"] +CODEOWNERS = ["@frankiboy1"] + +TuyaNumber = tuya_ns.class_("TuyaNumber", number.Number, cg.Component) + + +def validate_min_max(config): + if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]: + raise cv.Invalid("max_value must be greater than min_value") + return config + + +CONFIG_SCHEMA = cv.All( + number.NUMBER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TuyaNumber), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Required(CONF_NUMBER_DATAPOINT): cv.uint8_t, + cv.Required(CONF_MAX_VALUE): cv.float_, + cv.Required(CONF_MIN_VALUE): cv.float_, + cv.Required(CONF_STEP): cv.positive_float, + } + ).extend(cv.COMPONENT_SCHEMA), + validate_min_max, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await number.register_number( + var, + config, + min_value=config[CONF_MIN_VALUE], + max_value=config[CONF_MAX_VALUE], + step=config[CONF_STEP], + ) + + paren = await cg.get_variable(config[CONF_TUYA_ID]) + cg.add(var.set_tuya_parent(paren)) + + cg.add(var.set_number_id(config[CONF_NUMBER_DATAPOINT])) diff --git a/esphome/components/tuya/number/tuya_number.cpp b/esphome/components/tuya/number/tuya_number.cpp new file mode 100644 index 0000000000..5c7cafbf7a --- /dev/null +++ b/esphome/components/tuya/number/tuya_number.cpp @@ -0,0 +1,38 @@ +#include "esphome/core/log.h" +#include "tuya_number.h" + +namespace esphome { +namespace tuya { + +static const char *const TAG = "tuya.number"; + +void TuyaNumber::setup() { + this->parent_->register_listener(this->number_id_, [this](const TuyaDatapoint &datapoint) { + if (datapoint.type == TuyaDatapointType::INTEGER) { + ESP_LOGV(TAG, "MCU reported number %u is: %d", datapoint.id, datapoint.value_int); + this->publish_state(datapoint.value_int); + } else if (datapoint.type == TuyaDatapointType::ENUM) { + ESP_LOGV(TAG, "MCU reported number %u is: %u", datapoint.id, datapoint.value_enum); + this->publish_state(datapoint.value_enum); + } + this->type_ = datapoint.type; + }); +} + +void TuyaNumber::control(float value) { + ESP_LOGV(TAG, "Setting number %u: %f", this->number_id_, value); + if (this->type_ == TuyaDatapointType::INTEGER) { + this->parent_->set_integer_datapoint_value(this->number_id_, value); + } else if (this->type_ == TuyaDatapointType::ENUM) { + this->parent_->set_enum_datapoint_value(this->number_id_, value); + } + this->publish_state(value); +} + +void TuyaNumber::dump_config() { + LOG_NUMBER("", "Tuya Number", this); + ESP_LOGCONFIG(TAG, " Number has datapoint ID %u", this->number_id_); +} + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/number/tuya_number.h b/esphome/components/tuya/number/tuya_number.h new file mode 100644 index 0000000000..7cca9fc646 --- /dev/null +++ b/esphome/components/tuya/number/tuya_number.h @@ -0,0 +1,27 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/tuya/tuya.h" +#include "esphome/components/number/number.h" + +namespace esphome { +namespace tuya { + +class TuyaNumber : public number::Number, public Component { + public: + void setup() override; + void dump_config() override; + void set_number_id(uint8_t number_id) { this->number_id_ = number_id; } + + void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } + + protected: + void control(float value) override; + + Tuya *parent_; + uint8_t number_id_{0}; + TuyaDatapointType type_{}; +}; + +} // namespace tuya +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 28648412a5..970b3c6578 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -402,6 +402,7 @@ CONF_NUM_CHIPS = "num_chips" CONF_NUM_LEDS = "num_leds" CONF_NUM_SCANS = "num_scans" CONF_NUMBER = "number" +CONF_NUMBER_DATAPOINT = "number_datapoint" CONF_OFF_MODE = "off_mode" CONF_OFFSET = "offset" CONF_ON = "on" diff --git a/tests/test4.yaml b/tests/test4.yaml index bb8f0f15c6..545806ba87 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -441,7 +441,13 @@ display: wakeup_pin: GPIO1 vcom_pin: GPIO1 - +number: + - platform: tuya + id: tuya_number + number_datapoint: 102 + min_value: 0 + max_value: 17 + step: 1 text_sensor: - platform: pipsolar From 542fb2175b999e1cb87aee097083a1754d8825ed Mon Sep 17 00:00:00 2001 From: Jonas De Kegel Date: Mon, 20 Dec 2021 09:30:35 +0100 Subject: [PATCH 469/549] Support inverted tm1637 display (#2878) Co-authored-by: Oxan van Leeuwen --- esphome/components/tm1637/display.py | 6 ++++ esphome/components/tm1637/tm1637.cpp | 43 +++++++++++++++++++++------- esphome/components/tm1637/tm1637.h | 4 +++ tests/test1.yaml | 2 ++ 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/esphome/components/tm1637/display.py b/esphome/components/tm1637/display.py index 7999029f5a..609c62fd10 100644 --- a/esphome/components/tm1637/display.py +++ b/esphome/components/tm1637/display.py @@ -8,6 +8,8 @@ from esphome.const import ( CONF_ID, CONF_LAMBDA, CONF_INTENSITY, + CONF_INVERTED, + CONF_LENGTH, ) CODEOWNERS = ["@glmnet"] @@ -22,6 +24,8 @@ CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend( cv.Optional(CONF_INTENSITY, default=7): cv.All( cv.uint8_t, cv.Range(min=0, max=7) ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + cv.Optional(CONF_LENGTH, default=6): cv.All(cv.uint8_t, cv.Range(min=1, max=6)), cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_DIO_PIN): pins.gpio_output_pin_schema, } @@ -39,6 +43,8 @@ async def to_code(config): cg.add(var.set_dio_pin(dio)) cg.add(var.set_intensity(config[CONF_INTENSITY])) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_length(config[CONF_LENGTH])) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( diff --git a/esphome/components/tm1637/tm1637.cpp b/esphome/components/tm1637/tm1637.cpp index 488f3b6727..ff8d5ea1bd 100644 --- a/esphome/components/tm1637/tm1637.cpp +++ b/esphome/components/tm1637/tm1637.cpp @@ -130,7 +130,9 @@ void TM1637Display::setup() { } void TM1637Display::dump_config() { ESP_LOGCONFIG(TAG, "TM1637:"); - ESP_LOGCONFIG(TAG, " INTENSITY: %d", this->intensity_); + ESP_LOGCONFIG(TAG, " Intensity: %d", this->intensity_); + ESP_LOGCONFIG(TAG, " Inverted: %d", this->inverted_); + ESP_LOGCONFIG(TAG, " Length: %d", this->length_); LOG_PIN(" CLK Pin: ", this->clk_pin_); LOG_PIN(" DIO Pin: ", this->dio_pin_); LOG_UPDATE_INTERVAL(this); @@ -173,8 +175,14 @@ void TM1637Display::display() { this->send_byte_(TM1637_I2C_COMM2); // Write the data bytes - for (auto b : this->buffer_) { - this->send_byte_(b); + if (this->inverted_) { + for (uint8_t i = this->length_ - 1; i >= 0; i--) { + this->send_byte_(this->buffer_[i]); + } + } else { + for (auto b : this->buffer_) { + this->send_byte_(b); + } } this->stop_(); @@ -241,14 +249,27 @@ uint8_t TM1637Display::print(uint8_t start_pos, const char *str) { } // Remap segments, for compatibility with MAX7219 segment definition which is // XABCDEFG, but TM1637 is // XGFEDCBA - data = ((data & 0x80) ? 0x80 : 0) | // no move X - ((data & 0x40) ? 0x1 : 0) | // A - ((data & 0x20) ? 0x2 : 0) | // B - ((data & 0x10) ? 0x4 : 0) | // C - ((data & 0x8) ? 0x8 : 0) | // D - ((data & 0x4) ? 0x10 : 0) | // E - ((data & 0x2) ? 0x20 : 0) | // F - ((data & 0x1) ? 0x40 : 0); // G + if (this->inverted_) { + // XABCDEFG > XGCBAFED + data = ((data & 0x80) ? 0x80 : 0) | // no move X + ((data & 0x40) ? 0x8 : 0) | // A + ((data & 0x20) ? 0x10 : 0) | // B + ((data & 0x10) ? 0x20 : 0) | // C + ((data & 0x8) ? 0x1 : 0) | // D + ((data & 0x4) ? 0x2 : 0) | // E + ((data & 0x2) ? 0x4 : 0) | // F + ((data & 0x1) ? 0x40 : 0); // G + } else { + // XABCDEFG > XGFEDCBA + data = ((data & 0x80) ? 0x80 : 0) | // no move X + ((data & 0x40) ? 0x1 : 0) | // A + ((data & 0x20) ? 0x2 : 0) | // B + ((data & 0x10) ? 0x4 : 0) | // C + ((data & 0x8) ? 0x8 : 0) | // D + ((data & 0x4) ? 0x10 : 0) | // E + ((data & 0x2) ? 0x20 : 0) | // F + ((data & 0x1) ? 0x40 : 0); // G + } if (*str == '.') { if (pos != start_pos) pos--; diff --git a/esphome/components/tm1637/tm1637.h b/esphome/components/tm1637/tm1637.h index 63b30ac13e..9b2f014ff9 100644 --- a/esphome/components/tm1637/tm1637.h +++ b/esphome/components/tm1637/tm1637.h @@ -41,6 +41,8 @@ class TM1637Display : public PollingComponent { uint8_t print(const char *str); void set_intensity(uint8_t intensity) { this->intensity_ = intensity; } + void set_inverted(bool inverted) { this->inverted_ = inverted; } + void set_length(uint8_t length) { this->length_ = length; } void display(); @@ -62,6 +64,8 @@ class TM1637Display : public PollingComponent { GPIOPin *dio_pin_; GPIOPin *clk_pin_; uint8_t intensity_; + uint8_t length_; + bool inverted_; optional writer_{}; uint8_t buffer_[6] = {0}; }; diff --git a/tests/test1.yaml b/tests/test1.yaml index 18c6610b08..7494146d1e 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2119,6 +2119,8 @@ display: mcp23xxx: mcp23017_hub number: 2 intensity: 3 + inverted: true + length: 4 lambda: |- it.print("1234"); - platform: pcd8544 From 1ccee86705fc65301bee41d2f17b27d88015dff3 Mon Sep 17 00:00:00 2001 From: Jonas De Kegel Date: Mon, 20 Dec 2021 18:06:04 +0100 Subject: [PATCH 470/549] Fix tm1637 bootloop (#2929) --- esphome/components/tm1637/tm1637.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tm1637/tm1637.cpp b/esphome/components/tm1637/tm1637.cpp index ff8d5ea1bd..a21d2d438d 100644 --- a/esphome/components/tm1637/tm1637.cpp +++ b/esphome/components/tm1637/tm1637.cpp @@ -176,7 +176,7 @@ void TM1637Display::display() { // Write the data bytes if (this->inverted_) { - for (uint8_t i = this->length_ - 1; i >= 0; i--) { + for (int8_t i = this->length_ - 1; i >= 0; i--) { this->send_byte_(this->buffer_[i]); } } else { From 4907e6f6d782f22d22e24918c161d15b6ef1447c Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 20 Dec 2021 20:19:20 +0100 Subject: [PATCH 471/549] Fix MQTT button press action (#2917) --- esphome/components/mqtt/mqtt_button.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index 25ff327cf9..5f3aaa1dd9 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -17,7 +17,7 @@ MQTTButtonComponent::MQTTButtonComponent(button::Button *button) : MQTTComponent void MQTTButtonComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { - if (payload == "press") { + if (payload == "PRESS") { this->button_->press(); } else { ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str()); @@ -31,6 +31,7 @@ void MQTTButtonComponent::dump_config() { } void MQTTButtonComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { + config.state_topic = false; if (!this->button_->get_device_class().empty()) root[MQTT_DEVICE_CLASS] = this->button_->get_device_class(); } From f431c7402f0cdcc8fa032302a5ebe77d8020648a Mon Sep 17 00:00:00 2001 From: jsuanet <75206491+jsuanet@users.noreply.github.com> Date: Mon, 20 Dec 2021 22:25:36 +0100 Subject: [PATCH 472/549] Add shutdown and safe_mode button (#2918) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Jos Suanet --- CODEOWNERS | 4 +-- esphome/components/safe_mode/__init__.py | 2 +- .../components/safe_mode/button/__init__.py | 36 +++++++++++++++++++ .../safe_mode/button/safe_mode_button.cpp | 25 +++++++++++++ .../safe_mode/button/safe_mode_button.h | 21 +++++++++++ esphome/components/shutdown/__init__.py | 2 +- .../components/shutdown/button/__init__.py | 23 ++++++++++++ .../shutdown/button/shutdown_button.cpp | 33 +++++++++++++++++ .../shutdown/button/shutdown_button.h | 18 ++++++++++ .../{switch.py => switch/__init__.py} | 0 .../shutdown/{ => switch}/shutdown_switch.cpp | 0 .../shutdown/{ => switch}/shutdown_switch.h | 0 tests/test4.yaml | 4 +++ 13 files changed, 164 insertions(+), 4 deletions(-) create mode 100644 esphome/components/safe_mode/button/__init__.py create mode 100644 esphome/components/safe_mode/button/safe_mode_button.cpp create mode 100644 esphome/components/safe_mode/button/safe_mode_button.h create mode 100644 esphome/components/shutdown/button/__init__.py create mode 100644 esphome/components/shutdown/button/shutdown_button.cpp create mode 100644 esphome/components/shutdown/button/shutdown_button.h rename esphome/components/shutdown/{switch.py => switch/__init__.py} (100%) rename esphome/components/shutdown/{ => switch}/shutdown_switch.cpp (100%) rename esphome/components/shutdown/{ => switch}/shutdown_switch.h (100%) diff --git a/CODEOWNERS b/CODEOWNERS index fed6815a66..452c560938 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -133,7 +133,7 @@ esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz esphome/components/rgbct/* @jesserockz esphome/components/rtttl/* @glmnet -esphome/components/safe_mode/* @paulmonigatti +esphome/components/safe_mode/* @jsuanet @paulmonigatti esphome/components/scd4x/* @sjtrny esphome/components/script/* @esphome/core esphome/components/sdm_meter/* @jesserockz @polyfaces @@ -143,7 +143,7 @@ esphome/components/select/* @esphome/core esphome/components/sensor/* @esphome/core esphome/components/sgp40/* @SenexCrenshaw esphome/components/sht4x/* @sjtrny -esphome/components/shutdown/* @esphome/core +esphome/components/shutdown/* @esphome/core @jsuanet esphome/components/sim800l/* @glmnet esphome/components/sm2135/* @BoukeHaarsma23 esphome/components/socket/* @esphome/core diff --git a/esphome/components/safe_mode/__init__.py b/esphome/components/safe_mode/__init__.py index f150d6e086..ab884bfee4 100644 --- a/esphome/components/safe_mode/__init__.py +++ b/esphome/components/safe_mode/__init__.py @@ -1,5 +1,5 @@ import esphome.codegen as cg -CODEOWNERS = ["@paulmonigatti"] +CODEOWNERS = ["@paulmonigatti", "@jsuanet"] safe_mode_ns = cg.esphome_ns.namespace("safe_mode") diff --git a/esphome/components/safe_mode/button/__init__.py b/esphome/components/safe_mode/button/__init__.py new file mode 100644 index 0000000000..2cd8892afb --- /dev/null +++ b/esphome/components/safe_mode/button/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button +from esphome.components.ota import OTAComponent +from esphome.const import ( + CONF_ID, + CONF_OTA, + DEVICE_CLASS_RESTART, + ENTITY_CATEGORY_CONFIG, + ICON_RESTART_ALERT, +) + +DEPENDENCIES = ["ota"] + +safe_mode_ns = cg.esphome_ns.namespace("safe_mode") +SafeModeButton = safe_mode_ns.class_("SafeModeButton", button.Button, cg.Component) + +CONFIG_SCHEMA = ( + button.button_schema( + device_class=DEVICE_CLASS_RESTART, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_RESTART_ALERT, + ) + .extend({cv.GenerateID(): cv.declare_id(SafeModeButton)}) + .extend({cv.GenerateID(CONF_OTA): cv.use_id(OTAComponent)}) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await button.register_button(var, config) + + ota = await cg.get_variable(config[CONF_OTA]) + cg.add(var.set_ota(ota)) diff --git a/esphome/components/safe_mode/button/safe_mode_button.cpp b/esphome/components/safe_mode/button/safe_mode_button.cpp new file mode 100644 index 0000000000..2b8654de46 --- /dev/null +++ b/esphome/components/safe_mode/button/safe_mode_button.cpp @@ -0,0 +1,25 @@ +#include "safe_mode_button.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace safe_mode { + +static const char *const TAG = "safe_mode.button"; + +void SafeModeButton::set_ota(ota::OTAComponent *ota) { this->ota_ = ota; } + +void SafeModeButton::press_action() { + ESP_LOGI(TAG, "Restarting device in safe mode..."); + this->ota_->set_safe_mode_pending(true); + + // Let MQTT settle a bit + delay(100); // NOLINT + App.safe_reboot(); +} + +void SafeModeButton::dump_config() { LOG_BUTTON("", "Safe Mode Button", this); } + +} // namespace safe_mode +} // namespace esphome diff --git a/esphome/components/safe_mode/button/safe_mode_button.h b/esphome/components/safe_mode/button/safe_mode_button.h new file mode 100644 index 0000000000..63e0d1755e --- /dev/null +++ b/esphome/components/safe_mode/button/safe_mode_button.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/ota/ota_component.h" +#include "esphome/components/button/button.h" + +namespace esphome { +namespace safe_mode { + +class SafeModeButton : public button::Button, public Component { + public: + void dump_config() override; + void set_ota(ota::OTAComponent *ota); + + protected: + ota::OTAComponent *ota_; + void press_action() override; +}; + +} // namespace safe_mode +} // namespace esphome diff --git a/esphome/components/shutdown/__init__.py b/esphome/components/shutdown/__init__.py index f70ffa9520..480a6f3e31 100644 --- a/esphome/components/shutdown/__init__.py +++ b/esphome/components/shutdown/__init__.py @@ -1 +1 @@ -CODEOWNERS = ["@esphome/core"] +CODEOWNERS = ["@esphome/core", "@jsuanet"] diff --git a/esphome/components/shutdown/button/__init__.py b/esphome/components/shutdown/button/__init__.py new file mode 100644 index 0000000000..51cd6d6da2 --- /dev/null +++ b/esphome/components/shutdown/button/__init__.py @@ -0,0 +1,23 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button +from esphome.const import ( + CONF_ID, + ENTITY_CATEGORY_CONFIG, + ICON_POWER, +) + +shutdown_ns = cg.esphome_ns.namespace("shutdown") +ShutdownButton = shutdown_ns.class_("ShutdownButton", button.Button, cg.Component) + +CONFIG_SCHEMA = ( + button.button_schema(entity_category=ENTITY_CATEGORY_CONFIG, icon=ICON_POWER) + .extend({cv.GenerateID(): cv.declare_id(ShutdownButton)}) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await button.register_button(var, config) diff --git a/esphome/components/shutdown/button/shutdown_button.cpp b/esphome/components/shutdown/button/shutdown_button.cpp new file mode 100644 index 0000000000..be88a10d49 --- /dev/null +++ b/esphome/components/shutdown/button/shutdown_button.cpp @@ -0,0 +1,33 @@ +#include "shutdown_button.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +#ifdef USE_ESP32 +#include +#endif +#ifdef USE_ESP8266 +#include +#endif + +namespace esphome { +namespace shutdown { + +static const char *const TAG = "shutdown.button"; + +void ShutdownButton::dump_config() { LOG_BUTTON("", "Shutdown Button", this); } +void ShutdownButton::press_action() { + ESP_LOGI(TAG, "Shutting down..."); + // Let MQTT settle a bit + delay(100); // NOLINT + App.run_safe_shutdown_hooks(); +#ifdef USE_ESP8266 + ESP.deepSleep(0); // NOLINT(readability-static-accessed-through-instance) +#endif +#ifdef USE_ESP32 + esp_deep_sleep_start(); +#endif +} + +} // namespace shutdown +} // namespace esphome diff --git a/esphome/components/shutdown/button/shutdown_button.h b/esphome/components/shutdown/button/shutdown_button.h new file mode 100644 index 0000000000..d0094c899d --- /dev/null +++ b/esphome/components/shutdown/button/shutdown_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/button/button.h" + +namespace esphome { +namespace shutdown { + +class ShutdownButton : public button::Button, public Component { + public: + void dump_config() override; + + protected: + void press_action() override; +}; + +} // namespace shutdown +} // namespace esphome diff --git a/esphome/components/shutdown/switch.py b/esphome/components/shutdown/switch/__init__.py similarity index 100% rename from esphome/components/shutdown/switch.py rename to esphome/components/shutdown/switch/__init__.py diff --git a/esphome/components/shutdown/shutdown_switch.cpp b/esphome/components/shutdown/switch/shutdown_switch.cpp similarity index 100% rename from esphome/components/shutdown/shutdown_switch.cpp rename to esphome/components/shutdown/switch/shutdown_switch.cpp diff --git a/esphome/components/shutdown/shutdown_switch.h b/esphome/components/shutdown/switch/shutdown_switch.h similarity index 100% rename from esphome/components/shutdown/shutdown_switch.h rename to esphome/components/shutdown/switch/shutdown_switch.h diff --git a/tests/test4.yaml b/tests/test4.yaml index 545806ba87..7c1ddee6d8 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -531,3 +531,7 @@ xpt2046: button: - platform: restart name: Restart Button + - platform: safe_mode + name: Safe Mode Button + - platform: shutdown + name: Shutdown Button From f5c3b3446fa74f5a555cbdaa092e38f00c85b172 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 22 Dec 2021 12:56:52 +1300 Subject: [PATCH 473/549] Support inkplate10 (#2937) --- esphome/components/inkplate6/display.py | 13 +++++++++++++ esphome/components/inkplate6/inkplate.h | 25 +++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/esphome/components/inkplate6/display.py b/esphome/components/inkplate6/display.py index e4c71ea717..86b7069c55 100644 --- a/esphome/components/inkplate6/display.py +++ b/esphome/components/inkplate6/display.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_FULL_UPDATE_EVERY, CONF_ID, CONF_LAMBDA, + CONF_MODEL, CONF_PAGES, CONF_WAKEUP_PIN, ) @@ -40,6 +41,13 @@ Inkplate6 = inkplate6_ns.class_( "Inkplate6", cg.PollingComponent, i2c.I2CDevice, display.DisplayBuffer ) +InkplateModel = inkplate6_ns.enum("InkplateModel") + +MODELS = { + "inkplate_6": InkplateModel.INKPLATE_6, + "inkplate_10": InkplateModel.INKPLATE_10, +} + CONFIG_SCHEMA = cv.All( display.FULL_DISPLAY_SCHEMA.extend( { @@ -47,6 +55,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_GREYSCALE, default=False): cv.boolean, cv.Optional(CONF_PARTIAL_UPDATING, default=True): cv.boolean, cv.Optional(CONF_FULL_UPDATE_EVERY, default=10): cv.uint32_t, + cv.Optional(CONF_MODEL, default="inkplate_6"): cv.enum( + MODELS, lower=True, space="_" + ), # Control pins cv.Required(CONF_CKV_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_GMOD_PIN): pins.gpio_output_pin_schema, @@ -110,6 +121,8 @@ async def to_code(config): cg.add(var.set_partial_updating(config[CONF_PARTIAL_UPDATING])) cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY])) + cg.add(var.set_model(config[CONF_MODEL])) + ckv = await cg.gpio_pin_expression(config[CONF_CKV_PIN]) cg.add(var.set_ckv_pin(ckv)) diff --git a/esphome/components/inkplate6/inkplate.h b/esphome/components/inkplate6/inkplate.h index 56e95e95bb..2dac12a0c4 100644 --- a/esphome/components/inkplate6/inkplate.h +++ b/esphome/components/inkplate6/inkplate.h @@ -10,6 +10,11 @@ namespace esphome { namespace inkplate6 { +enum InkplateModel : uint8_t { + INKPLATE_6 = 0, + INKPLATE_10 = 1, +}; + class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public i2c::I2CDevice { public: const uint8_t LUT2[16] = {0b10101010, 0b10101001, 0b10100110, 0b10100101, 0b10011010, 0b10011001, @@ -43,6 +48,8 @@ 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_model(InkplateModel model) { this->model_ = model; } + 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; } @@ -101,9 +108,21 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public void pins_z_state_(); void pins_as_outputs_(); - int get_width_internal() override { return 800; } + int get_width_internal() override { + if (this->model_ == INKPLATE_6) + return 800; + else if (this->model_ == INKPLATE_10) + return 1200; + return 0; + } - int get_height_internal() override { return 600; } + int get_height_internal() override { + if (this->model_ == INKPLATE_6) + return 600; + else if (this->model_ == INKPLATE_10) + return 825; + return 0; + } size_t get_buffer_length_(); @@ -133,6 +152,8 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public bool greyscale_; bool partial_updating_; + InkplateModel model_; + InternalGPIOPin *display_data_0_pin_; InternalGPIOPin *display_data_1_pin_; InternalGPIOPin *display_data_2_pin_; From beb5f3dc9df6f6e3d4868820ab580f599cdf0537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Wed, 22 Dec 2021 03:27:16 +0100 Subject: [PATCH 474/549] bang_bang: respect set cool- and heat-only modes (#2926) --- esphome/components/bang_bang/bang_bang_climate.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index 5645f46f1c..4a95f8c339 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -80,21 +80,23 @@ void BangBangClimate::compute_state_() { climate::ClimateAction target_action; if (too_cold) { - // too cold -> enable heating if possible, else idle - if (this->supports_heat_) + // too cold -> enable heating if possible and enabled, else idle + if (this->supports_heat_ && + (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_HEAT)) target_action = climate::CLIMATE_ACTION_HEATING; else target_action = climate::CLIMATE_ACTION_IDLE; } else if (too_hot) { - // too hot -> enable cooling if possible, else idle - if (this->supports_cool_) + // too hot -> enable cooling if possible and enabled, else idle + if (this->supports_cool_ && + (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_COOL)) target_action = climate::CLIMATE_ACTION_COOLING; else target_action = climate::CLIMATE_ACTION_IDLE; } else { // neither too hot nor too cold -> in range - if (this->supports_cool_ && this->supports_heat_) { - // if supports both ends, go to idle action + if (this->supports_cool_ && this->supports_heat_ && this->mode == climate::CLIMATE_MODE_HEAT_COOL) { + // if supports both ends and both cooling and heating enabled, go to idle action target_action = climate::CLIMATE_ACTION_IDLE; } else { // else use current mode and don't change (hysteresis) From 99bd808ebe22a49541eb77eb836df2fac1d01ba0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 22 Dec 2021 15:27:34 +1300 Subject: [PATCH 475/549] Update curl package version in docker (#2939) --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 62a64c851d..e249689bd4 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -27,7 +27,7 @@ RUN \ python3-cryptography=3.3.2-1 \ iputils-ping=3:20210202-1 \ git=1:2.30.2-1 \ - curl=7.74.0-1.3+b1 \ + curl=7.74.0-1.3+deb11u1 \ && rm -rf \ /tmp/* \ /var/{cache,log}/* \ From e152f128c8ed2975aa410ccde0ab85df3d3d8f4e Mon Sep 17 00:00:00 2001 From: George Date: Wed, 22 Dec 2021 13:35:01 +1100 Subject: [PATCH 476/549] Change HDC1080 init instruction failure from error to warning (#2927) Co-authored-by: Oxan van Leeuwen --- esphome/components/hdc1080/hdc1080.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/hdc1080/hdc1080.cpp b/esphome/components/hdc1080/hdc1080.cpp index 60e8943e67..7186578a22 100644 --- a/esphome/components/hdc1080/hdc1080.cpp +++ b/esphome/components/hdc1080/hdc1080.cpp @@ -21,7 +21,9 @@ void HDC1080Component::setup() { }; if (!this->write_bytes(HDC1080_CMD_CONFIGURATION, data, 2)) { - this->mark_failed(); + // as instruction is same as powerup defaults (for now), interpret as warning if this fails + ESP_LOGW(TAG, "HDC1080 initial config instruction error"); + this->status_set_warning(); return; } } From cc5947467f742a05c0b85823cf5ef29ba2bba59b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 22 Dec 2021 20:08:54 +1300 Subject: [PATCH 477/549] Require arduino in webserver for better validation (#2941) --- esphome/components/web_server/__init__.py | 57 +++++++++++++---------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index d9ff84d501..62d5ec6f14 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -22,31 +22,38 @@ AUTO_LOAD = ["json", "web_server_base"] web_server_ns = cg.esphome_ns.namespace("web_server") WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller) -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(WebServer), - cv.Optional(CONF_PORT, default=80): cv.port, - cv.Optional( - CONF_CSS_URL, default="https://esphome.io/_static/webserver-v1.min.css" - ): cv.string, - cv.Optional(CONF_CSS_INCLUDE): cv.file_, - cv.Optional( - CONF_JS_URL, default="https://esphome.io/_static/webserver-v1.min.js" - ): cv.string, - cv.Optional(CONF_JS_INCLUDE): cv.file_, - cv.Optional(CONF_AUTH): cv.Schema( - { - cv.Required(CONF_USERNAME): cv.All(cv.string_strict, cv.Length(min=1)), - cv.Required(CONF_PASSWORD): cv.All(cv.string_strict, cv.Length(min=1)), - } - ), - cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( - web_server_base.WebServerBase - ), - cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, - cv.Optional(CONF_OTA, default=True): cv.boolean, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(WebServer), + cv.Optional(CONF_PORT, default=80): cv.port, + cv.Optional( + CONF_CSS_URL, default="https://esphome.io/_static/webserver-v1.min.css" + ): cv.string, + cv.Optional(CONF_CSS_INCLUDE): cv.file_, + cv.Optional( + CONF_JS_URL, default="https://esphome.io/_static/webserver-v1.min.js" + ): cv.string, + cv.Optional(CONF_JS_INCLUDE): cv.file_, + cv.Optional(CONF_AUTH): cv.Schema( + { + cv.Required(CONF_USERNAME): cv.All( + cv.string_strict, cv.Length(min=1) + ), + cv.Required(CONF_PASSWORD): cv.All( + cv.string_strict, cv.Length(min=1) + ), + } + ), + cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( + web_server_base.WebServerBase + ), + cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, + cv.Optional(CONF_OTA, default=True): cv.boolean, + }, + ).extend(cv.COMPONENT_SCHEMA), + cv.only_with_arduino, +) @coroutine_with_priority(40.0) From 79d73d8f8bc12c20cf839c346645162a6d2785c9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 22 Dec 2021 20:49:04 +1300 Subject: [PATCH 478/549] Add option to load docker image when building (#2938) --- docker/build.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/build.py b/docker/build.py index 1157d8287a..d5926ae3d4 100755 --- a/docker/build.py +++ b/docker/build.py @@ -32,6 +32,7 @@ parser.add_argument("--dry-run", action="store_true", help="Don't run any comman 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", action="store_true") +build_parser.add_argument("--load", help="Load the docker image locally", action="store_true") manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images") @@ -132,6 +133,8 @@ def main(): cmd += ["--tag", img] if args.push: cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"] + if args.load: + cmd += ["--load"] run_command(*cmd, ".") elif args.command == "manifest": From f48de6dd436289d9280b73b739b423c11f79e7d0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 22 Dec 2021 23:02:58 +1300 Subject: [PATCH 479/549] Disable nightly dev build (#2943) --- .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 d6895becc0..49dc9af5d1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,8 +4,8 @@ on: workflow_dispatch: release: types: [published] - schedule: - - cron: "0 2 * * *" + # schedule: + # - cron: "0 2 * * *" permissions: contents: read From b7aad39dafdda2ab728d20de50e272f4f18ab59f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 23 Dec 2021 08:31:56 +1300 Subject: [PATCH 480/549] Only allow internal pins for dht sensor (#2940) --- esphome/components/dht/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/dht/sensor.py b/esphome/components/dht/sensor.py index 1334f0270c..cd1886728e 100644 --- a/esphome/components/dht/sensor.py +++ b/esphome/components/dht/sensor.py @@ -33,7 +33,7 @@ DHT = dht_ns.class_("DHT", cg.PollingComponent) CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(DHT), - cv.Required(CONF_PIN): pins.gpio_input_pin_schema, + cv.Required(CONF_PIN): pins.internal_gpio_input_pin_schema, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, From 7927b5f6245db835b2edd212808df0f749022f8d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 23 Dec 2021 08:43:17 +1300 Subject: [PATCH 481/549] Workaround installing as editable package not working (#2936) --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index e249689bd4..f36239797b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -64,7 +64,7 @@ RUN \ # Copy esphome and install COPY . /esphome -RUN pip3 install --no-cache-dir -e /esphome +RUN pip3 install --no-cache-dir /esphome # Settings for dashboard ENV USERNAME="" PASSWORD="" From 72c6bfaa50e54f58e5dae494bc71040281d4d0a4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 23 Dec 2021 09:38:43 +1300 Subject: [PATCH 482/549] Revert "Disable nightly dev build" (#2944) --- .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 49dc9af5d1..d6895becc0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,8 +4,8 @@ on: workflow_dispatch: release: types: [published] - # schedule: - # - cron: "0 2 * * *" + schedule: + - cron: "0 2 * * *" permissions: contents: read From c6956527d1618110857af88885c7198881581f0d Mon Sep 17 00:00:00 2001 From: Daniel Hyles Date: Tue, 28 Dec 2021 07:32:17 +1100 Subject: [PATCH 483/549] Remove Content-Length header from camera snapshot response (#2860) * Update camera_web_server.cpp Removed the duplicated CONTENT_LENGTH header * Update camera_web_server.cpp * Update camera_web_server.cpp --- .../components/esp32_camera_web_server/camera_web_server.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.cpp b/esphome/components/esp32_camera_web_server/camera_web_server.cpp index 653a274bf4..f5ab0c7151 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.cpp +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp @@ -233,9 +233,6 @@ esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) { httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg"); - if (res == ESP_OK) { - res = httpd_resp_set_hdr(req, CONTENT_LENGTH, esphome::to_string(image->get_data_length()).c_str()); - } if (res == ESP_OK) { res = httpd_resp_send(req, (const char *) image->get_data_buffer(), image->get_data_length()); } From cb0677cafef87ff6040abcda6a68afbaa12a3e98 Mon Sep 17 00:00:00 2001 From: marsjan155 Date: Wed, 29 Dec 2021 22:34:30 +0100 Subject: [PATCH 484/549] ST7920 ESP32 fix (#2962) Co-authored-by: Marcin Depa --- esphome/components/st7920/st7920.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/st7920/st7920.h b/esphome/components/st7920/st7920.h index d0258d922c..5f32e7ff23 100644 --- a/esphome/components/st7920/st7920.h +++ b/esphome/components/st7920/st7920.h @@ -14,7 +14,7 @@ using st7920_writer_t = std::function; class ST7920 : public PollingComponent, public display::DisplayBuffer, public spi::SPIDevice { + spi::DATA_RATE_200KHZ> { public: void set_writer(st7920_writer_t &&writer) { this->writer_local_ = writer; } void set_height(uint16_t height) { this->height_ = height; } From f859b346a61874252672b6543b84b651c993c5ee Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 30 Dec 2021 10:42:22 +1300 Subject: [PATCH 485/549] Remove -e for hassio images (#2964) --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index f36239797b..25f2cf85d2 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -112,7 +112,7 @@ RUN \ # Copy esphome and install COPY . /esphome -RUN pip3 install --no-cache-dir -e /esphome +RUN pip3 install --no-cache-dir /esphome # Labels LABEL \ From 2cf36bdb46a86c46caca80a50a61ad7a588557d1 Mon Sep 17 00:00:00 2001 From: Sebastian Raff Date: Thu, 30 Dec 2021 04:05:31 +0100 Subject: [PATCH 486/549] Fix switch log state if inverted (#2960) --- esphome/components/switch/switch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index e4d20719e1..b9b99b4147 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -34,7 +34,7 @@ void Switch::publish_state(bool state) { this->state = state != this->inverted_; this->rtc_.save(&this->state); - ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), ONOFF(state)); + ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), ONOFF(this->state)); this->state_callback_.call(this->state); } bool Switch::assumed_state() { return false; } From 07ff3a853fc79ba6ff98ae9c99c91042486b5e77 Mon Sep 17 00:00:00 2001 From: arunderwood Date: Fri, 31 Dec 2021 02:11:28 -0500 Subject: [PATCH 487/549] Add pin aliases for featheresp32-s2 (#2970) --- esphome/components/esp32/boards.py | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/esphome/components/esp32/boards.py b/esphome/components/esp32/boards.py index 7f7bb2259f..56fd4932b4 100644 --- a/esphome/components/esp32/boards.py +++ b/esphome/components/esp32/boards.py @@ -261,6 +261,37 @@ ESP32_BOARD_PINS = { "SS": 33, "TX": 17, }, + "featheresp32-s2": { + "SDA": 3, + "SCL": 4, + "SS": 42, + "MOSI": 35, + "SCK": 36, + "MISO": 37, + "A0": 18, + "A1": 17, + "A10": 27, + "A11": 12, + "A12": 13, + "A13": 35, + "A2": 16, + "A3": 15, + "A4": 14, + "A5": 8, + "LED": 13, + "TX": 39, + "RX": 38, + "T5": 5, + "T8": 8, + "T9": 9, + "T10": 10, + "T11": 11, + "T12": 12, + "T13": 13, + "T14": 14, + "DAC1": 17, + "DAC2": 18, + }, "firebeetle32": {"LED": 2}, "fm-devkit": { "D0": 34, From 23edb18d7e76b5568a3d2af4a550449c0d5ab45f Mon Sep 17 00:00:00 2001 From: MrEditor97 Date: Fri, 31 Dec 2021 09:08:49 +0000 Subject: [PATCH 488/549] INA260 Current and Power Sensor support (#2788) --- CODEOWNERS | 1 + esphome/components/ina260/__init__.py | 0 esphome/components/ina260/ina260.cpp | 128 ++++++++++++++++++++++++++ esphome/components/ina260/ina260.h | 39 ++++++++ esphome/components/ina260/sensor.py | 71 ++++++++++++++ tests/test2.yaml | 9 ++ 6 files changed, 248 insertions(+) create mode 100644 esphome/components/ina260/__init__.py create mode 100644 esphome/components/ina260/ina260.cpp create mode 100644 esphome/components/ina260/ina260.h create mode 100644 esphome/components/ina260/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 452c560938..7dd73417b6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -75,6 +75,7 @@ esphome/components/homeassistant/* @OttoWinter esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/i2c/* @esphome/core esphome/components/improv_serial/* @esphome/core +esphome/components/ina260/* @MrEditor97 esphome/components/inkbird_ibsth1_mini/* @fkirill esphome/components/inkplate6/* @jesserockz esphome/components/integration/* @OttoWinter diff --git a/esphome/components/ina260/__init__.py b/esphome/components/ina260/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/ina260/ina260.cpp b/esphome/components/ina260/ina260.cpp new file mode 100644 index 0000000000..2f220e6a11 --- /dev/null +++ b/esphome/components/ina260/ina260.cpp @@ -0,0 +1,128 @@ +#include "ina260.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace ina260 { + +static const char *const TAG = "ina260"; + +// | A0 | A1 | Address | +// | GND | GND | 0x40 | +// | GND | V_S+ | 0x41 | +// | GND | SDA | 0x42 | +// | GND | SCL | 0x43 | +// | V_S+ | GND | 0x44 | +// | V_S+ | V_S+ | 0x45 | +// | V_S+ | SDA | 0x46 | +// | V_S+ | SCL | 0x47 | +// | SDA | GND | 0x48 | +// | SDA | V_S+ | 0x49 | +// | SDA | SDA | 0x4A | +// | SDA | SCL | 0x4B | +// | SCL | GND | 0x4C | +// | SCL | V_S+ | 0x4D | +// | SCL | SDA | 0x4E | +// | SCL | SCL | 0x4F | + +static const uint8_t INA260_REGISTER_CONFIG = 0x00; +static const uint8_t INA260_REGISTER_CURRENT = 0x01; +static const uint8_t INA260_REGISTER_BUS_VOLTAGE = 0x02; +static const uint8_t INA260_REGISTER_POWER = 0x03; +static const uint8_t INA260_REGISTER_MASK_ENABLE = 0x06; +static const uint8_t INA260_REGISTER_ALERT_LIMIT = 0x07; +static const uint8_t INA260_REGISTER_MANUFACTURE_ID = 0xFE; +static const uint8_t INA260_REGISTER_DEVICE_ID = 0xFF; + +void INA260Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up INA260..."); + + // Reset device on setup + if (!this->write_byte_16(INA260_REGISTER_CONFIG, 0x8000)) { + this->error_code_ = DEVICE_RESET_FAILED; + this->mark_failed(); + return; + } + + delay(2); + + this->read_byte_16(INA260_REGISTER_MANUFACTURE_ID, &this->manufacture_id_); + this->read_byte_16(INA260_REGISTER_DEVICE_ID, &this->device_id_); + + if (this->manufacture_id_ != (uint16_t) 0x5449 || this->device_id_ != (uint16_t) 0x2270) { + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + + if (!this->write_byte_16(INA260_REGISTER_CONFIG, (uint16_t) 0b0000001100000111)) { + this->error_code_ = FAILED_TO_UPDATE_CONFIGURATION; + this->mark_failed(); + return; + } +} + +void INA260Component::dump_config() { + ESP_LOGCONFIG(TAG, "INA260:"); + LOG_I2C_DEVICE(this); + LOG_UPDATE_INTERVAL(this); + + ESP_LOGCONFIG(TAG, " Manufacture ID: 0x%x", this->manufacture_id_); + ESP_LOGCONFIG(TAG, " Device ID: 0x%x", this->device_id_); + + LOG_SENSOR(" ", "Bus Voltage", this->bus_voltage_sensor_); + LOG_SENSOR(" ", "Current", this->current_sensor_); + LOG_SENSOR(" ", "Power", this->power_sensor_); + + switch (this->error_code_) { + case COMMUNICATION_FAILED: + ESP_LOGE(TAG, "Connected device does not match a known INA260 sensor"); + break; + case DEVICE_RESET_FAILED: + ESP_LOGE(TAG, "Device reset failed - Is the device connected?"); + break; + case FAILED_TO_UPDATE_CONFIGURATION: + ESP_LOGE(TAG, "Failed to update device configuration"); + break; + case NONE: + default: + break; + } +} + +void INA260Component::update() { + if (this->bus_voltage_sensor_ != nullptr) { + uint16_t raw_bus_voltage; + if (!this->read_byte_16(INA260_REGISTER_BUS_VOLTAGE, &raw_bus_voltage)) { + this->status_set_warning(); + return; + } + float bus_voltage_v = int16_t(raw_bus_voltage) * 0.00125f; + this->bus_voltage_sensor_->publish_state(bus_voltage_v); + } + + if (this->current_sensor_ != nullptr) { + uint16_t raw_current; + if (!this->read_byte_16(INA260_REGISTER_CURRENT, &raw_current)) { + this->status_set_warning(); + return; + } + float current_a = int16_t(raw_current) * 0.00125f; + this->current_sensor_->publish_state(current_a); + } + + if (this->power_sensor_ != nullptr) { + uint16_t raw_power; + if (!this->read_byte_16(INA260_REGISTER_POWER, &raw_power)) { + this->status_set_warning(); + return; + } + float power_w = ((int16_t(raw_power) * 10.0f) / 1000.0f); + this->power_sensor_->publish_state(power_w); + } + + this->status_clear_warning(); +} + +} // namespace ina260 +} // namespace esphome diff --git a/esphome/components/ina260/ina260.h b/esphome/components/ina260/ina260.h new file mode 100644 index 0000000000..8bad1cba6d --- /dev/null +++ b/esphome/components/ina260/ina260.h @@ -0,0 +1,39 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace ina260 { + +class INA260Component : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + void update() override; + + float get_setup_priority() const override { return setup_priority::DATA; } + + void set_bus_voltage_sensor(sensor::Sensor *bus_voltage_sensor) { this->bus_voltage_sensor_ = bus_voltage_sensor; } + void set_current_sensor(sensor::Sensor *current_sensor) { this->current_sensor_ = current_sensor; } + void set_power_sensor(sensor::Sensor *power_sensor) { this->power_sensor_ = power_sensor; } + + protected: + uint16_t manufacture_id_{0}; + uint16_t device_id_{0}; + + sensor::Sensor *bus_voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + + enum ErrorCode { + NONE, + COMMUNICATION_FAILED, + DEVICE_RESET_FAILED, + FAILED_TO_UPDATE_CONFIGURATION, + } error_code_{NONE}; +}; + +} // namespace ina260 +} // namespace esphome diff --git a/esphome/components/ina260/sensor.py b/esphome/components/ina260/sensor.py new file mode 100644 index 0000000000..048e713afa --- /dev/null +++ b/esphome/components/ina260/sensor.py @@ -0,0 +1,71 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_BUS_VOLTAGE, + CONF_CURRENT, + CONF_POWER, + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, +) + +DEPENDENCIES = ["i2c"] +CODEOWNERS = ["@MrEditor97"] + +ina260_ns = cg.esphome_ns.namespace("ina260") +INA260Component = ina260_ns.class_( + "INA260Component", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(INA260Component), + cv.Optional(CONF_BUS_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x40)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if CONF_BUS_VOLTAGE in config: + sens = await sensor.new_sensor(config[CONF_BUS_VOLTAGE]) + cg.add(var.set_bus_voltage_sensor(sens)) + + if CONF_CURRENT in config: + sens = await sensor.new_sensor(config[CONF_CURRENT]) + cg.add(var.set_current_sensor(sens)) + + if CONF_POWER in config: + sens = await sensor.new_sensor(config[CONF_POWER]) + cg.add(var.set_power_sensor(sens)) diff --git a/tests/test2.yaml b/tests/test2.yaml index 67b819a4d3..7920bf3fe3 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -307,6 +307,15 @@ sensor: name: "Wave Mini Pressure" tvoc: name: "Wave Mini VOC" + - platform: ina260 + address: 0x40 + current: + name: "INA260 Current" + power: + name: "INA260 Power" + bus_voltage: + name: "INA260 Voltage" + update_interval: 60s time: - platform: homeassistant From 33f17f75a0d03c6a550597fd902479779757346e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 1 Jan 2022 22:31:43 +1300 Subject: [PATCH 489/549] Upgrade ArduinoJson to 6.18.5 and migrate code (#2844) --- esphome/codegen.py | 3 +- esphome/components/http_request/__init__.py | 2 +- .../components/http_request/http_request.h | 8 +- esphome/components/json/__init__.py | 3 +- esphome/components/json/json_util.cpp | 131 +++++------------- esphome/components/json/json_util.h | 52 +------ .../components/light/light_json_schema.cpp | 10 +- esphome/components/light/light_json_schema.h | 6 +- esphome/components/mqtt/__init__.py | 6 +- esphome/components/mqtt/custom_mqtt_device.h | 18 ++- .../components/mqtt/mqtt_binary_sensor.cpp | 2 +- esphome/components/mqtt/mqtt_binary_sensor.h | 2 +- esphome/components/mqtt/mqtt_button.cpp | 2 +- esphome/components/mqtt/mqtt_button.h | 2 +- esphome/components/mqtt/mqtt_client.cpp | 8 +- esphome/components/mqtt/mqtt_client.h | 12 +- esphome/components/mqtt/mqtt_climate.cpp | 8 +- esphome/components/mqtt/mqtt_climate.h | 2 +- esphome/components/mqtt/mqtt_component.cpp | 4 +- esphome/components/mqtt/mqtt_component.h | 2 +- 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 | 10 +- 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 | 4 +- 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 +- esphome/components/web_server/web_server.cpp | 18 +-- esphome/cpp_types.py | 3 +- platformio.ini | 8 +- tests/unit_tests/test_codegen.py | 3 +- 40 files changed, 126 insertions(+), 231 deletions(-) diff --git a/esphome/codegen.py b/esphome/codegen.py index 5e1e934e58..3ea3df8706 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -75,8 +75,7 @@ from esphome.cpp_types import ( # noqa optional, arduino_json_ns, JsonObject, - JsonObjectRef, - JsonObjectConstRef, + JsonObjectConst, Controller, GPIOPin, InternalGPIOPin, diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index 774d6a0f91..e044e5fece 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -172,7 +172,7 @@ async def http_request_action_to_code(config, action_id, template_arg, args): if CONF_JSON in config: json_ = config[CONF_JSON] if isinstance(json_, Lambda): - args_ = args + [(cg.JsonObjectRef, "root")] + args_ = args + [(cg.JsonObject, "root")] lambda_ = await cg.process_lambda(json_, args_, return_type=cg.void) cg.add(var.set_json(lambda_)) else: diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index 9cc027b58d..a38bdf9c95 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -78,7 +78,7 @@ template class HttpRequestSendAction : public Action { void add_json(const char *key, TemplatableValue value) { this->json_.insert({key, value}); } - void set_json(std::function json_func) { this->json_func_ = json_func; } + void set_json(std::function json_func) { this->json_func_ = json_func; } void register_response_trigger(HttpRequestResponseTrigger *trigger) { this->response_triggers_.push_back(trigger); } @@ -118,17 +118,17 @@ template class HttpRequestSendAction : public Action { } protected: - void encode_json_(Ts... x, JsonObject &root) { + void encode_json_(Ts... x, JsonObject root) { for (const auto &item : this->json_) { auto val = item.second; root[item.first] = val.value(x...); } } - void encode_json_func_(Ts... x, JsonObject &root) { this->json_func_(x..., root); } + void encode_json_func_(Ts... x, JsonObject root) { this->json_func_(x..., root); } HttpRequestComponent *parent_; std::map> headers_{}; std::map> json_{}; - std::function json_func_{nullptr}; + std::function json_func_{nullptr}; std::vector response_triggers_; }; diff --git a/esphome/components/json/__init__.py b/esphome/components/json/__init__.py index fda0a552f1..6a0e4c50d2 100644 --- a/esphome/components/json/__init__.py +++ b/esphome/components/json/__init__.py @@ -7,12 +7,11 @@ 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): - cg.add_library("ottowinter/ArduinoJson-esphomelib", "5.13.3") + cg.add_library("bblanchon/ArduinoJson", "6.18.5") cg.add_define("USE_JSON") cg.add_global(json_ns.using) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 12c5beb73f..6a4eba78ab 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -1,8 +1,10 @@ -#ifdef USE_ARDUINO - #include "json_util.h" #include "esphome/core/log.h" +#ifdef USE_ESP8266 +#include +#endif + namespace esphome { namespace json { @@ -10,110 +12,49 @@ static const char *const TAG = "json"; static std::vector global_json_build_buffer; // NOLINT -const char *build_json(const json_build_t &f, size_t *length) { - global_json_buffer.clear(); - JsonObject &root = global_json_buffer.createObject(); +std::string build_json(const json_build_t &f) { + // Here we are allocating as much heap memory as available minus 2kb to be safe + // as we can not have a true dynamic sized document. + // The excess memory is freed below with `shrinkToFit()` +#ifdef USE_ESP8266 + const size_t free_heap = ESP.getMaxFreeBlockSize() - 2048; // NOLINT(readability-static-accessed-through-instance) +#elif defined(USE_ESP32) + const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT) - 2048; +#endif + DynamicJsonDocument json_document(free_heap); + JsonObject root = json_document.to(); f(root); + json_document.shrinkToFit(); - // The Json buffer size gives us a good estimate for the required size. - // Usually, it's a bit larger than the actual required string size - // | JSON Buffer Size | String Size | - // Discovery | 388 | 351 | - // Discovery | 372 | 356 | - // Discovery | 336 | 311 | - // Discovery | 408 | 393 | - 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.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.data(); + std::string output; + serializeJson(json_document, output); + return output; } -void parse_json(const std::string &data, const json_parse_t &f) { - global_json_buffer.clear(); - JsonObject &root = global_json_buffer.parseObject(data); - if (!root.success()) { +void parse_json(const std::string &data, const json_parse_t &f) { + // Here we are allocating as much heap memory as available minus 2kb to be safe + // as we can not have a true dynamic sized document. + // The excess memory is freed below with `shrinkToFit()` +#ifdef USE_ESP8266 + const size_t free_heap = ESP.getMaxFreeBlockSize() - 2048; // NOLINT(readability-static-accessed-through-instance) +#elif defined(USE_ESP32) + const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT) - 2048; +#endif + + DynamicJsonDocument json_document(free_heap); + DeserializationError err = deserializeJson(json_document, data); + json_document.shrinkToFit(); + + JsonObject root = json_document.as(); + + if (err) { ESP_LOGW(TAG, "Parsing JSON failed."); return; } f(root); } -std::string build_json(const json_build_t &f) { - size_t len; - const char *c_str = build_json(f, &len); - return std::string(c_str, len); -} - -VectorJsonBuffer::String::String(VectorJsonBuffer *parent) : parent_(parent), start_(parent->size_) {} -void VectorJsonBuffer::String::append(char c) const { - char *last = static_cast(this->parent_->do_alloc(1)); - *last = c; -} -const char *VectorJsonBuffer::String::c_str() const { - this->append('\0'); - return &this->parent_->buffer_[this->start_]; -} -void VectorJsonBuffer::clear() { - for (char *block : this->free_blocks_) - free(block); // NOLINT - - this->size_ = 0; - this->free_blocks_.clear(); -} -VectorJsonBuffer::String VectorJsonBuffer::startString() { return {this}; } // NOLINT -void *VectorJsonBuffer::alloc(size_t bytes) { - // Make sure memory addresses are aligned - uint32_t new_size = round_size_up(this->size_); - this->resize(new_size); - return this->do_alloc(bytes); -} -void *VectorJsonBuffer::do_alloc(size_t bytes) { // NOLINT - const uint32_t begin = this->size_; - this->resize(begin + bytes); - return &this->buffer_[begin]; -} -void VectorJsonBuffer::resize(size_t size) { // NOLINT - if (size <= this->size_) { - this->size_ = size; - return; - } - - this->reserve(size); - this->size_ = size; -} -void VectorJsonBuffer::reserve(size_t size) { // NOLINT - if (size <= this->capacity_) - return; - - uint32_t target_capacity = this->capacity_; - if (this->capacity_ == 0) { - // lazily initialize with a reasonable size - target_capacity = JSON_OBJECT_SIZE(16); - } - while (target_capacity < size) - target_capacity *= 2; - - char *old_buffer = this->buffer_; - 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_); - } - this->capacity_ = target_capacity; -} - -size_t VectorJsonBuffer::size() const { return this->size_; } - -VectorJsonBuffer global_json_buffer; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace json } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/json/json_util.h b/esphome/components/json/json_util.h index 577510e63a..57fe6107d8 100644 --- a/esphome/components/json/json_util.h +++ b/esphome/components/json/json_util.h @@ -1,68 +1,28 @@ #pragma once -#ifdef USE_ARDUINO - #include #include "esphome/core/helpers.h" + +#undef ARDUINOJSON_ENABLE_STD_STRING +#define ARDUINOJSON_ENABLE_STD_STRING 1 // NOLINT + #include namespace esphome { namespace json { /// Callback function typedef for parsing JsonObjects. -using json_parse_t = std::function; +using json_parse_t = std::function; /// Callback function typedef for building JsonObjects. -using json_build_t = std::function; +using json_build_t = std::function; /// Build a JSON string with the provided json build function. -const char *build_json(const json_build_t &f, size_t *length); - std::string build_json(const json_build_t &f); /// Parse a JSON string and run the provided json parse function if it's valid. void parse_json(const std::string &data, const json_parse_t &f); -class VectorJsonBuffer : public ArduinoJson::Internals::JsonBufferBase { - public: - class String { - public: - String(VectorJsonBuffer *parent); - - void append(char c) const; - - const char *c_str() const; - - protected: - VectorJsonBuffer *parent_; - uint32_t start_; - }; - - void *alloc(size_t bytes) override; - - size_t size() const; - - void clear(); - - String startString(); // NOLINT - - protected: - void *do_alloc(size_t bytes); // NOLINT - - void resize(size_t size); // NOLINT - - void reserve(size_t size); // NOLINT - - char *buffer_{nullptr}; - size_t size_{0}; - size_t capacity_{0}; - std::vector free_blocks_; -}; - -extern VectorJsonBuffer global_json_buffer; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - } // namespace json } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index 2e07d91046..c126859076 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -8,7 +8,7 @@ namespace light { // See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema -void LightJSONSchema::dump_json(LightState &state, JsonObject &root) { +void LightJSONSchema::dump_json(LightState &state, JsonObject root) { if (state.supports_effects()) root["effect"] = state.get_effect_name(); @@ -52,7 +52,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject &root) { if (values.get_color_mode() & ColorCapability::BRIGHTNESS) root["brightness"] = uint8_t(values.get_brightness() * 255); - JsonObject &color = root.createNestedObject("color"); + JsonObject color = root.createNestedObject("color"); if (values.get_color_mode() & ColorCapability::RGB) { color["r"] = uint8_t(values.get_color_brightness() * values.get_red() * 255); color["g"] = uint8_t(values.get_color_brightness() * values.get_green() * 255); @@ -72,7 +72,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject &root) { } } -void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject &root) { +void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject root) { if (root.containsKey("state")) { auto val = parse_on_off(root["state"]); switch (val) { @@ -95,7 +95,7 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO } if (root.containsKey("color")) { - JsonObject &color = root["color"]; + JsonObject color = root["color"]; // HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness. float max_rgb = 0.0f; if (color.containsKey("r")) { @@ -140,7 +140,7 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO } } -void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject &root) { +void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject root) { LightJSONSchema::parse_color_json(state, call, root); if (root.containsKey("flash")) { diff --git a/esphome/components/light/light_json_schema.h b/esphome/components/light/light_json_schema.h index 09a372f11c..c92dd7b655 100644 --- a/esphome/components/light/light_json_schema.h +++ b/esphome/components/light/light_json_schema.h @@ -14,12 +14,12 @@ namespace light { class LightJSONSchema { public: /// Dump the state of a light as JSON. - static void dump_json(LightState &state, JsonObject &root); + static void dump_json(LightState &state, JsonObject root); /// Parse the JSON state of a light to a LightCall. - static void parse_json(LightState &state, LightCall &call, JsonObject &root); + static void parse_json(LightState &state, LightCall &call, JsonObject root); protected: - static void parse_color_json(LightState &state, LightCall &call, JsonObject &root); + static void parse_color_json(LightState &state, LightCall &call, JsonObject root); }; } // namespace light diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index d677d54d23..755b0c685c 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -80,7 +80,7 @@ MQTTMessageTrigger = mqtt_ns.class_( "MQTTMessageTrigger", automation.Trigger.template(cg.std_string), cg.Component ) MQTTJsonMessageTrigger = mqtt_ns.class_( - "MQTTJsonMessageTrigger", automation.Trigger.template(cg.JsonObjectConstRef) + "MQTTJsonMessageTrigger", automation.Trigger.template(cg.JsonObjectConst) ) MQTTComponent = mqtt_ns.class_("MQTTComponent", cg.Component) MQTTConnectedCondition = mqtt_ns.class_("MQTTConnectedCondition", Condition) @@ -311,7 +311,7 @@ async def to_code(config): for conf in config.get(CONF_ON_JSON_MESSAGE, []): trig = cg.new_Pvariable(conf[CONF_TRIGGER_ID], conf[CONF_TOPIC], conf[CONF_QOS]) - await automation.build_automation(trig, [(cg.JsonObjectConstRef, "x")], conf) + await automation.build_automation(trig, [(cg.JsonObjectConst, "x")], conf) MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema( @@ -363,7 +363,7 @@ async def mqtt_publish_json_action_to_code(config, action_id, template_arg, args template_ = await cg.templatable(config[CONF_TOPIC], args, cg.std_string) cg.add(var.set_topic(template_)) - args_ = args + [(cg.JsonObjectRef, "root")] + args_ = args + [(cg.JsonObject, "root")] lambda_ = await cg.process_lambda(config[CONF_PAYLOAD], args_, return_type=cg.void) cg.add(var.set_payload(lambda_)) template_ = await cg.templatable(config[CONF_QOS], args, cg.uint8) diff --git a/esphome/components/mqtt/custom_mqtt_device.h b/esphome/components/mqtt/custom_mqtt_device.h index 9795d69304..0852a17cf1 100644 --- a/esphome/components/mqtt/custom_mqtt_device.h +++ b/esphome/components/mqtt/custom_mqtt_device.h @@ -74,9 +74,9 @@ class CustomMQTTDevice { * } * * // topic parameter can be remove if not needed: - * // e.g.: void on_json_message(JsonObject &payload) { + * // e.g.: void on_json_message(JsonObject payload) { * - * void on_json_message(const std::string &topic, JsonObject &payload) { + * void on_json_message(const std::string &topic, JsonObject payload) { * // do something with topic and payload * if (payload["number"] == 1) { * digitalWrite(5, HIGH); @@ -93,11 +93,9 @@ class CustomMQTTDevice { * @param qos The Quality of Service to subscribe with. Defaults to 0. */ template - void subscribe_json(const std::string &topic, void (T::*callback)(const std::string &, JsonObject &), - uint8_t qos = 0); + void subscribe_json(const std::string &topic, void (T::*callback)(const std::string &, JsonObject), uint8_t qos = 0); - template - void subscribe_json(const std::string &topic, void (T::*callback)(JsonObject &), uint8_t qos = 0); + template void subscribe_json(const std::string &topic, void (T::*callback)(JsonObject), uint8_t qos = 0); /** Publish an MQTT message with the given payload and QoS and retain settings. * @@ -155,7 +153,7 @@ class CustomMQTTDevice { * * ```cpp * void in_some_method() { - * publish("the/topic", [=](JsonObject &root) { + * publish("the/topic", [=](JsonObject root) { * root["the_key"] = "Hello World!"; * }, 0, false); * } @@ -174,7 +172,7 @@ class CustomMQTTDevice { * * ```cpp * void in_some_method() { - * publish("the/topic", [=](JsonObject &root) { + * publish("the/topic", [=](JsonObject root) { * root["the_key"] = "Hello World!"; * }); * } @@ -205,13 +203,13 @@ template void CustomMQTTDevice::subscribe(const std::string &topic, global_mqtt_client->subscribe(topic, f, qos); } template -void CustomMQTTDevice::subscribe_json(const std::string &topic, void (T::*callback)(const std::string &, JsonObject &), +void CustomMQTTDevice::subscribe_json(const std::string &topic, void (T::*callback)(const std::string &, JsonObject), uint8_t qos) { auto f = std::bind(callback, (T *) this, std::placeholders::_1, std::placeholders::_2); global_mqtt_client->subscribe_json(topic, f, qos); } template -void CustomMQTTDevice::subscribe_json(const std::string &topic, void (T::*callback)(JsonObject &), uint8_t qos) { +void CustomMQTTDevice::subscribe_json(const std::string &topic, void (T::*callback)(JsonObject), uint8_t qos) { auto f = std::bind(callback, (T *) this, std::placeholders::_2); global_mqtt_client->subscribe_json(topic, f, qos); } diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 0a161f89a1..0bf3b751fd 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -29,7 +29,7 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor } } -void MQTTBinarySensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { +void MQTTBinarySensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { if (!this->binary_sensor_->get_device_class().empty()) root[MQTT_DEVICE_CLASS] = this->binary_sensor_->get_device_class(); if (this->binary_sensor_->is_status_binary_sensor()) diff --git a/esphome/components/mqtt/mqtt_binary_sensor.h b/esphome/components/mqtt/mqtt_binary_sensor.h index 0efb490367..f6579fcd19 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.h +++ b/esphome/components/mqtt/mqtt_binary_sensor.h @@ -22,7 +22,7 @@ class MQTTBinarySensorComponent : public mqtt::MQTTComponent { void dump_config() override; - void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; void set_is_status(bool status); diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index 5f3aaa1dd9..52df63093a 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -30,7 +30,7 @@ void MQTTButtonComponent::dump_config() { LOG_MQTT_COMPONENT(true, true); } -void MQTTButtonComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { +void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { config.state_topic = false; if (!this->button_->get_device_class().empty()) root[MQTT_DEVICE_CLASS] = this->button_->get_device_class(); diff --git a/esphome/components/mqtt/mqtt_button.h b/esphome/components/mqtt/mqtt_button.h index 66e4b2609f..42389caecc 100644 --- a/esphome/components/mqtt/mqtt_button.h +++ b/esphome/components/mqtt/mqtt_button.h @@ -23,7 +23,7 @@ class MQTTButtonComponent : public mqtt::MQTTComponent { /// Buttons do not send a state so just return true. bool send_initial_state() override { return true; } - void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; protected: /// "button" component type. diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 43c49e9f7f..de25c5b2e3 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -1,4 +1,5 @@ #include "mqtt_client.h" +#define USE_MQTT #ifdef USE_MQTT @@ -346,7 +347,7 @@ void MQTTClientComponent::subscribe(const std::string &topic, mqtt_callback_t ca void MQTTClientComponent::subscribe_json(const std::string &topic, const mqtt_json_callback_t &callback, uint8_t qos) { auto f = [callback](const std::string &topic, const std::string &payload) { - json::parse_json(payload, [topic, callback](JsonObject &root) { callback(topic, root); }); + json::parse_json(payload, [topic, callback](JsonObject root) { callback(topic, root); }); }; MQTTSubscription subscription{ .topic = topic, @@ -416,9 +417,8 @@ bool MQTTClientComponent::publish(const MQTTMessage &message) { } bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos, bool retain) { - size_t len; - const char *message = json::build_json(f, &len); - return this->publish(topic, message, len, qos, retain); + std::string message = json::build_json(f); + return this->publish(topic, message, qos, retain); } /** Check if the message topic matches the given subscription topic diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index d6194da794..a6a7025c6f 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -20,7 +20,7 @@ namespace mqtt { * First parameter is the topic, the second one is the payload. */ using mqtt_callback_t = std::function; -using mqtt_json_callback_t = std::function; +using mqtt_json_callback_t = std::function; /// internal struct for MQTT messages. struct MQTTMessage { @@ -306,11 +306,11 @@ class MQTTMessageTrigger : public Trigger, public Component { optional payload_; }; -class MQTTJsonMessageTrigger : public Trigger { +class MQTTJsonMessageTrigger : public Trigger { public: explicit MQTTJsonMessageTrigger(const std::string &topic, uint8_t qos) { global_mqtt_client->subscribe_json( - topic, [this](const std::string &topic, JsonObject &root) { this->trigger(root); }, qos); + topic, [this](const std::string &topic, JsonObject root) { this->trigger(root); }, qos); } }; @@ -338,7 +338,7 @@ template class MQTTPublishJsonAction : public Action { TEMPLATABLE_VALUE(uint8_t, qos) TEMPLATABLE_VALUE(bool, retain) - void set_payload(std::function payload) { this->payload_ = payload; } + void set_payload(std::function payload) { this->payload_ = payload; } void play(Ts... x) override { auto f = std::bind(&MQTTPublishJsonAction::encode_, this, x..., std::placeholders::_1); @@ -349,8 +349,8 @@ template class MQTTPublishJsonAction : public Action { } protected: - void encode_(Ts... x, JsonObject &root) { this->payload_(x..., root); } - std::function payload_; + void encode_(Ts... x, JsonObject root) { this->payload_(x..., root); } + std::function payload_; MQTTClientComponent *parent_; }; diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index ebc708f444..f6ef3a5e8f 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -13,7 +13,7 @@ static const char *const TAG = "mqtt.climate"; using namespace esphome::climate; -void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { +void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { auto traits = this->device_->get_traits(); // current_temperature_topic if (traits.get_supports_current_temperature()) { @@ -25,7 +25,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC // mode_state_topic root[MQTT_MODE_STATE_TOPIC] = this->get_mode_state_topic(); // modes - JsonArray &modes = root.createNestedArray(MQTT_MODES); + JsonArray modes = root.createNestedArray(MQTT_MODES); // sort array for nice UI in HA if (traits.supports_mode(CLIMATE_MODE_AUTO)) modes.add("auto"); @@ -83,7 +83,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC // fan_mode_state_topic root[MQTT_FAN_MODE_STATE_TOPIC] = this->get_fan_mode_state_topic(); // fan_modes - JsonArray &fan_modes = root.createNestedArray("fan_modes"); + JsonArray fan_modes = root.createNestedArray("fan_modes"); if (traits.supports_fan_mode(CLIMATE_FAN_ON)) fan_modes.add("on"); if (traits.supports_fan_mode(CLIMATE_FAN_OFF)) @@ -112,7 +112,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC // swing_mode_state_topic root[MQTT_SWING_MODE_STATE_TOPIC] = this->get_swing_mode_state_topic(); // swing_modes - JsonArray &swing_modes = root.createNestedArray("swing_modes"); + JsonArray swing_modes = root.createNestedArray("swing_modes"); if (traits.supports_swing_mode(CLIMATE_SWING_OFF)) swing_modes.add("off"); if (traits.supports_swing_mode(CLIMATE_SWING_BOTH)) diff --git a/esphome/components/mqtt/mqtt_climate.h b/esphome/components/mqtt/mqtt_climate.h index 40ac4c18c1..ea3e2ab3fa 100644 --- a/esphome/components/mqtt/mqtt_climate.h +++ b/esphome/components/mqtt/mqtt_climate.h @@ -14,7 +14,7 @@ namespace mqtt { class MQTTClimateComponent : public mqtt::MQTTComponent { public: MQTTClimateComponent(climate::Climate *device); - void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; std::string component_type() const override; void setup() override; diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index bf9f5e34b8..62dbae3bcc 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -63,7 +63,7 @@ bool MQTTComponent::send_discovery_() { return global_mqtt_client->publish_json( this->get_discovery_topic_(discovery_info), - [this](JsonObject &root) { + [this](JsonObject root) { SendDiscoveryConfig config; config.state_topic = true; config.command_topic = true; @@ -127,7 +127,7 @@ bool MQTTComponent::send_discovery_() { } } - JsonObject &device_info = root.createNestedObject(MQTT_DEVICE); + JsonObject device_info = root.createNestedObject(MQTT_DEVICE); device_info[MQTT_DEVICE_IDENTIFIERS] = get_mac_address(); device_info[MQTT_DEVICE_NAME] = node_name; device_info[MQTT_DEVICE_SW_VERSION] = "esphome v" ESPHOME_VERSION " " + App.get_compilation_time(); diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 657ab7b608..e83523a712 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -70,7 +70,7 @@ class MQTTComponent : public Component { void call_dump_config() override; /// Send discovery info the Home Assistant, override this. - virtual void send_discovery(JsonObject &root, SendDiscoveryConfig &config) = 0; + virtual void send_discovery(JsonObject root, SendDiscoveryConfig &config) = 0; virtual bool send_initial_state() = 0; diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index 7e42abcd05..e5525bc0f7 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -63,7 +63,7 @@ void MQTTCoverComponent::dump_config() { ESP_LOGCONFIG(TAG, " Tilt Command Topic: '%s'", this->get_tilt_command_topic().c_str()); } } -void MQTTCoverComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { +void MQTTCoverComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { if (!this->cover_->get_device_class().empty()) root[MQTT_DEVICE_CLASS] = this->cover_->get_device_class(); diff --git a/esphome/components/mqtt/mqtt_cover.h b/esphome/components/mqtt/mqtt_cover.h index 149d46ac85..f3e6053d0b 100644 --- a/esphome/components/mqtt/mqtt_cover.h +++ b/esphome/components/mqtt/mqtt_cover.h @@ -16,7 +16,7 @@ class MQTTCoverComponent : public mqtt::MQTTComponent { explicit MQTTCoverComponent(cover::Cover *cover); void setup() override; - void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; MQTT_COMPONENT_CUSTOM_TOPIC(position, command) MQTT_COMPONENT_CUSTOM_TOPIC(position, state) diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index d58e3abc88..0f2eb6535f 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -120,7 +120,7 @@ void MQTTFanComponent::dump_config() { bool MQTTFanComponent::send_initial_state() { return this->publish_state(); } -void MQTTFanComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { +void MQTTFanComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { if (this->state_->get_traits().supports_oscillation()) { root[MQTT_OSCILLATION_COMMAND_TOPIC] = this->get_oscillation_command_topic(); root[MQTT_OSCILLATION_STATE_TOPIC] = this->get_oscillation_state_topic(); diff --git a/esphome/components/mqtt/mqtt_fan.h b/esphome/components/mqtt/mqtt_fan.h index a160d5366b..9d15a6cd0e 100644 --- a/esphome/components/mqtt/mqtt_fan.h +++ b/esphome/components/mqtt/mqtt_fan.h @@ -22,7 +22,7 @@ class MQTTFanComponent : public mqtt::MQTTComponent { MQTT_COMPONENT_CUSTOM_TOPIC(speed, command) MQTT_COMPONENT_CUSTOM_TOPIC(speed, state) - void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index 54204a9e7f..ee1cc36af7 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -18,7 +18,7 @@ std::string MQTTJSONLightComponent::component_type() const { return "light"; } const EntityBase *MQTTJSONLightComponent::get_entity() const { return this->state_; } void MQTTJSONLightComponent::setup() { - this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject &root) { + this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { LightCall call = this->state_->make_call(); LightJSONSchema::parse_json(*this->state_, call, root); call.perform(); @@ -32,16 +32,16 @@ MQTTJSONLightComponent::MQTTJSONLightComponent(LightState *state) : MQTTComponen bool MQTTJSONLightComponent::publish_state_() { return this->publish_json(this->get_state_topic_(), - [this](JsonObject &root) { LightJSONSchema::dump_json(*this->state_, root); }); + [this](JsonObject root) { LightJSONSchema::dump_json(*this->state_, root); }); } LightState *MQTTJSONLightComponent::get_state() const { return this->state_; } -void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { +void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { root["schema"] = "json"; auto traits = this->state_->get_traits(); root[MQTT_COLOR_MODE] = true; - JsonArray &color_modes = root.createNestedArray("supported_color_modes"); + JsonArray color_modes = root.createNestedArray("supported_color_modes"); if (traits.supports_color_mode(ColorMode::ON_OFF)) color_modes.add("onoff"); if (traits.supports_color_mode(ColorMode::BRIGHTNESS)) @@ -66,7 +66,7 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover if (this->state_->supports_effects()) { root["effect"] = true; - JsonArray &effect_list = root.createNestedArray(MQTT_EFFECT_LIST); + JsonArray effect_list = root.createNestedArray(MQTT_EFFECT_LIST); for (auto *effect : this->state_->get_effects()) effect_list.add(effect->get_name()); effect_list.add("None"); diff --git a/esphome/components/mqtt/mqtt_light.h b/esphome/components/mqtt/mqtt_light.h index 192cba39b6..3d1e770d4d 100644 --- a/esphome/components/mqtt/mqtt_light.h +++ b/esphome/components/mqtt/mqtt_light.h @@ -21,7 +21,7 @@ class MQTTJSONLightComponent : public mqtt::MQTTComponent { void dump_config() override; - void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 18e3a61417..73d37f7cd3 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -37,7 +37,7 @@ void MQTTNumberComponent::dump_config() { std::string MQTTNumberComponent::component_type() const { return "number"; } const EntityBase *MQTTNumberComponent::get_entity() const { return this->number_; } -void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { +void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { const auto &traits = number_->traits; // https://www.home-assistant.io/integrations/number.mqtt/ root[MQTT_MIN] = traits.get_min_value(); diff --git a/esphome/components/mqtt/mqtt_number.h b/esphome/components/mqtt/mqtt_number.h index 66622d7c29..10500c8333 100644 --- a/esphome/components/mqtt/mqtt_number.h +++ b/esphome/components/mqtt/mqtt_number.h @@ -25,7 +25,7 @@ class MQTTNumberComponent : public mqtt::MQTTComponent { void setup() override; void dump_config() override; - void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index b8371de00e..cb4c9c9052 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -32,10 +32,10 @@ void MQTTSelectComponent::dump_config() { std::string MQTTSelectComponent::component_type() const { return "select"; } const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_; } -void MQTTSelectComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { +void MQTTSelectComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { const auto &traits = select_->traits; // https://www.home-assistant.io/integrations/select.mqtt/ - JsonArray &options = root.createNestedArray(MQTT_OPTIONS); + JsonArray options = root.createNestedArray(MQTT_OPTIONS); for (const auto &option : traits.get_options()) options.add(option); diff --git a/esphome/components/mqtt/mqtt_select.h b/esphome/components/mqtt/mqtt_select.h index d77d0cf513..e0d8ac2417 100644 --- a/esphome/components/mqtt/mqtt_select.h +++ b/esphome/components/mqtt/mqtt_select.h @@ -25,7 +25,7 @@ class MQTTSelectComponent : public mqtt::MQTTComponent { void setup() override; void dump_config() override; - void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index dd6423e8f3..303aa0e753 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -42,7 +42,7 @@ uint32_t MQTTSensorComponent::get_expire_after() const { void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire_after_ = expire_after; } void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } -void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { +void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { if (!this->sensor_->get_device_class().empty()) root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); diff --git a/esphome/components/mqtt/mqtt_sensor.h b/esphome/components/mqtt/mqtt_sensor.h index 22609fdfef..adc201736a 100644 --- a/esphome/components/mqtt/mqtt_sensor.h +++ b/esphome/components/mqtt/mqtt_sensor.h @@ -27,7 +27,7 @@ class MQTTSensorComponent : public mqtt::MQTTComponent { /// Disable Home Assistant value expiry. void disable_expire_after(); - void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index edaa6e7859..2e91f8e502 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -44,7 +44,7 @@ void MQTTSwitchComponent::dump_config() { std::string MQTTSwitchComponent::component_type() const { return "switch"; } const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; } -void MQTTSwitchComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { +void MQTTSwitchComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { if (this->switch_->assumed_state()) root[MQTT_OPTIMISTIC] = true; } diff --git a/esphome/components/mqtt/mqtt_switch.h b/esphome/components/mqtt/mqtt_switch.h index a0a7a23220..c4d3f7164c 100644 --- a/esphome/components/mqtt/mqtt_switch.h +++ b/esphome/components/mqtt/mqtt_switch.h @@ -20,7 +20,7 @@ class MQTTSwitchComponent : public mqtt::MQTTComponent { void setup() override; void dump_config() override; - void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index 7b89915649..010364e221 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -12,7 +12,7 @@ static const char *const TAG = "mqtt.text_sensor"; using namespace esphome::text_sensor; MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : MQTTComponent(), sensor_(sensor) {} -void MQTTTextSensor::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { +void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { config.command_topic = false; } void MQTTTextSensor::setup() { diff --git a/esphome/components/mqtt/mqtt_text_sensor.h b/esphome/components/mqtt/mqtt_text_sensor.h index 83743245cc..fe53a6fefd 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.h +++ b/esphome/components/mqtt/mqtt_text_sensor.h @@ -15,7 +15,7 @@ class MQTTTextSensor : public mqtt::MQTTComponent { public: explicit MQTTTextSensor(text_sensor::TextSensor *sensor); - void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; void setup() override; diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 1e7696edfb..4cc77da256 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -316,7 +316,7 @@ void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlM request->send(404); } std::string WebServer::sensor_json(sensor::Sensor *obj, float value) { - return json::build_json([obj, value](JsonObject &root) { + return json::build_json([obj, value](JsonObject root) { root["id"] = "sensor-" + obj->get_object_id(); std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals()); if (!obj->get_unit_of_measurement().empty()) @@ -342,7 +342,7 @@ void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const request->send(404); } std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value) { - return json::build_json([obj, value](JsonObject &root) { + return json::build_json([obj, value](JsonObject root) { root["id"] = "text_sensor-" + obj->get_object_id(); root["state"] = value; root["value"] = value; @@ -355,7 +355,7 @@ void WebServer::on_switch_update(switch_::Switch *obj, bool state) { this->events_.send(this->switch_json(obj, state).c_str(), "state"); } std::string WebServer::switch_json(switch_::Switch *obj, bool value) { - return json::build_json([obj, value](JsonObject &root) { + return json::build_json([obj, value](JsonObject root) { root["id"] = "switch-" + obj->get_object_id(); root["state"] = value ? "ON" : "OFF"; root["value"] = value; @@ -410,7 +410,7 @@ void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool s this->events_.send(this->binary_sensor_json(obj, state).c_str(), "state"); } std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value) { - return json::build_json([obj, value](JsonObject &root) { + return json::build_json([obj, value](JsonObject root) { root["id"] = "binary_sensor-" + obj->get_object_id(); root["state"] = value ? "ON" : "OFF"; root["value"] = value; @@ -431,7 +431,7 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con #ifdef USE_FAN void WebServer::on_fan_update(fan::FanState *obj) { this->events_.send(this->fan_json(obj).c_str(), "state"); } std::string WebServer::fan_json(fan::FanState *obj) { - return json::build_json([obj](JsonObject &root) { + return json::build_json([obj](JsonObject root) { root["id"] = "fan-" + obj->get_object_id(); root["state"] = obj->state ? "ON" : "OFF"; root["value"] = obj->state; @@ -580,7 +580,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa request->send(404); } std::string WebServer::light_json(light::LightState *obj) { - return json::build_json([obj](JsonObject &root) { + return json::build_json([obj](JsonObject root) { root["id"] = "light-" + obj->get_object_id(); root["state"] = obj->remote_values.is_on() ? "ON" : "OFF"; light::LightJSONSchema::dump_json(*obj, root); @@ -632,7 +632,7 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa request->send(404); } std::string WebServer::cover_json(cover::Cover *obj) { - return json::build_json([obj](JsonObject &root) { + return json::build_json([obj](JsonObject root) { root["id"] = "cover-" + obj->get_object_id(); root["state"] = obj->is_fully_closed() ? "CLOSED" : "OPEN"; root["value"] = obj->position; @@ -659,7 +659,7 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM request->send(404); } std::string WebServer::number_json(number::Number *obj, float value) { - return json::build_json([obj, value](JsonObject &root) { + return json::build_json([obj, value](JsonObject root) { root["id"] = "number-" + obj->get_object_id(); char buffer[64]; snprintf(buffer, sizeof(buffer), "%f", value); @@ -703,7 +703,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM request->send(404); } std::string WebServer::select_json(select::Select *obj, const std::string &value) { - return json::build_json([obj, value](JsonObject &root) { + return json::build_json([obj, value](JsonObject root) { root["id"] = "select-" + obj->get_object_id(); root["state"] = value; root["value"] = value; diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 13d088e1cb..806a2d832c 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -27,8 +27,7 @@ Application = esphome_ns.class_("Application") optional = esphome_ns.class_("optional") arduino_json_ns = global_ns.namespace("ArduinoJson") JsonObject = arduino_json_ns.class_("JsonObject") -JsonObjectRef = JsonObject.operator("ref") -JsonObjectConstRef = JsonObjectRef.operator("const") +JsonObjectConst = arduino_json_ns.class_("JsonObjectConst") Controller = esphome_ns.class_("Controller") GPIOPin = esphome_ns.class_("GPIOPin") InternalGPIOPin = esphome_ns.class_("InternalGPIOPin", GPIOPin) diff --git a/platformio.ini b/platformio.ini index 3c0b725d65..d26b3c9c90 100644 --- a/platformio.ini +++ b/platformio.ini @@ -27,9 +27,10 @@ build_flags = [common] lib_deps = - esphome/noise-c@0.1.4 ; api - makuna/NeoPixelBus@2.6.9 ; neopixelbus - esphome/Improv@1.0.0 ; improv_serial / esp32_improv + esphome/noise-c@0.1.4 ; api + makuna/NeoPixelBus@2.6.9 ; neopixelbus + esphome/Improv@1.0.0 ; improv_serial / esp32_improv + bblanchon/ArduinoJson@6.18.5 ; json build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = @@ -42,7 +43,6 @@ extends = common lib_deps = ${common.lib_deps} ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt - ottowinter/ArduinoJson-esphomelib@5.13.3 ; json esphome/ESPAsyncWebServer-esphome@2.1.0 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps diff --git a/tests/unit_tests/test_codegen.py b/tests/unit_tests/test_codegen.py index 32d82b3062..3f32a117ff 100644 --- a/tests/unit_tests/test_codegen.py +++ b/tests/unit_tests/test_codegen.py @@ -68,8 +68,7 @@ from esphome import codegen as cg "optional", "arduino_json_ns", "JsonObject", - "JsonObjectRef", - "JsonObjectConstRef", + "JsonObjectConst", "Controller", "GPIOPin", ), From 72fa68849fdff84a0c2fe4333b1dcc2e903140c9 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 3 Jan 2022 10:11:28 +0100 Subject: [PATCH 490/549] Don't use pyproject.toml for esphome build (#2980) --- script/setup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/setup b/script/setup index 6d095af46c..71828deeaa 100755 --- a/script/setup +++ b/script/setup @@ -5,6 +5,6 @@ set -e cd "$(dirname "$0")/.." pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt -pip3 install -e . +pip3 install --no-use-pep517 -e . pre-commit install From a02d2e2e113df3bef6fc69c8929784d9b039b509 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 3 Jan 2022 16:37:21 +0100 Subject: [PATCH 491/549] Explicitly use overloaded begin() for I2C master initialization (#2978) Arduino 2.0.1 and newer support slave and master mode. The two modes have a begin() method with different signature: ``` // Slave Begin bool TwoWire::begin(uint8_t addr, int sdaPin, int sclPin, uint32_t frequency) // Master Begin bool TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency) ``` Use type casting to make sure that overloaded method for master mode is used. --- esphome/components/i2c/i2c_bus_arduino.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index eac2a47524..b605928692 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -25,7 +25,7 @@ void ArduinoI2CBus::setup() { wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer) #endif - wire_->begin(sda_pin_, scl_pin_); + wire_->begin(static_cast(sda_pin_), static_cast(scl_pin_)); wire_->setClock(frequency_); initialized_ = true; if (this->scan_) { From 998d4229af7040ee6fd9d2e2027dc235f6209b9f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 3 Jan 2022 08:57:09 -0800 Subject: [PATCH 492/549] Use template path (#2961) --- esphome/dashboard/dashboard.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 5e5cc4ecd2..1a247ec4eb 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -601,7 +601,7 @@ class MainRequestHandler(BaseHandler): begin = bool(self.get_argument("begin", False)) self.render( - get_template_path("index"), + "index.template.html", begin=begin, **template_args(), login_enabled=settings.using_password, @@ -778,7 +778,7 @@ class LoginHandler(BaseHandler): def render_login_page(self, error=None): self.render( - get_template_path("login"), + "login.template.html", error=error, hassio=settings.using_hassio_auth, has_username=bool(settings.username), @@ -872,10 +872,6 @@ def get_base_frontend_path(): 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) @@ -933,6 +929,7 @@ def make_app(debug=get_bool_env(ENV_DEV)): "cookie_secret": settings.cookie_secret, "log_function": log_function, "websocket_ping_interval": 30.0, + "template_path": get_base_frontend_path(), } rel = settings.relative_url app = tornado.web.Application( From 407661d56b818a61ced9da6970e7fdaaf1179cc3 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 3 Jan 2022 18:19:01 +0100 Subject: [PATCH 493/549] Fix compile error for idf projects with ArduinoJson 6 (#2979) Co-authored-by: Oxan van Leeuwen --- esphome/components/json/json_util.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 6a4eba78ab..7e88fb6e59 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -4,6 +4,9 @@ #ifdef USE_ESP8266 #include #endif +#ifdef USE_ESP32 +#include +#endif namespace esphome { namespace json { From 45ebe51e4f8d67e96b3e64e8e657993db8a99ea2 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 3 Jan 2022 18:28:28 +0100 Subject: [PATCH 494/549] Modbus: fix response parsing error for coil write (#2986) --- esphome/components/modbus/modbus.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 9ee3137be9..60ce50097c 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -69,7 +69,7 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { uint8_t data_len = raw[2]; uint8_t data_offset = 3; // the response for write command mirrors the requests and data startes at offset 2 instead of 3 for read commands - if (function_code == 0x5 || function_code == 0x06 || function_code == 0x10) { + if (function_code == 0x5 || function_code == 0x06 || function_code == 0xF || function_code == 0x10) { data_offset = 2; data_len = 4; } From 9124d9d6e606e1c236d82dd470fc5b0055c71beb Mon Sep 17 00:00:00 2001 From: David Buezas Date: Mon, 3 Jan 2022 18:58:35 +0100 Subject: [PATCH 495/549] Change unset ESPHOME_LOG_LEVEL fallback to NONE (#2982) Co-authored-by: David Buezas --- esphome/core/log.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/log.h b/esphome/core/log.h index 590ad26032..1e93ed4219 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -33,7 +33,7 @@ namespace esphome { #define ESPHOME_LOG_LEVEL_VERY_VERBOSE 7 #ifndef ESPHOME_LOG_LEVEL -#define ESPHOME_LOG_LEVEL ESPHOME_LOG_LEVEL_DEBUG +#define ESPHOME_LOG_LEVEL ESPHOME_LOG_LEVEL_NONE #endif #define ESPHOME_LOG_COLOR_BLACK "30" From 8ad06fb9eaf5016dbe48e807c958650abbfadba0 Mon Sep 17 00:00:00 2001 From: arunderwood Date: Mon, 3 Jan 2022 13:08:16 -0500 Subject: [PATCH 496/549] Add SH1107_128x64 to the ssd1306 component (#2967) --- esphome/components/ssd1306_base/__init__.py | 5 +++-- esphome/components/ssd1306_base/ssd1306_base.cpp | 15 ++++++++++++++- esphome/components/ssd1306_base/ssd1306_base.h | 1 + 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/esphome/components/ssd1306_base/__init__.py b/esphome/components/ssd1306_base/__init__.py index e4f62e5ff9..f2e4ef5811 100644 --- a/esphome/components/ssd1306_base/__init__.py +++ b/esphome/components/ssd1306_base/__init__.py @@ -31,6 +31,7 @@ MODELS = { "SH1106_128X64": SSD1306Model.SH1106_MODEL_128_64, "SH1106_96X16": SSD1306Model.SH1106_MODEL_96_16, "SH1106_64X48": SSD1306Model.SH1106_MODEL_64_48, + "SH1107_128X64": SSD1306Model.SH1107_MODEL_128_64, "SSD1305_128X32": SSD1306Model.SSD1305_MODEL_128_32, "SSD1305_128X64": SSD1306Model.SSD1305_MODEL_128_64, } @@ -61,8 +62,8 @@ SSD1306_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend( cv.Optional(CONF_EXTERNAL_VCC): cv.boolean, cv.Optional(CONF_FLIP_X, default=True): cv.boolean, cv.Optional(CONF_FLIP_Y, default=True): cv.boolean, - cv.Optional(CONF_OFFSET_X, default=0): cv.int_range(min=0, max=15), - cv.Optional(CONF_OFFSET_Y, default=0): cv.int_range(min=0, max=15), + cv.Optional(CONF_OFFSET_X, default=0): cv.int_range(min=-32, max=32), + cv.Optional(CONF_OFFSET_Y, default=0): cv.int_range(min=-32, max=32), cv.Optional(CONF_INVERT, default=False): cv.boolean, } ).extend(cv.polling_component_schema("1s")) diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index 4b9feb10ce..5ff220fce9 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -96,6 +96,7 @@ void SSD1306::setup() { case SSD1306_MODEL_64_48: case SSD1306_MODEL_64_32: case SH1106_MODEL_64_48: + case SH1107_MODEL_128_64: case SSD1305_MODEL_128_32: case SSD1305_MODEL_128_64: this->command(0x12); @@ -111,7 +112,14 @@ void SSD1306::setup() { // Set V_COM (0xDB) this->command(SSD1306_COMMAND_SET_VCOM_DETECT); - this->command(0x00); + switch (this->model_) { + case SH1107_MODEL_128_64: + this->command(0x35); + break; + default: + this->command(0x00); + break; + } // Display output follow RAM (0xA4) this->command(SSD1306_COMMAND_DISPLAY_ALL_ON_RESUME); @@ -198,6 +206,8 @@ void SSD1306::turn_off() { } int SSD1306::get_height_internal() { switch (this->model_) { + case SH1107_MODEL_128_64: + return 128; case SSD1306_MODEL_128_32: case SSD1306_MODEL_64_32: case SH1106_MODEL_128_32: @@ -232,6 +242,7 @@ int SSD1306::get_width_internal() { case SSD1306_MODEL_64_48: case SSD1306_MODEL_64_32: case SH1106_MODEL_64_48: + case SH1107_MODEL_128_64: return 64; default: return 0; @@ -289,6 +300,8 @@ const char *SSD1306::model_str_() { return "SH1106 96x16"; case SH1106_MODEL_64_48: return "SH1106 64x48"; + case SH1107_MODEL_128_64: + return "SH1107 128x64"; case SSD1305_MODEL_128_32: return "SSD1305 128x32"; case SSD1305_MODEL_128_64: diff --git a/esphome/components/ssd1306_base/ssd1306_base.h b/esphome/components/ssd1306_base/ssd1306_base.h index c77b1985e4..5ab68143c7 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.h +++ b/esphome/components/ssd1306_base/ssd1306_base.h @@ -17,6 +17,7 @@ enum SSD1306Model { SH1106_MODEL_128_64, SH1106_MODEL_96_16, SH1106_MODEL_64_48, + SH1107_MODEL_128_64, SSD1305_MODEL_128_32, SSD1305_MODEL_128_64, }; From f849d45bb6be2df5dc7af775b823ff47ace9a4da Mon Sep 17 00:00:00 2001 From: Christopher Masto Date: Mon, 3 Jan 2022 13:09:25 -0500 Subject: [PATCH 497/549] Add logging for some Nextion errors that didn't have any (#2957) --- esphome/components/nextion/nextion.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 494765db4d..fcb3885db9 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -329,6 +329,7 @@ void Nextion::process_nextion_commands_() { break; case 0x02: // invalid Component ID or name was used + ESP_LOGW(TAG, "Nextion reported component ID or name invalid!"); this->remove_from_q_(); break; case 0x03: // invalid Page ID or name was used @@ -387,6 +388,7 @@ void Nextion::process_nextion_commands_() { } break; case 0x1A: // variable name invalid + ESP_LOGW(TAG, "Nextion reported variable name invalid!"); this->remove_from_q_(); break; From dce3713f12ff88fa88418dee07cc36d92ae1b0cc Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 3 Jan 2022 19:40:05 +0100 Subject: [PATCH 498/549] Fix HTTPRequestComponent::get_string return value (#2987) Co-authored-by: Oxan van Leeuwen --- esphome/components/http_request/http_request.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index 309977a915..a80d095835 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -115,8 +115,11 @@ void HttpRequestComponent::close() { } const char *HttpRequestComponent::get_string() { - static const String STR = this->client_.getString(); - return STR.c_str(); + // The static variable is here because HTTPClient::getString() returns a String on ESP32, and we need something to + // to keep a buffer alive. + static std::string str; + str = this->client_.getString().c_str(); + return str.c_str(); } } // namespace http_request From dbc28120222030f75355941ae8a110cc5911adf6 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 3 Jan 2022 22:35:15 +0100 Subject: [PATCH 499/549] Improve PSRAM support (#2884) --- CODEOWNERS | 1 + esphome/components/display/display_buffer.cpp | 4 +- esphome/components/esp32_camera/__init__.py | 4 +- .../components/esp32_camera/esp32_camera.cpp | 3 - esphome/components/inkplate6/display.py | 3 +- esphome/components/inkplate6/inkplate.cpp | 24 +++---- esphome/components/nextion/nextion_upload.cpp | 50 ++++++--------- esphome/components/psram/__init__.py | 29 +++++++++ esphome/components/psram/psram.cpp | 32 ++++++++++ esphome/components/psram/psram.h | 17 +++++ esphome/core/helpers.h | 64 ++++++++++++++----- 11 files changed, 162 insertions(+), 69 deletions(-) create mode 100644 esphome/components/psram/__init__.py create mode 100644 esphome/components/psram/psram.cpp create mode 100644 esphome/components/psram/psram.h diff --git a/CODEOWNERS b/CODEOWNERS index 7dd73417b6..c3ca5410b1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -125,6 +125,7 @@ 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/psram/* @esphome/core esphome/components/pulse_meter/* @stevebaxter esphome/components/pvvx_mithermometer/* @pasiz esphome/components/rc522/* @glmnet diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 1458629acd..b97fb4ae23 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -5,6 +5,7 @@ #include "esphome/core/color.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" +#include "esphome/core/helpers.h" namespace esphome { namespace display { @@ -15,7 +16,8 @@ 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]; // NOLINT + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + this->buffer_ = allocator.allocate(buffer_length); if (this->buffer_ == nullptr) { ESP_LOGE(TAG, "Could not allocate buffer for display!"); return; diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 1135b31798..d42d4f5de3 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -19,6 +19,8 @@ from esphome.cpp_helpers import setup_entity DEPENDENCIES = ["esp32"] +AUTO_LOAD = ["psram"] + esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase) ESP32CameraFrameSize = esp32_camera_ns.enum("ESP32CameraFrameSize") @@ -153,9 +155,7 @@ async def to_code(config): cg.add(var.set_frame_size(config[CONF_RESOLUTION])) 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 a6d4a30c27..54307dce41 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -49,9 +49,6 @@ void ESP32Camera::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 Camera:"); ESP_LOGCONFIG(TAG, " Name: %s", this->name_.c_str()); ESP_LOGCONFIG(TAG, " Internal: %s", YESNO(this->internal_)); -#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/inkplate6/display.py b/esphome/components/inkplate6/display.py index 86b7069c55..dca764c6ed 100644 --- a/esphome/components/inkplate6/display.py +++ b/esphome/components/inkplate6/display.py @@ -12,6 +12,7 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c", "esp32"] +AUTO_LOAD = ["psram"] CONF_DISPLAY_DATA_0_PIN = "display_data_0_pin" CONF_DISPLAY_DATA_1_PIN = "display_data_1_pin" @@ -179,5 +180,3 @@ async def to_code(config): display_data_7 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_7_PIN]) cg.add(var.set_display_data_7_pin(display_data_7)) - - cg.add_build_flag("-DBOARD_HAS_PSRAM") diff --git a/esphome/components/inkplate6/inkplate.cpp b/esphome/components/inkplate6/inkplate.cpp index 8a05836db9..e62e594a49 100644 --- a/esphome/components/inkplate6/inkplate.cpp +++ b/esphome/components/inkplate6/inkplate.cpp @@ -42,32 +42,32 @@ void Inkplate6::setup() { this->display(); } void Inkplate6::initialize_() { + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); uint32_t buffer_size = this->get_buffer_length_(); + if (buffer_size == 0) + return; - if (this->partial_buffer_ != nullptr) { - free(this->partial_buffer_); // NOLINT - } - if (this->partial_buffer_2_ != nullptr) { - free(this->partial_buffer_2_); // NOLINT - } - if (this->buffer_ != nullptr) { - free(this->buffer_); // NOLINT - } + if (this->partial_buffer_ != nullptr) + allocator.deallocate(this->partial_buffer_, buffer_size); + if (this->partial_buffer_2_ != nullptr) + allocator.deallocate(this->partial_buffer_2_, buffer_size * 2); + if (this->buffer_ != nullptr) + allocator.deallocate(this->buffer_, buffer_size); - this->buffer_ = (uint8_t *) ps_malloc(buffer_size); + this->buffer_ = allocator.allocate(buffer_size); if (this->buffer_ == nullptr) { ESP_LOGE(TAG, "Could not allocate buffer for display!"); this->mark_failed(); return; } if (!this->greyscale_) { - this->partial_buffer_ = (uint8_t *) ps_malloc(buffer_size); + this->partial_buffer_ = allocator.allocate(buffer_size); if (this->partial_buffer_ == nullptr) { ESP_LOGE(TAG, "Could not allocate partial buffer for display!"); this->mark_failed(); return; } - this->partial_buffer_2_ = (uint8_t *) ps_malloc(buffer_size * 2); + this->partial_buffer_2_ = allocator.allocate(buffer_size * 2); if (this->partial_buffer_2_ == nullptr) { ESP_LOGE(TAG, "Could not allocate partial buffer 2 for display!"); this->mark_failed(); diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index b16f2fe7eb..1b60034bd1 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -8,6 +8,10 @@ #include "esphome/core/log.h" #include "esphome/components/network/util.h" +#ifdef USE_ESP32 +#include +#endif + namespace esphome { namespace nextion { static const char *const TAG = "nextion_upload"; @@ -158,12 +162,8 @@ void Nextion::upload_tft() { if (!begin_status) { this->is_updating_ = false; ESP_LOGD(TAG, "connection failed"); -#ifdef USE_ESP32 - if (psramFound()) - free(this->transfer_buffer_); // NOLINT - else -#endif - delete this->transfer_buffer_; + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + allocator.deallocate(this->transfer_buffer_, this->transfer_buffer_size_); return; } else { ESP_LOGD(TAG, "Connected"); @@ -252,7 +252,7 @@ void Nextion::upload_tft() { // Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096 #ifdef USE_ESP32 uint32_t chunk_size = 8192; - if (psramFound()) { + if (heap_caps_get_free_size(MALLOC_CAP_SPIRAM) > 0) { chunk_size = this->content_length_; } else { if (ESP.getFreeHeap() > 40960) { // 32K to keep on hand @@ -269,32 +269,18 @@ void Nextion::upload_tft() { #endif if (this->transfer_buffer_ == nullptr) { -#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); - if (this->transfer_buffer_ == nullptr) { - ESP_LOGE(TAG, "Could not allocate buffer size %d!", chunk_size); - this->upload_end_(); - } - } else { -#endif - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap()); - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size]; - 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); - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - this->transfer_buffer_ = new uint8_t[chunk_size]; + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap()); + this->transfer_buffer_ = allocator.allocate(chunk_size); + 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_ = allocator.allocate(chunk_size); - if (!this->transfer_buffer_) - this->upload_end_(); -#ifdef USE_ESP32 - } -#endif + if (!this->transfer_buffer_) + this->upload_end_(); } this->transfer_buffer_size_ = chunk_size; diff --git a/esphome/components/psram/__init__.py b/esphome/components/psram/__init__.py new file mode 100644 index 0000000000..ac6d034514 --- /dev/null +++ b/esphome/components/psram/__init__.py @@ -0,0 +1,29 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components.esp32 import add_idf_sdkconfig_option +from esphome.core import CORE +from esphome.const import ( + CONF_ID, +) + +CODEOWNERS = ["@esphome/core"] + +psram_ns = cg.esphome_ns.namespace("psram") +PsramComponent = psram_ns.class_("PsramComponent", cg.Component) + +CONFIG_SCHEMA = cv.All( + cv.Schema({cv.GenerateID(): cv.declare_id(PsramComponent)}), cv.only_on_esp32 +) + + +async def to_code(config): + if CORE.using_arduino: + cg.add_build_flag("-DBOARD_HAS_PSRAM") + + if CORE.using_esp_idf: + add_idf_sdkconfig_option("CONFIG_ESP32_SPIRAM_SUPPORT", True) + add_idf_sdkconfig_option("CONFIG_SPIRAM_USE_CAPS_ALLOC", True) + add_idf_sdkconfig_option("CONFIG_SPIRAM_IGNORE_NOTFOUND", True) + + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) diff --git a/esphome/components/psram/psram.cpp b/esphome/components/psram/psram.cpp new file mode 100644 index 0000000000..8325709632 --- /dev/null +++ b/esphome/components/psram/psram.cpp @@ -0,0 +1,32 @@ +#include "psram.h" + +#ifdef USE_ESP32 + +#include "esphome/core/log.h" + +#include +#include + +namespace esphome { +namespace psram { + +static const char *const TAG = "psram"; + +void PsramComponent::dump_config() { + // Technically this can be false if the PSRAM is full, but heap_caps_get_total_size() isn't always available, and it's + // very unlikely for the PSRAM to be full. + bool available = heap_caps_get_free_size(MALLOC_CAP_SPIRAM) > 0; + + ESP_LOGCONFIG(TAG, "PSRAM:"); + ESP_LOGCONFIG(TAG, " Available: %s", YESNO(available)); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0) + if (available) { + ESP_LOGCONFIG(TAG, " Size: %d MB", heap_caps_get_total_size(MALLOC_CAP_SPIRAM) / 1024 / 1024); + } +#endif +} + +} // namespace psram +} // namespace esphome + +#endif diff --git a/esphome/components/psram/psram.h b/esphome/components/psram/psram.h new file mode 100644 index 0000000000..8c891feee9 --- /dev/null +++ b/esphome/components/psram/psram.h @@ -0,0 +1,17 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/core/component.h" + +namespace esphome { +namespace psram { + +class PsramComponent : public Component { + void dump_config() override; +}; + +} // namespace psram +} // namespace esphome + +#endif diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 90f35ee4ca..c9bda18394 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -9,8 +9,8 @@ #include #include -#ifdef USE_ESP32_FRAMEWORK_ARDUINO -#include "esp32-hal-psram.h" +#ifdef USE_ESP32 +#include #endif #include "esphome/core/optional.h" @@ -261,21 +261,6 @@ template class Parented { uint32_t fnv1_hash(const std::string &str); -template T *new_buffer(size_t length) { - T *buffer; -#ifdef USE_ESP32_FRAMEWORK_ARDUINO - if (psramFound()) { - buffer = (T *) ps_malloc(length); - } else { - buffer = new T[length]; // NOLINT(cppcoreguidelines-owning-memory) - } -#else - buffer = new T[length]; // NOLINT(cppcoreguidelines-owning-memory) -#endif - - return buffer; -} - // --------------------------------------------------------------------------------------------------------------------- /// @name STL backports @@ -486,6 +471,51 @@ template T remap(U value, U min, U max, T min_out, T max ///@} +/// @name Memory management +///@{ + +/** An STL allocator that uses SPI RAM. + * + * By setting flags, it can be configured to don't try main memory if SPI RAM is full or unavailable, and to return + * `nulllptr` instead of aborting when no memory is available. + */ +template class ExternalRAMAllocator { + public: + using value_type = T; + + enum Flags { + NONE = 0, + REFUSE_INTERNAL = 1 << 0, ///< Refuse falling back to internal memory when external RAM is full or unavailable. + ALLOW_FAILURE = 1 << 1, ///< Don't abort when memory allocation fails. + }; + + ExternalRAMAllocator() = default; + ExternalRAMAllocator(Flags flags) : flags_{flags} {} + template constexpr ExternalRAMAllocator(const ExternalRAMAllocator &other) : flags_{other.flags} {} + + T *allocate(size_t n) { + size_t size = n * sizeof(T); + T *ptr = nullptr; +#ifdef USE_ESP32 + ptr = static_cast(heap_caps_malloc(size, MALLOC_CAP_SPIRAM)); +#endif + if (ptr == nullptr && (this->flags_ & Flags::REFUSE_INTERNAL) == 0) + ptr = static_cast(malloc(size)); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) + if (ptr == nullptr && (this->flags_ & Flags::ALLOW_FAILURE) == 0) + abort(); + return ptr; + } + + void deallocate(T *p, size_t n) { + free(p); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) + } + + private: + Flags flags_{Flags::NONE}; +}; + +/// @} + /// @name Deprecated functions ///@{ From 15ce27992e8acd89165c3701a48559e4a1742151 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 3 Jan 2022 23:06:43 +0100 Subject: [PATCH 500/549] Support ISR based pulse counter on ESP32-C3 (#2983) --- .../pulse_counter/pulse_counter_sensor.cpp | 6 +++--- .../pulse_counter/pulse_counter_sensor.h | 15 +++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.cpp b/esphome/components/pulse_counter/pulse_counter_sensor.cpp index d9f198f4fc..5232ebc427 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.cpp +++ b/esphome/components/pulse_counter/pulse_counter_sensor.cpp @@ -8,7 +8,7 @@ static const char *const TAG = "pulse_counter"; const char *const EDGE_MODE_TO_STRING[] = {"DISABLE", "INCREMENT", "DECREMENT"}; -#ifdef USE_ESP8266 +#ifndef HAS_PCNT void IRAM_ATTR PulseCounterStorage::gpio_intr(PulseCounterStorage *arg) { const uint32_t now = micros(); const bool discard = now - arg->last_pulse < arg->filter_us; @@ -43,7 +43,7 @@ pulse_counter_t PulseCounterStorage::read_raw_value() { } #endif -#ifdef USE_ESP32 +#ifdef HAS_PCNT bool PulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { static pcnt_unit_t next_pcnt_unit = PCNT_UNIT_0; this->pin = pin; @@ -96,7 +96,7 @@ bool PulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { } if (this->filter_us != 0) { - uint16_t filter_val = std::min(this->filter_us * 80u, 1023u); + uint16_t filter_val = std::min(static_cast(this->filter_us * 80u), 1023u); ESP_LOGCONFIG(TAG, " Filter Value: %uus (val=%u)", this->filter_us, filter_val); error = pcnt_set_filter_value(this->pcnt_unit, filter_val); if (error != ESP_OK) { diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.h b/esphome/components/pulse_counter/pulse_counter_sensor.h index 9ed2159ae3..86c387d52a 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.h +++ b/esphome/components/pulse_counter/pulse_counter_sensor.h @@ -4,8 +4,9 @@ #include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" -#ifdef USE_ESP32 +#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) #include +#define HAS_PCNT #endif namespace esphome { @@ -17,10 +18,9 @@ enum PulseCounterCountMode { PULSE_COUNTER_DECREMENT, }; -#ifdef USE_ESP32 +#ifdef HAS_PCNT using pulse_counter_t = int16_t; -#endif -#ifdef USE_ESP8266 +#else using pulse_counter_t = int32_t; #endif @@ -30,16 +30,15 @@ struct PulseCounterStorage { static void gpio_intr(PulseCounterStorage *arg); -#ifdef USE_ESP8266 +#ifndef HAS_PCNT volatile pulse_counter_t counter{0}; volatile uint32_t last_pulse{0}; #endif InternalGPIOPin *pin; -#ifdef USE_ESP32 +#ifdef HAS_PCNT pcnt_unit_t pcnt_unit; -#endif -#ifdef USE_ESP8266 +#else ISRInternalGPIOPin isr_pin; #endif PulseCounterCountMode rising_edge_mode{PULSE_COUNTER_INCREMENT}; From 5143a5b5c500c1f27425e164b9660592c460c528 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 3 Jan 2022 23:30:03 +0100 Subject: [PATCH 501/549] Use to_string() from STL when available (#2992) --- esphome/core/helpers.cpp | 46 ---------------------------------------- esphome/core/helpers.h | 30 ++++++++++++++++---------- 2 files changed, 19 insertions(+), 57 deletions(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index b82a2666e7..da5d0915c2 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -197,52 +197,6 @@ uint8_t reverse_bits_8(uint8_t x) { uint16_t reverse_bits_16(uint16_t x) { return uint16_t(reverse_bits_8(x & 0xFF) << 8) | uint16_t(reverse_bits_8(x >> 8)); } -std::string to_string(const std::string &val) { return val; } -std::string to_string(int val) { - char buf[64]; - sprintf(buf, "%d", val); - return buf; -} -std::string to_string(long val) { // NOLINT - char buf[64]; - sprintf(buf, "%ld", val); - return buf; -} -std::string to_string(long long val) { // NOLINT - char buf[64]; - sprintf(buf, "%lld", val); - return buf; -} -std::string to_string(unsigned val) { // NOLINT - char buf[64]; - sprintf(buf, "%u", val); - return buf; -} -std::string to_string(unsigned long val) { // NOLINT - char buf[64]; - sprintf(buf, "%lu", val); - return buf; -} -std::string to_string(unsigned long long val) { // NOLINT - char buf[64]; - sprintf(buf, "%llu", val); - return buf; -} -std::string to_string(float val) { - char buf[64]; - sprintf(buf, "%f", val); - return buf; -} -std::string to_string(double val) { - char buf[64]; - sprintf(buf, "%f", val); - return buf; -} -std::string to_string(long double val) { - char buf[64]; - sprintf(buf, "%Lf", val); - return buf; -} uint32_t fnv1_hash(const std::string &str) { uint32_t hash = 2166136261UL; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index c9bda18394..27df14d95d 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -36,17 +36,6 @@ std::string get_mac_address_pretty(); void set_mac_address(uint8_t *mac); #endif -std::string to_string(const std::string &val); -std::string to_string(int val); -std::string to_string(long val); // NOLINT -std::string to_string(long long val); // NOLINT -std::string to_string(unsigned val); // NOLINT -std::string to_string(unsigned long val); // NOLINT -std::string to_string(unsigned long long val); // NOLINT -std::string to_string(float val); -std::string to_string(double val); -std::string to_string(long double val); - /// Compare string a to string b (ignoring case) and return whether they are equal. bool str_equals_case_insensitive(const std::string &a, const std::string &b); bool str_startswith(const std::string &full, const std::string &start); @@ -266,6 +255,22 @@ uint32_t fnv1_hash(const std::string &str); /// @name STL backports ///@{ +// std::to_string() from C++11, available from libstdc++/g++ 8+ +// See https://github.com/espressif/esp-idf/issues/1445 +#if _GLIBCXX_RELEASE >= 8 +using std::to_string; +#else +inline std::string to_string(int value) { return str_snprintf("%d", 32, value); } // NOLINT +inline std::string to_string(long value) { return str_snprintf("%ld", 32, value); } // NOLINT +inline std::string to_string(long long value) { return str_snprintf("%lld", 32, value); } // NOLINT +inline std::string to_string(unsigned value) { return str_snprintf("%u", 32, value); } // NOLINT +inline std::string to_string(unsigned long value) { return str_snprintf("%lu", 32, value); } // NOLINT +inline std::string to_string(unsigned long long value) { return str_snprintf("%llu", 32, value); } // NOLINT +inline std::string to_string(float value) { return str_snprintf("%f", 32, value); } +inline std::string to_string(double value) { return str_snprintf("%f", 32, value); } +inline std::string to_string(long double value) { return str_snprintf("%Lf", 32, value); } +#endif + // std::byteswap is from C++23 and technically should be a template, but this will do for now. constexpr uint8_t byteswap(uint8_t n) { return n; } constexpr uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); } @@ -326,6 +331,9 @@ template::value, int> = 0> constexpr /// @name Strings ///@{ +/// Convert the value to a string (added as extra overload so that to_string() can be used on all stringifiable types). +inline std::string to_string(const std::string &val) { return val; } + /// Truncate a string to a specific length. std::string str_truncate(const std::string &str, size_t length); From 26dd1f85325145ed3eeae047b478d4fe9cf3a7e5 Mon Sep 17 00:00:00 2001 From: Igor Scheller Date: Tue, 4 Jan 2022 10:14:38 +0100 Subject: [PATCH 502/549] Set UTF-8 encoding and version for prometheus /metrics (#2993) --- esphome/components/prometheus/prometheus_handler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index fa7b4fe132..618c866d5b 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -7,7 +7,7 @@ namespace esphome { namespace prometheus { void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) { - AsyncResponseStream *stream = req->beginResponseStream("text/plain"); + AsyncResponseStream *stream = req->beginResponseStream("text/plain; version=0.0.4; charset=utf-8"); #ifdef USE_SENSOR this->sensor_type_(stream); From e5775cf81296494f57ec3be621fd5e4e14deb4e4 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 4 Jan 2022 10:14:57 +0100 Subject: [PATCH 503/549] Introduce bit_cast() backport (#2991) --- esphome/core/helpers.h | 28 +++++++++++++++++++++++++++- esphome/core/preferences.h | 17 +++++------------ 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 27df14d95d..2e6a978012 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -255,7 +255,7 @@ uint32_t fnv1_hash(const std::string &str); /// @name STL backports ///@{ -// std::to_string() from C++11, available from libstdc++/g++ 8+ +// std::to_string() from C++11, available from libstdc++/g++ 8 // See https://github.com/espressif/esp-idf/issues/1445 #if _GLIBCXX_RELEASE >= 8 using std::to_string; @@ -271,6 +271,32 @@ inline std::string to_string(double value) { return str_snprintf("%f", 32, value inline std::string to_string(long double value) { return str_snprintf("%Lf", 32, value); } #endif +// std::is_trivially_copyable from C++11, implemented in libstdc++/g++ 5.1 (but minor releases can't be detected) +#if _GLIBCXX_RELEASE >= 6 +using std::is_trivially_copyable; +#else +// Implementing this is impossible without compiler intrinsics, so don't bother. Invalid usage will be detected on +// other variants that use a newer compiler anyway. +// NOLINTNEXTLINE(readability-identifier-naming) +template struct is_trivially_copyable : public std::integral_constant {}; +#endif + +// std::bit_cast from C++20 +#if __cpp_lib_bit_cast >= 201806 +using std::bit_cast; +#else +/// Convert data between types, without aliasing issues or undefined behaviour. +template< + typename To, typename From, + enable_if_t::value && is_trivially_copyable::value, + int> = 0> +To bit_cast(const From &src) { + To dst; + memcpy(&dst, &src, sizeof(To)); + return dst; +} +#endif + // std::byteswap is from C++23 and technically should be a template, but this will do for now. constexpr uint8_t byteswap(uint8_t n) { return n; } constexpr uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); } diff --git a/esphome/core/preferences.h b/esphome/core/preferences.h index ad45cd9684..2b13061a59 100644 --- a/esphome/core/preferences.h +++ b/esphome/core/preferences.h @@ -2,7 +2,8 @@ #include #include -#include + +#include "esphome/core/helpers.h" namespace esphome { @@ -45,20 +46,12 @@ class ESPPreferences { */ virtual bool sync() = 0; -#ifndef USE_ESP8266 - template::value, bool>::type = true> -#else - // esp8266 toolchain doesn't have is_trivially_copyable - template -#endif + template::value, bool> = true> 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 + + template::value, bool> = true> ESPPreferenceObject make_preference(uint32_t type) { return this->make_preference(sizeof(T), type); } From b601560e81208801e264166e3d4f2e785033bfbb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 4 Jan 2022 22:16:02 +1300 Subject: [PATCH 504/549] Apply --no-use-pep517 for docker images (#2985) --- docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 25f2cf85d2..32113fe278 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -64,7 +64,7 @@ RUN \ # Copy esphome and install COPY . /esphome -RUN pip3 install --no-cache-dir /esphome +RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome # Settings for dashboard ENV USERNAME="" PASSWORD="" @@ -112,7 +112,7 @@ RUN \ # Copy esphome and install COPY . /esphome -RUN pip3 install --no-cache-dir /esphome +RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome # Labels LABEL \ From 3df0fee3de4dd60cc1335437abb03f824c7be165 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 4 Jan 2022 22:16:40 +1300 Subject: [PATCH 505/549] Dont validate baud_rate for sim800l platform (#2945) --- esphome/components/sim800l/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sim800l/__init__.py b/esphome/components/sim800l/__init__.py index 0887b8640f..4143627084 100644 --- a/esphome/components/sim800l/__init__.py +++ b/esphome/components/sim800l/__init__.py @@ -41,7 +41,7 @@ CONFIG_SCHEMA = cv.All( .extend(uart.UART_DEVICE_SCHEMA) ) FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( - "sim800l", baud_rate=9600, require_tx=True, require_rx=True + "sim800l", require_tx=True, require_rx=True ) From b924b179abd5e4cc97350975f120a95d6f50c96a Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Tue, 4 Jan 2022 10:19:18 +0100 Subject: [PATCH 506/549] Modbus: add binary output (#2931) Co-authored-by: Oxan van Leeuwen --- .../components/modbus_controller/__init__.py | 9 +- .../modbus_controller/output/__init__.py | 100 ++++++++++++------ .../output/modbus_output.cpp | 63 +++++++++-- .../modbus_controller/output/modbus_output.h | 39 +++++-- 4 files changed, 165 insertions(+), 46 deletions(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index b927faf9a7..f919cb0678 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -47,11 +47,16 @@ MODBUS_FUNCTION_CODE = { ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType") ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType") -MODBUS_REGISTER_TYPE = { + +MODBUS_WRITE_REGISTER_TYPE = { "custom": ModbusRegisterType.CUSTOM, "coil": ModbusRegisterType.COIL, - "discrete_input": ModbusRegisterType.DISCRETE_INPUT, "holding": ModbusRegisterType.HOLDING, +} + +MODBUS_REGISTER_TYPE = { + **MODBUS_WRITE_REGISTER_TYPE, + "discrete_input": ModbusRegisterType.DISCRETE_INPUT, "read": ModbusRegisterType.READ, } diff --git a/esphome/components/modbus_controller/output/__init__.py b/esphome/components/modbus_controller/output/__init__.py index a26d05a18b..1bf989ce8b 100644 --- a/esphome/components/modbus_controller/output/__init__.py +++ b/esphome/components/modbus_controller/output/__init__.py @@ -1,7 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import output - from esphome.const import ( CONF_ADDRESS, CONF_ID, @@ -11,13 +10,14 @@ from esphome.const import ( from .. import ( modbus_controller_ns, modbus_calc_properties, - validate_modbus_register, ModbusItemBaseSchema, SensorItem, + SENSOR_VALUE_TYPE, ) from ..const import ( CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_TYPE, CONF_USE_WRITE_MULTIPLE, CONF_VALUE_TYPE, CONF_WRITE_LAMBDA, @@ -27,45 +27,83 @@ DEPENDENCIES = ["modbus_controller"] CODEOWNERS = ["@martgras"] -ModbusOutput = modbus_controller_ns.class_( - "ModbusOutput", cg.Component, output.FloatOutput, SensorItem +ModbusFloatOutput = modbus_controller_ns.class_( + "ModbusFloatOutput", cg.Component, output.FloatOutput, SensorItem +) +ModbusBinaryOutput = modbus_controller_ns.class_( + "ModbusBinaryOutput", cg.Component, output.BinaryOutput, SensorItem ) -CONFIG_SCHEMA = cv.All( - output.FLOAT_OUTPUT_SCHEMA.extend(ModbusItemBaseSchema).extend( - { - cv.GenerateID(): cv.declare_id(ModbusOutput), - cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, - cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, - cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, - } - ), - validate_modbus_register, + +CONFIG_SCHEMA = cv.typed_schema( + { + "coil": output.BINARY_OUTPUT_SCHEMA.extend(ModbusItemBaseSchema).extend( + { + cv.GenerateID(): cv.declare_id(ModbusBinaryOutput), + cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, + } + ), + "holding": output.FLOAT_OUTPUT_SCHEMA.extend(ModbusItemBaseSchema).extend( + { + cv.GenerateID(): cv.declare_id(ModbusFloatOutput), + cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum( + SENSOR_VALUE_TYPE + ), + cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, + cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, + } + ), + }, + lower=True, + key=CONF_REGISTER_TYPE, + default_type="holding", ) async def to_code(config): byte_offset, reg_count = modbus_calc_properties(config) - var = cg.new_Pvariable( - config[CONF_ID], - config[CONF_ADDRESS], - byte_offset, - config[CONF_VALUE_TYPE], - reg_count, - ) + # Binary Output + if config[CONF_REGISTER_TYPE] == "coil": + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_ADDRESS], + byte_offset, + ) + if CONF_WRITE_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_WRITE_LAMBDA], + [ + (ModbusBinaryOutput.operator("ptr"), "item"), + (cg.bool_, "x"), + (cg.std_vector.template(cg.uint8).operator("ref"), "payload"), + ], + return_type=cg.optional.template(bool), + ) + # Float Output + else: + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_ADDRESS], + byte_offset, + config[CONF_VALUE_TYPE], + reg_count, + ) + cg.add(var.set_write_multiply(config[CONF_MULTIPLY])) + if CONF_WRITE_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_WRITE_LAMBDA], + [ + (ModbusFloatOutput.operator("ptr"), "item"), + (cg.float_, "x"), + (cg.std_vector.template(cg.uint16).operator("ref"), "payload"), + ], + return_type=cg.optional.template(float), + ) await output.register_output(var, config) - cg.add(var.set_write_multiply(config[CONF_MULTIPLY])) parent = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE])) cg.add(var.set_parent(parent)) if CONF_WRITE_LAMBDA in config: - template_ = await cg.process_lambda( - config[CONF_WRITE_LAMBDA], - [ - (ModbusOutput.operator("ptr"), "item"), - (cg.float_, "x"), - (cg.std_vector.template(cg.uint16).operator("ref"), "payload"), - ], - return_type=cg.optional.template(float), - ) cg.add(var.set_write_template(template_)) diff --git a/esphome/components/modbus_controller/output/modbus_output.cpp b/esphome/components/modbus_controller/output/modbus_output.cpp index 4c2e5775b9..b647312f52 100644 --- a/esphome/components/modbus_controller/output/modbus_output.cpp +++ b/esphome/components/modbus_controller/output/modbus_output.cpp @@ -1,18 +1,17 @@ #include #include "modbus_output.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" namespace esphome { namespace modbus_controller { static const char *const TAG = "modbus_controller.output"; -void ModbusOutput::setup() {} - /** Write a value to the device * */ -void ModbusOutput::write_state(float value) { +void ModbusFloatOutput::write_state(float value) { std::vector data; auto original_value = value; // Is there are lambda configured? @@ -39,7 +38,6 @@ void ModbusOutput::write_state(float value) { ESP_LOGD(TAG, "Updating register: start address=0x%X register count=%d new value=%.02f (val=%.02f)", this->start_address, this->register_count, value, original_value); - // Create and send the write command // Create and send the write command ModbusCommandItem write_cmd; if (this->register_count == 1 && !this->use_write_multiple_) { @@ -51,11 +49,62 @@ void ModbusOutput::write_state(float value) { parent_->queue_command(write_cmd); } -void ModbusOutput::dump_config() { +void ModbusFloatOutput::dump_config() { ESP_LOGCONFIG(TAG, "Modbus Float Output:"); LOG_FLOAT_OUTPUT(this); - ESP_LOGCONFIG(TAG, "Modbus device start address=0x%X register count=%d value type=%hhu", this->start_address, - this->register_count, this->sensor_value_type); + ESP_LOGCONFIG(TAG, " Device start address: 0x%X", this->start_address); + ESP_LOGCONFIG(TAG, " Register count: %d", this->register_count); + ESP_LOGCONFIG(TAG, " Value type: %d", static_cast(this->sensor_value_type)); +} + +// ModbusBinaryOutput +void ModbusBinaryOutput::write_state(bool state) { + // This will be called every time the user requests a state change. + ModbusCommandItem cmd; + std::vector data; + + // Is there are lambda configured? + if (this->write_transform_func_.has_value()) { + // data is passed by reference + // the lambda can fill the empty vector directly + // in that case the return value is ignored + auto val = (*this->write_transform_func_)(this, state, data); + if (val.has_value()) { + ESP_LOGV(TAG, "Value overwritten by lambda"); + state = val.value(); + } else { + ESP_LOGV(TAG, "Communication handled by lambda - exiting control"); + return; + } + } + if (!data.empty()) { + ESP_LOGV(TAG, "Modbus binary output write raw: %s", format_hex_pretty(data).c_str()); + cmd = ModbusCommandItem::create_custom_command( + this->parent_, data, + [this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { + this->parent_->on_write_register_response(cmd.register_type, this->start_address, data); + }); + } else { + ESP_LOGV(TAG, "Write new state: value is %s, type is %d address = %X, offset = %x", ONOFF(state), + (int) this->register_type, this->start_address, this->offset); + + // offset for coil and discrete inputs is the coil/register number not bytes + if (this->use_write_multiple_) { + std::vector states{state}; + cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states); + } else { + cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state); + } + } + this->parent_->queue_command(cmd); +} + +void ModbusBinaryOutput::dump_config() { + ESP_LOGCONFIG(TAG, "Modbus Binary Output:"); + LOG_BINARY_OUTPUT(this); + ESP_LOGCONFIG(TAG, " Device start address: 0x%X", this->start_address); + ESP_LOGCONFIG(TAG, " Register count: %d", this->register_count); + ESP_LOGCONFIG(TAG, " Value type: %d", static_cast(this->sensor_value_type)); } } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/output/modbus_output.h b/esphome/components/modbus_controller/output/modbus_output.h index 78d3474ad6..6237805d24 100644 --- a/esphome/components/modbus_controller/output/modbus_output.h +++ b/esphome/components/modbus_controller/output/modbus_output.h @@ -7,11 +7,9 @@ namespace esphome { namespace modbus_controller { -using value_to_data_t = std::function(float); - -class ModbusOutput : public output::FloatOutput, public Component, public SensorItem { +class ModbusFloatOutput : public output::FloatOutput, public Component, public SensorItem { public: - ModbusOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type, int register_count) + ModbusFloatOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type, int register_count) : output::FloatOutput(), Component() { this->register_type = ModbusRegisterType::HOLDING; this->start_address = start_address; @@ -23,7 +21,6 @@ class ModbusOutput : public output::FloatOutput, public Component, public Sensor this->start_address += offset; this->offset = 0; } - void setup() override; void dump_config() override; void set_parent(ModbusController *parent) { this->parent_ = parent; } @@ -31,7 +28,7 @@ class ModbusOutput : public output::FloatOutput, public Component, public Sensor // Do nothing void parse_and_publish(const std::vector &data) override{}; - using write_transform_func_t = std::function(ModbusOutput *, float, std::vector &)>; + using write_transform_func_t = std::function(ModbusFloatOutput *, float, std::vector &)>; void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } @@ -44,5 +41,35 @@ class ModbusOutput : public output::FloatOutput, public Component, public Sensor bool use_write_multiple_; }; +class ModbusBinaryOutput : public output::BinaryOutput, public Component, public SensorItem { + public: + ModbusBinaryOutput(uint16_t start_address, uint8_t offset) : output::BinaryOutput(), Component() { + this->register_type = ModbusRegisterType::COIL; + this->start_address = start_address; + this->bitmask = bitmask; + this->sensor_value_type = SensorValueType::BIT; + this->skip_updates = 0; + this->register_count = 1; + this->start_address += offset; + this->offset = 0; + } + void dump_config() override; + + void set_parent(ModbusController *parent) { this->parent_ = parent; } + // Do nothing + void parse_and_publish(const std::vector &data) override{}; + + using write_transform_func_t = std::function(ModbusBinaryOutput *, bool, std::vector &)>; + void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } + + protected: + void write_state(bool state) override; + optional write_transform_func_{nullopt}; + + ModbusController *parent_; + bool use_write_multiple_; +}; + } // namespace modbus_controller } // namespace esphome From c855bc31b477c4d3557da977d706ecd5ec1b3606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sn=C5=8Dwball?= Date: Tue, 4 Jan 2022 10:38:58 +0100 Subject: [PATCH 507/549] Add bl0940 component used by e.g. tuya devices (#1904) Co-authored-by: Oxan van Leeuwen --- CODEOWNERS | 1 + esphome/components/bl0940/__init__.py | 1 + esphome/components/bl0940/bl0940.cpp | 137 ++++++++++++++++++++++++++ esphome/components/bl0940/bl0940.h | 109 ++++++++++++++++++++ esphome/components/bl0940/sensor.py | 106 ++++++++++++++++++++ tests/test3.yaml | 16 ++- 6 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 esphome/components/bl0940/__init__.py create mode 100644 esphome/components/bl0940/bl0940.cpp create mode 100644 esphome/components/bl0940/bl0940.h create mode 100644 esphome/components/bl0940/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index c3ca5410b1..7f2aef7be8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -28,6 +28,7 @@ esphome/components/b_parasite/* @rbaron esphome/components/ballu/* @bazuchan esphome/components/bang_bang/* @OttoWinter esphome/components/binary_sensor/* @esphome/core +esphome/components/bl0940/* @tobias- esphome/components/ble_client/* @buxtronix esphome/components/bme680_bsec/* @trvrnrth esphome/components/button/* @esphome/core diff --git a/esphome/components/bl0940/__init__.py b/esphome/components/bl0940/__init__.py new file mode 100644 index 0000000000..087626a4e7 --- /dev/null +++ b/esphome/components/bl0940/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@tobias-"] diff --git a/esphome/components/bl0940/bl0940.cpp b/esphome/components/bl0940/bl0940.cpp new file mode 100644 index 0000000000..19672e98d0 --- /dev/null +++ b/esphome/components/bl0940/bl0940.cpp @@ -0,0 +1,137 @@ +#include "bl0940.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace bl0940 { + +static const char *const TAG = "bl0940"; + +static const uint8_t BL0940_READ_COMMAND = 0x50; // 0x58 according to documentation +static const uint8_t BL0940_FULL_PACKET = 0xAA; +static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to documentation + +static const uint8_t BL0940_WRITE_COMMAND = 0xA0; // 0xA8 according to documentation +static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10; +static const uint8_t BL0940_REG_MODE = 0x18; +static const uint8_t BL0940_REG_SOFT_RESET = 0x19; +static const uint8_t BL0940_REG_USR_WRPROT = 0x1A; +static const uint8_t BL0940_REG_TPS_CTRL = 0x1B; + +const uint8_t BL0940_INIT[5][6] = { + // Reset to default + {BL0940_WRITE_COMMAND, BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38}, + // Enable User Operation Write + {BL0940_WRITE_COMMAND, BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0}, + // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS + {BL0940_WRITE_COMMAND, BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37}, + // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS + {BL0940_WRITE_COMMAND, BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE}, + // 0x181C = Half cycle, Fast RMS threshold 6172 + {BL0940_WRITE_COMMAND, BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}}; + +void BL0940::loop() { + DataPacket buffer; + if (!this->available()) { + return; + } + if (read_array((uint8_t *) &buffer, sizeof(buffer))) { + if (validate_checksum(&buffer)) { + received_package_(&buffer); + } + } else { + ESP_LOGW(TAG, "Junk on wire. Throwing away partial message"); + while (read() >= 0) + ; + } +} + +bool BL0940::validate_checksum(const DataPacket *data) { + uint8_t checksum = BL0940_READ_COMMAND; + // Whole package but checksum + for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) { + checksum += data->raw[i]; + } + checksum ^= 0xFF; + if (checksum != data->checksum) { + ESP_LOGW(TAG, "BL0940 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum); + } + return checksum == data->checksum; +} + +void BL0940::update() { + this->flush(); + this->write_byte(BL0940_READ_COMMAND); + this->write_byte(BL0940_FULL_PACKET); +} + +void BL0940::setup() { + for (auto i : BL0940_INIT) { + this->write_array(i, 6); + delay(1); + } + this->flush(); +} + +float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const { + auto tb = (float) (temperature.h << 8 | temperature.l); + float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45; + if (sensor != nullptr) { + if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) { + ESP_LOGD("bl0940", "Invalid temperature change. Sensor: '%s', Old temperature: %f, New temperature: %f", + sensor->get_name().c_str(), sensor->get_state(), converted_temp); + return 0.0f; + } + sensor->publish_state(converted_temp); + } + return converted_temp; +} + +void BL0940::received_package_(const DataPacket *data) const { + // Bad header + if (data->frame_header != BL0940_PACKET_HEADER) { + ESP_LOGI("bl0940", "Invalid data. Header mismatch: %d", data->frame_header); + return; + } + + float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_; + float i_rms = (float) to_uint32_t(data->i_rms) / current_reference_; + float watt = (float) to_int32_t(data->watt) / power_reference_; + uint32_t cf_cnt = to_uint32_t(data->cf_cnt); + float total_energy_consumption = (float) cf_cnt / energy_reference_; + + float tps1 = update_temp_(internal_temperature_sensor_, data->tps1); + float tps2 = update_temp_(external_temperature_sensor_, data->tps2); + + if (voltage_sensor_ != nullptr) { + voltage_sensor_->publish_state(v_rms); + } + if (current_sensor_ != nullptr) { + current_sensor_->publish_state(i_rms); + } + if (power_sensor_ != nullptr) { + power_sensor_->publish_state(watt); + } + if (energy_sensor_ != nullptr) { + energy_sensor_->publish_state(total_energy_consumption); + } + + ESP_LOGV("bl0940", "BL0940: U %fV, I %fA, P %fW, Cnt %d, ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt, + total_energy_consumption, tps1, tps2); +} + +void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexity) + ESP_LOGCONFIG(TAG, "BL0940:"); + LOG_SENSOR("", "Voltage", this->voltage_sensor_); + LOG_SENSOR("", "Current", this->current_sensor_); + LOG_SENSOR("", "Power", this->power_sensor_); + LOG_SENSOR("", "Energy", this->energy_sensor_); + LOG_SENSOR("", "Internal temperature", this->internal_temperature_sensor_); + LOG_SENSOR("", "External temperature", this->external_temperature_sensor_); +} + +uint32_t BL0940::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; } + +int32_t BL0940::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; } + +} // namespace bl0940 +} // namespace esphome diff --git a/esphome/components/bl0940/bl0940.h b/esphome/components/bl0940/bl0940.h new file mode 100644 index 0000000000..49c8e50595 --- /dev/null +++ b/esphome/components/bl0940/bl0940.h @@ -0,0 +1,109 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace bl0940 { + +static const float BL0940_PREF = 1430; +static const float BL0940_UREF = 33000; +static const float BL0940_IREF = 275000; // 2750 from tasmota. Seems to generate values 100 times too high + +// Measured to 297J per click according to power consumption of 5 minutes +// Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4 +static const float BL0940_EREF = 3.6e6 / 297; + +struct ube24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align) + uint8_t l; + uint8_t m; + uint8_t h; +} __attribute__((packed)); + +struct ube16_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align) + uint8_t l; + uint8_t h; +} __attribute__((packed)); + +struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align) + uint8_t l; + uint8_t m; + int8_t h; +} __attribute__((packed)); + +// Caveat: All these values are big endian (low - middle - high) + +union DataPacket { // NOLINT(altera-struct-pack-align) + uint8_t raw[35]; + struct { + uint8_t frame_header; // value of 0x58 according to docs. 0x55 according to Tasmota real world tests. Reality wins. + ube24_t i_fast_rms; // 0x00 + ube24_t i_rms; // 0x04 + ube24_t RESERVED0; // reserved + ube24_t v_rms; // 0x06 + ube24_t RESERVED1; // reserved + sbe24_t watt; // 0x08 + ube24_t RESERVED2; // reserved + ube24_t cf_cnt; // 0x0A + ube24_t RESERVED3; // reserved + ube16_t tps1; // 0x0c + uint8_t RESERVED4; // value of 0x00 + ube16_t tps2; // 0x0c + uint8_t RESERVED5; // value of 0x00 + uint8_t checksum; // checksum + }; +} __attribute__((packed)); + +class BL0940 : public PollingComponent, public uart::UARTDevice { + public: + 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; } + void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } + void set_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) { + internal_temperature_sensor_ = internal_temperature_sensor; + } + void set_external_temperature_sensor(sensor::Sensor *external_temperature_sensor) { + external_temperature_sensor_ = external_temperature_sensor; + } + + void loop() override; + + void update() override; + void setup() override; + void dump_config() override; + + protected: + sensor::Sensor *voltage_sensor_; + sensor::Sensor *current_sensor_; + // NB This may be negative as the circuits is seemingly able to measure + // power in both directions + sensor::Sensor *power_sensor_; + sensor::Sensor *energy_sensor_; + sensor::Sensor *internal_temperature_sensor_; + sensor::Sensor *external_temperature_sensor_; + + // Max difference between two measurements of the temperature. Used to avoid noise. + float max_temperature_diff_{0}; + // Divide by this to turn into Watt + float power_reference_ = BL0940_PREF; + // Divide by this to turn into Volt + float voltage_reference_ = BL0940_UREF; + // Divide by this to turn into Ampere + float current_reference_ = BL0940_IREF; + // Divide by this to turn into kWh + float energy_reference_ = BL0940_EREF; + + float update_temp_(sensor::Sensor *sensor, ube16_t packed_temperature) const; + + static uint32_t to_uint32_t(ube24_t input); + + static int32_t to_int32_t(sbe24_t input); + + static bool validate_checksum(const DataPacket *data); + + void received_package_(const DataPacket *data) const; +}; +} // namespace bl0940 +} // namespace esphome diff --git a/esphome/components/bl0940/sensor.py b/esphome/components/bl0940/sensor.py new file mode 100644 index 0000000000..ce630b7408 --- /dev/null +++ b/esphome/components/bl0940/sensor.py @@ -0,0 +1,106 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, uart +from esphome.const import ( + CONF_CURRENT, + CONF_ENERGY, + CONF_ID, + CONF_POWER, + CONF_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_NONE, + UNIT_AMPERE, + UNIT_CELSIUS, + UNIT_KILOWATT_HOURS, + UNIT_VOLT, + UNIT_WATT, +) + +DEPENDENCIES = ["uart"] + +CONF_INTERNAL_TEMPERATURE = "internal_temperature" +CONF_EXTERNAL_TEMPERATURE = "external_temperature" + +bl0940_ns = cg.esphome_ns.namespace("bl0940") +BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(BL0940), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + UNIT_AMPERE, + ICON_EMPTY, + 2, + DEVICE_CLASS_CURRENT, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + cv.Optional(CONF_ENERGY): sensor.sensor_schema( + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 0, + DEVICE_CLASS_ENERGY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, + ICON_EMPTY, + 0, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_EXTERNAL_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, + ICON_EMPTY, + 0, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_NONE, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .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) + + if CONF_VOLTAGE in config: + conf = config[CONF_VOLTAGE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_voltage_sensor(sens)) + if CONF_CURRENT in config: + conf = config[CONF_CURRENT] + sens = await sensor.new_sensor(conf) + cg.add(var.set_current_sensor(sens)) + if CONF_POWER in config: + conf = config[CONF_POWER] + sens = await sensor.new_sensor(conf) + cg.add(var.set_power_sensor(sens)) + if CONF_ENERGY in config: + conf = config[CONF_ENERGY] + sens = await sensor.new_sensor(conf) + cg.add(var.set_energy_sensor(sens)) + if CONF_INTERNAL_TEMPERATURE in config: + conf = config[CONF_INTERNAL_TEMPERATURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_internal_temperature_sensor(sens)) + if CONF_EXTERNAL_TEMPERATURE in config: + conf = config[CONF_EXTERNAL_TEMPERATURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_external_temperature_sensor(sens)) diff --git a/tests/test3.yaml b/tests/test3.yaml index 61d68d824b..01600ad74b 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -455,10 +455,24 @@ sensor: active_power_b: name: ADE7953 Active Power B id: ade7953_active_power_b + - platform: bl0940 + uart_id: uart3 + voltage: + name: 'BL0940 Voltage' + current: + name: 'BL0940 Current' + power: + name: 'BL0940 Power' + energy: + name: 'BL0940 Energy' + internal_temperature: + name: 'BL0940 Internal temperature' + external_temperature: + name: 'BL0940 External temperature' - platform: pzem004t uart_id: uart3 voltage: - name: 'PZEM00T Voltage' + name: 'PZEM004T Voltage' current: name: 'PZEM004T Current' power: From c8f4fbb7ddb0498e83ef06dabce2e9448bb08292 Mon Sep 17 00:00:00 2001 From: Gonzalo Paniagua Javier Date: Tue, 4 Jan 2022 05:02:53 -0500 Subject: [PATCH 508/549] Honor user set values for col/row start for INITR_MINI_160X80. (#2976) If the caller sets a value for colstart and/or rowstart when using the INITR_MINI_160X80 model, use those values instead of the default 24 and 0. After this patch devices with a 160x80 TFT like the m5stick C can set row/col start (26, 1 for m5stick) and avoid garbage lines showing in the display. --- esphome/components/st7735/st7735.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index c5178986f3..a0c2d80d16 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -265,8 +265,8 @@ void ST7735::setup() { height_ == 0 ? height_ = ST7735_TFTHEIGHT_160 : height_; width_ == 0 ? width_ = ST7735_TFTWIDTH_80 : width_; display_init_(RCMD2GREEN160X80); - colstart_ = 24; - rowstart_ = 0; // For default rotation 0 + colstart_ == 0 ? colstart_ = 24 : colstart_; + rowstart_ == 0 ? rowstart_ = 0 : rowstart_; } else { // colstart, rowstart left at default '0' values display_init_(RCMD2RED); From 193d3e02068a8a25752b77957c8341cecc0c852e Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 4 Jan 2022 20:34:17 +0100 Subject: [PATCH 509/549] Fix clang-tidy with multiple ESP32 toolchains installed (#2998) --- script/clang-tidy | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/script/clang-tidy b/script/clang-tidy index 7450084634..8ad25a3dbf 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -58,10 +58,14 @@ def clang_options(idedata): # defines cmd.extend(f'-D{define}' for define in idedata['defines']) - # add include directories, using -isystem for dependencies to suppress their errors + # add toolchain include directories using -isystem + # idedata contains include directories for all toolchains of this platform, only use those from the one in use + toolchain_dir = os.path.normpath(f"{idedata['cxx_path']}/../../") for directory in idedata['includes']['toolchain']: - if 'xtensa-esp32s2-elf' not in directory: + if directory.startswith(toolchain_dir): cmd.extend(['-isystem', directory]) + + # add include directories, using -isystem for dependencies to suppress their errors 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]) From ffea3597f4ac7481121d0cd3abb4e67d7346c775 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 4 Jan 2022 21:59:34 +0100 Subject: [PATCH 510/549] Set correct include_dir in platformio.ini (#2999) --- platformio.ini | 7 ++++++- script/clang-tidy | 14 +++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/platformio.ini b/platformio.ini index d26b3c9c90..1e39254434 100644 --- a/platformio.ini +++ b/platformio.ini @@ -5,8 +5,13 @@ [platformio] default_envs = esp8266, esp32, esp32-idf +; Ideally, we want src_dir to be the root directory of the repository, to mimic the runtime build +; environment as best as possible. Unfortunately, the ESP-IDF toolchain really doesn't like this +; being the root directory. Instead, set esphome/ as the source directory, all our sources are in +; there anyway. Set the root directory as the include_dir, so that the esphome/ directory is on the +; include path. src_dir = esphome -include_dir = +include_dir = . [runtime] ; This are the flags as set by the runtime. diff --git a/script/clang-tidy b/script/clang-tidy index 8ad25a3dbf..e6165ff23b 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from helpers import print_error_for_file, get_output, filter_grep, \ - build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata, basepath + build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata, root_path, basepath import argparse import click import colorama @@ -58,17 +58,21 @@ def clang_options(idedata): # defines cmd.extend(f'-D{define}' for define in idedata['defines']) - # add toolchain include directories using -isystem + # add toolchain include directories using -isystem to suppress their errors # idedata contains include directories for all toolchains of this platform, only use those from the one in use toolchain_dir = os.path.normpath(f"{idedata['cxx_path']}/../../") for directory in idedata['includes']['toolchain']: if directory.startswith(toolchain_dir): cmd.extend(['-isystem', directory]) - # add include directories, using -isystem for dependencies to suppress their errors + # add library include directories using -isystem to suppress their errors 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]) + # skip our own directories, we add those later + if not directory.startswith(f"{root_path}/") or directory.startswith(f"{root_path}/.pio/"): + cmd.extend(['-isystem', directory]) + + # add the esphome include directory using -I + cmd.extend(['-I', root_path]) return cmd From ed5930e934c639a6de9e5bd18513aa5d52e11590 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Tue, 4 Jan 2022 22:05:19 +0100 Subject: [PATCH 511/549] SGP40 - Reduce delay in measurement (#2996) --- esphome/components/sgp40/sgp40.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index 9561efcde2..da6659c90f 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -211,7 +211,7 @@ uint16_t SGP40Component::measure_raw_() { ESP_LOGD(TAG, "write error"); return UINT16_MAX; } - delay(250); // NOLINT + delay(30); uint16_t raw_data[1]; if (!this->read_data_(raw_data, 1)) { From 3067e482fcfdff16815ddf3d898b1b82dc3dd4c7 Mon Sep 17 00:00:00 2001 From: mknjc Date: Wed, 5 Jan 2022 04:43:37 +0100 Subject: [PATCH 512/549] atc mithermometer: Add possibility to report signal strength (#3000) --- .../atc_mithermometer/atc_mithermometer.cpp | 2 ++ .../atc_mithermometer/atc_mithermometer.h | 2 ++ esphome/components/atc_mithermometer/sensor.py | 13 +++++++++++++ esphome/const.py | 1 + 4 files changed, 18 insertions(+) diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.cpp b/esphome/components/atc_mithermometer/atc_mithermometer.cpp index 42c30598ad..9d550fcf8c 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.cpp +++ b/esphome/components/atc_mithermometer/atc_mithermometer.cpp @@ -45,6 +45,8 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device this->battery_voltage_->publish_state(*res->battery_voltage); success = true; } + if (this->signal_strength_ != nullptr) + this->signal_strength_->publish_state(device.get_rssi()); return success; } diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.h b/esphome/components/atc_mithermometer/atc_mithermometer.h index ca079bf8c1..9398c02bcf 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.h +++ b/esphome/components/atc_mithermometer/atc_mithermometer.h @@ -28,6 +28,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; } + void set_signal_strength(sensor::Sensor *signal_strength) { signal_strength_ = signal_strength; } protected: uint64_t address_; @@ -35,6 +36,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice sensor::Sensor *humidity_{nullptr}; sensor::Sensor *battery_level_{nullptr}; sensor::Sensor *battery_voltage_{nullptr}; + sensor::Sensor *signal_strength_{nullptr}; optional parse_header_(const esp32_ble_tracker::ServiceData &service_data); bool parse_message_(const std::vector &message, ParseResult &result); diff --git a/esphome/components/atc_mithermometer/sensor.py b/esphome/components/atc_mithermometer/sensor.py index bde83c28b6..7baab51944 100644 --- a/esphome/components/atc_mithermometer/sensor.py +++ b/esphome/components/atc_mithermometer/sensor.py @@ -6,15 +6,18 @@ from esphome.const import ( CONF_BATTERY_VOLTAGE, CONF_MAC_ADDRESS, CONF_HUMIDITY, + CONF_SIGNAL_STRENGTH, CONF_TEMPERATURE, CONF_ID, DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, + UNIT_DECIBEL_MILLIWATT, UNIT_PERCENT, UNIT_VOLT, ) @@ -59,6 +62,13 @@ CONFIG_SCHEMA = ( state_class=STATE_CLASS_MEASUREMENT, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), + cv.Optional(CONF_SIGNAL_STRENGTH): sensor.sensor_schema( + unit_of_measurement=UNIT_DECIBEL_MILLIWATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_SIGNAL_STRENGTH, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), } ) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) @@ -85,3 +95,6 @@ async def to_code(config): if CONF_BATTERY_VOLTAGE in config: sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE]) cg.add(var.set_battery_voltage(sens)) + if CONF_SIGNAL_STRENGTH in config: + sens = await sensor.new_sensor(config[CONF_SIGNAL_STRENGTH]) + cg.add(var.set_signal_strength(sens)) diff --git a/esphome/const.py b/esphome/const.py index 970b3c6578..03f1da0e91 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -599,6 +599,7 @@ CONF_SHOW_VALUES = "show_values" CONF_SHUNT_RESISTANCE = "shunt_resistance" CONF_SHUNT_VOLTAGE = "shunt_voltage" CONF_SHUTDOWN_MESSAGE = "shutdown_message" +CONF_SIGNAL_STRENGTH = "signal_strength" CONF_SINGLE_LIGHT_ID = "single_light_id" CONF_SIZE = "size" CONF_SLEEP_DURATION = "sleep_duration" From d8e719d1c4e5f479f30fca22e23001ac2032e902 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 5 Jan 2022 21:30:15 +0100 Subject: [PATCH 513/549] Support clang-tidy for ESP32 variants (#3001) --- .github/workflows/ci.yml | 20 ++++----- platformio.ini | 87 ++++++++++++++++++++++++++++++---------- script/clang-tidy | 24 +++++++---- 3 files changed, 91 insertions(+), 40 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 93a29874f7..9473dc87dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,26 +51,26 @@ jobs: name: Run script/clang-format - id: clang-tidy name: Run script/clang-tidy for ESP8266 - options: --environment esp8266-tidy --grep USE_ESP8266 + options: --environment esp8266-arduino-tidy --grep USE_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 + name: Run script/clang-tidy for ESP32 Arduino 1/4 + options: --environment esp32-arduino-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 + name: Run script/clang-tidy for ESP32 Arduino 2/4 + options: --environment esp32-arduino-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 + name: Run script/clang-tidy for ESP32 Arduino 3/4 + options: --environment esp32-arduino-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 + name: Run script/clang-tidy for ESP32 Arduino 4/4 + options: --environment esp32-arduino-tidy --split-num 4 --split-at 4 pio_cache_key: tidyesp32 - id: clang-tidy - name: Run script/clang-tidy for ESP32 esp-idf + name: Run script/clang-tidy for ESP32 IDF options: --environment esp32-idf-tidy --grep USE_ESP_IDF pio_cache_key: tidyesp32-idf diff --git a/platformio.ini b/platformio.ini index 1e39254434..1a81b4e75a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -4,7 +4,7 @@ ; It's *not* used during runtime. [platformio] -default_envs = esp8266, esp32, esp32-idf +default_envs = esp8266-arduino, esp32-arduino, esp32-idf ; Ideally, we want src_dir to be the root directory of the repository, to mimic the runtime build ; environment as best as possible. Unfortunately, the ESP-IDF toolchain really doesn't like this ; being the root directory. Instead, set esphome/ as the source directory, all our sources are in @@ -13,14 +13,14 @@ default_envs = esp8266, esp32, esp32-idf src_dir = esphome include_dir = . -[runtime] -; This are the flags as set by the runtime. +; This are just the build flags as set by the runtime. +[flags:runtime] build_flags = -Wno-unused-but-set-variable -Wno-sign-compare -[clangtidy] -; This are the flags for clang-tidy. +; This are just the build flags for clang-tidy. +[flags:clangtidy] build_flags = -Wall -Wextra @@ -30,6 +30,7 @@ build_flags = -Wshadow-field-in-constructor -Wshadow-uncaptured-local +; This are common settings for all environments. [common] lib_deps = esphome/noise-c@0.1.4 ; api @@ -43,6 +44,7 @@ src_filter = +<../tests/dummy_main.cpp> +<../.temp/all-include.cpp> +; This are common settings for all Arduino-framework based environments. [common:arduino] extends = common lib_deps = @@ -62,13 +64,15 @@ build_flags = ${common.build_flags} -DUSE_ARDUINO +; This are common settings for all IDF-framework based environments. [common:idf] extends = common build_flags = ${common.build_flags} -DUSE_ESP_IDF -[common:esp8266] +; This are common settings for the ESP8266 using Arduino. +[common:esp8266-arduino] extends = common:arduino ; when changing this also copy it to esphome-docker-base images platform = platformio/espressif8266 @ 3.2.0 @@ -76,7 +80,6 @@ platform_packages = platformio/framework-arduinoespressif8266 @ ~3.30002.0 framework = arduino -board = nodemcuv2 lib_deps = ${common:arduino.lib_deps} ESP8266WiFi ; wifi (Arduino built-in) @@ -87,6 +90,7 @@ build_flags = -DUSE_ESP8266 -DUSE_ESP8266_FRAMEWORK_ARDUINO +; This are common settings for the ESP32 (all variants) using Arduino. [common:esp32-arduino] extends = common:arduino ; when changing this also copy it to esphome-docker-base images @@ -104,6 +108,7 @@ build_flags = -DUSE_ESP32 -DUSE_ESP32_FRAMEWORK_ARDUINO +; This are common settings for the ESP32 (all variants) using IDF. [common:esp32-idf] extends = common:idf ; when changing this also copy it to esphome-docker-base images @@ -112,7 +117,6 @@ platform_packages = platformio/framework-espidf @ ~3.40300.0 framework = espidf -board = nodemcu-32s lib_deps = ${common:idf.lib_deps} espressif/esp32-camera@1.0.0 ; esp32_camera @@ -122,40 +126,79 @@ build_flags = -DUSE_ESP32 -DUSE_ESP32_FRAMEWORK_ESP_IDF -[env:esp8266] -extends = common:esp8266 +; All the actual environments are defined below. +[env:esp8266-arduino] +extends = common:esp8266-arduino +board = nodemcuv2 build_flags = - ${common:esp8266.build_flags} - ${runtime.build_flags} + ${common:esp8266-arduino.build_flags} + ${flags:runtime.build_flags} -[env:esp8266-tidy] -extends = common:esp8266 +[env:esp8266-arduino-tidy] +extends = common:esp8266-arduino +board = nodemcuv2 build_flags = - ${common:esp8266.build_flags} - ${clangtidy.build_flags} + ${common:esp8266-arduino.build_flags} + ${flags:clangtidy.build_flags} -[env:esp32] +[env:esp32-arduino] extends = common:esp32-arduino +board = esp32dev build_flags = ${common:esp32-arduino.build_flags} - ${runtime.build_flags} + ${flags:runtime.build_flags} -[env:esp32-tidy] +[env:esp32-arduino-tidy] extends = common:esp32-arduino +board = esp32dev build_flags = ${common:esp32-arduino.build_flags} - ${clangtidy.build_flags} + ${flags:clangtidy.build_flags} [env:esp32-idf] extends = common:esp32-idf +board = esp32dev board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32-idf build_flags = ${common:esp32-idf.build_flags} - ${runtime.build_flags} + ${flags:runtime.build_flags} [env:esp32-idf-tidy] extends = common:esp32-idf +board = esp32dev board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32-idf-tidy build_flags = ${common:esp32-idf.build_flags} - ${clangtidy.build_flags} + ${flags:clangtidy.build_flags} + +[env:esp32c3-idf] +extends = common:esp32-idf +board = esp32-c3-devkitm-1 +board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32c3-idf +build_flags = + ${common:esp32-idf.build_flags} + ${flags:runtime.build_flags} + +[env:esp32c3-idf-tidy] +extends = common:esp32-idf +board = esp32-c3-devkitm-1 +board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32c3-idf-tidy +build_flags = + ${common:esp32-idf.build_flags} + ${flags:clangtidy.build_flags} + +[env:esp32s2-idf] +extends = common:esp32-idf +board = esp32-s2-kaluga-1 +board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s2-idf +build_flags = + ${common:esp32-idf.build_flags} + ${flags:runtime.build_flags} + +[env:esp32s2-idf-tidy] +extends = common:esp32-idf +board = esp32-s2-kaluga-1 +board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s2-idf-tidy +build_flags = + ${common:esp32-idf.build_flags} + ${flags:clangtidy.build_flags} diff --git a/script/clang-tidy b/script/clang-tidy index e6165ff23b..8a7d229887 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -17,9 +17,19 @@ import threading def clang_options(idedata): - cmd = [ - # target 32-bit arch (this prevents size mismatch errors on a 64-bit host) - '-m32', + cmd = [] + + # extract target architecture from triplet in g++ filename + triplet = os.path.basename(idedata['cxx_path'])[:-4] + if triplet.startswith("xtensa-"): + # clang doesn't support Xtensa (yet?), so compile in 32-bit mode and pretend we're the Xtensa compiler + cmd.append('-m32') + cmd.append('-D__XTENSA__') + else: + cmd.append(f'--target={triplet}') + + # set flags + cmd.extend([ # disable built-in include directories from the host '-nostdinc', '-nostdinc++', @@ -39,15 +49,13 @@ def clang_options(idedata): # 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 '-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. cmd.extend(flag for flag in idedata['cxx_flags'].split(' ') @@ -126,8 +134,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='esp32-tidy', - help='the PlatformIO environment to run against (esp8266-tidy or esp32-tidy)') + parser.add_argument('-e', '--environment', default='esp32-arduino-tidy', + help='the PlatformIO environment to use (as defined in platformio.ini)') 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') From df929f9445ec147639b2e81f3f772b46b433dd91 Mon Sep 17 00:00:00 2001 From: Pavel Skuratovich Date: Wed, 5 Jan 2022 23:31:11 +0300 Subject: [PATCH 514/549] Fix SlowPWM output switch at the end of period (#2984) --- esphome/components/slow_pwm/slow_pwm_output.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/slow_pwm/slow_pwm_output.cpp b/esphome/components/slow_pwm/slow_pwm_output.cpp index 9b2589e735..8c88b3537c 100644 --- a/esphome/components/slow_pwm/slow_pwm_output.cpp +++ b/esphome/components/slow_pwm/slow_pwm_output.cpp @@ -15,7 +15,7 @@ void SlowPWMOutput::loop() { uint32_t now = millis(); float scaled_state = this->state_ * this->period_; - if (now - this->period_start_time_ > this->period_) { + if (now - this->period_start_time_ >= this->period_) { ESP_LOGVV(TAG, "End of period. State: %f, Scaled state: %f", this->state_, scaled_state); this->period_start_time_ += this->period_; } From 5e1e543b06c384ddb7e8a2a9fe54ac5ed2d5f34e Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Thu, 6 Jan 2022 03:01:50 +0100 Subject: [PATCH 515/549] Add support for BMP388 / BMP 390 pressure and temperature sensor (#2716) --- CODEOWNERS | 1 + esphome/components/bmp3xx/__init__.py | 0 esphome/components/bmp3xx/bmp3xx.cpp | 388 ++++++++++++++++++++++++++ esphome/components/bmp3xx/bmp3xx.h | 237 ++++++++++++++++ esphome/components/bmp3xx/sensor.py | 100 +++++++ tests/test5.yaml | 9 + 6 files changed, 735 insertions(+) create mode 100644 esphome/components/bmp3xx/__init__.py create mode 100644 esphome/components/bmp3xx/bmp3xx.cpp create mode 100644 esphome/components/bmp3xx/bmp3xx.h create mode 100644 esphome/components/bmp3xx/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 7f2aef7be8..f47b36ff73 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -31,6 +31,7 @@ esphome/components/binary_sensor/* @esphome/core esphome/components/bl0940/* @tobias- esphome/components/ble_client/* @buxtronix esphome/components/bme680_bsec/* @trvrnrth +esphome/components/bmp3xx/* @martgras esphome/components/button/* @esphome/core esphome/components/canbus/* @danielschramm @mvturnho esphome/components/cap1188/* @MrEditor97 diff --git a/esphome/components/bmp3xx/__init__.py b/esphome/components/bmp3xx/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/bmp3xx/bmp3xx.cpp b/esphome/components/bmp3xx/bmp3xx.cpp new file mode 100644 index 0000000000..410b7a3173 --- /dev/null +++ b/esphome/components/bmp3xx/bmp3xx.cpp @@ -0,0 +1,388 @@ +/* + based on BMP388_DEV by Martin Lindupp + under MIT License (MIT) + Copyright (C) Martin Lindupp 2020 + http://github.com/MartinL1/BMP388_DEV +*/ + +#include "bmp3xx.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace bmp3xx { + +static const char *const TAG = "bmp3xx.sensor"; + +static const LogString *chip_type_to_str(uint8_t chip_type) { + switch (chip_type) { + case BMP388_ID: + return LOG_STR("BMP 388"); + case BMP390_ID: + return LOG_STR("BMP 390"); + default: + return LOG_STR("Unknown Chip Type"); + } +} + +static const LogString *oversampling_to_str(Oversampling oversampling) { + switch (oversampling) { + case Oversampling::OVERSAMPLING_NONE: + return LOG_STR("None"); + case Oversampling::OVERSAMPLING_X2: + return LOG_STR("2x"); + case Oversampling::OVERSAMPLING_X4: + return LOG_STR("4x"); + case Oversampling::OVERSAMPLING_X8: + return LOG_STR("8x"); + case Oversampling::OVERSAMPLING_X16: + return LOG_STR("16x"); + case Oversampling::OVERSAMPLING_X32: + return LOG_STR("32x"); + default: + return LOG_STR(""); + } +} + +static const LogString *iir_filter_to_str(IIRFilter filter) { + switch (filter) { + case IIRFilter::IIR_FILTER_OFF: + return LOG_STR("OFF"); + case IIRFilter::IIR_FILTER_2: + return LOG_STR("2x"); + case IIRFilter::IIR_FILTER_4: + return LOG_STR("4x"); + case IIRFilter::IIR_FILTER_8: + return LOG_STR("8x"); + case IIRFilter::IIR_FILTER_16: + return LOG_STR("16x"); + case IIRFilter::IIR_FILTER_32: + return LOG_STR("32x"); + case IIRFilter::IIR_FILTER_64: + return LOG_STR("64x"); + case IIRFilter::IIR_FILTER_128: + return LOG_STR("128x"); + default: + return LOG_STR(""); + } +} + +void BMP3XXComponent::setup() { + this->error_code_ = NONE; + ESP_LOGCONFIG(TAG, "Setting up BMP3XX..."); + // Call the Device base class "initialise" function + if (!reset()) { + ESP_LOGE(TAG, "Failed to reset BMP3XX..."); + this->error_code_ = ERROR_SENSOR_RESET; + this->mark_failed(); + } + + if (!read_byte(BMP388_CHIP_ID, &this->chip_id_.reg)) { + ESP_LOGE(TAG, "Can't read chip id"); + this->error_code_ = ERROR_COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + ESP_LOGCONFIG(TAG, "Chip %s Id 0x%X", LOG_STR_ARG(chip_type_to_str(this->chip_id_.reg)), this->chip_id_.reg); + + if (chip_id_.reg != BMP388_ID && chip_id_.reg != BMP390_ID) { + ESP_LOGE(TAG, "Unknown chip id - is this really a BMP388 or BMP390?"); + this->error_code_ = ERROR_WRONG_CHIP_ID; + this->mark_failed(); + return; + } + // set sensor in sleep mode + stop_conversion(); + // Read the calibration parameters into the params structure + if (!read_bytes(BMP388_TRIM_PARAMS, (uint8_t *) &compensation_params_, sizeof(compensation_params_))) { + ESP_LOGE(TAG, "Can't read calibration data"); + this->error_code_ = ERROR_COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + compensation_float_params_.param_T1 = + (float) compensation_params_.param_T1 / powf(2.0f, -8.0f); // Calculate the floating point trim parameters + compensation_float_params_.param_T2 = (float) compensation_params_.param_T2 / powf(2.0f, 30.0f); + compensation_float_params_.param_T3 = (float) compensation_params_.param_T3 / powf(2.0f, 48.0f); + compensation_float_params_.param_P1 = ((float) compensation_params_.param_P1 - powf(2.0f, 14.0f)) / powf(2.0f, 20.0f); + compensation_float_params_.param_P2 = ((float) compensation_params_.param_P2 - powf(2.0f, 14.0f)) / powf(2.0f, 29.0f); + compensation_float_params_.param_P3 = (float) compensation_params_.param_P3 / powf(2.0f, 32.0f); + compensation_float_params_.param_P4 = (float) compensation_params_.param_P4 / powf(2.0f, 37.0f); + compensation_float_params_.param_P5 = (float) compensation_params_.param_P5 / powf(2.0f, -3.0f); + compensation_float_params_.param_P6 = (float) compensation_params_.param_P6 / powf(2.0f, 6.0f); + compensation_float_params_.param_P7 = (float) compensation_params_.param_P7 / powf(2.0f, 8.0f); + compensation_float_params_.param_P8 = (float) compensation_params_.param_P8 / powf(2.0f, 15.0f); + compensation_float_params_.param_P9 = (float) compensation_params_.param_P9 / powf(2.0f, 48.0f); + compensation_float_params_.param_P10 = (float) compensation_params_.param_P10 / powf(2.0f, 48.0f); + compensation_float_params_.param_P11 = (float) compensation_params_.param_P11 / powf(2.0f, 65.0f); + + // Initialise the BMP388 IIR filter register + if (!set_iir_filter(this->iir_filter_)) { + ESP_LOGE(TAG, "Failed to set IIR filter"); + this->error_code_ = ERROR_COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + + // Set power control registers + pwr_ctrl_.bit.press_en = 1; + pwr_ctrl_.bit.temp_en = 1; + // Disable pressure if no sensor defined + // keep temperature enabled since it's needed for compensation + if (this->pressure_sensor_ == nullptr) { + pwr_ctrl_.bit.press_en = 0; + this->pressure_oversampling_ = OVERSAMPLING_NONE; + } + // just disable oeversampling for temp if not used + if (this->temperature_sensor_ == nullptr) { + this->temperature_oversampling_ = OVERSAMPLING_NONE; + } + // Initialise the BMP388 oversampling register + if (!set_oversampling_register(this->pressure_oversampling_, this->temperature_oversampling_)) { + ESP_LOGE(TAG, "Failed to set oversampling register"); + this->error_code_ = ERROR_COMMUNICATION_FAILED; + this->mark_failed(); + return; + } +} + +void BMP3XXComponent::dump_config() { + ESP_LOGCONFIG(TAG, "BMP3XX:"); + ESP_LOGCONFIG(TAG, " Type: %s (0x%X)", LOG_STR_ARG(chip_type_to_str(this->chip_id_.reg)), this->chip_id_.reg); + LOG_I2C_DEVICE(this); + switch (this->error_code_) { + case NONE: + break; + case ERROR_COMMUNICATION_FAILED: + ESP_LOGE(TAG, "Communication with BMP3XX failed!"); + break; + case ERROR_WRONG_CHIP_ID: + ESP_LOGE( + TAG, + "BMP3XX has wrong chip ID (reported id: 0x%X) - please check if you are really using a BMP 388 or BMP 390", + this->chip_id_.reg); + break; + case ERROR_SENSOR_RESET: + ESP_LOGE(TAG, "BMP3XX failed to reset"); + break; + default: + ESP_LOGE(TAG, "BMP3XX error code %d", (int) this->error_code_); + break; + } + ESP_LOGCONFIG(TAG, " IIR Filter: %s", LOG_STR_ARG(iir_filter_to_str(this->iir_filter_))); + LOG_UPDATE_INTERVAL(this); + if (this->temperature_sensor_) { + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->temperature_oversampling_))); + } + if (this->pressure_sensor_) { + LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); + ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->pressure_oversampling_))); + } +} +float BMP3XXComponent::get_setup_priority() const { return setup_priority::DATA; } + +inline uint8_t oversampling_to_time(Oversampling over_sampling) { return (1 << uint8_t(over_sampling)); } + +void BMP3XXComponent::update() { + // Enable sensor + ESP_LOGV(TAG, "Sending conversion request..."); + float meas_time = 1.0f; + // Ref: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp390-ds002.pdf 3.9.2 + meas_time += 2.02f * oversampling_to_time(this->temperature_oversampling_) + 0.163f; + meas_time += 2.02f * oversampling_to_time(this->pressure_oversampling_) + 0.392f; + meas_time += 0.234f; + if (!set_mode(FORCED_MODE)) { + ESP_LOGE(TAG, "Failed start forced mode"); + this->mark_failed(); + return; + } + + ESP_LOGVV(TAG, "measurement time %d", uint32_t(ceilf(meas_time))); + this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { + float temperature = 0.0f; + float pressure = 0.0f; + if (this->pressure_sensor_ != nullptr) { + if (!get_measurements(temperature, pressure)) { + ESP_LOGW(TAG, "Failed to read pressure and temperature - skipping update"); + this->status_set_warning(); + return; + } + ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa", temperature, pressure); + } else { + if (!get_temperature(temperature)) { + ESP_LOGW(TAG, "Failed to read temperature - skipping update"); + this->status_set_warning(); + return; + } + ESP_LOGD(TAG, "Got temperature=%.1f°C", temperature); + } + + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(temperature); + if (this->pressure_sensor_ != nullptr) + this->pressure_sensor_->publish_state(pressure); + this->status_clear_warning(); + set_mode(SLEEP_MODE); + }); +} + +// Reset the BMP3XX +uint8_t BMP3XXComponent::reset() { + write_byte(BMP388_CMD, RESET_CODE); // Write the reset code to the command register + // Wait for 10ms + delay(10); + this->read_byte(BMP388_EVENT, &event_.reg); // Read the BMP388's event register + return event_.bit.por_detected; // Return if device reset is complete +} + +// Start a one shot measurement in FORCED_MODE +bool BMP3XXComponent::start_forced_conversion() { + // Only set FORCED_MODE if we're already in SLEEP_MODE + if (pwr_ctrl_.bit.mode == SLEEP_MODE) { + return set_mode(FORCED_MODE); + } + return true; +} + +// Stop the conversion and return to SLEEP_MODE +bool BMP3XXComponent::stop_conversion() { return set_mode(SLEEP_MODE); } + +// Set the pressure oversampling rate +bool BMP3XXComponent::set_pressure_oversampling(Oversampling oversampling) { + osr_.bit.osr_p = oversampling; + return this->write_byte(BMP388_OSR, osr_.reg); +} + +// Set the temperature oversampling rate +bool BMP3XXComponent::set_temperature_oversampling(Oversampling oversampling) { + osr_.bit.osr_t = oversampling; + return this->write_byte(BMP388_OSR, osr_.reg); +} + +// Set the IIR filter setting +bool BMP3XXComponent::set_iir_filter(IIRFilter iir_filter) { + config_.bit.iir_filter = iir_filter; + return this->write_byte(BMP388_CONFIG, config_.reg); +} + +// Get temperature +bool BMP3XXComponent::get_temperature(float &temperature) { + // Check if a measurement is ready + if (!data_ready()) { + return false; + } + uint8_t data[3]; + // Read the temperature + if (!this->read_bytes(BMP388_DATA_3, &data[0], 3)) { + ESP_LOGE(TAG, "Failed to read temperature"); + return false; + } + // Copy the temperature data into the adc variables + int32_t adc_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0]; + // Temperature compensation (function from BMP388 datasheet) + temperature = bmp388_compensate_temperature_((float) adc_temp); + return true; +} + +// Get the pressure +bool BMP3XXComponent::get_pressure(float &pressure) { + float temperature; + return get_measurements(temperature, pressure); +} + +// Get temperature and pressure +bool BMP3XXComponent::get_measurements(float &temperature, float &pressure) { + // Check if a measurement is ready + if (!data_ready()) { + ESP_LOGD(TAG, "BMP3XX Get measurement - data not ready skipping update"); + return false; + } + + uint8_t data[6]; + // Read the temperature and pressure data + if (!this->read_bytes(BMP388_DATA_0, &data[0], 6)) { + ESP_LOGE(TAG, "Failed to read measurements"); + return false; + } + // Copy the temperature and pressure data into the adc variables + int32_t adc_pres = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0]; + int32_t adc_temp = (int32_t) data[5] << 16 | (int32_t) data[4] << 8 | (int32_t) data[3]; + + // Temperature compensation (function from BMP388 datasheet) + temperature = bmp388_compensate_temperature_((float) adc_temp); + // Pressure compensation (function from BMP388 datasheet) + pressure = bmp388_compensate_pressure_((float) adc_pres, temperature); + // Calculate the pressure in millibar/hPa + pressure /= 100.0f; + return true; +} + +// Set the BMP388's mode in the power control register +bool BMP3XXComponent::set_mode(OperationMode mode) { + pwr_ctrl_.bit.mode = mode; + return this->write_byte(BMP388_PWR_CTRL, pwr_ctrl_.reg); +} + +// Set the BMP388 oversampling register +bool BMP3XXComponent::set_oversampling_register(Oversampling pressure_oversampling, + Oversampling temperature_oversampling) { + osr_.reg = temperature_oversampling << 3 | pressure_oversampling; + return this->write_byte(BMP388_OSR, osr_.reg); +} + +// Check if measurement data is ready +bool BMP3XXComponent::data_ready() { + // If we're in SLEEP_MODE return immediately + if (pwr_ctrl_.bit.mode == SLEEP_MODE) { + ESP_LOGD(TAG, "Not ready - sensor is in sleep mode"); + return false; + } + // Read the interrupt status register + uint8_t status; + if (!this->read_byte(BMP388_INT_STATUS, &status)) { + ESP_LOGE(TAG, "Failed to read status register"); + return false; + } + int_status_.reg = status; + ESP_LOGVV(TAG, "data ready status %d", status); + // If we're in FORCED_MODE switch back to SLEEP_MODE + if (int_status_.bit.drdy) { + if (pwr_ctrl_.bit.mode == FORCED_MODE) { + pwr_ctrl_.bit.mode = SLEEP_MODE; + } + return true; // The measurement is ready + } + return false; // The measurement is still pending +} + +//////////////////////////////////////////////////////////////////////////////// +// Bosch BMP3XXComponent (Private) Member Functions +//////////////////////////////////////////////////////////////////////////////// + +float BMP3XXComponent::bmp388_compensate_temperature_(float uncomp_temp) { + float partial_data1 = uncomp_temp - compensation_float_params_.param_T1; + float partial_data2 = partial_data1 * compensation_float_params_.param_T2; + return partial_data2 + partial_data1 * partial_data1 * compensation_float_params_.param_T3; +} + +float BMP3XXComponent::bmp388_compensate_pressure_(float uncomp_press, float t_lin) { + float partial_data1 = compensation_float_params_.param_P6 * t_lin; + float partial_data2 = compensation_float_params_.param_P7 * t_lin * t_lin; + float partial_data3 = compensation_float_params_.param_P8 * t_lin * t_lin * t_lin; + float partial_out1 = compensation_float_params_.param_P5 + partial_data1 + partial_data2 + partial_data3; + partial_data1 = compensation_float_params_.param_P2 * t_lin; + partial_data2 = compensation_float_params_.param_P3 * t_lin * t_lin; + partial_data3 = compensation_float_params_.param_P4 * t_lin * t_lin * t_lin; + float partial_out2 = + uncomp_press * (compensation_float_params_.param_P1 + partial_data1 + partial_data2 + partial_data3); + partial_data1 = uncomp_press * uncomp_press; + partial_data2 = compensation_float_params_.param_P9 + compensation_float_params_.param_P10 * t_lin; + partial_data3 = partial_data1 * partial_data2; + float partial_data4 = + partial_data3 + uncomp_press * uncomp_press * uncomp_press * compensation_float_params_.param_P11; + return partial_out1 + partial_out2 + partial_data4; +} + +} // namespace bmp3xx +} // namespace esphome diff --git a/esphome/components/bmp3xx/bmp3xx.h b/esphome/components/bmp3xx/bmp3xx.h new file mode 100644 index 0000000000..ab20abfe9b --- /dev/null +++ b/esphome/components/bmp3xx/bmp3xx.h @@ -0,0 +1,237 @@ +/* + based on BMP388_DEV by Martin Lindupp + under MIT License (MIT) + Copyright (C) Martin Lindupp 2020 + http://github.com/MartinL1/BMP388_DEV +*/ + +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace bmp3xx { + +static const uint8_t BMP388_ID = 0x50; // The BMP388 device ID +static const uint8_t BMP390_ID = 0x60; // The BMP390 device ID +static const uint8_t RESET_CODE = 0xB6; // The BMP388 reset code + +/// BMP388_DEV Registers +enum { + BMP388_CHIP_ID = 0x00, // Chip ID register sub-address + BMP388_ERR_REG = 0x02, // Error register sub-address + BMP388_STATUS = 0x03, // Status register sub-address + BMP388_DATA_0 = 0x04, // Pressure eXtended Least Significant Byte (XLSB) register sub-address + BMP388_DATA_1 = 0x05, // Pressure Least Significant Byte (LSB) register sub-address + BMP388_DATA_2 = 0x06, // Pressure Most Significant Byte (MSB) register sub-address + BMP388_DATA_3 = 0x07, // Temperature eXtended Least Significant Byte (XLSB) register sub-address + BMP388_DATA_4 = 0x08, // Temperature Least Significant Byte (LSB) register sub-address + BMP388_DATA_5 = 0x09, // Temperature Most Significant Byte (MSB) register sub-address + BMP388_SENSORTIME_0 = 0x0C, // Sensor time register 0 sub-address + BMP388_SENSORTIME_1 = 0x0D, // Sensor time register 1 sub-address + BMP388_SENSORTIME_2 = 0x0E, // Sensor time register 2 sub-address + BMP388_EVENT = 0x10, // Event register sub-address + BMP388_INT_STATUS = 0x11, // Interrupt Status register sub-address + BMP388_INT_CTRL = 0x19, // Interrupt Control register sub-address + BMP388_IF_CONFIG = 0x1A, // Interface Configuration register sub-address + BMP388_PWR_CTRL = 0x1B, // Power Control register sub-address + BMP388_OSR = 0x1C, // Oversampling register sub-address + BMP388_ODR = 0x1D, // Output Data Rate register sub-address + BMP388_CONFIG = 0x1F, // Configuration register sub-address + BMP388_TRIM_PARAMS = 0x31, // Trim parameter registers' base sub-address + BMP388_CMD = 0x7E // Command register sub-address +}; + +/// Device mode bitfield in the control and measurement register +enum OperationMode { SLEEP_MODE = 0x00, FORCED_MODE = 0x01, NORMAL_MODE = 0x03 }; + +/// Oversampling bit fields in the control and measurement register +enum Oversampling { + OVERSAMPLING_NONE = 0x00, + OVERSAMPLING_X2 = 0x01, + OVERSAMPLING_X4 = 0x02, + OVERSAMPLING_X8 = 0x03, + OVERSAMPLING_X16 = 0x04, + OVERSAMPLING_X32 = 0x05 +}; + +/// Infinite Impulse Response (IIR) filter bit field in the configuration register +enum IIRFilter { + IIR_FILTER_OFF = 0x00, + IIR_FILTER_2 = 0x01, + IIR_FILTER_4 = 0x02, + IIR_FILTER_8 = 0x03, + IIR_FILTER_16 = 0x04, + IIR_FILTER_32 = 0x05, + IIR_FILTER_64 = 0x06, + IIR_FILTER_128 = 0x07 +}; + +/// This class implements support for the BMP3XX Temperature+Pressure i2c sensor. +class BMP3XXComponent : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } + + /// Set the oversampling value for the temperature sensor. Default is 16x. + void set_temperature_oversampling_config(Oversampling temperature_oversampling) { + this->temperature_oversampling_ = temperature_oversampling; + } + /// Set the oversampling value for the pressure sensor. Default is 16x. + void set_pressure_oversampling_config(Oversampling pressure_oversampling) { + this->pressure_oversampling_ = pressure_oversampling; + } + /// Set the IIR Filter used to increase accuracy, defaults to no IIR Filter. + void set_iir_filter_config(IIRFilter iir_filter) { this->iir_filter_ = iir_filter; } + + /// Soft reset the sensor + uint8_t reset(); + /// Start continuous measurement in NORMAL_MODE + bool start_normal_conversion(); + /// Start a one shot measurement in FORCED_MODE + bool start_forced_conversion(); + /// Stop the conversion and return to SLEEP_MODE + bool stop_conversion(); + /// Set the pressure oversampling: OFF, X1, X2, X4, X8, X16, X32 + bool set_pressure_oversampling(Oversampling pressure_oversampling); + /// Set the temperature oversampling: OFF, X1, X2, X4, X8, X16, X32 + bool set_temperature_oversampling(Oversampling temperature_oversampling); + /// Set the IIR filter setting: OFF, 2, 3, 8, 16, 32 + bool set_iir_filter(IIRFilter iir_filter); + /// Get a temperature measurement + bool get_temperature(float &temperature); + /// Get a pressure measurement + bool get_pressure(float &pressure); + /// Get a temperature and pressure measurement + bool get_measurements(float &temperature, float &pressure); + /// Get a temperature and pressure measurement + bool get_measurement(); + /// Set the barometer mode + bool set_mode(OperationMode mode); + /// Set the BMP388 oversampling register + bool set_oversampling_register(Oversampling pressure_oversampling, Oversampling temperature_oversampling); + /// Checks if a measurement is ready + bool data_ready(); + + protected: + Oversampling temperature_oversampling_{OVERSAMPLING_X16}; + Oversampling pressure_oversampling_{OVERSAMPLING_X16}; + IIRFilter iir_filter_{IIR_FILTER_OFF}; + OperationMode operation_mode_{FORCED_MODE}; + sensor::Sensor *temperature_sensor_; + sensor::Sensor *pressure_sensor_; + enum ErrorCode { + NONE = 0, + ERROR_COMMUNICATION_FAILED, + ERROR_WRONG_CHIP_ID, + ERROR_SENSOR_STATUS, + ERROR_SENSOR_RESET, + } error_code_{NONE}; + + struct { // The BMP388 compensation trim parameters (coefficients) + uint16_t param_T1; + uint16_t param_T2; + int8_t param_T3; + int16_t param_P1; + int16_t param_P2; + int8_t param_P3; + int8_t param_P4; + uint16_t param_P5; + uint16_t param_P6; + int8_t param_P7; + int8_t param_P8; + int16_t param_P9; + int8_t param_P10; + int8_t param_P11; + } __attribute__((packed)) compensation_params_; + + struct FloatParams { // The BMP388 float point compensation trim parameters + float param_T1; + float param_T2; + float param_T3; + float param_P1; + float param_P2; + float param_P3; + float param_P4; + float param_P5; + float param_P6; + float param_P7; + float param_P8; + float param_P9; + float param_P10; + float param_P11; + } compensation_float_params_; + + union { // Copy of the BMP388's chip id register + struct { + uint8_t chip_id_nvm : 4; + uint8_t chip_id_fixed : 4; + } bit; + uint8_t reg; + } chip_id_ = {.reg = 0}; + + union { // Copy of the BMP388's event register + struct { + uint8_t por_detected : 1; + } bit; + uint8_t reg; + } event_ = {.reg = 0}; + + union { // Copy of the BMP388's interrupt status register + struct { + uint8_t fwm_int : 1; + uint8_t ffull_int : 1; + uint8_t : 1; + uint8_t drdy : 1; + } bit; + uint8_t reg; + } int_status_ = {.reg = 0}; + + union { // Copy of the BMP388's power control register + struct { + uint8_t press_en : 1; + uint8_t temp_en : 1; + uint8_t : 2; + uint8_t mode : 2; + } bit; + uint8_t reg; + } pwr_ctrl_ = {.reg = 0}; + + union { // Copy of the BMP388's oversampling register + struct { + uint8_t osr_p : 3; + uint8_t osr_t : 3; + } bit; + uint8_t reg; + } osr_ = {.reg = 0}; + + union { // Copy of the BMP388's output data rate register + struct { + uint8_t odr_sel : 5; + } bit; + uint8_t reg; + } odr_ = {.reg = 0}; + + union { // Copy of the BMP388's configuration register + struct { + uint8_t : 1; + uint8_t iir_filter : 3; + } bit; + uint8_t reg; + } config_ = {.reg = 0}; + + // Bosch temperature compensation function + float bmp388_compensate_temperature_(float uncomp_temp); + // Bosch pressure compensation function + float bmp388_compensate_pressure_(float uncomp_press, float t_lin); +}; + +} // namespace bmp3xx +} // namespace esphome diff --git a/esphome/components/bmp3xx/sensor.py b/esphome/components/bmp3xx/sensor.py new file mode 100644 index 0000000000..736e6df3d8 --- /dev/null +++ b/esphome/components/bmp3xx/sensor.py @@ -0,0 +1,100 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_IIR_FILTER, + CONF_OVERSAMPLING, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, +) + +CODEOWNERS = ["@martgras"] +DEPENDENCIES = ["i2c"] + +bmp3xx_ns = cg.esphome_ns.namespace("bmp3xx") +Oversampling = bmp3xx_ns.enum("Oversampling") +OVERSAMPLING_OPTIONS = { + "NONE": Oversampling.OVERSAMPLING_NONE, + "2X": Oversampling.OVERSAMPLING_X2, + "4X": Oversampling.OVERSAMPLING_X4, + "8X": Oversampling.OVERSAMPLING_X8, + "16X": Oversampling.OVERSAMPLING_X16, + "32x": Oversampling.OVERSAMPLING_X32, +} + +IIRFilter = bmp3xx_ns.enum("IIRFilter") +IIR_FILTER_OPTIONS = { + "OFF": IIRFilter.IIR_FILTER_OFF, + "2X": IIRFilter.IIR_FILTER_2, + "4X": IIRFilter.IIR_FILTER_4, + "8X": IIRFilter.IIR_FILTER_8, + "16X": IIRFilter.IIR_FILTER_16, + "32X": IIRFilter.IIR_FILTER_32, + "64X": IIRFilter.IIR_FILTER_64, + "128X": IIRFilter.IIR_FILTER_128, +} + +BMP3XXComponent = bmp3xx_ns.class_( + "BMP3XXComponent", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(BMP3XXComponent), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_OVERSAMPLING, default="2X"): cv.enum( + OVERSAMPLING_OPTIONS, upper=True + ), + } + ), + 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, + ).extend( + { + cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( + OVERSAMPLING_OPTIONS, upper=True + ), + } + ), + cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum( + IIR_FILTER_OPTIONS, upper=True + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x77)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + cg.add(var.set_iir_filter_config(config[CONF_IIR_FILTER])) + if CONF_TEMPERATURE in config: + conf = config[CONF_TEMPERATURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_temperature_sensor(sens)) + cg.add(var.set_temperature_oversampling_config(conf[CONF_OVERSAMPLING])) + + if CONF_PRESSURE in config: + conf = config[CONF_PRESSURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_pressure_sensor(sens)) + cg.add(var.set_pressure_oversampling_config(conf[CONF_OVERSAMPLING])) diff --git a/tests/test5.yaml b/tests/test5.yaml index 37e65e7da2..40db645097 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -180,6 +180,15 @@ sensor: co2: name: CO2 Sensor + - platform: bmp3xx + temperature: + name: "BMP Temperature" + oversampling: 16x + pressure: + name: "BMP Pressure" + address: 0x77 + iir_filter: 2X + script: - id: automation_test then: From a4931f5d788d24bfeeefb13fb3ead037cb5eaa6e Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 6 Jan 2022 12:54:58 +0100 Subject: [PATCH 516/549] Clean-up reverse_bits helpers (#3011) --- .../components/remote_base/midea_protocol.cpp | 4 ++-- esphome/components/ttp229_lsf/ttp229_lsf.cpp | 2 +- esphome/core/helpers.cpp | 15 ------------- esphome/core/helpers.h | 21 +++++++++++++++---- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/esphome/components/remote_base/midea_protocol.cpp b/esphome/components/remote_base/midea_protocol.cpp index baf64f246f..a19a5b50c1 100644 --- a/esphome/components/remote_base/midea_protocol.cpp +++ b/esphome/components/remote_base/midea_protocol.cpp @@ -9,8 +9,8 @@ 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); + cs -= reverse_bits(*it); + return reverse_bits(cs); } bool MideaData::check_compliment(const MideaData &rhs) const { diff --git a/esphome/components/ttp229_lsf/ttp229_lsf.cpp b/esphome/components/ttp229_lsf/ttp229_lsf.cpp index 21c7b02740..773d51b76e 100644 --- a/esphome/components/ttp229_lsf/ttp229_lsf.cpp +++ b/esphome/components/ttp229_lsf/ttp229_lsf.cpp @@ -35,7 +35,7 @@ void TTP229LSFComponent::loop() { } touched = i2c::i2ctohs(touched); this->status_clear_warning(); - touched = reverse_bits_16(touched); + touched = reverse_bits(touched); for (auto *channel : this->channels_) { channel->process(touched); } diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index da5d0915c2..c70aa2d6dd 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -187,17 +187,6 @@ void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trig ; } -uint8_t reverse_bits_8(uint8_t x) { - x = ((x & 0xAA) >> 1) | ((x & 0x55) << 1); - x = ((x & 0xCC) >> 2) | ((x & 0x33) << 2); - x = ((x & 0xF0) >> 4) | ((x & 0x0F) << 4); - return x; -} - -uint16_t reverse_bits_16(uint16_t x) { - return uint16_t(reverse_bits_8(x & 0xFF) << 8) | uint16_t(reverse_bits_8(x >> 8)); -} - uint32_t fnv1_hash(const std::string &str) { uint32_t hash = 2166136261UL; for (char c : str) { @@ -210,10 +199,6 @@ bool str_equals_case_insensitive(const std::string &a, const std::string &b) { return strcasecmp(a.c_str(), b.c_str()) == 0; } -template uint32_t reverse_bits(uint32_t x) { - return uint32_t(reverse_bits_16(x & 0xFFFF) << 16) | uint32_t(reverse_bits_16(x >> 16)); -} - static int high_freq_num_requests = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) void HighFrequencyLoopRequester::start() { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 2e6a978012..4699e8071c 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -120,10 +120,6 @@ std::string uint64_to_string(uint64_t num); /// Convert a uint32_t to a hex string std::string uint32_to_string(uint32_t num); -uint8_t reverse_bits_8(uint8_t x); -uint16_t reverse_bits_16(uint16_t x); -uint32_t reverse_bits_32(uint32_t x); - /// Convert RGB floats (0-1) to hue (0-360) & saturation/value percentage (0-1) void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value); /// Convert hue (0-360) & saturation/value percentage (0-1) to RGB floats (0-1) @@ -343,6 +339,23 @@ inline std::array decode_value(T val) { return ret; } +/// Reverse the order of 8 bits. +inline uint8_t reverse_bits(uint8_t x) { + x = ((x & 0xAA) >> 1) | ((x & 0x55) << 1); + x = ((x & 0xCC) >> 2) | ((x & 0x33) << 2); + x = ((x & 0xF0) >> 4) | ((x & 0x0F) << 4); + return x; +} +/// Reverse the order of 16 bits. +inline uint16_t reverse_bits(uint16_t x) { + return (reverse_bits(static_cast(x & 0xFF)) << 8) | reverse_bits(static_cast((x >> 8) & 0xFF)); +} +/// Reverse the order of 32 bits. +inline uint32_t reverse_bits(uint32_t x) { + return (reverse_bits(static_cast(x & 0xFFFF)) << 16) | + reverse_bits(static_cast((x >> 16) & 0xFFFF)); +} + /// Convert a value between host byte order and big endian (most significant byte first) order. template::value, int> = 0> constexpr T convert_big_endian(T val) { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ From 5c339d459794dc97741a58f7951a060ed0c448e0 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 6 Jan 2022 12:56:10 +0100 Subject: [PATCH 517/549] Convert clamp() helper to backport of std::clamp() (#3010) --- esphome/core/helpers.cpp | 11 ----------- esphome/core/helpers.h | 21 ++++++++++++--------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index c70aa2d6dd..457c8ef37f 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -215,17 +215,6 @@ void HighFrequencyLoopRequester::stop() { } bool HighFrequencyLoopRequester::is_high_frequency() { return high_freq_num_requests > 0; } -template T clamp(const T val, const T min, const T max) { - if (val < min) - return min; - if (val > max) - return max; - return val; -} -template uint8_t clamp(uint8_t, uint8_t, uint8_t); -template float clamp(float, float, float); -template int clamp(int, int, int); - float lerp(float completion, float start, float end) { return start + (end - start) * completion; } bool str_startswith(const std::string &full, const std::string &start) { return full.rfind(start, 0) == 0; } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 4699e8071c..bbe6b49827 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -58,15 +58,6 @@ class HighFrequencyLoopRequester { bool started_{false}; }; -/** Clamp the value between min and max. - * - * @param val The value. - * @param min The minimum value. - * @param max The maximum value. - * @return val clamped in between min and max. - */ -template T clamp(T val, T min, T max); - /** Linearly interpolate between end start and end by completion. * * @tparam T The input/output typename. @@ -277,6 +268,18 @@ using std::is_trivially_copyable; template struct is_trivially_copyable : public std::integral_constant {}; #endif +// std::clamp from C++17 +#if __cpp_lib_clamp >= 201603 +using std::clamp; +#else +template constexpr const T &clamp(const T &v, const T &lo, const T &hi, Compare comp) { + return comp(v, lo) ? lo : comp(hi, v) ? hi : v; +} +template constexpr const T &clamp(const T &v, const T &lo, const T &hi) { + return clamp(v, lo, hi, std::less{}); +} +#endif + // std::bit_cast from C++20 #if __cpp_lib_bit_cast >= 201806 using std::bit_cast; From 640142fc0c84e5d9dc4fb288bb85a934290baa30 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 6 Jan 2022 16:35:59 +0100 Subject: [PATCH 518/549] Introduce str_lower_case() and str_upper_case() helpers (#3008) --- esphome/core/helpers.cpp | 11 +++++++++++ esphome/core/helpers.h | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 457c8ef37f..62f4e87201 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" #include #include +#include #include #include @@ -335,6 +336,16 @@ std::string str_until(const char *str, char ch) { return pos == nullptr ? std::string(str) : std::string(str, pos - str); } std::string str_until(const std::string &str, char ch) { return str.substr(0, str.find(ch)); } +// wrapper around std::transform to run safely on functions from the ctype.h header +// see https://en.cppreference.com/w/cpp/string/byte/toupper#Notes +template std::string str_ctype_transform(const std::string &str) { + std::string result; + result.resize(str.length()); + std::transform(str.begin(), str.end(), result.begin(), [](unsigned char ch) { return fn(ch); }); + return result; +} +std::string str_lower_case(const std::string &str) { return str_ctype_transform(str); } +std::string str_upper_case(const std::string &str) { return str_ctype_transform(str); } std::string str_snake_case(const std::string &str) { std::string result; result.resize(str.length()); diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index bbe6b49827..c2f2e04339 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -385,6 +385,10 @@ std::string str_until(const char *str, char ch); /// Extract the part of the string until either the first occurence of the specified character, or the end. std::string str_until(const std::string &str, char ch); +/// Convert the string to lower case. +std::string str_lower_case(const std::string &str); +/// Convert the string to upper case. +std::string str_upper_case(const std::string &str); /// Convert the string to snake case (lowercase with underscores). std::string str_snake_case(const std::string &str); From 07e790f900d0cd66ab1466d312908afca2cb08eb Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 6 Jan 2022 16:36:11 +0100 Subject: [PATCH 519/549] Drop uint{32,64}_to_string() helper functions (#3009) --- esphome/components/dallas/dallas_component.cpp | 10 ++++------ esphome/components/debug/debug_component.cpp | 2 +- esphome/core/helpers.cpp | 12 ------------ esphome/core/helpers.h | 6 ------ 4 files changed, 5 insertions(+), 25 deletions(-) diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 8d7f2e4deb..3610e79447 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -38,10 +38,9 @@ void DallasComponent::setup() { raw_sensors = this->one_wire_->search_vec(); for (auto &address : raw_sensors) { - std::string s = uint64_to_string(address); auto *address8 = reinterpret_cast(&address); if (crc8(address8, 7) != address8[7]) { - ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", s.c_str()); + ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str()); continue; } if (address8[0] != DALLAS_MODEL_DS18S20 && address8[0] != DALLAS_MODEL_DS1822 && @@ -77,8 +76,7 @@ void DallasComponent::dump_config() { } else { ESP_LOGD(TAG, " Found sensors:"); for (auto &address : this->found_sensors_) { - std::string s = uint64_to_string(address); - ESP_LOGD(TAG, " 0x%s", s.c_str()); + ESP_LOGD(TAG, " 0x%s", format_hex(address).c_str()); } } @@ -147,7 +145,7 @@ void DallasTemperatureSensor::set_index(uint8_t index) { this->index_ = index; } uint8_t *DallasTemperatureSensor::get_address8() { return reinterpret_cast(&this->address_); } const std::string &DallasTemperatureSensor::get_address_name() { if (this->address_name_.empty()) { - this->address_name_ = std::string("0x") + uint64_to_string(this->address_); + this->address_name_ = std::string("0x") + format_hex(this->address_); } return this->address_name_; @@ -237,7 +235,7 @@ float DallasTemperatureSensor::get_temp_c() { return temp / 128.0f; } -std::string DallasTemperatureSensor::unique_id() { return "dallas-" + uint64_to_string(this->address_); } +std::string DallasTemperatureSensor::unique_id() { return "dallas-" + str_upper_case(format_hex(this->address_)); } } // namespace dallas } // namespace esphome diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index 40eb20fa6e..f3d0bded13 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -101,7 +101,7 @@ void DebugComponent::dump_config() { info.features &= ~CHIP_FEATURE_BT; } if (info.features) - features += "Other:" + uint64_to_string(info.features); + features += "Other:" + format_hex(info.features); ESP_LOGD(TAG, "Chip: Model=%s, Features=%s Cores=%u, Revision=%u", model, features.c_str(), info.cores, info.revision); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 62f4e87201..db6bfeb79b 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -130,18 +130,6 @@ std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value); return std::string(tmp); } -std::string uint64_to_string(uint64_t num) { - char buffer[17]; - auto *address16 = reinterpret_cast(&num); - snprintf(buffer, sizeof(buffer), "%04X%04X%04X%04X", address16[3], address16[2], address16[1], address16[0]); - return std::string(buffer); -} -std::string uint32_to_string(uint32_t num) { - char buffer[9]; - auto *address16 = reinterpret_cast(&num); - snprintf(buffer, sizeof(buffer), "%04X%04X", address16[1], address16[0]); - return std::string(buffer); -} 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 c2f2e04339..22ed018855 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -105,12 +105,6 @@ float gamma_uncorrect(float value, float gamma); /// Create a string from a value and an accuracy in decimals. std::string value_accuracy_to_string(float value, int8_t accuracy_decimals); -/// Convert a uint64_t to a hex string -std::string uint64_to_string(uint64_t num); - -/// Convert a uint32_t to a hex string -std::string uint32_to_string(uint32_t num); - /// Convert RGB floats (0-1) to hue (0-360) & saturation/value percentage (0-1) void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value); /// Convert hue (0-360) & saturation/value percentage (0-1) to RGB floats (0-1) From e62c3e00c1f7c755386b344ac2f74585f0fb0570 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 6 Jan 2022 16:36:23 +0100 Subject: [PATCH 520/549] Bump PlatformIO to 5.2.4 and zeroconf to 0.37.0 (#3007) --- docker/Dockerfile | 2 +- requirements.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 32113fe278..2139c00d27 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -43,7 +43,7 @@ RUN \ # Ubuntu python3-pip is missing wheel pip3 install --no-cache-dir \ wheel==0.36.2 \ - platformio==5.2.2 \ + platformio==5.2.4 \ # Change some platformio settings && platformio settings set enable_telemetry No \ && platformio settings set check_libraries_interval 1000000 \ diff --git a/requirements.txt b/requirements.txt index c45797a71f..f99d779f6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,12 +6,12 @@ tornado==6.1 tzlocal==4.1 # from time tzdata>=2021.1 # from time pyserial==3.5 -platformio==5.2.2 # When updating platformio, also update Dockerfile +platformio==5.2.4 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 esphome-dashboard==20211211.0 aioesphomeapi==10.6.0 -zeroconf==0.36.13 +zeroconf==0.37.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 84a830195fb991bcf7792c26d36999b5b8d60e0d Mon Sep 17 00:00:00 2001 From: stegm Date: Thu, 6 Jan 2022 16:40:22 +0100 Subject: [PATCH 521/549] Fix offset bug in modbus text sensor. (#3006) --- .../text_sensor/modbus_textsensor.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp index 25b79474e8..c90890c88f 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp @@ -14,18 +14,18 @@ void ModbusTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Modbus Controller Te void ModbusTextSensor::parse_and_publish(const std::vector &data) { std::ostringstream output; uint8_t max_items = this->response_bytes; + uint8_t index = this->offset; char buffer[4]; - bool add_comma = false; - for (auto b : data) { + while ((max_items != 0) && index < data.size()) { + uint8_t b = data[index]; switch (this->encode_) { case RawEncoding::HEXBYTES: sprintf(buffer, "%02x", b); output << buffer; break; case RawEncoding::COMMA: - sprintf(buffer, add_comma ? ",%d" : "%d", b); + sprintf(buffer, index != this->offset ? ",%d" : "%d", b); output << buffer; - add_comma = true; break; // Anything else no encoding case RawEncoding::NONE: @@ -33,9 +33,8 @@ void ModbusTextSensor::parse_and_publish(const std::vector &data) { output << (char) b; break; } - if (--max_items == 0) { - break; - } + + index++; } auto result = output.str(); From ea1be8e7bf63bf60628af95436167ba5474905f9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 8 Jan 2022 21:35:55 +1300 Subject: [PATCH 522/549] Add MCP47A1 DAC output (#3014) --- CODEOWNERS | 1 + esphome/components/mcp47a1/__init__.py | 0 esphome/components/mcp47a1/mcp47a1.cpp | 21 ++++++++++++++++++++ esphome/components/mcp47a1/mcp47a1.h | 17 ++++++++++++++++ esphome/components/mcp47a1/output.py | 27 ++++++++++++++++++++++++++ tests/test5.yaml | 3 +++ 6 files changed, 69 insertions(+) create mode 100644 esphome/components/mcp47a1/__init__.py create mode 100644 esphome/components/mcp47a1/mcp47a1.cpp create mode 100644 esphome/components/mcp47a1/mcp47a1.h create mode 100644 esphome/components/mcp47a1/output.py diff --git a/CODEOWNERS b/CODEOWNERS index f47b36ff73..62e49055a2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -96,6 +96,7 @@ esphome/components/mcp23x08_base/* @jesserockz esphome/components/mcp23x17_base/* @jesserockz esphome/components/mcp23xxx_base/* @jesserockz esphome/components/mcp2515/* @danielschramm @mvturnho +esphome/components/mcp47a1/* @jesserockz esphome/components/mcp9808/* @k7hpn esphome/components/md5/* @esphome/core esphome/components/mdns/* @esphome/core diff --git a/esphome/components/mcp47a1/__init__.py b/esphome/components/mcp47a1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/mcp47a1/mcp47a1.cpp b/esphome/components/mcp47a1/mcp47a1.cpp new file mode 100644 index 0000000000..58f3b2ac72 --- /dev/null +++ b/esphome/components/mcp47a1/mcp47a1.cpp @@ -0,0 +1,21 @@ +#include "mcp47a1.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mcp47a1 { + +static const char *const TAG = "mcp47a1"; + +void MCP47A1::dump_config() { + ESP_LOGCONFIG(TAG, "MCP47A1 Output:"); + LOG_I2C_DEVICE(this); +} + +void MCP47A1::write_state(float state) { + const uint8_t value = remap(state, 0.0f, 1.0f, 63, 0); + this->write_byte(0, value); +} + +} // namespace mcp47a1 +} // namespace esphome diff --git a/esphome/components/mcp47a1/mcp47a1.h b/esphome/components/mcp47a1/mcp47a1.h new file mode 100644 index 0000000000..5c02e062ad --- /dev/null +++ b/esphome/components/mcp47a1/mcp47a1.h @@ -0,0 +1,17 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/output/float_output.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace mcp47a1 { + +class MCP47A1 : public Component, public output::FloatOutput, public i2c::I2CDevice { + public: + void dump_config() override; + void write_state(float state) override; +}; + +} // namespace mcp47a1 +} // namespace esphome diff --git a/esphome/components/mcp47a1/output.py b/esphome/components/mcp47a1/output.py new file mode 100644 index 0000000000..60235107e9 --- /dev/null +++ b/esphome/components/mcp47a1/output.py @@ -0,0 +1,27 @@ +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.components import output, i2c +from esphome.const import CONF_ID + +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["i2c"] + +mcp47a1_ns = cg.esphome_ns.namespace("mcp47a1") +MCP47A1 = mcp47a1_ns.class_("MCP47A1", output.FloatOutput, cg.Component, i2c.I2CDevice) + +CONFIG_SCHEMA = ( + output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(MCP47A1), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x2E)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + await output.register_output(var, config) diff --git a/tests/test5.yaml b/tests/test5.yaml index 40db645097..d6acbf1e65 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -72,6 +72,9 @@ output: channel: 0 max_power: 0.8 + - platform: mcp47a1 + id: output_mcp47a1 + demo: esp32_ble: From 470071e0b09a19fffb5f1b74fadff227b035f5ce Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 8 Jan 2022 14:15:05 +0100 Subject: [PATCH 523/549] Bump docker dependencies (#3019) --- docker/Dockerfile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 2139c00d27..330901a776 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,12 +5,12 @@ # One of "docker", "hassio" ARG BASEIMGTYPE=docker -FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.1 AS base-hassio-amd64 -FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.1 AS base-hassio-arm64 -FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.1 AS base-hassio-armv7 -FROM debian:bullseye-20211011-slim AS base-docker-amd64 -FROM debian:bullseye-20211011-slim AS base-docker-arm64 -FROM debian:bullseye-20211011-slim AS base-docker-armv7 +FROM ghcr.io/hassio-addons/debian-base/amd64:5.2.3 AS base-hassio-amd64 +FROM ghcr.io/hassio-addons/debian-base/aarch64:5.2.3 AS base-hassio-arm64 +FROM ghcr.io/hassio-addons/debian-base/armv7:5.2.3 AS base-hassio-armv7 +FROM debian:bullseye-20211220-slim AS base-docker-amd64 +FROM debian:bullseye-20211220-slim AS base-docker-arm64 +FROM debian:bullseye-20211220-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 @@ -42,7 +42,7 @@ ENV \ RUN \ # Ubuntu python3-pip is missing wheel pip3 install --no-cache-dir \ - wheel==0.36.2 \ + wheel==0.37.1 \ platformio==5.2.4 \ # Change some platformio settings && platformio settings set enable_telemetry No \ From e4555f6997199f7e21eaea085f1528c776fd22b4 Mon Sep 17 00:00:00 2001 From: stegm Date: Sun, 9 Jan 2022 16:24:23 +0100 Subject: [PATCH 524/549] Fix register ranges in modbus controller (#2981) --- .../modbus_controller/modbus_controller.cpp | 248 +++++++++--------- .../modbus_controller/modbus_controller.h | 79 +++--- 2 files changed, 180 insertions(+), 147 deletions(-) diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 8b96c20691..2fdedfd786 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -24,15 +24,22 @@ bool ModbusController::send_next_command_() { if ((last_send > this->command_throttle_) && !waiting_for_response() && !command_queue_.empty()) { auto &command = command_queue_.front(); - ESP_LOGV(TAG, "Sending next modbus command to device %d register 0x%02X count %d", this->address_, - command->register_address, command->register_count); - command->send(); - this->last_command_timestamp_ = millis(); - // remove from queue if no handler is defined or command was sent too often - if (!command->on_data_func || command->send_countdown < 1) { - ESP_LOGD(TAG, "Modbus command to device=%d register=0x%02X countdown=%d removed from queue after send", - this->address_, command->register_address, command->send_countdown); + // remove from queue if command was sent too often + if (command->send_countdown < 1) { + ESP_LOGD( + TAG, + "Modbus command to device=%d register=0x%02X countdown=%d no response received - removed from send queue", + this->address_, command->register_address, command->send_countdown); command_queue_.pop_front(); + } else { + ESP_LOGV(TAG, "Sending next modbus command to device %d register 0x%02X count %d", this->address_, + command->register_address, command->register_count); + command->send(); + this->last_command_timestamp_ = millis(); + // remove from queue if no handler is defined + if (!command->on_data_func) { + command_queue_.pop_front(); + } } } return (!command_queue_.empty()); @@ -72,36 +79,28 @@ void ModbusController::on_modbus_error(uint8_t function_code, uint8_t exception_ } } -std::map::iterator ModbusController::find_register_(ModbusRegisterType register_type, - uint16_t start_address) { - auto vec_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) { +SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const { + auto reg_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) { return (r.start_address == start_address && r.register_type == register_type); }); - if (vec_it == register_ranges_.end()) { - ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address); + if (reg_it == register_ranges_.end()) { + ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address); } else { - auto map_it = sensormap_.find(vec_it->first_sensorkey); - if (map_it == sensormap_.end()) { - ESP_LOGE(TAG, "No sensor found in at start_address : 0x%X (0x%llX)", start_address, vec_it->first_sensorkey); - } else { - return sensormap_.find(vec_it->first_sensorkey); - } + return reg_it->sensors; } + // not found - return std::end(sensormap_); + return {}; } void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address); - auto map_it = find_register_(register_type, start_address); // loop through all sensors with the same start address - while (map_it != sensormap_.end() && map_it->second->start_address == start_address) { - if (map_it->second->register_type == register_type) { - map_it->second->parse_and_publish(data); - } - map_it++; + auto sensors = find_sensors_(register_type, start_address); + for (auto sensor : sensors) { + sensor->parse_and_publish(data); } } @@ -127,15 +126,16 @@ void ModbusController::update_range_(RegisterRange &r) { if (r.skip_updates_counter == 0) { // if a custom command is used the user supplied custom_data is only available in the SensorItem. if (r.register_type == ModbusRegisterType::CUSTOM) { - auto it = this->find_register_(r.register_type, r.start_address); - if (it != sensormap_.end()) { + auto sensors = this->find_sensors_(r.register_type, r.start_address); + if (!sensors.empty()) { + auto sensor = sensors.cbegin(); auto command_item = ModbusCommandItem::create_custom_command( - this, it->second->custom_data, + this, (*sensor)->custom_data, [this](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { this->on_register_data(ModbusRegisterType::CUSTOM, start_address, data); }); - command_item.register_address = it->second->start_address; - command_item.register_count = it->second->register_count; + command_item.register_address = (*sensor)->start_address; + command_item.register_count = (*sensor)->register_count; command_item.function_code = ModbusFunctionCode::CUSTOM; queue_command(command_item); } @@ -164,102 +164,110 @@ void ModbusController::update() { } } -// walk through the sensors and determine the registerranges to read +// walk through the sensors and determine the register ranges to read size_t ModbusController::create_register_ranges_() { register_ranges_.clear(); - uint8_t n = 0; - if (sensormap_.empty()) { + if (sensorset_.empty()) { + ESP_LOGW(TAG, "No sensors registered"); return 0; } - auto ix = sensormap_.begin(); - auto prev = ix; - int total_register_count = 0; - uint16_t current_start_address = ix->second->start_address; - uint8_t buffer_offset = ix->second->offset; - uint8_t skip_updates = ix->second->skip_updates; - auto first_sensorkey = ix->second->getkey(); - total_register_count = 0; - while (ix != sensormap_.end()) { - ESP_LOGV(TAG, "Register: 0x%X %d %d 0x%llx (%d) buffer_offset = %d (0x%X) skip=%u", ix->second->start_address, - ix->second->register_count, ix->second->offset, ix->second->getkey(), total_register_count, buffer_offset, - buffer_offset, ix->second->skip_updates); - // if this is a sequential address based on number of registers and address of previous sensor - // convert to an offset to the previous sensor (address 0x101 becomes address 0x100 offset 2 bytes) - if (!ix->second->force_new_range && total_register_count >= 0 && - prev->second->register_type == ix->second->register_type && - prev->second->start_address + total_register_count == ix->second->start_address && - prev->second->start_address < ix->second->start_address) { - ix->second->start_address = prev->second->start_address; - ix->second->offset += prev->second->offset + prev->second->get_register_size(); + // iterator is sorted see SensorItemsComparator for details + auto ix = sensorset_.begin(); + RegisterRange r = {}; + uint8_t buffer_offset = 0; + SensorItem *prev = nullptr; + while (ix != sensorset_.end()) { + SensorItem *curr = *ix; - // replace entry in sensormap_ - auto const value = ix->second; - sensormap_.erase(ix); - sensormap_.insert({value->getkey(), value}); - // move iterator back to new element - ix = sensormap_.find(value->getkey()); // next(prev, 1); - } - if (current_start_address != ix->second->start_address || - // ( prev->second->start_address + prev->second->offset != ix->second->start_address) || - ix->second->register_type != prev->second->register_type) { - // Difference doesn't match so we have a gap - if (n > 0) { - RegisterRange r; - r.start_address = current_start_address; - r.register_count = total_register_count; - if (prev->second->register_type == ModbusRegisterType::COIL || - prev->second->register_type == ModbusRegisterType::DISCRETE_INPUT) { - r.register_count = prev->second->offset + 1; - } - r.register_type = prev->second->register_type; - r.first_sensorkey = first_sensorkey; - r.skip_updates = skip_updates; - r.skip_updates_counter = 0; - ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates); - register_ranges_.push_back(r); - } - skip_updates = ix->second->skip_updates; - current_start_address = ix->second->start_address; - first_sensorkey = ix->second->getkey(); - total_register_count = ix->second->register_count; - buffer_offset = ix->second->offset; - n = 1; + ESP_LOGV(TAG, "Register: 0x%X %d %d %d offset=%u skip=%u addr=%p", curr->start_address, curr->register_count, + curr->offset, curr->get_register_size(), curr->offset, curr->skip_updates, curr); + + if (r.register_count == 0) { + // this is the first register in range + r.start_address = curr->start_address; + r.register_count = curr->register_count; + r.register_type = curr->register_type; + r.sensors.insert(curr); + r.skip_updates = curr->skip_updates; + r.skip_updates_counter = 0; + buffer_offset = curr->get_register_size(); + + ESP_LOGV(TAG, "Started new range"); } else { - n++; - if (ix->second->offset != prev->second->offset || n == 1) { - total_register_count += ix->second->register_count; - buffer_offset += ix->second->get_register_size(); + // this is not the first register in range so it might be possible + // to reuse the last register or extend the current range + if (!curr->force_new_range && r.register_type == curr->register_type && + curr->register_type != ModbusRegisterType::CUSTOM) { + if (curr->start_address == (r.start_address + r.register_count - prev->register_count) && + curr->register_count == prev->register_count && curr->get_register_size() == prev->get_register_size()) { + // this register can re-use the data from the previous register + + // remove this sensore because start_address is changed (sort-order) + ix = sensorset_.erase(ix); + + curr->start_address = r.start_address; + curr->offset += prev->offset; + + sensorset_.insert(curr); + // move iterator backwards because it will be incremented later + ix--; + + ESP_LOGV(TAG, "Re-use previous register - change to register: 0x%X %d offset=%u", curr->start_address, + curr->register_count, curr->offset); + } else if (curr->start_address == (r.start_address + r.register_count)) { + // this register can extend the current range + + // remove this sensore because start_address is changed (sort-order) + ix = sensorset_.erase(ix); + + curr->start_address = r.start_address; + curr->offset += buffer_offset; + buffer_offset += curr->get_register_size(); + r.register_count += curr->register_count; + + sensorset_.insert(curr); + // move iterator backwards because it will be incremented later + ix--; + + ESP_LOGV(TAG, "Extend range - change to register: 0x%X %d offset=%u", curr->start_address, + curr->register_count, curr->offset); + } } + } + + if (curr->start_address == r.start_address) { // use the lowest non zero value for the whole range // Because zero is the default value for skip_updates it is excluded from getting the min value. - if (ix->second->skip_updates != 0) { - if (skip_updates != 0) { - skip_updates = std::min(skip_updates, ix->second->skip_updates); + if (curr->skip_updates != 0) { + if (r.skip_updates != 0) { + r.skip_updates = std::min(r.skip_updates, curr->skip_updates); } else { - skip_updates = ix->second->skip_updates; + r.skip_updates = curr->skip_updates; } } + + // add sensor to this range + r.sensors.insert(curr); + + ix++; + } else { + ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates); + register_ranges_.push_back(r); + r = {}; + buffer_offset = 0; + // do not increment the iterator here because the current sensor has to be re-evaluated } - prev = ix++; + + prev = curr; } - // Add the last range - if (n > 0) { - RegisterRange r; - r.start_address = current_start_address; - // r.register_count = prev->second->offset>>1 + prev->second->get_register_size(); - r.register_count = total_register_count; - if (prev->second->register_type == ModbusRegisterType::COIL || - prev->second->register_type == ModbusRegisterType::DISCRETE_INPUT) { - r.register_count = prev->second->offset + 1; - } - r.register_type = prev->second->register_type; - r.first_sensorkey = first_sensorkey; - r.skip_updates = skip_updates; - r.skip_updates_counter = 0; + + if (r.register_count > 0) { + // Add the last range ESP_LOGV(TAG, "Add last range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates); register_ranges_.push_back(r); } + return register_ranges_.size(); } @@ -268,9 +276,15 @@ void ModbusController::dump_config() { ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE ESP_LOGCONFIG(TAG, "sensormap"); - for (auto &it : sensormap_) { - ESP_LOGCONFIG("TAG", " Sensor 0x%llX start=0x%X count=%d size=%d", it.second->getkey(), it.second->start_address, - it.second->register_count, it.second->get_register_size()); + for (auto &it : sensorset_) { + ESP_LOGCONFIG(TAG, " Sensor type=%zu start=0x%X offset=0x%X count=%d size=%d", + static_cast(it->register_type), it->start_address, it->offset, it->register_count, + it->get_register_size()); + } + ESP_LOGCONFIG(TAG, "ranges"); + for (auto &it : register_ranges_) { + ESP_LOGCONFIG(TAG, " Range type=%zu start=0x%X count=%d skip_updates=%d", static_cast(it.register_type), + it.start_address, it.register_count, it.skip_updates); } #endif } @@ -294,11 +308,11 @@ void ModbusController::on_write_register_response(ModbusRegisterType register_ty ESP_LOGV(TAG, "Command ACK 0x%X %d ", get_data(data, 0), get_data(data, 1)); } -void ModbusController::dump_sensormap_() { - ESP_LOGV("modbuscontroller.h", "sensormap"); - for (auto &it : sensormap_) { - ESP_LOGV("modbuscontroller.h", " Sensor 0x%llX start=0x%X count=%d size=%d", it.second->getkey(), - it.second->start_address, it.second->register_count, it.second->get_register_size()); +void ModbusController::dump_sensors_() { + ESP_LOGV(TAG, "sensors"); + for (auto &it : sensorset_) { + ESP_LOGV(TAG, " Sensor start=0x%X count=%d size=%d offset=%d", it->start_address, it->register_count, + it->get_register_size(), it->offset); } } diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index f4948e6ff9..6dbabac71e 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -6,7 +6,7 @@ #include "esphome/components/modbus/modbus.h" #include -#include +#include #include #include @@ -37,7 +37,7 @@ enum class ModbusFunctionCode { READ_FIFO_QUEUE = 0x18, // not implemented }; -enum class ModbusRegisterType : int { +enum class ModbusRegisterType : uint8_t { CUSTOM = 0x0, COIL = 0x01, DISCRETE_INPUT = 0x02, @@ -62,15 +62,6 @@ enum class SensorValueType : uint8_t { FP32_R = 0xD }; -struct RegisterRange { - uint16_t start_address; - ModbusRegisterType register_type; - uint8_t register_count; - uint8_t skip_updates; // the config value - uint64_t first_sensorkey; - uint8_t skip_updates_counter; // the running value -} __attribute__((packed)); - inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) { switch (reg_type) { case ModbusRegisterType::COIL: @@ -108,18 +99,6 @@ inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_ } } -/** All sensors are stored in a map - * to enable binary sensors for values encoded as bits in the same register the key of each sensor - * the key is a 64 bit integer that combines the register properties - * sensormap_ is sorted by this key. The key ensures the correct order when creating consequtive ranges - * Format: function_code (8 bit) | start address (16 bit)| offset (8bit)| bitmask (32 bit) - */ -inline uint64_t calc_key(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset = 0, - uint32_t bitmask = 0) { - return uint64_t((uint16_t(register_type) << 24) + (uint32_t(start_address) << 8) + (offset & 0xFF)) << 32 | bitmask; -} -inline uint16_t register_from_key(uint64_t key) { return (key >> 40) & 0xFFFF; } - inline uint8_t c_to_hex(char c) { return (c >= 'A') ? (c >= 'a') ? (c - 'a' + 10) : (c - 'A' + 10) : (c - '0'); } /** Get a byte from a hex string @@ -250,7 +229,6 @@ class SensorItem { virtual void parse_and_publish(const std::vector &data) = 0; void set_custom_data(const std::vector &data) { custom_data = data; } - uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); } size_t virtual get_register_size() const { if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) return 1; @@ -271,6 +249,48 @@ class SensorItem { bool force_new_range{false}; }; +// ModbusController::create_register_ranges_ tries to optimize register range +// for this the sensors must be ordered by register_type, start_address and bitmask +class SensorItemsComparator { + public: + bool operator()(const SensorItem *lhs, const SensorItem *rhs) const { + // first sort according to register type + if (lhs->register_type != rhs->register_type) { + return lhs->register_type < rhs->register_type; + } + + // ensure that sensor with force_new_range set are before the others + if (lhs->force_new_range != rhs->force_new_range) { + return lhs->force_new_range > rhs->force_new_range; + } + + // sort by start address + if (lhs->start_address != rhs->start_address) { + return lhs->start_address < rhs->start_address; + } + + // sort by offset (ensures update of sensors in ascending order) + if (lhs->offset != rhs->offset) { + return lhs->offset < rhs->offset; + } + + // The pointer to the sensor is used last to ensure that + // multiple sensors with the same values can be added with a stable sort order. + return lhs < rhs; + } +}; + +using SensorSet = std::set; + +struct RegisterRange { + uint16_t start_address; + ModbusRegisterType register_type; + uint8_t register_count; + uint8_t skip_updates; // the config value + SensorSet sensors; // all sensors of this range + uint8_t skip_updates_counter; // the running value +}; + class ModbusCommandItem { public: static const size_t MAX_PAYLOAD_BYTES = 240; @@ -382,8 +402,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { /// queues a modbus command in the send queue void queue_command(const ModbusCommandItem &command); /// Registers a sensor with the controller. Called by esphomes code generator - void add_sensor_item(SensorItem *item) { sensormap_[item->getkey()] = item; } - /// called when a modbus response was prased without errors + void add_sensor_item(SensorItem *item) { sensorset_.insert(item); } + /// called when a modbus response was parsed without errors void on_modbus_data(const std::vector &data) override; /// called when a modbus error response was received void on_modbus_error(uint8_t function_code, uint8_t exception_code) override; @@ -400,7 +420,7 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { /// parse sensormap_ and create range of sequential addresses size_t create_register_ranges_(); // find register in sensormap. Returns iterator with all registers having the same start address - std::map::iterator find_register_(ModbusRegisterType register_type, uint16_t start_address); + SensorSet find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const; /// submit the read command for the address range to the send queue void update_range_(RegisterRange &r); /// parse incoming modbus data @@ -410,10 +430,9 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { /// get the number of queued modbus commands (should be mostly empty) size_t get_command_queue_length_() { return command_queue_.size(); } /// dump the parsed sensormap for diagnostics - void dump_sensormap_(); + void dump_sensors_(); /// Collection of all sensors for this component - /// see calc_key how the key is contructed - std::map sensormap_; + SensorSet sensorset_; /// Continous range of modbus registers std::vector register_ranges_; /// Hold the pending requests to be sent From 15fe049a99143e55212532a88ee90f82b3a31f08 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Mon, 10 Jan 2022 05:47:00 +1100 Subject: [PATCH 525/549] Add `restore_mode` to output switch (#3016) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/output/switch/__init__.py | 17 +++++++++- .../output/switch/output_switch.cpp | 33 ++++++++++++++----- .../components/output/switch/output_switch.h | 12 +++++++ 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/esphome/components/output/switch/__init__.py b/esphome/components/output/switch/__init__.py index 11d073d28c..46135d117e 100644 --- a/esphome/components/output/switch/__init__.py +++ b/esphome/components/output/switch/__init__.py @@ -1,15 +1,28 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import output, switch -from esphome.const import CONF_ID, CONF_OUTPUT +from esphome.const import CONF_ID, CONF_OUTPUT, CONF_RESTORE_MODE from .. import output_ns OutputSwitch = output_ns.class_("OutputSwitch", switch.Switch, cg.Component) +OutputSwitchRestoreMode = output_ns.enum("OutputSwitchRestoreMode") +RESTORE_MODES = { + "RESTORE_DEFAULT_OFF": OutputSwitchRestoreMode.OUTPUT_SWITCH_RESTORE_DEFAULT_OFF, + "RESTORE_DEFAULT_ON": OutputSwitchRestoreMode.OUTPUT_SWITCH_RESTORE_DEFAULT_ON, + "ALWAYS_OFF": OutputSwitchRestoreMode.OUTPUT_SWITCH_ALWAYS_OFF, + "ALWAYS_ON": OutputSwitchRestoreMode.OUTPUT_SWITCH_ALWAYS_ON, + "RESTORE_INVERTED_DEFAULT_OFF": OutputSwitchRestoreMode.OUTPUT_SWITCH_RESTORE_INVERTED_DEFAULT_OFF, + "RESTORE_INVERTED_DEFAULT_ON": OutputSwitchRestoreMode.OUTPUT_SWITCH_RESTORE_INVERTED_DEFAULT_ON, +} + CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(OutputSwitch), cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), + cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_OFF"): cv.enum( + RESTORE_MODES, upper=True, space="_" + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -21,3 +34,5 @@ async def to_code(config): output_ = await cg.get_variable(config[CONF_OUTPUT]) cg.add(var.set_output(output_)) + + cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) diff --git a/esphome/components/output/switch/output_switch.cpp b/esphome/components/output/switch/output_switch.cpp index 8db45f3a2b..3691896cbe 100644 --- a/esphome/components/output/switch/output_switch.cpp +++ b/esphome/components/output/switch/output_switch.cpp @@ -8,15 +8,32 @@ static const char *const TAG = "output.switch"; void OutputSwitch::dump_config() { LOG_SWITCH("", "Output Switch", this); } void OutputSwitch::setup() { - auto restored = this->get_initial_state(); - if (!restored.has_value()) - return; - - if (*restored) { - this->turn_on(); - } else { - this->turn_off(); + bool initial_state = false; + switch (this->restore_mode_) { + case OUTPUT_SWITCH_RESTORE_DEFAULT_OFF: + initial_state = this->get_initial_state().value_or(false); + break; + case OUTPUT_SWITCH_RESTORE_DEFAULT_ON: + initial_state = this->get_initial_state().value_or(true); + break; + case OUTPUT_SWITCH_RESTORE_INVERTED_DEFAULT_OFF: + initial_state = !this->get_initial_state().value_or(true); + break; + case OUTPUT_SWITCH_RESTORE_INVERTED_DEFAULT_ON: + initial_state = !this->get_initial_state().value_or(false); + break; + case OUTPUT_SWITCH_ALWAYS_OFF: + initial_state = false; + break; + case OUTPUT_SWITCH_ALWAYS_ON: + initial_state = true; + break; } + + if (initial_state) + this->turn_on(); + else + this->turn_off(); } void OutputSwitch::write_state(bool state) { if (state) { diff --git a/esphome/components/output/switch/output_switch.h b/esphome/components/output/switch/output_switch.h index a184a342fe..fc2c110662 100644 --- a/esphome/components/output/switch/output_switch.h +++ b/esphome/components/output/switch/output_switch.h @@ -7,10 +7,21 @@ namespace esphome { namespace output { +enum OutputSwitchRestoreMode { + OUTPUT_SWITCH_RESTORE_DEFAULT_OFF, + OUTPUT_SWITCH_RESTORE_DEFAULT_ON, + OUTPUT_SWITCH_ALWAYS_OFF, + OUTPUT_SWITCH_ALWAYS_ON, + OUTPUT_SWITCH_RESTORE_INVERTED_DEFAULT_OFF, + OUTPUT_SWITCH_RESTORE_INVERTED_DEFAULT_ON, +}; + class OutputSwitch : public switch_::Switch, public Component { public: void set_output(BinaryOutput *output) { output_ = output; } + void set_restore_mode(OutputSwitchRestoreMode restore_mode) { restore_mode_ = restore_mode; } + void setup() override; float get_setup_priority() const override { return setup_priority::HARDWARE - 1.0f; } void dump_config() override; @@ -19,6 +30,7 @@ class OutputSwitch : public switch_::Switch, public Component { void write_state(bool state) override; output::BinaryOutput *output_; + OutputSwitchRestoreMode restore_mode_; }; } // namespace output From 6b773553fc49df633a15ab7fc34be8e56cf557db Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Sun, 9 Jan 2022 19:49:57 +0100 Subject: [PATCH 526/549] Add turn_on/off trigger to slow_pwm (#2921) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/slow_pwm/output.py | 44 +++++++++++++++++-- .../components/slow_pwm/slow_pwm_output.cpp | 35 ++++++++++++--- esphome/components/slow_pwm/slow_pwm_output.h | 35 ++++++++++++--- 3 files changed, 99 insertions(+), 15 deletions(-) diff --git a/esphome/components/slow_pwm/output.py b/esphome/components/slow_pwm/output.py index 4f44582eba..0ce1c9f9e2 100644 --- a/esphome/components/slow_pwm/output.py +++ b/esphome/components/slow_pwm/output.py @@ -2,15 +2,37 @@ from esphome import pins, core from esphome.components import output import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID, CONF_PIN, CONF_PERIOD +from esphome import automation +from esphome.const import ( + CONF_ID, + CONF_PIN, + CONF_PERIOD, + CONF_TURN_ON_ACTION, + CONF_TURN_OFF_ACTION, +) slow_pwm_ns = cg.esphome_ns.namespace("slow_pwm") SlowPWMOutput = slow_pwm_ns.class_("SlowPWMOutput", output.FloatOutput, cg.Component) +CONF_STATE_CHANGE_ACTION = "state_change_action" + CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { cv.Required(CONF_ID): cv.declare_id(SlowPWMOutput), - cv.Required(CONF_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_PIN): pins.gpio_output_pin_schema, + cv.Inclusive( + CONF_TURN_ON_ACTION, + "on_off", + f"{CONF_TURN_ON_ACTION} and {CONF_TURN_OFF_ACTION} must both be defined", + ): automation.validate_automation(single=True), + cv.Inclusive( + CONF_TURN_OFF_ACTION, + "on_off", + f"{CONF_TURN_ON_ACTION} and {CONF_TURN_OFF_ACTION} must both be defined", + ): automation.validate_automation(single=True), + cv.Optional(CONF_STATE_CHANGE_ACTION): automation.validate_automation( + single=True + ), cv.Required(CONF_PERIOD): cv.All( cv.positive_time_period_milliseconds, cv.Range(min=core.TimePeriod(milliseconds=100)), @@ -23,7 +45,21 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await output.register_output(var, config) + if CONF_PIN in config: + pin = await cg.gpio_pin_expression(config[CONF_PIN]) + cg.add(var.set_pin(pin)) + if CONF_STATE_CHANGE_ACTION in config: + await automation.build_automation( + var.get_state_change_trigger(), + [(bool, "state")], + config[CONF_STATE_CHANGE_ACTION], + ) + if CONF_TURN_ON_ACTION in config: + await automation.build_automation( + var.get_turn_on_trigger(), [], config[CONF_TURN_ON_ACTION] + ) + await automation.build_automation( + var.get_turn_off_trigger(), [], config[CONF_TURN_OFF_ACTION] + ) - pin = await cg.gpio_pin_expression(config[CONF_PIN]) - cg.add(var.set_pin(pin)) cg.add(var.set_period(config[CONF_PERIOD])) diff --git a/esphome/components/slow_pwm/slow_pwm_output.cpp b/esphome/components/slow_pwm/slow_pwm_output.cpp index 8c88b3537c..573adbe3dc 100644 --- a/esphome/components/slow_pwm/slow_pwm_output.cpp +++ b/esphome/components/slow_pwm/slow_pwm_output.cpp @@ -7,10 +7,31 @@ namespace slow_pwm { static const char *const TAG = "output.slow_pwm"; void SlowPWMOutput::setup() { - this->pin_->setup(); + if (this->pin_) + this->pin_->setup(); this->turn_off(); } +/// turn on/off the configured output +void SlowPWMOutput::set_output_state_(bool new_state) { + if (this->pin_) { + this->pin_->digital_write(new_state); + } + if (new_state != current_state_) { + if (this->state_change_trigger_) { + this->state_change_trigger_->trigger(new_state); + } + if (new_state) { + if (this->turn_on_trigger_) + this->turn_on_trigger_->trigger(); + } else { + if (this->turn_off_trigger_) + this->turn_off_trigger_->trigger(); + } + current_state_ = new_state; + } +} + void SlowPWMOutput::loop() { uint32_t now = millis(); float scaled_state = this->state_ * this->period_; @@ -21,20 +42,24 @@ void SlowPWMOutput::loop() { } if (scaled_state > now - this->period_start_time_) { - this->pin_->digital_write(true); + this->set_output_state_(true); } else { - this->pin_->digital_write(false); + this->set_output_state_(false); } } void SlowPWMOutput::dump_config() { ESP_LOGCONFIG(TAG, "Slow PWM Output:"); LOG_PIN(" Pin: ", this->pin_); + if (this->state_change_trigger_) + ESP_LOGCONFIG(TAG, " State change automation configured"); + if (this->turn_on_trigger_) + ESP_LOGCONFIG(TAG, " Turn on automation configured"); + if (this->turn_off_trigger_) + ESP_LOGCONFIG(TAG, " Turn off automation configured"); ESP_LOGCONFIG(TAG, " Period: %d ms", this->period_); LOG_FLOAT_OUTPUT(this); } -void SlowPWMOutput::write_state(float state) { this->state_ = state; } - } // namespace slow_pwm } // namespace esphome diff --git a/esphome/components/slow_pwm/slow_pwm_output.h b/esphome/components/slow_pwm/slow_pwm_output.h index f0524f36d8..d5c5883f25 100644 --- a/esphome/components/slow_pwm/slow_pwm_output.h +++ b/esphome/components/slow_pwm/slow_pwm_output.h @@ -1,5 +1,5 @@ #pragma once - +#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" #include "esphome/components/output/float_output.h" @@ -11,19 +11,42 @@ class SlowPWMOutput : public output::FloatOutput, public Component { public: void set_pin(GPIOPin *pin) { pin_ = pin; }; void set_period(unsigned int period) { period_ = period; }; - /// Initialize pin void setup() override; void dump_config() override; /// HARDWARE setup_priority float get_setup_priority() const override { return setup_priority::HARDWARE; } - protected: - void write_state(float state) override; - void loop() override; + Trigger<> *get_turn_on_trigger() { + // Lazy create + if (!this->turn_on_trigger_) + this->turn_on_trigger_ = make_unique>(); + return this->turn_on_trigger_.get(); + } + Trigger<> *get_turn_off_trigger() { + if (!this->turn_off_trigger_) + this->turn_off_trigger_ = make_unique>(); + return this->turn_off_trigger_.get(); + } - GPIOPin *pin_; + Trigger *get_state_change_trigger() { + if (!this->state_change_trigger_) + this->state_change_trigger_ = make_unique>(); + return this->state_change_trigger_.get(); + } + + protected: + void loop() override; + void write_state(float state) override { state_ = state; } + /// turn on/off the configured output + void set_output_state_(bool state); + + GPIOPin *pin_{nullptr}; + std::unique_ptr> turn_on_trigger_{nullptr}; + std::unique_ptr> turn_off_trigger_{nullptr}; + std::unique_ptr> state_change_trigger_{nullptr}; float state_{0}; + bool current_state_{false}; unsigned int period_start_time_{0}; unsigned int period_{5000}; }; From 499625f266298227c3134d11a42e7421acdcc013 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 9 Jan 2022 23:07:37 +0100 Subject: [PATCH 527/549] Convert is_callable to a backport of std::is_invocable (#3023) --- .../components/api/homeassistant_service.h | 4 ++-- esphome/core/automation.h | 4 ++-- esphome/core/helpers.h | 23 ++++++++++--------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index 26269bcae4..90cfe751b6 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -12,10 +12,10 @@ template class TemplatableStringValue : public TemplatableValue() {} - template::value, int> = 0> + template::value, int> = 0> TemplatableStringValue(F value) : TemplatableValue(value) {} - template::value, int> = 0> + template::value, int> = 0> TemplatableStringValue(F f) : TemplatableValue([f](X... x) -> std::string { return to_string(f(x...)); }) {} }; diff --git a/esphome/core/automation.h b/esphome/core/automation.h index e5460bef34..f43fb98f20 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -21,10 +21,10 @@ template class TemplatableValue { public: TemplatableValue() : type_(EMPTY) {} - template::value, int> = 0> + template::value, int> = 0> TemplatableValue(F value) : type_(VALUE), value_(value) {} - template::value, int> = 0> + template::value, int> = 0> TemplatableValue(F f) : type_(LAMBDA), f_(f) {} bool has_value() { return this->type_ != EMPTY; } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 22ed018855..d677e34649 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -186,17 +186,6 @@ template class CallbackManager { std::vector> callbacks_; }; -// https://stackoverflow.com/a/37161919/8924614 -template -struct is_callable // NOLINT -{ - template static auto test(U *p) -> decltype((*p)(std::declval()...), void(), std::true_type()); - - template static auto test(...) -> decltype(std::false_type()); - - static constexpr auto value = decltype(test(nullptr))::value; // NOLINT -}; - void delay_microseconds_safe(uint32_t us); template class Deduplicator { @@ -274,6 +263,18 @@ template constexpr const T &clamp(const T &v, const T &lo, const T & } #endif +// std::is_invocable from C++17 +#if __cpp_lib_is_invocable >= 201703 +using std::is_invocable; +#else +// https://stackoverflow.com/a/37161919/8924614 +template struct is_invocable { // NOLINT(readability-identifier-naming) + template static auto test(U *p) -> decltype((*p)(std::declval()...), void(), std::true_type()); + template static auto test(...) -> decltype(std::false_type()); + static constexpr auto value = decltype(test(nullptr))::value; // NOLINT +}; +#endif + // std::bit_cast from C++20 #if __cpp_lib_bit_cast >= 201806 using std::bit_cast; From b406c6403c055f089aaa3978e269e7d28a471dbc Mon Sep 17 00:00:00 2001 From: Valentin Ochs Date: Sun, 9 Jan 2022 23:44:36 +0100 Subject: [PATCH 528/549] Create new kalman_combinator component (#2965) --- CODEOWNERS | 1 + .../components/kalman_combinator/__init__.py | 1 + .../kalman_combinator/kalman_combinator.cpp | 82 +++++++++++++++++ .../kalman_combinator/kalman_combinator.h | 46 ++++++++++ .../components/kalman_combinator/sensor.py | 87 +++++++++++++++++++ esphome/core/entity_helpers.py | 42 ++++++--- tests/test1.yaml | 11 +++ 7 files changed, 260 insertions(+), 10 deletions(-) create mode 100644 esphome/components/kalman_combinator/__init__.py create mode 100644 esphome/components/kalman_combinator/kalman_combinator.cpp create mode 100644 esphome/components/kalman_combinator/kalman_combinator.h create mode 100644 esphome/components/kalman_combinator/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 62e49055a2..deecd094dc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -83,6 +83,7 @@ esphome/components/inkplate6/* @jesserockz esphome/components/integration/* @OttoWinter esphome/components/interval/* @esphome/core esphome/components/json/* @OttoWinter +esphome/components/kalman_combinator/* @Cat-Ion esphome/components/ledc/* @OttoWinter esphome/components/light/* @esphome/core esphome/components/logger/* @esphome/core diff --git a/esphome/components/kalman_combinator/__init__.py b/esphome/components/kalman_combinator/__init__.py new file mode 100644 index 0000000000..3356e61bb2 --- /dev/null +++ b/esphome/components/kalman_combinator/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@Cat-Ion"] diff --git a/esphome/components/kalman_combinator/kalman_combinator.cpp b/esphome/components/kalman_combinator/kalman_combinator.cpp new file mode 100644 index 0000000000..d55f26126f --- /dev/null +++ b/esphome/components/kalman_combinator/kalman_combinator.cpp @@ -0,0 +1,82 @@ +#include "kalman_combinator.h" +#include "esphome/core/hal.h" +#include +#include + +namespace esphome { +namespace kalman_combinator { + +void KalmanCombinatorComponent::dump_config() { + ESP_LOGCONFIG("kalman_combinator", "Kalman Combinator:"); + ESP_LOGCONFIG("kalman_combinator", " Update variance: %f per ms", this->update_variance_value_); + ESP_LOGCONFIG("kalman_combinator", " Sensors:"); + for (const auto &sensor : this->sensors_) { + auto &entity = *sensor.first; + ESP_LOGCONFIG("kalman_combinator", " - %s", entity.get_name().c_str()); + } +} + +void KalmanCombinatorComponent::setup() { + for (const auto &sensor : this->sensors_) { + const auto stddev = sensor.second; + sensor.first->add_on_state_callback([this, stddev](float x) -> void { this->correct_(x, stddev(x)); }); + } +} + +void KalmanCombinatorComponent::add_source(Sensor *sensor, std::function const &stddev) { + this->sensors_.emplace_back(sensor, stddev); +} + +void KalmanCombinatorComponent::add_source(Sensor *sensor, float stddev) { + this->add_source(sensor, std::function{[stddev](float) -> float { return stddev; }}); +} + +void KalmanCombinatorComponent::update_variance_() { + uint32_t now = millis(); + + // Variance increases by update_variance_ each millisecond + auto dt = now - this->last_update_; + auto dv = this->update_variance_value_ * dt; + this->variance_ += dv; + this->last_update_ = now; +} + +void KalmanCombinatorComponent::correct_(float value, float stddev) { + if (std::isnan(value) || std::isinf(stddev)) { + return; + } + + if (std::isnan(this->state_) || std::isinf(this->variance_)) { + this->state_ = value; + this->variance_ = stddev * stddev; + if (this->std_dev_sensor_ != nullptr) { + this->std_dev_sensor_->publish_state(stddev); + } + return; + } + + this->update_variance_(); + + // Combine two gaussian distributions mu1+-var1, mu2+-var2 to a new one around mu + // Use the value with the smaller variance as mu1 to prevent precision errors + const bool this_first = this->variance_ < (stddev * stddev); + const float mu1 = this_first ? this->state_ : value; + const float mu2 = this_first ? value : this->state_; + + const float var1 = this_first ? this->variance_ : stddev * stddev; + const float var2 = this_first ? stddev * stddev : this->variance_; + + const float mu = mu1 + var1 * (mu2 - mu1) / (var1 + var2); + const float var = var1 - (var1 * var1) / (var1 + var2); + + // Update and publish state + this->state_ = mu; + this->variance_ = var; + + this->publish_state(mu); + if (this->std_dev_sensor_ != nullptr) { + this->std_dev_sensor_->publish_state(std::sqrt(var)); + } +} +} // namespace kalman_combinator +} // namespace esphome diff --git a/esphome/components/kalman_combinator/kalman_combinator.h b/esphome/components/kalman_combinator/kalman_combinator.h new file mode 100644 index 0000000000..afbe3ece92 --- /dev/null +++ b/esphome/components/kalman_combinator/kalman_combinator.h @@ -0,0 +1,46 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include +#include + +namespace esphome { +namespace kalman_combinator { + +class KalmanCombinatorComponent : public Component, public sensor::Sensor { + public: + KalmanCombinatorComponent() = default; + + float get_setup_priority() const override { return esphome::setup_priority::DATA; } + + void dump_config() override; + void setup() override; + + void add_source(Sensor *sensor, std::function const &stddev); + void add_source(Sensor *sensor, float stddev); + void set_process_std_dev(float process_std_dev) { + this->update_variance_value_ = process_std_dev * process_std_dev * 0.001f; + } + void set_std_dev_sensor(Sensor *sensor) { this->std_dev_sensor_ = sensor; } + + private: + void update_variance_(); + void correct_(float value, float stddev); + + // Source sensors and their error functions + std::vector>> sensors_; + + // Optional sensor for publishing the current error + sensor::Sensor *std_dev_sensor_{nullptr}; + + // Tick of the last update + uint32_t last_update_{0}; + // Change of the variance, per ms + float update_variance_value_{0.f}; + + // Best guess for the state and its variance + float state_{NAN}; + float variance_{INFINITY}; +}; +} // namespace kalman_combinator +} // namespace esphome diff --git a/esphome/components/kalman_combinator/sensor.py b/esphome/components/kalman_combinator/sensor.py new file mode 100644 index 0000000000..9223f883b2 --- /dev/null +++ b/esphome/components/kalman_combinator/sensor.py @@ -0,0 +1,87 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_ID, + CONF_SOURCE, + CONF_ACCURACY_DECIMALS, + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_UNIT_OF_MEASUREMENT, +) +from esphome.core.entity_helpers import inherit_property_from + +kalman_combinator_ns = cg.esphome_ns.namespace("kalman_combinator") +KalmanCombinatorComponent = kalman_combinator_ns.class_( + "KalmanCombinatorComponent", cg.Component, sensor.Sensor +) + +CONF_ERROR = "error" +CONF_SOURCES = "sources" +CONF_PROCESS_STD_DEV = "process_std_dev" +CONF_STD_DEV = "std_dev" + + +CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( + { + cv.GenerateID(): cv.declare_id(KalmanCombinatorComponent), + cv.Required(CONF_PROCESS_STD_DEV): cv.positive_float, + cv.Required(CONF_SOURCES): cv.ensure_list( + cv.Schema( + { + cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor), + cv.Required(CONF_ERROR): cv.templatable(cv.positive_float), + } + ), + ), + cv.Optional(CONF_STD_DEV): sensor.SENSOR_SCHEMA, + } +) + +# Inherit some sensor values from the first source, for both the state and the error value +properties_to_inherit = [ + CONF_ACCURACY_DECIMALS, + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_UNIT_OF_MEASUREMENT, + # CONF_STATE_CLASS could also be inherited, but might lead to unexpected behaviour with "total_increasing" +] +inherit_schema_for_state = [ + inherit_property_from(property, [CONF_SOURCES, 0, CONF_SOURCE]) + for property in properties_to_inherit +] +inherit_schema_for_std_dev = [ + inherit_property_from([CONF_STD_DEV, property], [CONF_SOURCES, 0, CONF_SOURCE]) + for property in properties_to_inherit +] + +FINAL_VALIDATE_SCHEMA = cv.All( + CONFIG_SCHEMA.extend( + {cv.Required(CONF_ID): cv.use_id(KalmanCombinatorComponent)}, + extra=cv.ALLOW_EXTRA, + ), + *inherit_schema_for_state, + *inherit_schema_for_std_dev, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + + cg.add(var.set_process_std_dev(config[CONF_PROCESS_STD_DEV])) + for source_conf in config[CONF_SOURCES]: + source = await cg.get_variable(source_conf[CONF_SOURCE]) + error = await cg.templatable( + source_conf[CONF_ERROR], + [(float, "x")], + cg.float_, + ) + cg.add(var.add_source(source, error)) + + if CONF_STD_DEV in config: + sens = await sensor.new_sensor(config[CONF_STD_DEV]) + cg.add(var.set_std_dev_sensor(sens)) diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index b2dbe2116e..7f7d78aaa2 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -5,27 +5,49 @@ from esphome.const import CONF_ID def inherit_property_from(property_to_inherit, parent_id_property): """Validator that inherits a configuration property from another entity, for use with FINAL_VALIDATE_SCHEMA. - If a property is already set, it will not be inherited. - Keyword arguments: - property_to_inherit -- the name of the property to inherit, e.g. CONF_ICON - parent_id_property -- the name of the property that holds the ID of the parent, e.g. CONF_POWER_ID + property_to_inherit -- the name or path of the property to inherit, e.g. CONF_ICON or [CONF_SENSOR, 0, CONF_ICON] + (the parent must exist, otherwise nothing is done). + parent_id_property -- the name or path of the property that holds the ID of the parent, e.g. CONF_POWER_ID or + [CONF_SENSOR, 1, CONF_POWER_ID]. """ + def _walk_config(config, path): + walk = [path] if not isinstance(path, list) else path + for item_or_index in walk: + config = config[item_or_index] + return config + def inherit_property(config): - if property_to_inherit not in config: + # Split the property into its path and name + if not isinstance(property_to_inherit, list): + property_path, property = [], property_to_inherit + else: + property_path, property = property_to_inherit[:-1], property_to_inherit[-1] + + # Check if the property to inherit is accessible + try: + config_part = _walk_config(config, property_path) + except KeyError: + return config + + # Only inherit the property if it does not exist yet + if property not in config_part: fconf = fv.full_config.get() # Get config for the parent entity - path = fconf.get_path_for_id(config[parent_id_property])[:-1] - parent_config = fconf.get_config_for_path(path) + parent_id = _walk_config(config, parent_id_property) + parent_path = fconf.get_path_for_id(parent_id)[:-1] + parent_config = fconf.get_config_for_path(parent_path) # If parent sensor has the property set, inherit it - if property_to_inherit in parent_config: + if property in parent_config: path = fconf.get_path_for_id(config[CONF_ID])[:-1] - this_config = fconf.get_config_for_path(path) - this_config[property_to_inherit] = parent_config[property_to_inherit] + this_config = _walk_config( + fconf.get_config_for_path(path), property_path + ) + this_config[property] = parent_config[property] return config diff --git a/tests/test1.yaml b/tests/test1.yaml index 7494146d1e..2836a97e4f 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -657,6 +657,15 @@ sensor: name: 'INA3221 Channel 1 Shunt Voltage' update_interval: 15s i2c_id: i2c_bus + - platform: kalman_combinator + name: "Kalman-filtered temperature" + process_std_dev: 0.00139 + sources: + - source: scd30_temperature + error: !lambda |- + return 0.4 + std::abs(x - 25) * 0.023; + - source: scd4x_temperature + error: 1.5 - platform: htu21d temperature: name: 'Living Room Temperature 6' @@ -820,6 +829,7 @@ sensor: co2: name: 'Living Room CO2 9' temperature: + id: scd30_temperature name: 'Living Room Temperature 9' humidity: name: 'Living Room Humidity 9' @@ -834,6 +844,7 @@ sensor: co2: name: "SCD4X CO2" temperature: + id: scd4x_temperature name: "SCD4X Temperature" humidity: name: "SCD4X Humidity" From 9a70bfa47187011ce9eccf7d2939bd067f53d8d6 Mon Sep 17 00:00:00 2001 From: Sergey Dudanov Date: Mon, 10 Jan 2022 02:47:19 +0400 Subject: [PATCH 529/549] New Midea IR component, improvements and fixes (#2847) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/climate_ir/climate_ir.cpp | 16 +- esphome/components/climate_ir/climate_ir.h | 4 +- esphome/components/coolix/coolix.cpp | 159 ++++---------- esphome/components/coolix/coolix.h | 6 +- esphome/components/midea/ir_transmitter.h | 8 +- esphome/components/midea_ir/__init__.py | 0 esphome/components/midea_ir/climate.py | 25 +++ esphome/components/midea_ir/midea_data.h | 92 ++++++++ esphome/components/midea_ir/midea_ir.cpp | 201 ++++++++++++++++++ esphome/components/midea_ir/midea_ir.h | 47 ++++ esphome/components/remote_base/__init__.py | 39 ++++ .../remote_base/coolix_protocol.cpp | 84 ++++++++ .../components/remote_base/coolix_protocol.h | 30 +++ .../components/remote_base/midea_protocol.cpp | 104 ++++----- .../components/remote_base/midea_protocol.h | 62 +++--- esphome/core/helpers.cpp | 4 +- esphome/core/helpers.h | 9 +- tests/test1.yaml | 3 + 19 files changed, 664 insertions(+), 230 deletions(-) create mode 100644 esphome/components/midea_ir/__init__.py create mode 100644 esphome/components/midea_ir/climate.py create mode 100644 esphome/components/midea_ir/midea_data.h create mode 100644 esphome/components/midea_ir/midea_ir.cpp create mode 100644 esphome/components/midea_ir/midea_ir.h create mode 100644 esphome/components/remote_base/coolix_protocol.cpp create mode 100644 esphome/components/remote_base/coolix_protocol.h diff --git a/CODEOWNERS b/CODEOWNERS index deecd094dc..5f9a579827 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -102,6 +102,7 @@ esphome/components/mcp9808/* @k7hpn esphome/components/md5/* @esphome/core esphome/components/mdns/* @esphome/core esphome/components/midea/* @dudanov +esphome/components/midea_ir/* @dudanov esphome/components/mitsubishi/* @RubyBailey esphome/components/modbus_controller/* @martgras esphome/components/modbus_controller/binary_sensor/* @martgras diff --git a/esphome/components/climate_ir/climate_ir.cpp b/esphome/components/climate_ir/climate_ir.cpp index b47d9b0141..76adfb42bb 100644 --- a/esphome/components/climate_ir/climate_ir.cpp +++ b/esphome/components/climate_ir/climate_ir.cpp @@ -10,21 +10,22 @@ climate::ClimateTraits ClimateIR::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->sensor_ != nullptr); traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); - if (supports_cool_) + if (this->supports_cool_) traits.add_supported_mode(climate::CLIMATE_MODE_COOL); - if (supports_heat_) + if (this->supports_heat_) traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); - if (supports_dry_) + if (this->supports_dry_) traits.add_supported_mode(climate::CLIMATE_MODE_DRY); - if (supports_fan_only_) + if (this->supports_fan_only_) traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); traits.set_supports_two_point_target_temperature(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_); - traits.set_supported_fan_modes(fan_modes_); - traits.set_supported_swing_modes(swing_modes_); + traits.set_supported_fan_modes(this->fan_modes_); + traits.set_supported_swing_modes(this->swing_modes_); + traits.set_supported_presets(this->presets_); return traits; } @@ -50,6 +51,7 @@ void ClimateIR::setup() { roundf(clamp(this->current_temperature, this->minimum_temperature_, this->maximum_temperature_)); this->fan_mode = climate::CLIMATE_FAN_AUTO; this->swing_mode = climate::CLIMATE_SWING_OFF; + this->preset = climate::CLIMATE_PRESET_NONE; } // Never send nan to HA if (std::isnan(this->target_temperature)) @@ -65,6 +67,8 @@ void ClimateIR::control(const climate::ClimateCall &call) { this->fan_mode = *call.get_fan_mode(); if (call.get_swing_mode().has_value()) this->swing_mode = *call.get_swing_mode(); + if (call.get_preset().has_value()) + this->preset = *call.get_preset(); this->transmit_state(); this->publish_state(); } diff --git a/esphome/components/climate_ir/climate_ir.h b/esphome/components/climate_ir/climate_ir.h index 677021da29..5be4fc06f5 100644 --- a/esphome/components/climate_ir/climate_ir.h +++ b/esphome/components/climate_ir/climate_ir.h @@ -22,7 +22,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base: public: ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f, bool supports_dry = false, bool supports_fan_only = false, std::set fan_modes = {}, - std::set swing_modes = {}) { + std::set swing_modes = {}, std::set presets = {}) { this->minimum_temperature_ = minimum_temperature; this->maximum_temperature_ = maximum_temperature; this->temperature_step_ = temperature_step; @@ -30,6 +30,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base: this->supports_fan_only_ = supports_fan_only; this->fan_modes_ = std::move(fan_modes); this->swing_modes_ = std::move(swing_modes); + this->presets_ = std::move(presets); } void setup() override; @@ -61,6 +62,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base: bool supports_fan_only_{false}; std::set fan_modes_ = {}; std::set swing_modes_ = {}; + std::set presets_ = {}; remote_transmitter::RemoteTransmitterComponent *transmitter_; sensor::Sensor *sensor_{nullptr}; diff --git a/esphome/components/coolix/coolix.cpp b/esphome/components/coolix/coolix.cpp index c9145e4ecf..76ec1627c2 100644 --- a/esphome/components/coolix/coolix.cpp +++ b/esphome/components/coolix/coolix.cpp @@ -1,4 +1,5 @@ #include "coolix.h" +#include "esphome/components/remote_base/coolix_protocol.h" #include "esphome/core/log.h" namespace esphome { @@ -6,29 +7,29 @@ namespace coolix { static const char *const TAG = "coolix.climate"; -const uint32_t COOLIX_OFF = 0xB27BE0; -const uint32_t COOLIX_SWING = 0xB26BE0; -const uint32_t COOLIX_LED = 0xB5F5A5; -const uint32_t COOLIX_SILENCE_FP = 0xB5F5B6; +static const uint32_t COOLIX_OFF = 0xB27BE0; +static const uint32_t COOLIX_SWING = 0xB26BE0; +static const uint32_t COOLIX_LED = 0xB5F5A5; +static const uint32_t COOLIX_SILENCE_FP = 0xB5F5B6; // On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore. -const uint8_t COOLIX_COOL = 0b0000; -const uint8_t COOLIX_DRY_FAN = 0b0100; -const uint8_t COOLIX_AUTO = 0b1000; -const uint8_t COOLIX_HEAT = 0b1100; -const uint32_t COOLIX_MODE_MASK = 0b1100; -const uint32_t COOLIX_FAN_MASK = 0xF000; -const uint32_t COOLIX_FAN_MODE_AUTO_DRY = 0x1000; -const uint32_t COOLIX_FAN_AUTO = 0xB000; -const uint32_t COOLIX_FAN_MIN = 0x9000; -const uint32_t COOLIX_FAN_MED = 0x5000; -const uint32_t COOLIX_FAN_MAX = 0x3000; +static const uint8_t COOLIX_COOL = 0b0000; +static const uint8_t COOLIX_DRY_FAN = 0b0100; +static const uint8_t COOLIX_AUTO = 0b1000; +static const uint8_t COOLIX_HEAT = 0b1100; +static const uint32_t COOLIX_MODE_MASK = 0b1100; +static const uint32_t COOLIX_FAN_MASK = 0xF000; +static const uint32_t COOLIX_FAN_MODE_AUTO_DRY = 0x1000; +static const uint32_t COOLIX_FAN_AUTO = 0xB000; +static const uint32_t COOLIX_FAN_MIN = 0x9000; +static const uint32_t COOLIX_FAN_MED = 0x5000; +static const uint32_t COOLIX_FAN_MAX = 0x3000; // Temperature -const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1; -const uint8_t COOLIX_FAN_TEMP_CODE = 0b11100000; // Part of Fan Mode. -const uint32_t COOLIX_TEMP_MASK = 0b11110000; -const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = { +static const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1; +static const uint8_t COOLIX_FAN_TEMP_CODE = 0b11100000; // Part of Fan Mode. +static const uint32_t COOLIX_TEMP_MASK = 0b11110000; +static const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = { 0b00000000, // 17C 0b00010000, // 18c 0b00110000, // 19C @@ -45,17 +46,6 @@ const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = { 0b10110000 // 30C }; -// Constants -static const uint32_t BIT_MARK_US = 660; -static const uint32_t HEADER_MARK_US = 560 * 8; -static const uint32_t HEADER_SPACE_US = 560 * 8; -static const uint32_t BIT_ONE_SPACE_US = 1500; -static const uint32_t BIT_ZERO_SPACE_US = 450; -static const uint32_t FOOTER_MARK_US = BIT_MARK_US; -static const uint32_t FOOTER_SPACE_US = HEADER_SPACE_US; - -const uint16_t COOLIX_BITS = 24; - void CoolixClimate::transmit_state() { uint32_t remote_state = 0xB20F00; @@ -111,119 +101,60 @@ void CoolixClimate::transmit_state() { } } } - ESP_LOGV(TAG, "Sending coolix code: 0x%02X", remote_state); + ESP_LOGV(TAG, "Sending coolix code: 0x%06X", remote_state); auto transmit = this->transmitter_->transmit(); auto data = transmit.get_data(); - - data->set_carrier_frequency(38000); - uint16_t repeat = 1; - for (uint16_t r = 0; r <= repeat; r++) { - // Header - data->mark(HEADER_MARK_US); - data->space(HEADER_SPACE_US); - // Data - // Break data into bytes, starting at the Most Significant - // Byte. Each byte then being sent normal, then followed inverted. - for (uint16_t i = 8; i <= COOLIX_BITS; i += 8) { - // Grab a bytes worth of data. - uint8_t byte = (remote_state >> (COOLIX_BITS - i)) & 0xFF; - // Normal - for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) { - data->mark(BIT_MARK_US); - data->space((byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US); - } - // Inverted - for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) { - data->mark(BIT_MARK_US); - data->space(!(byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US); - } - } - // Footer - data->mark(BIT_MARK_US); - data->space(FOOTER_SPACE_US); // Pause before repeating - } - + remote_base::CoolixProtocol().encode(data, remote_state); transmit.perform(); } -bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) { +bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteReceiveData data) { + auto decoded = remote_base::CoolixProtocol().decode(data); + if (!decoded.has_value()) + return false; // Decoded remote state y 3 bytes long code. - uint32_t remote_state = 0; - // The protocol sends the data twice, read here - uint32_t loop_read; - for (uint16_t loop = 1; loop <= 2; loop++) { - if (!data.expect_item(HEADER_MARK_US, HEADER_SPACE_US)) - return false; - loop_read = 0; - for (uint8_t a_byte = 0; a_byte < 3; a_byte++) { - uint8_t byte = 0; - for (int8_t a_bit = 7; a_bit >= 0; a_bit--) { - if (data.expect_item(BIT_MARK_US, BIT_ONE_SPACE_US)) - byte |= 1 << a_bit; - else if (!data.expect_item(BIT_MARK_US, BIT_ZERO_SPACE_US)) - return false; - } - // Need to see this segment inverted - for (int8_t a_bit = 7; a_bit >= 0; a_bit--) { - bool bit = byte & (1 << a_bit); - if (!data.expect_item(BIT_MARK_US, bit ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US)) - return false; - } - // Receiving MSB first: reorder bytes - loop_read |= byte << ((2 - a_byte) * 8); - } - // Footer Mark - if (!data.expect_mark(BIT_MARK_US)) - return false; - if (loop == 1) { - // Back up state on first loop - remote_state = loop_read; - if (!data.expect_space(FOOTER_SPACE_US)) - return false; - } - } - - ESP_LOGV(TAG, "Decoded 0x%02X", remote_state); - if (remote_state != loop_read || (remote_state & 0xFF0000) != 0xB20000) + uint32_t remote_state = *decoded; + ESP_LOGV(TAG, "Decoded 0x%06X", remote_state); + if ((remote_state & 0xFF0000) != 0xB20000) return false; if (remote_state == COOLIX_OFF) { - this->mode = climate::CLIMATE_MODE_OFF; + parent->mode = climate::CLIMATE_MODE_OFF; } else if (remote_state == COOLIX_SWING) { - this->swing_mode = - this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; + parent->swing_mode = + parent->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; } else { if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT) - this->mode = climate::CLIMATE_MODE_HEAT; + parent->mode = climate::CLIMATE_MODE_HEAT; else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO) - this->mode = climate::CLIMATE_MODE_HEAT_COOL; + parent->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; + parent->mode = climate::CLIMATE_MODE_DRY; else - this->mode = climate::CLIMATE_MODE_FAN_ONLY; + parent->mode = climate::CLIMATE_MODE_FAN_ONLY; } else - this->mode = climate::CLIMATE_MODE_COOL; + parent->mode = climate::CLIMATE_MODE_COOL; // Fan Speed - 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; + if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || parent->mode == climate::CLIMATE_MODE_HEAT_COOL || + parent->mode == climate::CLIMATE_MODE_DRY) + parent->fan_mode = climate::CLIMATE_FAN_AUTO; else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN) - this->fan_mode = climate::CLIMATE_FAN_LOW; + parent->fan_mode = climate::CLIMATE_FAN_LOW; else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED) - this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + parent->fan_mode = climate::CLIMATE_FAN_MEDIUM; else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX) - this->fan_mode = climate::CLIMATE_FAN_HIGH; + parent->fan_mode = climate::CLIMATE_FAN_HIGH; // Temperature uint8_t temperature_code = remote_state & COOLIX_TEMP_MASK; for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++) if (COOLIX_TEMP_MAP[i] == temperature_code) - this->target_temperature = i + COOLIX_TEMP_MIN; + parent->target_temperature = i + COOLIX_TEMP_MIN; } - this->publish_state(); + parent->publish_state(); return true; } diff --git a/esphome/components/coolix/coolix.h b/esphome/components/coolix/coolix.h index caf93f7621..3419795875 100644 --- a/esphome/components/coolix/coolix.h +++ b/esphome/components/coolix/coolix.h @@ -26,11 +26,15 @@ class CoolixClimate : public climate_ir::ClimateIR { climate_ir::ClimateIR::control(call); } + /// This static method can be used in other climate components that accept the Coolix protocol. See midea_ir for + /// example. + static bool on_coolix(climate::Climate *parent, remote_base::RemoteReceiveData data); + protected: /// Transmit via IR the state of this climate controller. void transmit_state() override; /// Handle received IR Buffer - bool on_receive(remote_base::RemoteReceiveData data) override; + bool on_receive(remote_base::RemoteReceiveData data) override { return CoolixClimate::on_coolix(this, data); } bool send_swing_cmd_{false}; }; diff --git a/esphome/components/midea/ir_transmitter.h b/esphome/components/midea/ir_transmitter.h index 34a9f8498e..a8b89f9b7b 100644 --- a/esphome/components/midea/ir_transmitter.h +++ b/esphome/components/midea/ir_transmitter.h @@ -23,12 +23,12 @@ class IrFollowMeData : public IrData { } /* 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; } + uint8_t temp() const { return this->get_value_(4) - 1; } + void set_temp(uint8_t val) { this->set_value_(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); } + bool beeper() const { return this->get_value_(3, 128); } + void set_beeper(bool val) { this->set_mask_(3, val, 128); } protected: static const uint8_t MAX_TEMP = 37; diff --git a/esphome/components/midea_ir/__init__.py b/esphome/components/midea_ir/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/midea_ir/climate.py b/esphome/components/midea_ir/climate.py new file mode 100644 index 0000000000..140e4ee4e0 --- /dev/null +++ b/esphome/components/midea_ir/climate.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate_ir +from esphome.const import CONF_ID + +AUTO_LOAD = ["climate_ir", "coolix"] +CODEOWNERS = ["@dudanov"] + +midea_ir_ns = cg.esphome_ns.namespace("midea_ir") +MideaIR = midea_ir_ns.class_("MideaIR", climate_ir.ClimateIR) + +CONF_USE_FAHRENHEIT = "use_fahrenheit" + +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(MideaIR), + cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean, + } +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await climate_ir.register_climate_ir(var, config) + cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT])) diff --git a/esphome/components/midea_ir/midea_data.h b/esphome/components/midea_ir/midea_data.h new file mode 100644 index 0000000000..0f7e24907d --- /dev/null +++ b/esphome/components/midea_ir/midea_data.h @@ -0,0 +1,92 @@ +#pragma once + +#include "esphome/components/remote_base/midea_protocol.h" +#include "esphome/components/climate/climate_mode.h" + +namespace esphome { +namespace midea_ir { + +using climate::ClimateMode; +using climate::ClimateFanMode; +using remote_base::MideaData; + +class ControlData : public MideaData { + public: + // Default constructor (power: ON, mode: AUTO, fan: AUTO, temp: 25C) + ControlData() : MideaData({MIDEA_TYPE_CONTROL, 0x82, 0x48, 0xFF, 0xFF}) {} + // Copy from Base + ControlData(const MideaData &data) : MideaData(data) {} + + void set_temp(float temp); + float get_temp() const; + + void set_mode(ClimateMode mode); + ClimateMode get_mode() const; + + void set_fan_mode(ClimateFanMode mode); + ClimateFanMode get_fan_mode() const; + + void set_sleep_preset(bool value) { this->set_mask_(1, value, 64); } + bool get_sleep_preset() const { return this->get_value_(1, 64); } + + void set_fahrenheit(bool value) { this->set_mask_(2, value, 32); } + bool get_fahrenheit() const { return this->get_value_(2, 32); } + + void fix(); + + protected: + enum Mode : uint8_t { + MODE_COOL, + MODE_DRY, + MODE_AUTO, + MODE_HEAT, + MODE_FAN_ONLY, + }; + enum FanMode : uint8_t { + FAN_AUTO, + FAN_LOW, + FAN_MEDIUM, + FAN_HIGH, + }; + void set_fan_mode_(FanMode mode) { this->set_value_(1, mode, 3, 3); } + FanMode get_fan_mode_() const { return static_cast(this->get_value_(1, 3, 3)); } + void set_mode_(Mode mode) { this->set_value_(1, mode, 7); } + Mode get_mode_() const { return static_cast(this->get_value_(1, 7)); } + void set_power_(bool value) { this->set_mask_(1, value, 128); } + bool get_power_() const { return this->get_value_(1, 128); } +}; + +class FollowMeData : public MideaData { + public: + // Default constructor (temp: 30C, beeper: off) + FollowMeData() : MideaData({MIDEA_TYPE_FOLLOW_ME, 0x82, 0x48, 0x7F, 0x1F}) {} + // Copy from Base + FollowMeData(const MideaData &data) : MideaData(data) {} + // Direct from temperature and beeper values + FollowMeData(uint8_t temp, bool beeper = false) : FollowMeData() { + this->set_temp(temp); + this->set_beeper(beeper); + } + + /* TEMPERATURE */ + uint8_t temp() const { return this->get_value_(4) - 1; } + void set_temp(uint8_t val) { this->set_value_(4, std::min(MAX_TEMP, val) + 1); } + + /* BEEPER */ + bool beeper() const { return this->get_value_(3, 128); } + void set_beeper(bool value) { this->set_mask_(3, value, 128); } + + protected: + static const uint8_t MAX_TEMP = 37; +}; + +class SpecialData : public MideaData { + public: + SpecialData(uint8_t code) : MideaData({MIDEA_TYPE_SPECIAL, code, 0xFF, 0xFF, 0xFF}) {} + static const uint8_t VSWING_STEP = 1; + static const uint8_t VSWING_TOGGLE = 2; + static const uint8_t TURBO_TOGGLE = 9; +}; + +} // namespace midea_ir +} // namespace esphome diff --git a/esphome/components/midea_ir/midea_ir.cpp b/esphome/components/midea_ir/midea_ir.cpp new file mode 100644 index 0000000000..5e507cbbb0 --- /dev/null +++ b/esphome/components/midea_ir/midea_ir.cpp @@ -0,0 +1,201 @@ +#include "midea_ir.h" +#include "midea_data.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "esphome/components/coolix/coolix.h" + +namespace esphome { +namespace midea_ir { + +static const char *const TAG = "midea_ir.climate"; + +void ControlData::set_temp(float temp) { + uint8_t min; + if (this->get_fahrenheit()) { + min = MIDEA_TEMPF_MIN; + temp = esphome::clamp(celsius_to_fahrenheit(temp), MIDEA_TEMPF_MIN, MIDEA_TEMPF_MAX); + } else { + min = MIDEA_TEMPC_MIN; + temp = esphome::clamp(temp, MIDEA_TEMPC_MIN, MIDEA_TEMPC_MAX); + } + this->set_value_(2, lroundf(temp) - min, 31); +} + +float ControlData::get_temp() const { + const uint8_t temp = this->get_value_(2, 31); + if (this->get_fahrenheit()) + return fahrenheit_to_celsius(static_cast(temp + MIDEA_TEMPF_MIN)); + return static_cast(temp + MIDEA_TEMPC_MIN); +} + +void ControlData::fix() { + // In FAN_AUTO, modes COOL, HEAT and FAN_ONLY bit #5 in byte #1 must be set + const uint8_t value = this->get_value_(1, 31); + if (value == 0 || value == 3 || value == 4) + this->set_mask_(1, true, 32); + // In FAN_ONLY mode we need to set all temperature bits + if (this->get_mode_() == MODE_FAN_ONLY) + this->set_mask_(2, true, 31); +} + +void ControlData::set_mode(ClimateMode mode) { + switch (mode) { + case ClimateMode::CLIMATE_MODE_OFF: + this->set_power_(false); + return; + case ClimateMode::CLIMATE_MODE_COOL: + this->set_mode_(MODE_COOL); + break; + case ClimateMode::CLIMATE_MODE_DRY: + this->set_mode_(MODE_DRY); + break; + case ClimateMode::CLIMATE_MODE_FAN_ONLY: + this->set_mode_(MODE_FAN_ONLY); + break; + case ClimateMode::CLIMATE_MODE_HEAT: + this->set_mode_(MODE_HEAT); + break; + default: + this->set_mode_(MODE_AUTO); + break; + } + this->set_power_(true); +} + +ClimateMode ControlData::get_mode() const { + if (!this->get_power_()) + return ClimateMode::CLIMATE_MODE_OFF; + switch (this->get_mode_()) { + case MODE_COOL: + return ClimateMode::CLIMATE_MODE_COOL; + case MODE_DRY: + return ClimateMode::CLIMATE_MODE_DRY; + case MODE_FAN_ONLY: + return ClimateMode::CLIMATE_MODE_FAN_ONLY; + case MODE_HEAT: + return ClimateMode::CLIMATE_MODE_HEAT; + default: + return ClimateMode::CLIMATE_MODE_HEAT_COOL; + } +} + +void ControlData::set_fan_mode(ClimateFanMode mode) { + switch (mode) { + case ClimateFanMode::CLIMATE_FAN_LOW: + this->set_fan_mode_(FAN_LOW); + break; + case ClimateFanMode::CLIMATE_FAN_MEDIUM: + this->set_fan_mode_(FAN_MEDIUM); + break; + case ClimateFanMode::CLIMATE_FAN_HIGH: + this->set_fan_mode_(FAN_HIGH); + break; + default: + this->set_fan_mode_(FAN_AUTO); + break; + } +} + +ClimateFanMode ControlData::get_fan_mode() const { + switch (this->get_fan_mode_()) { + case FAN_LOW: + return ClimateFanMode::CLIMATE_FAN_LOW; + case FAN_MEDIUM: + return ClimateFanMode::CLIMATE_FAN_MEDIUM; + case FAN_HIGH: + return ClimateFanMode::CLIMATE_FAN_HIGH; + default: + return ClimateFanMode::CLIMATE_FAN_AUTO; + } +} + +void MideaIR::control(const climate::ClimateCall &call) { + // swing and preset resets after unit powered off + if (call.get_mode() == climate::CLIMATE_MODE_OFF) { + this->swing_mode = climate::CLIMATE_SWING_OFF; + this->preset = climate::CLIMATE_PRESET_NONE; + } else if (call.get_swing_mode().has_value() && ((*call.get_swing_mode() == climate::CLIMATE_SWING_OFF && + this->swing_mode == climate::CLIMATE_SWING_VERTICAL) || + (*call.get_swing_mode() == climate::CLIMATE_SWING_VERTICAL && + this->swing_mode == climate::CLIMATE_SWING_OFF))) { + this->swing_ = true; + } else if (call.get_preset().has_value() && + ((*call.get_preset() == climate::CLIMATE_PRESET_NONE && this->preset == climate::CLIMATE_PRESET_BOOST) || + (*call.get_preset() == climate::CLIMATE_PRESET_BOOST && this->preset == climate::CLIMATE_PRESET_NONE))) { + this->boost_ = true; + } + climate_ir::ClimateIR::control(call); +} + +void MideaIR::transmit_(MideaData &data) { + data.finalize(); + auto transmit = this->transmitter_->transmit(); + remote_base::MideaProtocol().encode(transmit.get_data(), data); + transmit.perform(); +} + +void MideaIR::transmit_state() { + if (this->swing_) { + SpecialData data(SpecialData::VSWING_TOGGLE); + this->transmit_(data); + this->swing_ = false; + return; + } + if (this->boost_) { + SpecialData data(SpecialData::TURBO_TOGGLE); + this->transmit_(data); + this->boost_ = false; + return; + } + ControlData data; + data.set_fahrenheit(this->fahrenheit_); + data.set_temp(this->target_temperature); + data.set_mode(this->mode); + data.set_fan_mode(this->fan_mode.value_or(ClimateFanMode::CLIMATE_FAN_AUTO)); + data.set_sleep_preset(this->preset == climate::CLIMATE_PRESET_SLEEP); + data.fix(); + this->transmit_(data); +} + +bool MideaIR::on_receive(remote_base::RemoteReceiveData data) { + auto midea = remote_base::MideaProtocol().decode(data); + if (midea.has_value()) + return this->on_midea_(*midea); + return coolix::CoolixClimate::on_coolix(this, data); +} + +bool MideaIR::on_midea_(const MideaData &data) { + ESP_LOGV(TAG, "Decoded Midea IR data: %s", data.to_string().c_str()); + if (data.type() == MideaData::MIDEA_TYPE_CONTROL) { + const ControlData status = data; + if (status.get_mode() != climate::CLIMATE_MODE_FAN_ONLY) + this->target_temperature = status.get_temp(); + this->mode = status.get_mode(); + this->fan_mode = status.get_fan_mode(); + if (status.get_sleep_preset()) + this->preset = climate::CLIMATE_PRESET_SLEEP; + else if (this->preset == climate::CLIMATE_PRESET_SLEEP) + this->preset = climate::CLIMATE_PRESET_NONE; + this->publish_state(); + return true; + } + if (data.type() == MideaData::MIDEA_TYPE_SPECIAL) { + switch (data[1]) { + case SpecialData::VSWING_TOGGLE: + this->swing_mode = this->swing_mode == climate::CLIMATE_SWING_VERTICAL ? climate::CLIMATE_SWING_OFF + : climate::CLIMATE_SWING_VERTICAL; + break; + case SpecialData::TURBO_TOGGLE: + this->preset = this->preset == climate::CLIMATE_PRESET_BOOST ? climate::CLIMATE_PRESET_NONE + : climate::CLIMATE_PRESET_BOOST; + break; + } + this->publish_state(); + return true; + } + + return false; +} + +} // namespace midea_ir +} // namespace esphome diff --git a/esphome/components/midea_ir/midea_ir.h b/esphome/components/midea_ir/midea_ir.h new file mode 100644 index 0000000000..b89b2a7efc --- /dev/null +++ b/esphome/components/midea_ir/midea_ir.h @@ -0,0 +1,47 @@ +#pragma once + +#include "esphome/components/climate_ir/climate_ir.h" +#include "midea_data.h" + +namespace esphome { +namespace midea_ir { + +// Temperature +const uint8_t MIDEA_TEMPC_MIN = 17; // Celsius +const uint8_t MIDEA_TEMPC_MAX = 30; // Celsius +const uint8_t MIDEA_TEMPF_MIN = 62; // Fahrenheit +const uint8_t MIDEA_TEMPF_MAX = 86; // Fahrenheit + +class MideaIR : public climate_ir::ClimateIR { + public: + MideaIR() + : climate_ir::ClimateIR( + MIDEA_TEMPC_MIN, MIDEA_TEMPC_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_PRESET_NONE, climate::CLIMATE_PRESET_SLEEP, climate::CLIMATE_PRESET_BOOST}) {} + + /// Override control to change settings of the climate device. + void control(const climate::ClimateCall &call) override; + + /// Set use of Fahrenheit units + void set_fahrenheit(bool value) { + this->fahrenheit_ = value; + this->temperature_step_ = value ? 0.5f : 1.0f; + } + + protected: + /// Transmit via IR the state of this climate controller. + void transmit_state() override; + void transmit_(MideaData &data); + /// Handle received IR Buffer + bool on_receive(remote_base::RemoteReceiveData data) override; + bool on_midea_(const MideaData &data); + bool fahrenheit_{false}; + bool swing_{false}; + bool boost_{false}; +}; + +} // namespace midea_ir +} // namespace esphome diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 914ce42efe..9982447988 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -234,6 +234,45 @@ async def build_dumpers(config): return dumpers +# Coolix +( + CoolixData, + CoolixBinarySensor, + CoolixTrigger, + CoolixAction, + CoolixDumper, +) = declare_protocol("Coolix") +COOLIX_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_uint32_t}) + + +@register_binary_sensor("coolix", CoolixBinarySensor, COOLIX_SCHEMA) +def coolix_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + CoolixData, + ("data", config[CONF_DATA]), + ) + ) + ) + + +@register_trigger("coolix", CoolixTrigger, CoolixData) +def coolix_trigger(var, config): + pass + + +@register_dumper("coolix", CoolixDumper) +def coolix_dumper(var, config): + pass + + +@register_action("coolix", CoolixAction, COOLIX_SCHEMA) +async def coolix_action(var, config, args): + template_ = await cg.templatable(config[CONF_DATA], args, cg.uint32) + cg.add(var.set_data(template_)) + + # Dish DishData, DishBinarySensor, DishTrigger, DishAction, DishDumper = declare_protocol( "Dish" diff --git a/esphome/components/remote_base/coolix_protocol.cpp b/esphome/components/remote_base/coolix_protocol.cpp new file mode 100644 index 0000000000..3e6e7e185a --- /dev/null +++ b/esphome/components/remote_base/coolix_protocol.cpp @@ -0,0 +1,84 @@ +#include "coolix_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.coolix"; + +static const int32_t TICK_US = 560; +static const int32_t HEADER_MARK_US = 8 * TICK_US; +static const int32_t HEADER_SPACE_US = 8 * TICK_US; +static const int32_t BIT_MARK_US = 1 * TICK_US; +static const int32_t BIT_ONE_SPACE_US = 3 * TICK_US; +static const int32_t BIT_ZERO_SPACE_US = 1 * TICK_US; +static const int32_t FOOTER_MARK_US = 1 * TICK_US; +static const int32_t FOOTER_SPACE_US = 10 * TICK_US; + +static void encode_data(RemoteTransmitData *dst, const CoolixData &src) { + // Break data into bytes, starting at the Most Significant + // Byte. Each byte then being sent normal, then followed inverted. + for (unsigned shift = 16;; shift -= 8) { + // Grab a bytes worth of data. + const uint8_t byte = src >> shift; + // Normal + for (uint8_t mask = 1 << 7; mask; mask >>= 1) + dst->item(BIT_MARK_US, (byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US); + // Inverted + for (uint8_t mask = 1 << 7; mask; mask >>= 1) + dst->item(BIT_MARK_US, (byte & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US); + // Data end + if (shift == 0) + break; + } +} + +void CoolixProtocol::encode(RemoteTransmitData *dst, const CoolixData &data) { + dst->set_carrier_frequency(38000); + dst->reserve(2 + 2 * 48 + 2 + 2 + 2 * 48 + 1); + dst->item(HEADER_MARK_US, HEADER_SPACE_US); + encode_data(dst, data); + dst->item(FOOTER_MARK_US, FOOTER_SPACE_US); + dst->item(HEADER_MARK_US, HEADER_SPACE_US); + encode_data(dst, data); + dst->mark(FOOTER_MARK_US); +} + +static bool decode_data(RemoteReceiveData &src, CoolixData &dst) { + uint32_t data = 0; + for (unsigned n = 3;; data <<= 8) { + // Read byte + for (uint32_t mask = 1 << 7; mask; mask >>= 1) { + if (!src.expect_mark(BIT_MARK_US)) + return false; + if (src.expect_space(BIT_ONE_SPACE_US)) + data |= mask; + else if (!src.expect_space(BIT_ZERO_SPACE_US)) + return false; + } + // Check for inverse byte + for (uint32_t mask = 1 << 7; mask; mask >>= 1) { + if (!src.expect_item(BIT_MARK_US, (data & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US)) + return false; + } + // Checking the end of reading + if (--n == 0) { + dst = data; + return true; + } + } +} + +optional CoolixProtocol::decode(RemoteReceiveData data) { + CoolixData first, second; + if (data.expect_item(HEADER_MARK_US, HEADER_SPACE_US) && decode_data(data, first) && + data.expect_item(FOOTER_MARK_US, FOOTER_SPACE_US) && data.expect_item(HEADER_MARK_US, HEADER_SPACE_US) && + decode_data(data, second) && data.expect_mark(FOOTER_MARK_US) && first == second) + return first; + return {}; +} + +void CoolixProtocol::dump(const CoolixData &data) { ESP_LOGD(TAG, "Received Coolix: 0x%06X", data); } + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/coolix_protocol.h b/esphome/components/remote_base/coolix_protocol.h new file mode 100644 index 0000000000..9ce3eabb0e --- /dev/null +++ b/esphome/components/remote_base/coolix_protocol.h @@ -0,0 +1,30 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +using CoolixData = uint32_t; + +class CoolixProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const CoolixData &data) override; + optional decode(RemoteReceiveData data) override; + void dump(const CoolixData &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(Coolix) + +template class CoolixAction : public RemoteTransmitterActionBase { + TEMPLATABLE_VALUE(CoolixData, data) + void encode(RemoteTransmitData *dst, Ts... x) override { + CoolixData data = this->data_.value(x...); + CoolixProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/midea_protocol.cpp b/esphome/components/remote_base/midea_protocol.cpp index a19a5b50c1..bf67429001 100644 --- a/esphome/components/remote_base/midea_protocol.cpp +++ b/esphome/components/remote_base/midea_protocol.cpp @@ -6,89 +6,63 @@ namespace remote_base { static const char *const TAG = "remote.midea"; +static const int32_t TICK_US = 560; +static const int32_t HEADER_MARK_US = 8 * TICK_US; +static const int32_t HEADER_SPACE_US = 8 * TICK_US; +static const int32_t BIT_MARK_US = 1 * TICK_US; +static const int32_t BIT_ONE_SPACE_US = 3 * TICK_US; +static const int32_t BIT_ZERO_SPACE_US = 1 * TICK_US; +static const int32_t FOOTER_MARK_US = 1 * TICK_US; +static const int32_t FOOTER_SPACE_US = 10 * TICK_US; + 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(*it); + for (uint8_t idx = 0; idx < OFFSET_CS; idx++) + cs -= reverse_bits(this->data_[idx]); return reverse_bits(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; +bool MideaData::is_compliment(const MideaData &rhs) const { + return std::equal(this->data_.begin(), this->data_.end(), rhs.data_.begin(), + [](const uint8_t &a, const uint8_t &b) { return a + b == 255; }); } -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) { +void MideaProtocol::encode(RemoteTransmitData *dst, const MideaData &src) { 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); + dst->reserve(2 + 48 * 2 + 2 + 2 + 48 * 2 + 1); + dst->item(HEADER_MARK_US, HEADER_SPACE_US); + for (unsigned idx = 0; idx < 6; idx++) + for (uint8_t mask = 1 << 7; mask; mask >>= 1) + dst->item(BIT_MARK_US, (src[idx] & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US); + dst->item(FOOTER_MARK_US, FOOTER_SPACE_US); + dst->item(HEADER_MARK_US, HEADER_SPACE_US); + for (unsigned idx = 0; idx < 6; idx++) + for (uint8_t mask = 1 << 7; mask; mask >>= 1) + dst->item(BIT_MARK_US, (src[idx] & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US); + dst->mark(FOOTER_MARK_US); } -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)) +static bool decode_data(RemoteReceiveData &src, MideaData &dst) { + for (unsigned idx = 0; idx < 6; idx++) { + uint8_t data = 0; + for (uint8_t mask = 1 << 7; mask; mask >>= 1) { + if (!src.expect_mark(BIT_MARK_US)) + return false; + if (src.expect_space(BIT_ONE_SPACE_US)) + data |= mask; + else if (!src.expect_space(BIT_ZERO_SPACE_US)) return false; } + dst[idx] = data; } 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)) + if (src.expect_item(HEADER_MARK_US, HEADER_SPACE_US) && decode_data(src, out) && out.is_valid() && + src.expect_item(FOOTER_MARK_US, FOOTER_SPACE_US) && src.expect_item(HEADER_MARK_US, HEADER_SPACE_US) && + decode_data(src, inv) && src.expect_mark(FOOTER_MARK_US) && out.is_compliment(inv)) return out; return {}; } diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h index 61e511601b..135a93b36d 100644 --- a/esphome/components/remote_base/midea_protocol.h +++ b/esphome/components/remote_base/midea_protocol.h @@ -1,5 +1,6 @@ #pragma once +#include #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "remote_base.h" @@ -9,70 +10,61 @@ namespace remote_base { class MideaData { public: - // Make zero-filled - MideaData() { memset(this->data_, 0, sizeof(this->data_)); } + // Make default + MideaData() {} // Make from initializer_list - MideaData(std::initializer_list data) { std::copy(data.begin(), data.end(), this->data()); } + MideaData(std::initializer_list data) { + std::copy_n(data.begin(), std::min(data.size(), this->data_.size()), this->data_.begin()); + } // Make from vector MideaData(const std::vector &data) { - memcpy(this->data_, data.data(), std::min(data.size(), sizeof(this->data_))); + std::copy_n(data.begin(), std::min(data.size(), this->data_.size()), this->data_.begin()); } // 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_); } + uint8_t *data() { return this->data_.data(); } + const uint8_t *data() const { return this->data_.data(); } + uint8_t size() const { return this->data_.size(); } 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 format_hex_pretty(this->data_, sizeof(this->data_)); } + bool is_compliment(const MideaData &rhs) const; + std::string to_string() const { return format_hex_pretty(this->data_.data(), this->data_.size()); } // compare only 40-bits - bool operator==(const MideaData &rhs) const { return !memcmp(this->data_, rhs.data_, OFFSET_CS); } + bool operator==(const MideaData &rhs) const { + return std::equal(this->data_.begin(), this->data_.begin() + OFFSET_CS, rhs.data_.begin()); + } enum MideaDataType : uint8_t { - MIDEA_TYPE_COMMAND = 0xA1, + MIDEA_TYPE_CONTROL = 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); } + uint8_t &operator[](size_t idx) { return this->data_[idx]; } + const uint8_t &operator[](size_t idx) const { return this->data_[idx]; } 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); + uint8_t get_value_(uint8_t idx, uint8_t mask = 255, uint8_t shift = 0) const { + return (this->data_[idx] >> shift) & mask; } + void set_value_(uint8_t idx, uint8_t value, uint8_t mask = 255, uint8_t shift = 0) { + this->data_[idx] &= ~(mask << shift); + this->data_[idx] |= (value << shift); + } + void set_mask_(uint8_t idx, bool state, uint8_t mask = 255) { this->set_value_(idx, state ? mask : 0, mask); } static const uint8_t OFFSET_CS = 5; // 48-bits data - uint8_t data_[6]; + std::array data_; // Calculate checksum uint8_t calc_cs_() const; }; class MideaProtocol : public RemoteProtocol { public: - void encode(RemoteTransmitData *dst, const MideaData &data) override; + void encode(RemoteTransmitData *dst, const MideaData &src) 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 { diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index db6bfeb79b..c039447fef 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -378,7 +378,7 @@ std::string format_hex(const uint8_t *data, size_t length) { } return ret; } -std::string format_hex(std::vector data) { return format_hex(data.data(), data.size()); } +std::string format_hex(const std::vector &data) { return format_hex(data.data(), data.size()); } static char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; } std::string format_hex_pretty(const uint8_t *data, size_t length) { @@ -396,6 +396,6 @@ std::string format_hex_pretty(const uint8_t *data, size_t length) { return ret + " (" + to_string(length) + ")"; return ret; } -std::string format_hex_pretty(std::vector data) { return format_hex_pretty(data.data(), data.size()); } +std::string format_hex_pretty(const std::vector &data) { return format_hex_pretty(data.data(), data.size()); } } // namespace esphome diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index d677e34649..5f793df1ea 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -110,6 +110,11 @@ void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, /// Convert hue (0-360) & saturation/value percentage (0-1) to RGB floats (0-1) void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue); +/// Convert degrees Celsius to degrees Fahrenheit. +static inline float celsius_to_fahrenheit(float value) { return value * 1.8f + 32.0f; } +/// Convert degrees Fahrenheit to degrees Celsius. +static inline float fahrenheit_to_celsius(float value) { return (value - 32.0f) / 1.8f; } + /*** * An interrupt helper class. * @@ -491,7 +496,7 @@ template::value, int> = 0> optional< /// Format the byte array \p data of length \p len in lowercased hex. std::string format_hex(const uint8_t *data, size_t length); /// Format the vector \p data in lowercased hex. -std::string format_hex(std::vector data); +std::string format_hex(const std::vector &data); /// Format an unsigned integer in lowercased hex, starting with the most significant byte. template::value, int> = 0> std::string format_hex(T val) { val = convert_big_endian(val); @@ -501,7 +506,7 @@ template::value, int> = 0> std::stri /// Format the byte array \p data of length \p len in pretty-printed, human-readable hex. std::string format_hex_pretty(const uint8_t *data, size_t length); /// Format the vector \p data in pretty-printed, human-readable hex. -std::string format_hex_pretty(std::vector data); +std::string format_hex_pretty(const std::vector &data); /// Format an unsigned integer in pretty-printed, human-readable hex, starting with the most significant byte. template::value, int> = 0> std::string format_hex_pretty(T val) { val = convert_big_endian(val); diff --git a/tests/test1.yaml b/tests/test1.yaml index 2836a97e4f..959ffb0d2d 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1718,6 +1718,9 @@ climate: name: HeatpumpIR Climate min_temperature: 18 max_temperature: 30 + - platform: midea_ir + name: Midea IR + use_fahrenheit: true - platform: midea on_state: logger.log: "State changed!" From 5844c1767bed07610bdf63ddb20e33aefad0d751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Sun, 9 Jan 2022 23:58:49 +0100 Subject: [PATCH 530/549] Extend esp32_camera with requester to improve performance (#2813) --- esphome/components/api/api_connection.cpp | 16 ++++++-- .../components/esp32_camera/esp32_camera.cpp | 39 ++++++++----------- .../components/esp32_camera/esp32_camera.h | 16 +++++--- .../camera_web_server.cpp | 16 ++++---- 4 files changed, 47 insertions(+), 40 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index f615815023..1f629c2c85 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -20,6 +20,7 @@ namespace esphome { namespace api { static const char *const TAG = "api.connection"; +static const int ESP32_CAMERA_STOP_STREAM = 5000; APIConnection::APIConnection(std::unique_ptr sock, APIServer *parent) : parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) { @@ -704,7 +705,9 @@ void APIConnection::send_camera_state(std::shared_ptr return; if (this->image_reader_.available()) return; - this->image_reader_.set_image(std::move(image)); + if (image->was_requested_by(esphome::esp32_camera::API_REQUESTER) || + image->was_requested_by(esphome::esp32_camera::IDLE)) + this->image_reader_.set_image(std::move(image)); } bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { ListEntitiesCameraResponse msg; @@ -722,9 +725,14 @@ void APIConnection::camera_image(const CameraImageRequest &msg) { return; if (msg.single) - esp32_camera::global_esp32_camera->request_image(); - if (msg.stream) - esp32_camera::global_esp32_camera->request_stream(); + esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::API_REQUESTER); + if (msg.stream) { + esp32_camera::global_esp32_camera->start_stream(esphome::esp32_camera::API_REQUESTER); + + App.scheduler.set_timeout(this->parent_, "api_esp32_camera_stop_stream", ESP32_CAMERA_STOP_STREAM, []() { + esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::API_REQUESTER); + }); + } } #endif diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 54307dce41..7d11f98d09 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -133,6 +133,13 @@ void ESP32Camera::loop() { this->current_image_.reset(); } + // request idle image every idle_update_interval + const uint32_t now = millis(); + if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) { + this->last_idle_request_ = now; + this->request_image(IDLE); + } + // Check if we should fetch a new image if (!this->has_requested_image_()) return; @@ -140,7 +147,6 @@ void ESP32Camera::loop() { // image is still in use return; } - const uint32_t now = millis(); if (now - this->last_update_ <= this->max_update_interval_) return; @@ -157,12 +163,12 @@ void ESP32Camera::loop() { xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY); return; } - this->current_image_ = std::make_shared(fb); + this->current_image_ = std::make_shared(fb, this->single_requesters_ | this->stream_requesters_); ESP_LOGD(TAG, "Got Image: len=%u", fb->len); this->new_image_callback_.call(this->current_image_); this->last_update_ = now; - this->single_requester_ = false; + this->single_requesters_ = 0; } void ESP32Camera::framebuffer_task(void *pv) { while (true) { @@ -258,24 +264,10 @@ void ESP32Camera::set_brightness(int brightness) { this->brightness_ = brightnes void ESP32Camera::set_saturation(int saturation) { this->saturation_ = saturation; } float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; } uint32_t ESP32Camera::hash_base() { return 3010542557UL; } -void ESP32Camera::request_image() { this->single_requester_ = true; } -void ESP32Camera::request_stream() { this->last_stream_request_ = millis(); } -bool ESP32Camera::has_requested_image_() const { - if (this->single_requester_) - // single request - return true; - - uint32_t now = millis(); - if (now - this->last_stream_request_ < 5000) - // stream request - return true; - - if (this->idle_update_interval_ != 0 && now - this->last_update_ > this->idle_update_interval_) - // idle update - return true; - - return false; -} +void ESP32Camera::request_image(CameraRequester requester) { this->single_requesters_ |= 1 << requester; } +void ESP32Camera::start_stream(CameraRequester requester) { this->stream_requesters_ |= 1 << requester; } +void ESP32Camera::stop_stream(CameraRequester requester) { this->stream_requesters_ &= ~(1 << requester); } +bool ESP32Camera::has_requested_image_() const { return this->single_requesters_ || this->stream_requesters_; } bool ESP32Camera::can_return_image_() const { return this->current_image_.use_count() == 1; } void ESP32Camera::set_max_update_interval(uint32_t max_update_interval) { this->max_update_interval_ = max_update_interval; @@ -304,7 +296,10 @@ uint8_t *CameraImageReader::peek_data_buffer() { return this->image_->get_data_b camera_fb_t *CameraImage::get_raw_buffer() { return this->buffer_; } uint8_t *CameraImage::get_data_buffer() { return this->buffer_->buf; } size_t CameraImage::get_data_length() { return this->buffer_->len; } -CameraImage::CameraImage(camera_fb_t *buffer) : buffer_(buffer) {} +bool CameraImage::was_requested_by(CameraRequester requester) const { + return (this->requesters_ & (1 << requester)) != 0; +} +CameraImage::CameraImage(camera_fb_t *buffer, uint8_t requesters) : buffer_(buffer), requesters_(requesters) {} } // namespace esp32_camera } // namespace esphome diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index b20485a0f7..b2670078f3 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -14,15 +14,19 @@ namespace esp32_camera { class ESP32Camera; +enum CameraRequester { IDLE, API_REQUESTER, WEB_REQUESTER }; + class CameraImage { public: - CameraImage(camera_fb_t *buffer); + CameraImage(camera_fb_t *buffer, uint8_t requester); camera_fb_t *get_raw_buffer(); uint8_t *get_data_buffer(); size_t get_data_length(); + bool was_requested_by(CameraRequester requester) const; protected: camera_fb_t *buffer_; + uint8_t requesters_; }; class CameraImageReader { @@ -81,8 +85,9 @@ class ESP32Camera : public Component, public EntityBase { void dump_config() override; void add_image_callback(std::function)> &&f); float get_setup_priority() const override; - void request_stream(); - void request_image(); + void start_stream(CameraRequester requester); + void stop_stream(CameraRequester requester); + void request_image(CameraRequester requester); protected: uint32_t hash_base() override; @@ -104,13 +109,14 @@ class ESP32Camera : public Component, public EntityBase { esp_err_t init_error_{ESP_OK}; std::shared_ptr current_image_; - uint32_t last_stream_request_{0}; - bool single_requester_{false}; + uint8_t single_requesters_{0}; + uint8_t stream_requesters_{0}; QueueHandle_t framebuffer_get_queue_; QueueHandle_t framebuffer_return_queue_; CallbackManager)> new_image_callback_; uint32_t max_update_interval_{1000}; uint32_t idle_update_interval_{15000}; + uint32_t last_idle_request_{0}; uint32_t last_update_{0}; }; diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.cpp b/esphome/components/esp32_camera_web_server/camera_web_server.cpp index f5ab0c7151..39b110bc85 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.cpp +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp @@ -14,7 +14,7 @@ namespace esphome { namespace esp32_camera_web_server { -static const int IMAGE_REQUEST_TIMEOUT = 2000; +static const int IMAGE_REQUEST_TIMEOUT = 5000; static const char *const TAG = "esp32_camera_web_server"; #define PART_BOUNDARY "123456789000000000000987654321" @@ -68,7 +68,7 @@ void CameraWebServer::setup() { httpd_register_uri_handler(this->httpd_, &uri); esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr image) { - if (this->running_) { + if (this->running_ && image->was_requested_by(esp32_camera::WEB_REQUESTER)) { this->image_ = std::move(image); xSemaphoreGive(this->semaphore_); } @@ -169,11 +169,9 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) { uint32_t last_frame = millis(); uint32_t frames = 0; - while (res == ESP_OK && this->running_) { - if (esp32_camera::global_esp32_camera != nullptr) { - esp32_camera::global_esp32_camera->request_stream(); - } + esp32_camera::global_esp32_camera->start_stream(esphome::esp32_camera::WEB_REQUESTER); + while (res == ESP_OK && this->running_) { auto image = this->wait_for_image_(); if (!image) { @@ -204,6 +202,8 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) { res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR)); } + esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::WEB_REQUESTER); + ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames); return res; @@ -212,9 +212,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) { esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) { esp_err_t res = ESP_OK; - if (esp32_camera::global_esp32_camera != nullptr) { - esp32_camera::global_esp32_camera->request_image(); - } + esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::WEB_REQUESTER); auto image = this->wait_for_image_(); From a4431abea82c2300b07ca1780bf3351452b83504 Mon Sep 17 00:00:00 2001 From: rsumner Date: Sun, 9 Jan 2022 17:04:48 -0600 Subject: [PATCH 531/549] MCP3204 4-channel 12-bit ADC component (#2895) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/mcp3204/__init__.py | 27 ++++++++++++++ esphome/components/mcp3204/mcp3204.cpp | 35 +++++++++++++++++++ esphome/components/mcp3204/mcp3204.h | 28 +++++++++++++++ esphome/components/mcp3204/sensor/__init__.py | 32 +++++++++++++++++ .../mcp3204/sensor/mcp3204_sensor.cpp | 23 ++++++++++++ .../mcp3204/sensor/mcp3204_sensor.h | 30 ++++++++++++++++ tests/test4.yaml | 6 ++++ 8 files changed, 182 insertions(+) create mode 100644 esphome/components/mcp3204/__init__.py create mode 100644 esphome/components/mcp3204/mcp3204.cpp create mode 100644 esphome/components/mcp3204/mcp3204.h create mode 100644 esphome/components/mcp3204/sensor/__init__.py create mode 100644 esphome/components/mcp3204/sensor/mcp3204_sensor.cpp create mode 100644 esphome/components/mcp3204/sensor/mcp3204_sensor.h diff --git a/CODEOWNERS b/CODEOWNERS index 5f9a579827..59648b6dbc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -97,6 +97,7 @@ esphome/components/mcp23x08_base/* @jesserockz esphome/components/mcp23x17_base/* @jesserockz esphome/components/mcp23xxx_base/* @jesserockz esphome/components/mcp2515/* @danielschramm @mvturnho +esphome/components/mcp3204/* @rsumner esphome/components/mcp47a1/* @jesserockz esphome/components/mcp9808/* @k7hpn esphome/components/md5/* @esphome/core diff --git a/esphome/components/mcp3204/__init__.py b/esphome/components/mcp3204/__init__.py new file mode 100644 index 0000000000..0536166e56 --- /dev/null +++ b/esphome/components/mcp3204/__init__.py @@ -0,0 +1,27 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import spi +from esphome.const import CONF_ID + +DEPENDENCIES = ["spi"] +MULTI_CONF = True +CODEOWNERS = ["@rsumner"] + +mcp3204_ns = cg.esphome_ns.namespace("mcp3204") +MCP3204 = mcp3204_ns.class_("MCP3204", cg.Component, spi.SPIDevice) + +CONF_REFERENCE_VOLTAGE = "reference_voltage" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(MCP3204), + cv.Optional(CONF_REFERENCE_VOLTAGE, default="3.3V"): cv.voltage, + } +).extend(spi.spi_device_schema(cs_pin_required=True)) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_reference_voltage(config[CONF_REFERENCE_VOLTAGE])) + await cg.register_component(var, config) + await spi.register_spi_device(var, config) diff --git a/esphome/components/mcp3204/mcp3204.cpp b/esphome/components/mcp3204/mcp3204.cpp new file mode 100644 index 0000000000..44044349a3 --- /dev/null +++ b/esphome/components/mcp3204/mcp3204.cpp @@ -0,0 +1,35 @@ +#include "mcp3204.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mcp3204 { + +static const char *const TAG = "mcp3204"; + +float MCP3204::get_setup_priority() const { return setup_priority::HARDWARE; } + +void MCP3204::setup() { + ESP_LOGCONFIG(TAG, "Setting up mcp3204"); + this->spi_setup(); +} + +void MCP3204::dump_config() { + ESP_LOGCONFIG(TAG, "MCP3204:"); + LOG_PIN(" CS Pin:", this->cs_); + ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_); +} + +float MCP3204::read_data(uint8_t pin) { + uint8_t adc_primary_config = 0b00000110 & 0b00000111; + uint8_t adc_secondary_config = pin << 6; + this->enable(); + this->transfer_byte(adc_primary_config); + uint8_t adc_primary_byte = this->transfer_byte(adc_secondary_config); + uint8_t adc_secondary_byte = this->transfer_byte(0x00); + this->disable(); + uint16_t digital_value = (adc_primary_byte << 8 | adc_secondary_byte) & 0b111111111111; + return float(digital_value) / 4096.000 * this->reference_voltage_; +} + +} // namespace mcp3204 +} // namespace esphome diff --git a/esphome/components/mcp3204/mcp3204.h b/esphome/components/mcp3204/mcp3204.h new file mode 100644 index 0000000000..27261aa373 --- /dev/null +++ b/esphome/components/mcp3204/mcp3204.h @@ -0,0 +1,28 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { +namespace mcp3204 { + +class MCP3204 : public Component, + public spi::SPIDevice { + public: + MCP3204() = default; + + void set_reference_voltage(float reference_voltage) { this->reference_voltage_ = reference_voltage; } + + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + float read_data(uint8_t pin); + + protected: + float reference_voltage_; +}; + +} // namespace mcp3204 +} // namespace esphome diff --git a/esphome/components/mcp3204/sensor/__init__.py b/esphome/components/mcp3204/sensor/__init__.py new file mode 100644 index 0000000000..1d8701a91e --- /dev/null +++ b/esphome/components/mcp3204/sensor/__init__.py @@ -0,0 +1,32 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, voltage_sampler +from esphome.const import CONF_ID, CONF_NUMBER +from .. import mcp3204_ns, MCP3204 + +AUTO_LOAD = ["voltage_sampler"] + +DEPENDENCIES = ["mcp3204"] + +MCP3204Sensor = mcp3204_ns.class_( + "MCP3204Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler +) +CONF_MCP3204_ID = "mcp3204_id" + +CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(MCP3204Sensor), + cv.GenerateID(CONF_MCP3204_ID): cv.use_id(MCP3204), + cv.Required(CONF_NUMBER): cv.int_range(min=0, max=3), + } +).extend(cv.polling_component_schema("60s")) + + +async def to_code(config): + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_NUMBER], + ) + await cg.register_parented(var, config[CONF_MCP3204_ID]) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) diff --git a/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp b/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp new file mode 100644 index 0000000000..ce0fd25462 --- /dev/null +++ b/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp @@ -0,0 +1,23 @@ +#include "mcp3204_sensor.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace mcp3204 { + +static const char *const TAG = "mcp3204.sensor"; + +MCP3204Sensor::MCP3204Sensor(uint8_t pin) : pin_(pin) {} + +float MCP3204Sensor::get_setup_priority() const { return setup_priority::DATA; } + +void MCP3204Sensor::dump_config() { + LOG_SENSOR("", "MCP3204 Sensor", this); + ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); + LOG_UPDATE_INTERVAL(this); +} +float MCP3204Sensor::sample() { return this->parent_->read_data(this->pin_); } +void MCP3204Sensor::update() { this->publish_state(this->sample()); } + +} // namespace mcp3204 +} // namespace esphome diff --git a/esphome/components/mcp3204/sensor/mcp3204_sensor.h b/esphome/components/mcp3204/sensor/mcp3204_sensor.h new file mode 100644 index 0000000000..21c45590ab --- /dev/null +++ b/esphome/components/mcp3204/sensor/mcp3204_sensor.h @@ -0,0 +1,30 @@ +#pragma once + +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/voltage_sampler/voltage_sampler.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" + +#include "../mcp3204.h" + +namespace esphome { +namespace mcp3204 { + +class MCP3204Sensor : public PollingComponent, + public Parented, + public sensor::Sensor, + public voltage_sampler::VoltageSampler { + public: + MCP3204Sensor(uint8_t pin); + + void update() override; + void dump_config() override; + float get_setup_priority() const override; + float sample() override; + + protected: + uint8_t pin_; +}; + +} // namespace mcp3204 +} // namespace esphome diff --git a/tests/test4.yaml b/tests/test4.yaml index 7c1ddee6d8..eec1c2eb5e 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -65,6 +65,9 @@ sx1509: - id: sx1509_hub address: 0x3E +mcp3204: + cs_pin: GPIO23 + sensor: - platform: homeassistant entity_id: sensor.hello_world @@ -215,6 +218,9 @@ sensor: - or: - throttle: "20min" - delta: 0.02 + - platform: mcp3204 + name: "MCP3204 Pin 1" + number: 1 # # platform sensor.apds9960 requires component apds9960 From 9e8b701deafa1342b2436ffed5727cfdf00d9c30 Mon Sep 17 00:00:00 2001 From: MiKuBB Date: Mon, 10 Jan 2022 00:23:01 +0100 Subject: [PATCH 532/549] Adding sdm_meter ability to report total power (#2959) --- esphome/components/sdm_meter/sdm_meter.cpp | 9 +++++++-- esphome/components/sdm_meter/sdm_meter.h | 2 ++ esphome/components/sdm_meter/sensor.py | 11 +++++++++++ esphome/const.py | 1 + 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/esphome/components/sdm_meter/sdm_meter.cpp b/esphome/components/sdm_meter/sdm_meter.cpp index 2348c88938..9c35d306ad 100644 --- a/esphome/components/sdm_meter/sdm_meter.cpp +++ b/esphome/components/sdm_meter/sdm_meter.cpp @@ -57,15 +57,19 @@ void SDMMeter::on_modbus_data(const std::vector &data) { phase.phase_angle_sensor_->publish_state(phase_angle); } + float total_power = sdm_meter_get_float(SDM_TOTAL_SYSTEM_POWER * 2); float frequency = sdm_meter_get_float(SDM_FREQUENCY * 2); float import_active_energy = sdm_meter_get_float(SDM_IMPORT_ACTIVE_ENERGY * 2); float export_active_energy = sdm_meter_get_float(SDM_EXPORT_ACTIVE_ENERGY * 2); float import_reactive_energy = sdm_meter_get_float(SDM_IMPORT_REACTIVE_ENERGY * 2); float export_reactive_energy = sdm_meter_get_float(SDM_EXPORT_REACTIVE_ENERGY * 2); - ESP_LOGD(TAG, "SDMMeter: F=%.3f Hz, Im.A.E=%.3f Wh, Ex.A.E=%.3f Wh, Im.R.E=%.3f VARh, Ex.R.E=%.3f VARh", frequency, - import_active_energy, export_active_energy, import_reactive_energy, export_reactive_energy); + ESP_LOGD(TAG, "SDMMeter: F=%.3f Hz, Im.A.E=%.3f Wh, Ex.A.E=%.3f Wh, Im.R.E=%.3f VARh, Ex.R.E=%.3f VARh, T.P=%.3f W", + frequency, import_active_energy, export_active_energy, import_reactive_energy, export_reactive_energy, + total_power); + if (this->total_power_sensor_ != nullptr) + this->total_power_sensor_->publish_state(total_power); if (this->frequency_sensor_ != nullptr) this->frequency_sensor_->publish_state(frequency); if (this->import_active_energy_sensor_ != nullptr) @@ -95,6 +99,7 @@ void SDMMeter::dump_config() { LOG_SENSOR(" ", "Power Factor", phase.power_factor_sensor_); LOG_SENSOR(" ", "Phase Angle", phase.phase_angle_sensor_); } + LOG_SENSOR(" ", "Total Power", this->total_power_sensor_); LOG_SENSOR(" ", "Frequency", this->frequency_sensor_); LOG_SENSOR(" ", "Import Active Energy", this->import_active_energy_sensor_); LOG_SENSOR(" ", "Export Active Energy", this->export_active_energy_sensor_); diff --git a/esphome/components/sdm_meter/sdm_meter.h b/esphome/components/sdm_meter/sdm_meter.h index 07ebe65bb7..66f0fb8c5e 100644 --- a/esphome/components/sdm_meter/sdm_meter.h +++ b/esphome/components/sdm_meter/sdm_meter.h @@ -37,6 +37,7 @@ class SDMMeter : public PollingComponent, public modbus::ModbusDevice { this->phases_[phase].setup = true; this->phases_[phase].phase_angle_sensor_ = phase_angle_sensor; } + void set_total_power_sensor(sensor::Sensor *total_power_sensor) { this->total_power_sensor_ = total_power_sensor; } void set_frequency_sensor(sensor::Sensor *frequency_sensor) { this->frequency_sensor_ = frequency_sensor; } void set_import_active_energy_sensor(sensor::Sensor *import_active_energy_sensor) { this->import_active_energy_sensor_ = import_active_energy_sensor; @@ -69,6 +70,7 @@ class SDMMeter : public PollingComponent, public modbus::ModbusDevice { sensor::Sensor *phase_angle_sensor_{nullptr}; } phases_[3]; sensor::Sensor *frequency_sensor_{nullptr}; + sensor::Sensor *total_power_sensor_{nullptr}; sensor::Sensor *import_active_energy_sensor_{nullptr}; sensor::Sensor *export_active_energy_sensor_{nullptr}; sensor::Sensor *import_reactive_energy_sensor_{nullptr}; diff --git a/esphome/components/sdm_meter/sensor.py b/esphome/components/sdm_meter/sensor.py index 87c99c9152..4f439ac506 100644 --- a/esphome/components/sdm_meter/sensor.py +++ b/esphome/components/sdm_meter/sensor.py @@ -8,6 +8,7 @@ from esphome.const import ( CONF_CURRENT, CONF_EXPORT_ACTIVE_ENERGY, CONF_EXPORT_REACTIVE_ENERGY, + CONF_TOTAL_POWER, CONF_FREQUENCY, CONF_ID, CONF_IMPORT_ACTIVE_ENERGY, @@ -98,6 +99,12 @@ CONFIG_SCHEMA = ( accuracy_decimals=3, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_TOTAL_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Optional(CONF_IMPORT_ACTIVE_ENERGY): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=2, @@ -132,6 +139,10 @@ async def to_code(config): await cg.register_component(var, config) await modbus.register_modbus_device(var, config) + if CONF_TOTAL_POWER in config: + sens = await sensor.new_sensor(config[CONF_TOTAL_POWER]) + cg.add(var.set_total_power_sensor(sens)) + if CONF_FREQUENCY in config: sens = await sensor.new_sensor(config[CONF_FREQUENCY]) cg.add(var.set_frequency_sensor(sens)) diff --git a/esphome/const.py b/esphome/const.py index 03f1da0e91..fdc880caf3 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -685,6 +685,7 @@ CONF_TOLERANCE = "tolerance" CONF_TOPIC = "topic" CONF_TOPIC_PREFIX = "topic_prefix" CONF_TOTAL = "total" +CONF_TOTAL_POWER = "total_power" CONF_TRACES = "traces" CONF_TRANSITION_LENGTH = "transition_length" CONF_TRIGGER_ID = "trigger_id" From e55bd1e559bc3de2b5ac32f3f6397358f904d5e9 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 10 Jan 2022 00:29:29 +0100 Subject: [PATCH 533/549] [Modbus_controller] Fix binary sensor lambda (#3020) --- .../components/modbus_controller/binary_sensor/__init__.py | 2 +- .../modbus_controller/binary_sensor/modbus_binarysensor.h | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/components/modbus_controller/binary_sensor/__init__.py b/esphome/components/modbus_controller/binary_sensor/__init__.py index 99d56fed67..557d76479d 100644 --- a/esphome/components/modbus_controller/binary_sensor/__init__.py +++ b/esphome/components/modbus_controller/binary_sensor/__init__.py @@ -57,4 +57,4 @@ async def to_code(config): paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) cg.add(paren.add_sensor_item(var)) - await add_modbus_base_properties(var, config, ModbusBinarySensor, cg.float_, bool) + await add_modbus_base_properties(var, config, ModbusBinarySensor, bool, bool) diff --git a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h index c516d6b916..21afbc7053 100644 --- a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h +++ b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h @@ -31,12 +31,11 @@ class ModbusBinarySensor : public Component, public binary_sensor::BinarySensor, void dump_config() override; - using transform_func_t = - optional(ModbusBinarySensor *, bool, const std::vector &)>>; + using transform_func_t = std::function(ModbusBinarySensor *, bool, const std::vector &)>; void set_template(transform_func_t &&f) { this->transform_func_ = f; } protected: - transform_func_t transform_func_{nullopt}; + optional transform_func_{nullopt}; }; } // namespace modbus_controller From 6383eca54a043bfb736ee012178c9244f85c6081 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 10 Jan 2022 01:50:26 +0100 Subject: [PATCH 534/549] Clean-up random helper functions (#3022) --- esphome/components/api/api_frame_helper.cpp | 2 +- .../light/addressable_light_effect.h | 5 +- esphome/core/helpers.cpp | 63 +++++++------------ esphome/core/helpers.h | 31 ++++----- 4 files changed, 40 insertions(+), 61 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 151c2512de..094dd67e33 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -721,7 +721,7 @@ APIError APINoiseFrameHelper::shutdown(int how) { } 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); } +void noise_rand_bytes(void *output, size_t len) { esphome::random_bytes(reinterpret_cast(output), len); } } #endif // USE_API_NOISE diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index 5091bae2d5..d404898edf 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -331,9 +331,10 @@ class AddressableFlickerEffect : public AddressableLightEffect { return; this->last_update_ = now; - fast_random_set_seed(random_uint32()); + uint32_t rng_state = random_uint32(); for (auto var : it) { - const uint8_t flicker = fast_random_8() % intensity; + rng_state = (rng_state * 0x9E3779B9) + 0x9E37; + const uint8_t flicker = (rng_state & 0xFF) % intensity; // scale down by random factor var = var.get() * (255 - flicker); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index c039447fef..e15e3a8ea3 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -64,45 +64,6 @@ void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); } std::string generate_hostname(const std::string &base) { return base + std::string("-") + get_mac_address(); } -uint32_t random_uint32() { -#ifdef USE_ESP32 - return esp_random(); -#elif defined(USE_ESP8266) - return os_random(); -#endif -} - -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) { -#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO) - esp_fill_random(data, len); -#elif defined(USE_ESP8266) - int err = os_get_random(data, len); - assert(err == 0); -#else -#error "No random source for this system config" -#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; } -uint32_t fast_random_32() { - fast_random_seed = (fast_random_seed * 2654435769ULL) + 40503ULL; - return fast_random_seed; -} -uint16_t fast_random_16() { - uint32_t rand32 = fast_random_32(); - return (rand32 & 0xFFFF) + (rand32 >> 16); -} -uint8_t fast_random_8() { - uint32_t rand32 = fast_random_32(); - return (rand32 & 0xFF) + ((rand32 >> 8) & 0xFF); -} - float gamma_correct(float value, float gamma) { if (value <= 0.0f) return 0.0f; @@ -314,6 +275,30 @@ IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } // --------------------------------------------------------------------------------------------------------------------- +// Mathematics + +uint32_t random_uint32() { +#ifdef USE_ESP32 + return esp_random(); +#elif defined(USE_ESP8266) + return os_random(); +#else +#error "No random source available for this configuration." +#endif +} +float random_float() { return static_cast(random_uint32()) / static_cast(UINT32_MAX); } +void random_bytes(uint8_t *data, size_t len) { +#ifdef USE_ESP32 + esp_fill_random(data, len); +#elif defined(USE_ESP8266) + if (os_get_random(data, len) != 0) { + ESP_LOGE(TAG, "Failed to generate random bytes!"); + } +#else +#error "No random source available for this configuration." +#endif +} + // Strings std::string str_truncate(const std::string &str, size_t length) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 5f793df1ea..f4e814203f 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -78,25 +78,6 @@ template std::unique_ptr make_unique(Args &&... } #endif -/// Return a random 32 bit unsigned integer. -uint32_t random_uint32(); - -/** Returns a random double between 0 and 1. - * - * Note: This function probably doesn't provide a truly uniform distribution. - */ -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(); -uint8_t fast_random_8(); - /// Applies gamma correction with the provided gamma to value. float gamma_correct(float value, float gamma); /// Reverts gamma correction with the provided gamma to value. @@ -304,6 +285,18 @@ constexpr uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); } ///@} +/// @name Mathematics +///@{ + +/// Return a random 32-bit unsigned integer. +uint32_t random_uint32(); +/// Return a random float between 0 and 1. +float random_float(); +/// Generate \p len number of random bytes. +void random_bytes(uint8_t *data, size_t len); + +///@} + /// @name Bit manipulation ///@{ From 7217a4f7a47700f34a367b0803a62d129ef074a9 Mon Sep 17 00:00:00 2001 From: Lubos Horacek Date: Mon, 10 Jan 2022 02:08:38 +0100 Subject: [PATCH 535/549] Fix display picture for nextion display (#3018) --- esphome/components/nextion/nextion_commands.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/nextion/nextion_commands.cpp b/esphome/components/nextion/nextion_commands.cpp index 931b934ba2..f83aafc595 100644 --- a/esphome/components/nextion/nextion_commands.cpp +++ b/esphome/components/nextion/nextion_commands.cpp @@ -171,7 +171,7 @@ void Nextion::set_component_coordinates(const char *component, int x, int 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); + 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) { From f34b46a621a7d680d720c6880207f14d2ec73da4 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Mon, 10 Jan 2022 14:48:05 +1100 Subject: [PATCH 536/549] Fix heatpumpir codegen min/max temperatures (#3025) --- esphome/components/heatpumpir/climate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py index 592e03f959..744ef5e527 100644 --- a/esphome/components/heatpumpir/climate.py +++ b/esphome/components/heatpumpir/climate.py @@ -109,8 +109,8 @@ def to_code(config): cg.add(var.set_protocol(config[CONF_PROTOCOL])) cg.add(var.set_horizontal_default(config[CONF_HORIZONTAL_DEFAULT])) cg.add(var.set_vertical_default(config[CONF_VERTICAL_DEFAULT])) - cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE])) - cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE])) + cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE])) + cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) # PIO isn't updating releases, so referencing the release tag directly. See: # https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd From a0ea2aae6ebc6d14400804ea39930b43cfd7b254 Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Mon, 10 Jan 2022 04:13:39 -0600 Subject: [PATCH 537/549] Add an action for pzemac to reset the total energy (#2480) --- esphome/components/pzemac/pzemac.cpp | 8 ++++++++ esphome/components/pzemac/pzemac.h | 16 ++++++++++++++++ esphome/components/pzemac/sensor.py | 19 +++++++++++++++++++ tests/test3.yaml | 6 ++++++ 4 files changed, 49 insertions(+) diff --git a/esphome/components/pzemac/pzemac.cpp b/esphome/components/pzemac/pzemac.cpp index b1a9607304..c3738d1852 100644 --- a/esphome/components/pzemac/pzemac.cpp +++ b/esphome/components/pzemac/pzemac.cpp @@ -7,6 +7,7 @@ namespace pzemac { static const char *const TAG = "pzemac"; static const uint8_t PZEM_CMD_READ_IN_REGISTERS = 0x04; +static const uint8_t PZEM_CMD_RESET_ENERGY = 0x42; static const uint8_t PZEM_REGISTER_COUNT = 10; // 10x 16-bit registers void PZEMAC::on_modbus_data(const std::vector &data) { @@ -73,5 +74,12 @@ void PZEMAC::dump_config() { LOG_SENSOR("", "Power Factor", this->power_factor_sensor_); } +void PZEMAC::reset_energy_() { + std::vector cmd; + cmd.push_back(this->address_); + cmd.push_back(PZEM_CMD_RESET_ENERGY); + this->send_raw(cmd); +} + } // namespace pzemac } // namespace esphome diff --git a/esphome/components/pzemac/pzemac.h b/esphome/components/pzemac/pzemac.h index 07f661535f..e9f76972a3 100644 --- a/esphome/components/pzemac/pzemac.h +++ b/esphome/components/pzemac/pzemac.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/modbus/modbus.h" @@ -7,6 +8,8 @@ namespace esphome { namespace pzemac { +template class ResetEnergyAction; + class PZEMAC : public PollingComponent, public modbus::ModbusDevice { public: void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } @@ -23,12 +26,25 @@ class PZEMAC : public PollingComponent, public modbus::ModbusDevice { void dump_config() override; protected: + template friend class ResetEnergyAction; sensor::Sensor *voltage_sensor_; sensor::Sensor *current_sensor_; sensor::Sensor *power_sensor_; sensor::Sensor *energy_sensor_; sensor::Sensor *frequency_sensor_; sensor::Sensor *power_factor_sensor_; + + void reset_energy_(); +}; + +template class ResetEnergyAction : public Action { + public: + ResetEnergyAction(PZEMAC *pzemac) : pzemac_(pzemac) {} + + void play(Ts... x) override { this->pzemac_->reset_energy_(); } + + protected: + PZEMAC *pzemac_; }; } // namespace pzemac diff --git a/esphome/components/pzemac/sensor.py b/esphome/components/pzemac/sensor.py index b6697e3d19..ab7dd3e202 100644 --- a/esphome/components/pzemac/sensor.py +++ b/esphome/components/pzemac/sensor.py @@ -1,5 +1,7 @@ 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 sensor, modbus from esphome.const import ( CONF_CURRENT, @@ -29,6 +31,9 @@ AUTO_LOAD = ["modbus"] pzemac_ns = cg.esphome_ns.namespace("pzemac") PZEMAC = pzemac_ns.class_("PZEMAC", cg.PollingComponent, modbus.ModbusDevice) +# Actions +ResetEnergyAction = pzemac_ns.class_("ResetEnergyAction", automation.Action) + CONFIG_SCHEMA = ( cv.Schema( { @@ -75,6 +80,20 @@ CONFIG_SCHEMA = ( ) +@automation.register_action( + "pzemac.reset_energy", + ResetEnergyAction, + maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(PZEMAC), + } + ), +) +async def reset_energy_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]) await cg.register_component(var, config) diff --git a/tests/test3.yaml b/tests/test3.yaml index 01600ad74b..82e3a58725 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -478,6 +478,7 @@ sensor: power: name: 'PZEM004T Power' - platform: pzemac + id: pzemac1 voltage: name: 'PZEMAC Voltage' current: @@ -776,6 +777,11 @@ binary_sensor: on_press: then: - cover.toggle: time_based_cover + - platform: template + id: 'pzemac_reset_energy' + on_press: + then: + - pzemac.reset_energy: pzemac1 globals: - id: my_global_string From 41bcc8c0f40d206f15eb7fb4db195357fb350545 Mon Sep 17 00:00:00 2001 From: Stefan Grufman Date: Mon, 10 Jan 2022 11:35:39 +0100 Subject: [PATCH 538/549] Nexa 433MHz RF protocol (#2037) Co-authored-by: Stefan Grufman Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/remote_base/__init__.py | 53 ++++ .../components/remote_base/nexa_protocol.cpp | 235 ++++++++++++++++++ .../components/remote_base/nexa_protocol.h | 52 ++++ esphome/components/remote_base/remote_base.h | 10 + 4 files changed, 350 insertions(+) create mode 100644 esphome/components/remote_base/nexa_protocol.cpp create mode 100644 esphome/components/remote_base/nexa_protocol.h diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 9982447988..72a91a99dd 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -27,6 +27,7 @@ from esphome.const import ( CONF_CARRIER_FREQUENCY, CONF_RC_CODE_1, CONF_RC_CODE_2, + CONF_LEVEL, ) from esphome.core import coroutine from esphome.jsonschema import jschema_extractor @@ -1163,6 +1164,58 @@ async def panasonic_action(var, config, args): cg.add(var.set_command(template_)) +# Nexa +NexaData, NexaBinarySensor, NexaTrigger, NexaAction, NexaDumper = declare_protocol( + "Nexa" +) +NEXA_SCHEMA = cv.Schema( + { + cv.Required(CONF_DEVICE): cv.hex_uint32_t, + cv.Required(CONF_GROUP): cv.hex_uint8_t, + cv.Required(CONF_STATE): cv.hex_uint8_t, + cv.Required(CONF_CHANNEL): cv.hex_uint8_t, + cv.Required(CONF_LEVEL): cv.hex_uint8_t, + } +) + + +@register_binary_sensor("nexa", NexaBinarySensor, NEXA_SCHEMA) +def nexa_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + NexaData, + ("device", config[CONF_DEVICE]), + ("group", config[CONF_GROUP]), + ("state", config[CONF_STATE]), + ("channel", config[CONF_CHANNEL]), + ("level", config[CONF_LEVEL]), + ) + ) + ) + + +@register_trigger("nexa", NexaTrigger, NexaData) +def nexa_trigger(var, config): + pass + + +@register_dumper("nexa", NexaDumper) +def nexa_dumper(var, config): + pass + + +@register_action("nexa", NexaAction, NEXA_SCHEMA) +def nexa_action(var, config, args): + cg.add(var.set_device((yield cg.templatable(config[CONF_DEVICE], args, cg.uint32)))) + cg.add(var.set_group((yield cg.templatable(config[CONF_GROUP], args, cg.uint8)))) + cg.add(var.set_state((yield cg.templatable(config[CONF_STATE], args, cg.uint8)))) + cg.add( + var.set_channel((yield cg.templatable(config[CONF_CHANNEL], args, cg.uint8))) + ) + cg.add(var.set_level((yield cg.templatable(config[CONF_LEVEL], args, cg.uint8)))) + + # Midea MideaData, MideaBinarySensor, MideaTrigger, MideaAction, MideaDumper = declare_protocol( "Midea" diff --git a/esphome/components/remote_base/nexa_protocol.cpp b/esphome/components/remote_base/nexa_protocol.cpp new file mode 100644 index 0000000000..814b46135a --- /dev/null +++ b/esphome/components/remote_base/nexa_protocol.cpp @@ -0,0 +1,235 @@ +#include "nexa_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.nexa"; + +static const uint8_t NBITS = 32; +static const uint32_t HEADER_HIGH_US = 319; +static const uint32_t HEADER_LOW_US = 2610; +static const uint32_t BIT_HIGH_US = 319; +static const uint32_t BIT_ONE_LOW_US = 1000; +static const uint32_t BIT_ZERO_LOW_US = 140; + +static const uint32_t TX_HEADER_HIGH_US = 250; +static const uint32_t TX_HEADER_LOW_US = TX_HEADER_HIGH_US * 10; +static const uint32_t TX_BIT_HIGH_US = 250; +static const uint32_t TX_BIT_ONE_LOW_US = TX_BIT_HIGH_US * 5; +static const uint32_t TX_BIT_ZERO_LOW_US = TX_BIT_HIGH_US * 1; + +void NexaProtocol::one(RemoteTransmitData *dst) const { + // '1' => '10' + dst->item(TX_BIT_HIGH_US, TX_BIT_ONE_LOW_US); + dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US); +} + +void NexaProtocol::zero(RemoteTransmitData *dst) const { + // '0' => '01' + dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US); + dst->item(TX_BIT_HIGH_US, TX_BIT_ONE_LOW_US); +} + +void NexaProtocol::sync(RemoteTransmitData *dst) const { dst->item(TX_HEADER_HIGH_US, TX_HEADER_LOW_US); } + +void NexaProtocol::encode(RemoteTransmitData *dst, const NexaData &data) { + dst->set_carrier_frequency(0); + + // Send SYNC + this->sync(dst); + + // Device (26 bits) + for (int16_t i = 26 - 1; i >= 0; i--) { + if (data.device & (1 << i)) + this->one(dst); + else + this->zero(dst); + } + + // Group (1 bit) + if (data.group != 0) + this->one(dst); + else + this->zero(dst); + + // State (1 bit) + if (data.state == 2) { + // Special case for dimmers...send 00 as state + dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US); + dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US); + } else if (data.state == 1) + this->one(dst); + else + this->zero(dst); + + // Channel (4 bits) + for (int16_t i = 4 - 1; i >= 0; i--) { + if (data.channel & (1 << i)) + this->one(dst); + else + this->zero(dst); + } + + // Level (4 bits) + if (data.state == 2) { + for (int16_t i = 4 - 1; i >= 0; i--) { + if (data.level & (1 << i)) + this->one(dst); + else + this->zero(dst); + } + } + + // Send finishing Zero + dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US); +} + +optional NexaProtocol::decode(RemoteReceiveData src) { + NexaData out{ + .device = 0, + .group = 0, + .state = 0, + .channel = 0, + .level = 0, + }; + + // From: http://tech.jolowe.se/home-automation-rf-protocols/ + // New data: http://tech.jolowe.se/old-home-automation-rf-protocols/ + /* + + SHHHH HHHH HHHH HHHH HHHH HHHH HHGO EE BB DDDD 0 P + + S = Sync bit. + H = The first 26 bits are transmitter unique codes, and it is this code that the reciever "learns" to recognize. + G = Group code, set to one for the whole group. + O = On/Off bit. Set to 1 for on, 0 for off. + E = Unit to be turned on or off. The code is inverted, i.e. '11' equals 1, '00' equals 4. + B = Button code. The code is inverted, i.e. '11' equals 1, '00' equals 4. + D = Dim level bits. + 0 = packet always ends with a zero. + P = Pause, a 10 ms pause in between re-send. + + Update: First of all the '1' and '0' bit seems to be reversed (and be the same as Jula I protocol below), i.e. + + */ + + // Require a SYNC pulse + long gap + if (!src.expect_pulse_with_gap(HEADER_HIGH_US, HEADER_LOW_US)) + return {}; + + // Device + for (uint8_t i = 0; i < 26; i++) { + out.device <<= 1UL; + if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US) && + (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US))) { + // '1' => '10' + out.device |= 0x01; + } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US) && + (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US))) { + // '0' => '01' + out.device |= 0x00; + } else { + // This should not happen...failed command + return {}; + } + } + + // GROUP + for (uint8_t i = 0; i < 1; i++) { + out.group <<= 1UL; + if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US) && + (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US))) { + // '1' => '10' + out.group |= 0x01; + } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US) && + (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US))) { + // '0' => '01' + out.group |= 0x00; + } else { + // This should not happen...failed command + return {}; + } + } + + // STATE + for (uint8_t i = 0; i < 1; i++) { + out.state <<= 1UL; + + // Special treatment as we should handle 01, 10 and 00 + // We need to care for the advance made in the expect functions + // hence take them one at a time so that we do not get out of sync + // in decoding + + if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US)) { + // Starts with '1' + if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + // '10' => 1 + out.state |= 0x01; + } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US)) { + // '11' => NOT OK + // This case is here to make sure we advance through the correct index + // This should not happen...failed command + return {}; + } + } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + // Starts with '0' + if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US)) { + // '01' => 0 + out.state |= 0x00; + } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + // '00' => Special case for dimmer! => 2 + out.state |= 0x02; + } + } + } + + // CHANNEL (EE and BB bits) + for (uint8_t i = 0; i < 4; i++) { + out.channel <<= 1UL; + if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US) && + (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US))) { + // '1' => '10' + out.channel |= 0x01; + } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US) && + (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US))) { + // '0' => '01' + out.channel |= 0x00; + } else { + // This should not happen...failed command + return {}; + } + } + + // Optional to transmit LEVEL data (8 bits more) + if (int32_t(src.get_index() + 8) >= src.size()) { + return out; + } + + // LEVEL + for (uint8_t i = 0; i < 4; i++) { + out.level <<= 1UL; + if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US) && + (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US))) { + // '1' => '10' + out.level |= 0x01; + } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US) && + (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US))) { + // '0' => '01' + out.level |= 0x00; + } else { + // This should not happen...failed command + break; + } + } + + return out; +} + +void NexaProtocol::dump(const NexaData &data) { + ESP_LOGD(TAG, "Received NEXA: device=0x%04X group=%d state=%d channel=%d level=%d", data.device, data.group, + data.state, data.channel, data.level); +} + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/nexa_protocol.h b/esphome/components/remote_base/nexa_protocol.h new file mode 100644 index 0000000000..f1ce380780 --- /dev/null +++ b/esphome/components/remote_base/nexa_protocol.h @@ -0,0 +1,52 @@ +#pragma once + +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +struct NexaData { + uint32_t device; + uint8_t group; + uint8_t state; + uint8_t channel; + uint8_t level; + bool operator==(const NexaData &rhs) const { + return device == rhs.device && group == rhs.group && state == rhs.state && channel == rhs.channel && + level == rhs.level; + } +}; + +class NexaProtocol : public RemoteProtocol { + public: + void one(RemoteTransmitData *dst) const; + void zero(RemoteTransmitData *dst) const; + void sync(RemoteTransmitData *dst) const; + + void encode(RemoteTransmitData *dst, const NexaData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const NexaData &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(Nexa) + +template class NexaAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(uint32_t, device) + TEMPLATABLE_VALUE(uint8_t, group) + TEMPLATABLE_VALUE(uint8_t, state) + TEMPLATABLE_VALUE(uint8_t, channel) + TEMPLATABLE_VALUE(uint8_t, level) + void encode(RemoteTransmitData *dst, Ts... x) override { + NexaData data{}; + data.device = this->device_.value(x...); + data.group = this->group_.value(x...); + data.state = this->state_.value(x...); + data.channel = this->channel_.value(x...); + data.level = this->level_.value(x...); + NexaProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/remote_base.h b/esphome/components/remote_base/remote_base.h index dd6f7c3482..e1af41274e 100644 --- a/esphome/components/remote_base/remote_base.h +++ b/esphome/components/remote_base/remote_base.h @@ -116,6 +116,16 @@ class RemoteReceiveData { return false; } + bool expect_pulse_with_gap(uint32_t mark, uint32_t space) { + if (this->peek_mark(mark, 0) && this->peek_space_at_least(space, 1)) { + this->advance(2); + return true; + } + return false; + } + + uint32_t get_index() { return index_; } + void reset() { this->index_ = 0; } int32_t pos(uint32_t index) const { return (*this->data_)[index]; } From 073828235f67318df933fbe7350a3e0dc17bb773 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 10 Jan 2022 13:32:39 +0100 Subject: [PATCH 539/549] Deprecate virtual methods to set entity properties (#3021) --- .../binary_sensor/binary_sensor.cpp | 3 ++ .../components/binary_sensor/binary_sensor.h | 1 + esphome/components/cover/cover.cpp | 3 ++ esphome/components/cover/cover.h | 1 + .../integration/integration_sensor.cpp | 25 ---------------- .../integration/integration_sensor.h | 2 -- esphome/components/integration/sensor.py | 29 ++++++++++++++++++- esphome/components/sensor/sensor.cpp | 12 ++++++++ esphome/components/sensor/sensor.h | 4 +++ .../components/total_daily_energy/sensor.py | 19 ++++++++++++ .../total_daily_energy/total_daily_energy.h | 2 -- esphome/core/entity_helpers.py | 7 +++-- 12 files changed, 76 insertions(+), 32 deletions(-) diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index 41da83aa3e..71422609d7 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -48,7 +48,10 @@ void BinarySensor::set_device_class(const std::string &device_class) { this->dev std::string BinarySensor::get_device_class() { if (this->device_class_.has_value()) return *this->device_class_; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" return this->device_class(); +#pragma GCC diagnostic pop } void BinarySensor::add_filter(Filter *filter) { filter->parent_ = this; diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index 9c0d43fa98..ecf68de74c 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -75,6 +75,7 @@ class BinarySensor : public EntityBase { // ========== OVERRIDE METHODS ========== // (You'll only need this when creating your own custom binary sensor) /// Get the default device class for this sensor, or empty string for no default. + ESPDEPRECATED("device_class() is deprecated, set property during config validation instead.", "2022.01") virtual std::string device_class(); protected: diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index a8d3d691a4..21f35f14de 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -210,7 +210,10 @@ Cover::Cover() : Cover("") {} std::string Cover::get_device_class() { if (this->device_class_override_.has_value()) return *this->device_class_override_; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" return this->device_class(); +#pragma GCC diagnostic pop } bool Cover::is_fully_open() const { return this->position == COVER_OPEN; } bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; } diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index a67f8d2393..779e4a2a46 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -169,6 +169,7 @@ class Cover : public EntityBase { friend CoverCall; virtual void control(const CoverCall &call) = 0; + ESPDEPRECATED("device_class() is deprecated, set property during config validation instead.", "2022.01") virtual std::string device_class(); optional restore_state_(); diff --git a/esphome/components/integration/integration_sensor.cpp b/esphome/components/integration/integration_sensor.cpp index 2a398e5240..642116152c 100644 --- a/esphome/components/integration/integration_sensor.cpp +++ b/esphome/components/integration/integration_sensor.cpp @@ -23,31 +23,6 @@ void IntegrationSensor::setup() { this->sensor_->add_on_state_callback([this](float state) { this->process_sensor_value_(state); }); } void IntegrationSensor::dump_config() { LOG_SENSOR("", "Integration Sensor", this); } -std::string IntegrationSensor::unit_of_measurement() { - std::string suffix; - switch (this->time_) { - case INTEGRATION_SENSOR_TIME_MILLISECOND: - suffix = "ms"; - break; - case INTEGRATION_SENSOR_TIME_SECOND: - suffix = "s"; - break; - case INTEGRATION_SENSOR_TIME_MINUTE: - suffix = "min"; - break; - case INTEGRATION_SENSOR_TIME_HOUR: - suffix = "h"; - break; - case INTEGRATION_SENSOR_TIME_DAY: - suffix = "d"; - break; - } - std::string base = this->sensor_->get_unit_of_measurement(); - if (str_endswith(base, "/" + suffix)) { - return base.substr(0, base.size() - suffix.size() - 1); - } - return base + suffix; -} void IntegrationSensor::process_sensor_value_(float value) { const uint32_t now = millis(); const double old_value = this->last_value_; diff --git a/esphome/components/integration/integration_sensor.h b/esphome/components/integration/integration_sensor.h index 437649c1dd..1d46973086 100644 --- a/esphome/components/integration/integration_sensor.h +++ b/esphome/components/integration/integration_sensor.h @@ -63,8 +63,6 @@ class IntegrationSensor : public sensor::Sensor, public Component { this->last_save_ = now; this->rtc_.save(&result_f); } - std::string unit_of_measurement() override; - int8_t accuracy_decimals() override { return this->sensor_->get_accuracy_decimals() + 2; } sensor::Sensor *sensor_; IntegrationSensorTime time_; diff --git a/esphome/components/integration/sensor.py b/esphome/components/integration/sensor.py index 26c7c2871a..c35d42f385 100644 --- a/esphome/components/integration/sensor.py +++ b/esphome/components/integration/sensor.py @@ -2,7 +2,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation from esphome.components import sensor -from esphome.const import CONF_ICON, CONF_ID, CONF_SENSOR, CONF_RESTORE +from esphome.const import ( + CONF_ICON, + CONF_ID, + CONF_SENSOR, + CONF_RESTORE, + CONF_UNIT_OF_MEASUREMENT, + CONF_ACCURACY_DECIMALS, +) from esphome.core.entity_helpers import inherit_property_from integration_ns = cg.esphome_ns.namespace("integration") @@ -30,6 +37,18 @@ CONF_TIME_UNIT = "time_unit" CONF_INTEGRATION_METHOD = "integration_method" CONF_MIN_SAVE_INTERVAL = "min_save_interval" + +def inherit_unit_of_measurement(uom, config): + suffix = config[CONF_TIME_UNIT] + if uom.endswith("/" + suffix): + return uom[0 : -len("/" + suffix)] + return uom + suffix + + +def inherit_accuracy_decimals(decimals, config): + return decimals + 2 + + CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(IntegrationSensor), @@ -51,11 +70,19 @@ FINAL_VALIDATE_SCHEMA = cv.All( { cv.Required(CONF_ID): cv.use_id(IntegrationSensor), cv.Optional(CONF_ICON): cv.icon, + cv.Optional(CONF_UNIT_OF_MEASUREMENT): sensor.validate_unit_of_measurement, + cv.Optional(CONF_ACCURACY_DECIMALS): sensor.validate_accuracy_decimals, cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), }, extra=cv.ALLOW_EXTRA, ), inherit_property_from(CONF_ICON, CONF_SENSOR), + inherit_property_from( + CONF_UNIT_OF_MEASUREMENT, CONF_SENSOR, transform=inherit_unit_of_measurement + ), + inherit_property_from( + CONF_ACCURACY_DECIMALS, CONF_SENSOR, transform=inherit_accuracy_decimals + ), ) diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 793ae170c3..73730f6482 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -24,7 +24,10 @@ Sensor::Sensor() : Sensor("") {} std::string Sensor::get_unit_of_measurement() { if (this->unit_of_measurement_.has_value()) return *this->unit_of_measurement_; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" return this->unit_of_measurement(); +#pragma GCC diagnostic pop } void Sensor::set_unit_of_measurement(const std::string &unit_of_measurement) { this->unit_of_measurement_ = unit_of_measurement; @@ -34,7 +37,10 @@ std::string Sensor::unit_of_measurement() { return ""; } int8_t Sensor::get_accuracy_decimals() { if (this->accuracy_decimals_.has_value()) return *this->accuracy_decimals_; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" return this->accuracy_decimals(); +#pragma GCC diagnostic pop } void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; } int8_t Sensor::accuracy_decimals() { return 0; } @@ -42,7 +48,10 @@ int8_t Sensor::accuracy_decimals() { return 0; } std::string Sensor::get_device_class() { if (this->device_class_.has_value()) return *this->device_class_; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" return this->device_class(); +#pragma GCC diagnostic pop } void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } std::string Sensor::device_class() { return ""; } @@ -51,7 +60,10 @@ void Sensor::set_state_class(StateClass state_class) { this->state_class_ = stat StateClass Sensor::get_state_class() { if (this->state_class_.has_value()) return *this->state_class_; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" return this->state_class(); +#pragma GCC diagnostic pop } StateClass Sensor::state_class() { return StateClass::STATE_CLASS_NONE; } diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 6cab46f7f9..794aecca95 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -151,15 +151,19 @@ class Sensor : public EntityBase { protected: /// Override this to set the default unit of measurement. + ESPDEPRECATED("unit_of_measurement() is deprecated, set property during config validation instead.", "2022.01") virtual std::string unit_of_measurement(); // NOLINT /// Override this to set the default accuracy in decimals. + ESPDEPRECATED("accuracy_decimals() is deprecated, set property during config validation instead.", "2022.01") virtual int8_t accuracy_decimals(); // NOLINT /// Override this to set the default device class. + ESPDEPRECATED("device_class() is deprecated, set property during config validation instead.", "2022.01") virtual std::string device_class(); // NOLINT /// Override this to set the default state class. + ESPDEPRECATED("state_class() is deprecated, set property during config validation instead.", "2022.01") virtual StateClass state_class(); // NOLINT uint32_t hash_base() override; diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index 0c20ccd27c..1af8db8332 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -9,6 +9,8 @@ from esphome.const import ( DEVICE_CLASS_ENERGY, CONF_METHOD, STATE_CLASS_TOTAL_INCREASING, + CONF_UNIT_OF_MEASUREMENT, + CONF_ACCURACY_DECIMALS, ) from esphome.core.entity_helpers import inherit_property_from @@ -27,6 +29,15 @@ TotalDailyEnergy = total_daily_energy_ns.class_( "TotalDailyEnergy", sensor.Sensor, cg.Component ) + +def inherit_unit_of_measurement(uom, config): + return uom + "h" + + +def inherit_accuracy_decimals(decimals, config): + return decimals + 2 + + CONFIG_SCHEMA = ( sensor.sensor_schema( device_class=DEVICE_CLASS_ENERGY, @@ -54,11 +65,19 @@ FINAL_VALIDATE_SCHEMA = cv.All( { cv.Required(CONF_ID): cv.use_id(TotalDailyEnergy), cv.Optional(CONF_ICON): cv.icon, + cv.Optional(CONF_UNIT_OF_MEASUREMENT): sensor.validate_unit_of_measurement, + cv.Optional(CONF_ACCURACY_DECIMALS): sensor.validate_accuracy_decimals, cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), }, extra=cv.ALLOW_EXTRA, ), inherit_property_from(CONF_ICON, CONF_POWER_ID), + inherit_property_from( + CONF_UNIT_OF_MEASUREMENT, CONF_POWER_ID, transform=inherit_unit_of_measurement + ), + inherit_property_from( + CONF_ACCURACY_DECIMALS, CONF_POWER_ID, transform=inherit_accuracy_decimals + ), ) diff --git a/esphome/components/total_daily_energy/total_daily_energy.h b/esphome/components/total_daily_energy/total_daily_energy.h index 498f65891e..a35edfd11b 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.h +++ b/esphome/components/total_daily_energy/total_daily_energy.h @@ -25,8 +25,6 @@ class TotalDailyEnergy : public sensor::Sensor, public Component { void setup() override; void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } - std::string unit_of_measurement() override { return this->parent_->get_unit_of_measurement() + "h"; } - int8_t accuracy_decimals() override { return this->parent_->get_accuracy_decimals() + 2; } void loop() override; void publish_state_and_save(float state); diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index 7f7d78aaa2..f921711ec2 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -3,7 +3,7 @@ import esphome.final_validate as fv from esphome.const import CONF_ID -def inherit_property_from(property_to_inherit, parent_id_property): +def inherit_property_from(property_to_inherit, parent_id_property, transform=None): """Validator that inherits a configuration property from another entity, for use with FINAL_VALIDATE_SCHEMA. If a property is already set, it will not be inherited. Keyword arguments: @@ -47,7 +47,10 @@ def inherit_property_from(property_to_inherit, parent_id_property): this_config = _walk_config( fconf.get_config_for_path(path), property_path ) - this_config[property] = parent_config[property] + value = parent_config[property] + if transform: + value = transform(value, config) + this_config[property] = value return config From ece71a02281bf3ce23bf423e19d8863f2e3dbe07 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 11 Jan 2022 15:24:26 +1300 Subject: [PATCH 540/549] Run post scripts for factory binaries for flashing (#3003) Co-authored-by: Oxan van Leeuwen --- esphome/components/esp32/__init__.py | 12 ++++++- esphome/components/esp32/post_build.py | 43 ++++++++++++++++++++++++ esphome/components/esp8266/__init__.py | 15 +++++++++ esphome/components/esp8266/post_build.py | 15 +++++++++ esphome/dashboard/dashboard.py | 11 ++++++ esphome/writer.py | 5 +++ platformio.ini | 3 ++ 7 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 esphome/components/esp32/post_build.py create mode 100644 esphome/components/esp8266/post_build.py diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index d6f1180aa7..161803eaf4 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -2,8 +2,9 @@ from dataclasses import dataclass from typing import Union from pathlib import Path import logging +import os -from esphome.helpers import write_file_if_changed +from esphome.helpers import copy_file_if_changed, write_file_if_changed from esphome.const import ( CONF_BOARD, CONF_FRAMEWORK, @@ -295,6 +296,8 @@ async def to_code(config): conf = config[CONF_FRAMEWORK] cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) + cg.add_platformio_option("extra_scripts", ["post:post_build.py"]) + if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: cg.add_platformio_option("framework", "espidf") cg.add_build_flag("-DUSE_ESP_IDF") @@ -412,3 +415,10 @@ def copy_files(): CORE.relative_build_path("partitions.csv"), IDF_PARTITIONS_CSV, ) + + dir = os.path.dirname(__file__) + post_build_file = os.path.join(dir, "post_build.py") + copy_file_if_changed( + post_build_file, + CORE.relative_build_path("post_build.py"), + ) diff --git a/esphome/components/esp32/post_build.py b/esphome/components/esp32/post_build.py new file mode 100644 index 0000000000..7feaf9e8e5 --- /dev/null +++ b/esphome/components/esp32/post_build.py @@ -0,0 +1,43 @@ +# Source https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1005864664 + +import esptool + +# pylint: disable=E0602 +Import("env") # noqa + + +def esp32_create_combined_bin(source, target, env): + print("Generating combined binary for serial flashing") + app_offset = 0x10000 + + new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin") + sections = env.subst(env.get("FLASH_EXTRA_IMAGES")) + firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin") + chip = env.get("BOARD_MCU") + flash_size = env.BoardConfig().get("upload.flash_size") + cmd = [ + "--chip", + chip, + "merge_bin", + "-o", + new_file_name, + "--flash_size", + flash_size, + ] + print(" Offset | File") + for section in sections: + sect_adr, sect_file = section.split(" ", 1) + print(f" - {sect_adr} | {sect_file}") + cmd += [sect_adr, sect_file] + + print(f" - {hex(app_offset)} | {firmware_name}") + cmd += [hex(app_offset), firmware_name] + + print() + print(f"Using esptool.py arguments: {' '.join(cmd)}") + print() + esptool.main(cmd) + + +# pylint: disable=E0602 +env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) # noqa diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 34c792499d..34a4a2fadb 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -1,4 +1,5 @@ import logging +import os from esphome.const import ( CONF_BOARD, @@ -14,6 +15,7 @@ from esphome.const import ( from esphome.core import CORE, coroutine_with_priority import esphome.config_validation as cv import esphome.codegen as cg +from esphome.helpers import copy_file_if_changed from .const import CONF_RESTORE_FROM_FLASH, KEY_BOARD, KEY_ESP8266, esp8266_ns from .boards import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS @@ -158,6 +160,8 @@ async def to_code(config): cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) cg.add_define("ESPHOME_VARIANT", "ESP8266") + cg.add_platformio_option("extra_scripts", ["post:post_build.py"]) + conf = config[CONF_FRAMEWORK] cg.add_platformio_option("framework", "arduino") cg.add_build_flag("-DUSE_ARDUINO") @@ -210,3 +214,14 @@ async def to_code(config): if ld_script is not None: cg.add_platformio_option("board_build.ldscript", ld_script) + + +# Called by writer.py +def copy_files(): + + dir = os.path.dirname(__file__) + post_build_file = os.path.join(dir, "post_build.py") + copy_file_if_changed( + post_build_file, + CORE.relative_build_path("post_build.py"), + ) diff --git a/esphome/components/esp8266/post_build.py b/esphome/components/esp8266/post_build.py new file mode 100644 index 0000000000..4dab1cbd27 --- /dev/null +++ b/esphome/components/esp8266/post_build.py @@ -0,0 +1,15 @@ +import shutil + +# pylint: disable=E0602 +Import("env") # noqa + + +def esp8266_copy_factory_bin(source, target, env): + firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin") + new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin") + + shutil.copyfile(firmware_name, new_file_name) + + +# pylint: disable=E0602 +env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp8266_copy_factory_bin) # noqa diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 1a247ec4eb..ca257d93b4 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -410,6 +410,17 @@ class DownloadBinaryRequestHandler(BaseHandler): filename = f"{storage_json.name}.bin" path = storage_json.firmware_bin_path + elif type == "firmware-factory.bin": + storage_path = ext_storage_path(settings.config_dir, configuration) + storage_json = StorageJSON.load(storage_path) + if storage_json is None: + self.send_error(404) + return + filename = f"{storage_json.name}.bin" + path = storage_json.firmware_bin_path.replace( + "firmware.bin", "firmware-factory.bin" + ) + else: args = ["esphome", "idedata", settings.rel_path(configuration)] rc, stdout, _ = run_system_command(*args) diff --git a/esphome/writer.py b/esphome/writer.py index 8963572752..89a074683a 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -290,6 +290,11 @@ def copy_src_tree(): copy_files() + elif CORE.is_esp8266: + from esphome.components.esp8266 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 1a81b4e75a..589624a71d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -89,6 +89,7 @@ build_flags = ${common:arduino.build_flags} -DUSE_ESP8266 -DUSE_ESP8266_FRAMEWORK_ARDUINO +extra_scripts = post:esphome/components/esp8266/post_build.py ; This are common settings for the ESP32 (all variants) using Arduino. [common:esp32-arduino] @@ -107,6 +108,7 @@ build_flags = ${common:arduino.build_flags} -DUSE_ESP32 -DUSE_ESP32_FRAMEWORK_ARDUINO +extra_scripts = post:esphome/components/esp32/post_build.py ; This are common settings for the ESP32 (all variants) using IDF. [common:esp32-idf] @@ -125,6 +127,7 @@ build_flags = -Wno-nonnull-compare -DUSE_ESP32 -DUSE_ESP32_FRAMEWORK_ESP_IDF +extra_scripts = post:esphome/components/esp32/post_build.py ; All the actual environments are defined below. [env:esp8266-arduino] From 27364ee72c553f59aeb0743a033949b2e9d43cef Mon Sep 17 00:00:00 2001 From: Andreas Soehlke Date: Tue, 11 Jan 2022 04:59:57 +0100 Subject: [PATCH 541/549] Add cd74hc4067 multiplexer (#2431) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: asoehlke --- CODEOWNERS | 1 + esphome/components/cd74hc4067/__init__.py | 53 ++++++++++++ esphome/components/cd74hc4067/cd74hc4067.cpp | 86 ++++++++++++++++++++ esphome/components/cd74hc4067/cd74hc4067.h | 65 +++++++++++++++ esphome/components/cd74hc4067/sensor.py | 55 +++++++++++++ tests/test3.yaml | 10 +++ 6 files changed, 270 insertions(+) create mode 100644 esphome/components/cd74hc4067/__init__.py create mode 100644 esphome/components/cd74hc4067/cd74hc4067.cpp create mode 100644 esphome/components/cd74hc4067/cd74hc4067.h create mode 100644 esphome/components/cd74hc4067/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 59648b6dbc..18694b50ba 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -37,6 +37,7 @@ esphome/components/canbus/* @danielschramm @mvturnho esphome/components/cap1188/* @MrEditor97 esphome/components/captive_portal/* @OttoWinter esphome/components/ccs811/* @habbie +esphome/components/cd74hc4067/* @asoehlke esphome/components/climate/* @esphome/core esphome/components/climate_ir/* @glmnet esphome/components/color_temperature/* @jesserockz diff --git a/esphome/components/cd74hc4067/__init__.py b/esphome/components/cd74hc4067/__init__.py new file mode 100644 index 0000000000..f8efdf4b2a --- /dev/null +++ b/esphome/components/cd74hc4067/__init__.py @@ -0,0 +1,53 @@ +import esphome.codegen as cg +from esphome import pins +import esphome.config_validation as cv +from esphome.const import ( + CONF_DELAY, + CONF_ID, +) + +CODEOWNERS = ["@asoehlke"] +AUTO_LOAD = ["sensor", "voltage_sampler"] + +cd74hc4067_ns = cg.esphome_ns.namespace("cd74hc4067") + +CD74HC4067Component = cd74hc4067_ns.class_( + "CD74HC4067Component", cg.Component, cg.PollingComponent +) + +CONF_PIN_S0 = "pin_s0" +CONF_PIN_S1 = "pin_s1" +CONF_PIN_S2 = "pin_s2" +CONF_PIN_S3 = "pin_s3" + +DEFAULT_DELAY = "2ms" + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(CD74HC4067Component), + cv.Required(CONF_PIN_S0): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_PIN_S1): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_PIN_S2): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_PIN_S3): pins.internal_gpio_output_pin_schema, + cv.Optional( + CONF_DELAY, default=DEFAULT_DELAY + ): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + pin_s0 = await cg.gpio_pin_expression(config[CONF_PIN_S0]) + cg.add(var.set_pin_s0(pin_s0)) + pin_s1 = await cg.gpio_pin_expression(config[CONF_PIN_S1]) + cg.add(var.set_pin_s1(pin_s1)) + pin_s2 = await cg.gpio_pin_expression(config[CONF_PIN_S2]) + cg.add(var.set_pin_s2(pin_s2)) + pin_s3 = await cg.gpio_pin_expression(config[CONF_PIN_S3]) + cg.add(var.set_pin_s3(pin_s3)) + + cg.add(var.set_switch_delay(config[CONF_DELAY])) diff --git a/esphome/components/cd74hc4067/cd74hc4067.cpp b/esphome/components/cd74hc4067/cd74hc4067.cpp new file mode 100644 index 0000000000..ea789c2d8c --- /dev/null +++ b/esphome/components/cd74hc4067/cd74hc4067.cpp @@ -0,0 +1,86 @@ +#include "cd74hc4067.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace cd74hc4067 { + +static const char *const TAG = "cd74hc4067"; + +float CD74HC4067Component::get_setup_priority() const { return setup_priority::DATA; } + +void CD74HC4067Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up CD74HC4067..."); + + this->pin_s0_->setup(); + this->pin_s1_->setup(); + this->pin_s2_->setup(); + this->pin_s3_->setup(); + + // set other pin, so that activate_pin will really switch + this->active_pin_ = 1; + this->activate_pin(0); +} + +void CD74HC4067Component::dump_config() { + ESP_LOGCONFIG(TAG, "CD74HC4067 Multiplexer:"); + LOG_PIN(" S0 Pin: ", this->pin_s0_); + LOG_PIN(" S1 Pin: ", this->pin_s1_); + LOG_PIN(" S2 Pin: ", this->pin_s2_); + LOG_PIN(" S3 Pin: ", this->pin_s3_); + ESP_LOGCONFIG(TAG, "switch delay: %d", this->switch_delay_); +} + +void CD74HC4067Component::activate_pin(uint8_t pin) { + if (this->active_pin_ != pin) { + ESP_LOGD(TAG, "switch to input %d", pin); + + static int mux_channel[16][4] = { + {0, 0, 0, 0}, // channel 0 + {1, 0, 0, 0}, // channel 1 + {0, 1, 0, 0}, // channel 2 + {1, 1, 0, 0}, // channel 3 + {0, 0, 1, 0}, // channel 4 + {1, 0, 1, 0}, // channel 5 + {0, 1, 1, 0}, // channel 6 + {1, 1, 1, 0}, // channel 7 + {0, 0, 0, 1}, // channel 8 + {1, 0, 0, 1}, // channel 9 + {0, 1, 0, 1}, // channel 10 + {1, 1, 0, 1}, // channel 11 + {0, 0, 1, 1}, // channel 12 + {1, 0, 1, 1}, // channel 13 + {0, 1, 1, 1}, // channel 14 + {1, 1, 1, 1} // channel 15 + }; + this->pin_s0_->digital_write(mux_channel[pin][0]); + this->pin_s1_->digital_write(mux_channel[pin][1]); + this->pin_s2_->digital_write(mux_channel[pin][2]); + this->pin_s3_->digital_write(mux_channel[pin][3]); + // small delay is needed to let the multiplexer switch + delay(this->switch_delay_); + this->active_pin_ = pin; + } +} + +CD74HC4067Sensor::CD74HC4067Sensor(CD74HC4067Component *parent) : parent_(parent) {} + +void CD74HC4067Sensor::update() { + float value_v = this->sample(); + this->publish_state(value_v); +} + +float CD74HC4067Sensor::get_setup_priority() const { return this->parent_->get_setup_priority() - 1.0f; } + +float CD74HC4067Sensor::sample() { + this->parent_->activate_pin(this->pin_); + return this->source_->sample(); +} + +void CD74HC4067Sensor::dump_config() { + LOG_SENSOR(TAG, "CD74HC4067 Sensor", this); + ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); + LOG_UPDATE_INTERVAL(this); +} + +} // namespace cd74hc4067 +} // namespace esphome diff --git a/esphome/components/cd74hc4067/cd74hc4067.h b/esphome/components/cd74hc4067/cd74hc4067.h new file mode 100644 index 0000000000..4a5c2e4e35 --- /dev/null +++ b/esphome/components/cd74hc4067/cd74hc4067.h @@ -0,0 +1,65 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/voltage_sampler/voltage_sampler.h" + +namespace esphome { +namespace cd74hc4067 { + +class CD74HC4067Component : public Component { + public: + /// Set up the internal sensor array. + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + + /// setting pin active by setting the right combination of the four multiplexer input pins + void activate_pin(uint8_t pin); + + /// set the pin connected to multiplexer control pin 0 + void set_pin_s0(InternalGPIOPin *pin) { this->pin_s0_ = pin; } + /// set the pin connected to multiplexer control pin 1 + void set_pin_s1(InternalGPIOPin *pin) { this->pin_s1_ = pin; } + /// set the pin connected to multiplexer control pin 2 + void set_pin_s2(InternalGPIOPin *pin) { this->pin_s2_ = pin; } + /// set the pin connected to multiplexer control pin 3 + void set_pin_s3(InternalGPIOPin *pin) { this->pin_s3_ = pin; } + + /// set the delay needed after an input switch + void set_switch_delay(uint32_t switch_delay) { this->switch_delay_ = switch_delay; } + + private: + InternalGPIOPin *pin_s0_; + InternalGPIOPin *pin_s1_; + InternalGPIOPin *pin_s2_; + InternalGPIOPin *pin_s3_; + /// the currently active pin + uint8_t active_pin_; + uint32_t switch_delay_; +}; + +class CD74HC4067Sensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { + public: + CD74HC4067Sensor(CD74HC4067Component *parent); + + void update() override; + + 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_source(voltage_sampler::VoltageSampler *source) { this->source_ = source; } + + float sample() override; + + protected: + CD74HC4067Component *parent_; + /// The sampling source to read values from. + voltage_sampler::VoltageSampler *source_; + + uint8_t pin_; +}; +} // namespace cd74hc4067 +} // namespace esphome diff --git a/esphome/components/cd74hc4067/sensor.py b/esphome/components/cd74hc4067/sensor.py new file mode 100644 index 0000000000..7c7cf9ccb7 --- /dev/null +++ b/esphome/components/cd74hc4067/sensor.py @@ -0,0 +1,55 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, voltage_sampler +from esphome.const import ( + CONF_ID, + CONF_SENSOR, + CONF_NUMBER, + ICON_FLASH, + UNIT_VOLT, + STATE_CLASS_MEASUREMENT, + DEVICE_CLASS_VOLTAGE, +) +from . import cd74hc4067_ns, CD74HC4067Component + +DEPENDENCIES = ["cd74hc4067"] + +CD74HC4067Sensor = cd74hc4067_ns.class_( + "CD74HC4067Sensor", + sensor.Sensor, + cg.PollingComponent, + voltage_sampler.VoltageSampler, +) + +CONF_CD74HC4067_ID = "cd74hc4067_id" + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + icon=ICON_FLASH, + ) + .extend( + { + cv.GenerateID(): cv.declare_id(CD74HC4067Sensor), + cv.GenerateID(CONF_CD74HC4067_ID): cv.use_id(CD74HC4067Component), + cv.Required(CONF_NUMBER): cv.int_range(0, 15), + cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler), + } + ) + .extend(cv.polling_component_schema("60s")) +) + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_CD74HC4067_ID]) + + var = cg.new_Pvariable(config[CONF_ID], parent) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + cg.add(var.set_pin(config[CONF_NUMBER])) + + sens = await cg.get_variable(config[CONF_SENSOR]) + cg.add(var.set_source(sens)) diff --git a/tests/test3.yaml b/tests/test3.yaml index 82e3a58725..607d985704 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -378,6 +378,10 @@ sensor: - 400 -> 500 - -50 -> -1000 - -100 -> -10000 + - platform: cd74hc4067 + id: cd74hc4067_0 + number: 0 + sensor: my_sensor - platform: resistance sensor: my_sensor configuration: DOWNSTREAM @@ -1345,3 +1349,9 @@ dsmr: daly_bms: update_interval: 20s uart_id: uart1 + +cd74hc4067: + pin_s0: GPIO12 + pin_s1: GPIO13 + pin_s2: GPIO14 + pin_s3: GPIO15 From 5026bc7a780919cb88e510b82acd5883417e730c Mon Sep 17 00:00:00 2001 From: Sympatron GmbH <35803463+Sympatron@users.noreply.github.com> Date: Tue, 11 Jan 2022 19:54:35 +0000 Subject: [PATCH 542/549] Native ESP32 CAN support (#1629) Co-authored-by: Guillermo Ruffino Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/esp32_can/__init__.py | 0 esphome/components/esp32_can/canbus.py | 39 +++++++ esphome/components/esp32_can/esp32_can.cpp | 123 +++++++++++++++++++++ esphome/components/esp32_can/esp32_can.h | 29 +++++ tests/test1.yaml | 25 +++++ 6 files changed, 217 insertions(+) create mode 100644 esphome/components/esp32_can/__init__.py create mode 100644 esphome/components/esp32_can/canbus.py create mode 100644 esphome/components/esp32_can/esp32_can.cpp create mode 100644 esphome/components/esp32_can/esp32_can.h diff --git a/CODEOWNERS b/CODEOWNERS index 18694b50ba..351d9f5fc9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -58,6 +58,7 @@ esphome/components/esp32/* @esphome/core esphome/components/esp32_ble/* @jesserockz esphome/components/esp32_ble_server/* @jesserockz esphome/components/esp32_camera_web_server/* @ayufan +esphome/components/esp32_can/* @Sympatron esphome/components/esp32_improv/* @jesserockz esphome/components/esp8266/* @esphome/core esphome/components/exposure_notifications/* @OttoWinter diff --git a/esphome/components/esp32_can/__init__.py b/esphome/components/esp32_can/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/esp32_can/canbus.py b/esphome/components/esp32_can/canbus.py new file mode 100644 index 0000000000..7761418c6a --- /dev/null +++ b/esphome/components/esp32_can/canbus.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import canbus +from esphome.const import CONF_ID, CONF_RX_PIN, CONF_TX_PIN +from esphome.components.canbus import CanbusComponent, CanSpeed, CONF_BIT_RATE + +CODEOWNERS = ["@Sympatron"] +DEPENDENCIES = ["esp32"] + +esp32_can_ns = cg.esphome_ns.namespace("esp32_can") +esp32_can = esp32_can_ns.class_("ESP32Can", CanbusComponent) + +# Currently the driver only supports a subset of the bit rates defined in canbus +CAN_SPEEDS = { + "50KBPS": CanSpeed.CAN_50KBPS, + "100KBPS": CanSpeed.CAN_100KBPS, + "125KBPS": CanSpeed.CAN_125KBPS, + "250KBPS": CanSpeed.CAN_250KBPS, + "500KBPS": CanSpeed.CAN_500KBPS, + "1000KBPS": CanSpeed.CAN_1000KBPS, +} + +CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(esp32_can), + cv.Optional(CONF_BIT_RATE, default="125KBPS"): cv.enum(CAN_SPEEDS, upper=True), + cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_number, + cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_number, + } +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await canbus.register_canbus(var, config) + + cg.add(var.set_rx(config[CONF_RX_PIN])) + cg.add(var.set_tx(config[CONF_TX_PIN])) diff --git a/esphome/components/esp32_can/esp32_can.cpp b/esphome/components/esp32_can/esp32_can.cpp new file mode 100644 index 0000000000..baae683988 --- /dev/null +++ b/esphome/components/esp32_can/esp32_can.cpp @@ -0,0 +1,123 @@ +#ifdef USE_ESP32 +#include "esp32_can.h" +#include "esphome/core/log.h" + +#include + +// WORKAROUND, because CAN_IO_UNUSED is just defined as (-1) in this version +// of the framework which does not work with -fpermissive +#undef CAN_IO_UNUSED +#define CAN_IO_UNUSED ((gpio_num_t) -1) + +namespace esphome { +namespace esp32_can { + +static const char *const TAG = "esp32_can"; + +static bool get_bitrate(canbus::CanSpeed bitrate, can_timing_config_t *t_config) { + switch (bitrate) { + case canbus::CAN_50KBPS: + *t_config = (can_timing_config_t) CAN_TIMING_CONFIG_50KBITS(); + return true; + case canbus::CAN_100KBPS: + *t_config = (can_timing_config_t) CAN_TIMING_CONFIG_100KBITS(); + return true; + case canbus::CAN_125KBPS: + *t_config = (can_timing_config_t) CAN_TIMING_CONFIG_125KBITS(); + return true; + case canbus::CAN_250KBPS: + *t_config = (can_timing_config_t) CAN_TIMING_CONFIG_250KBITS(); + return true; + case canbus::CAN_500KBPS: + *t_config = (can_timing_config_t) CAN_TIMING_CONFIG_500KBITS(); + return true; + case canbus::CAN_1000KBPS: + *t_config = (can_timing_config_t) CAN_TIMING_CONFIG_1MBITS(); + return true; + default: + return false; + } +} + +bool ESP32Can::setup_internal() { + can_general_config_t g_config = + CAN_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, CAN_MODE_NORMAL); + can_filter_config_t f_config = CAN_FILTER_CONFIG_ACCEPT_ALL(); + can_timing_config_t t_config; + + if (!get_bitrate(this->bit_rate_, &t_config)) { + // invalid bit rate + this->mark_failed(); + return false; + } + + // Install CAN driver + if (can_driver_install(&g_config, &t_config, &f_config) != ESP_OK) { + // Failed to install driver + this->mark_failed(); + return false; + } + + // Start CAN driver + if (can_start() != ESP_OK) { + // Failed to start driver + this->mark_failed(); + return false; + } + return true; +} + +canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) { + if (frame->can_data_length_code > canbus::CAN_MAX_DATA_LENGTH) { + return canbus::ERROR_FAILTX; + } + + uint32_t flags = CAN_MSG_FLAG_NONE; + if (frame->use_extended_id) { + flags |= CAN_MSG_FLAG_EXTD; + } + if (frame->remote_transmission_request) { + flags |= CAN_MSG_FLAG_RTR; + } + + can_message_t message = { + .flags = flags, + .identifier = frame->can_id, + .data_length_code = frame->can_data_length_code, + }; + if (!frame->remote_transmission_request) { + memcpy(message.data, frame->data, frame->can_data_length_code); + } + + if (can_transmit(&message, pdMS_TO_TICKS(1000)) == ESP_OK) { + return canbus::ERROR_OK; + } else { + return canbus::ERROR_ALLTXBUSY; + } +} + +canbus::Error ESP32Can::read_message(struct canbus::CanFrame *frame) { + can_message_t message; + + if (can_receive(&message, 0) != ESP_OK) { + return canbus::ERROR_NOMSG; + } + + frame->can_id = message.identifier; + frame->use_extended_id = message.flags & CAN_MSG_FLAG_EXTD; + frame->remote_transmission_request = message.flags & CAN_MSG_FLAG_RTR; + frame->can_data_length_code = message.data_length_code; + + if (!frame->remote_transmission_request) { + size_t dlc = + message.data_length_code < canbus::CAN_MAX_DATA_LENGTH ? message.data_length_code : canbus::CAN_MAX_DATA_LENGTH; + memcpy(frame->data, message.data, dlc); + } + + return canbus::ERROR_OK; +} + +} // namespace esp32_can +} // namespace esphome + +#endif diff --git a/esphome/components/esp32_can/esp32_can.h b/esphome/components/esp32_can/esp32_can.h new file mode 100644 index 0000000000..a428834f65 --- /dev/null +++ b/esphome/components/esp32_can/esp32_can.h @@ -0,0 +1,29 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/components/canbus/canbus.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace esp32_can { + +class ESP32Can : public canbus::Canbus { + public: + void set_rx(int rx) { rx_ = rx; } + void set_tx(int tx) { tx_ = tx; } + ESP32Can(){}; + + protected: + bool setup_internal() override; + canbus::Error send_message(struct canbus::CanFrame *frame) override; + canbus::Error read_message(struct canbus::CanFrame *frame) override; + + int rx_{-1}; + int tx_{-1}; +}; + +} // namespace esp32_can +} // namespace esphome + +#endif diff --git a/tests/test1.yaml b/tests/test1.yaml index 959ffb0d2d..d3351e3b12 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2472,6 +2472,11 @@ text_sensor: id: glob_int value: '0' - canbus.send: + canbus_id: mcp2515_can + can_id: 23 + data: [0x10, 0x20, 0x30] + - canbus.send: + canbus_id: esp32_internal_can can_id: 23 data: [0x10, 0x20, 0x30] - platform: template @@ -2509,6 +2514,7 @@ rtttl: canbus: - platform: mcp2515 + id: mcp2515_can cs_pin: GPIO17 can_id: 4 bit_rate: 50kbps @@ -2525,6 +2531,25 @@ canbus: lambda: 'return x[0] == 0x11;' then: light.toggle: ${roomname}_lights + - platform: esp32_can + id: esp32_internal_can + rx_pin: GPIO04 + tx_pin: GPIO05 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("canid 500", "%s", &b[0] ); + - can_id: 23 + then: + - if: + condition: + lambda: 'return x[0] == 0x11;' + then: + light.toggle: ${roomname}_lights teleinfo: id: myteleinfo From 56547b3d50d7a1039cd9d6cd9649393219f4aff7 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Wed, 12 Jan 2022 04:38:13 +0100 Subject: [PATCH 543/549] [Modbus_controller] Fix duplicate cmd check (#3031) --- esphome/components/modbus_controller/modbus_controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 2fdedfd786..d07a6d5335 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -109,7 +109,7 @@ void ModbusController::queue_command(const ModbusCommandItem &command) { // not very effective but the queue is never really large for (auto &item : command_queue_) { if (item->register_address == command.register_address && item->register_count == command.register_count && - item->register_type == command.register_type) { + item->register_type == command.register_type && item->function_code == command.function_code) { ESP_LOGW(TAG, "Duplicate modbus command found"); // update the payload of the queued command // replaces a previous command From d9c938de33e8e4498e1f277c078b37e3aaa4c14c Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 12 Jan 2022 04:50:03 +0100 Subject: [PATCH 544/549] Introduce big- and little-endian integer types (#2997) --- esphome/core/datatypes.h | 61 ++++++++++++++++++++++++++++++++++++++++ esphome/core/helpers.h | 48 ++++++++++++++++++++++++------- 2 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 esphome/core/datatypes.h diff --git a/esphome/core/datatypes.h b/esphome/core/datatypes.h new file mode 100644 index 0000000000..5356be6b52 --- /dev/null +++ b/esphome/core/datatypes.h @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include "esphome/core/helpers.h" + +namespace esphome { + +namespace internal { + +/// Wrapper class for memory using big endian data layout, transparently converting it to native order. +template class BigEndianLayout { + public: + constexpr14 operator T() { return convert_big_endian(val_); } + + private: + T val_; +} __attribute__((packed)); + +/// Wrapper class for memory using big endian data layout, transparently converting it to native order. +template class LittleEndianLayout { + public: + constexpr14 operator T() { return convert_little_endian(val_); } + + private: + T val_; +} __attribute__((packed)); + +} // namespace internal + +/// 24-bit unsigned integer type, transparently converting to 32-bit. +struct uint24_t { // NOLINT(readability-identifier-naming) + operator uint32_t() { return val; } + uint32_t val : 24; +} __attribute__((packed)); + +/// 24-bit signed integer type, transparently converting to 32-bit. +struct int24_t { // NOLINT(readability-identifier-naming) + operator int32_t() { return val; } + int32_t val : 24; +} __attribute__((packed)); + +// Integer types in big or little endian data layout. +using uint64_be_t = internal::BigEndianLayout; +using uint32_be_t = internal::BigEndianLayout; +using uint24_be_t = internal::BigEndianLayout; +using uint16_be_t = internal::BigEndianLayout; +using int64_be_t = internal::BigEndianLayout; +using int32_be_t = internal::BigEndianLayout; +using int24_be_t = internal::BigEndianLayout; +using int16_be_t = internal::BigEndianLayout; +using uint64_le_t = internal::LittleEndianLayout; +using uint32_le_t = internal::LittleEndianLayout; +using uint24_le_t = internal::LittleEndianLayout; +using uint16_le_t = internal::LittleEndianLayout; +using int64_le_t = internal::LittleEndianLayout; +using int32_le_t = internal::LittleEndianLayout; +using int24_le_t = internal::LittleEndianLayout; +using int16_le_t = internal::LittleEndianLayout; + +} // namespace esphome diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index f4e814203f..f071b4a814 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -20,6 +20,14 @@ #define ALWAYS_INLINE __attribute__((always_inline)) #define PACKED __attribute__((packed)) +// Various functions can be constexpr in C++14, but not in C++11 (because their body isn't just a return statement). +// Define a substitute constexpr keyword for those functions, until we can drop C++11 support. +#if __cplusplus >= 201402L +#define constexpr14 constexpr +#else +#define constexpr14 inline // constexpr implies inline +#endif + namespace esphome { /// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes). @@ -277,11 +285,21 @@ To bit_cast(const From &src) { } #endif -// std::byteswap is from C++23 and technically should be a template, but this will do for now. -constexpr uint8_t byteswap(uint8_t n) { return n; } -constexpr uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); } -constexpr uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); } -constexpr uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); } +// std::byteswap from C++23 +template constexpr14 T byteswap(T n) { + T m; + for (size_t i = 0; i < sizeof(T); i++) + reinterpret_cast(&m)[i] = reinterpret_cast(&n)[sizeof(T) - 1 - i]; + return m; +} +template<> constexpr14 uint8_t byteswap(uint8_t n) { return n; } +template<> constexpr14 uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); } +template<> constexpr14 uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); } +template<> constexpr14 uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); } +template<> constexpr14 int8_t byteswap(int8_t n) { return n; } +template<> constexpr14 int16_t byteswap(int16_t n) { return __builtin_bswap16(n); } +template<> constexpr14 int32_t byteswap(int32_t n) { return __builtin_bswap32(n); } +template<> constexpr14 int64_t byteswap(int64_t n) { return __builtin_bswap64(n); } ///@} @@ -311,7 +329,8 @@ constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, ui } /// Encode a value from its constituent bytes (from most to least significant) in an array with length sizeof(T). -template::value, int> = 0> inline T encode_value(const uint8_t *bytes) { +template::value, int> = 0> +constexpr14 T encode_value(const uint8_t *bytes) { T val = 0; for (size_t i = 0; i < sizeof(T); i++) { val <<= 8; @@ -321,12 +340,12 @@ template::value, int> = 0> inline T } /// Encode a value from its constituent bytes (from most to least significant) in an std::array with length sizeof(T). template::value, int> = 0> -inline T encode_value(const std::array bytes) { +constexpr14 T encode_value(const std::array bytes) { return encode_value(bytes.data()); } /// Decode a value into its constituent bytes (from most to least significant). template::value, int> = 0> -inline std::array decode_value(T val) { +constexpr14 std::array decode_value(T val) { std::array ret{}; for (size_t i = sizeof(T); i > 0; i--) { ret[i - 1] = val & 0xFF; @@ -353,7 +372,7 @@ inline uint32_t reverse_bits(uint32_t x) { } /// Convert a value between host byte order and big endian (most significant byte first) order. -template::value, int> = 0> constexpr T convert_big_endian(T val) { +template constexpr14 T convert_big_endian(T val) { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ return byteswap(val); #else @@ -361,6 +380,15 @@ template::value, int> = 0> constexpr #endif } +/// Convert a value between host byte order and little endian (least significant byte first) order. +template constexpr14 T convert_little_endian(T val) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + return val; +#else + return byteswap(val); +#endif +} + ///@} /// @name Strings @@ -512,7 +540,7 @@ template::value, int> = 0> std::stri ///@{ /// Remap a number from one range to another. -template T remap(U value, U min, U max, T min_out, T max_out) { +template constexpr T remap(U value, U min, U max, T min_out, T max_out) { return (value - min) * (max_out - min_out) / (max - min) + min_out; } From c0ff8998122e70b4f0e67df75be351046afaf0ed Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 12 Jan 2022 19:37:56 +1300 Subject: [PATCH 545/549] Generate basic config for esphome-web devices (#3036) --- .../components/dashboard_import/__init__.py | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/esphome/components/dashboard_import/__init__.py b/esphome/components/dashboard_import/__init__.py index 4c47c32ccc..6194a55205 100644 --- a/esphome/components/dashboard_import/__init__.py +++ b/esphome/components/dashboard_import/__init__.py @@ -3,6 +3,7 @@ from pathlib import Path import esphome.codegen as cg import esphome.config_validation as cv from esphome.components.packages import validate_source_shorthand +from esphome.wizard import wizard_file from esphome.yaml_util import dump @@ -48,12 +49,24 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N if p.exists(): raise FileExistsError - config = { - "substitutions": {"name": name}, - "packages": {project_name: import_url}, - "esphome": {"name_add_mac_suffix": False}, - } - p.write_text( - dump(config) + WIFI_CONFIG, - encoding="utf8", - ) + if project_name == "esphome.web": + p.write_text( + wizard_file( + name=name, + platform="ESP32" if "esp32" in import_url else "ESP8266", + board="esp32dev" if "esp32" in import_url else "esp01_1m", + ssid="!secret wifi_ssid", + psk="!secret wifi_password", + ), + encoding="utf8", + ) + else: + config = { + "substitutions": {"name": name}, + "packages": {project_name: import_url}, + "esphome": {"name_add_mac_suffix": False}, + } + p.write_text( + dump(config) + WIFI_CONFIG, + encoding="utf8", + ) From ee58ad1ac04de9a4661fac15da4e1c83c141f9b6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 13 Jan 2022 10:52:57 +1300 Subject: [PATCH 546/549] Bump esphome-dashboard to 20220113.1 (#3038) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f99d779f6c..b21218f511 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.4 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20211211.0 +esphome-dashboard==20220113.1 aioesphomeapi==10.6.0 zeroconf==0.37.0 From 1fe89fb3642193399c8bbf2ee6f02aff9aafa428 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 13 Jan 2022 11:02:08 +1300 Subject: [PATCH 547/549] Bump version to 2022.2.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index fdc880caf3..8dbfd8d1dc 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.1.0-dev" +__version__ = "2022.2.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From cc0d4336211b60ef587f1d82ef13f1b1f1aed0ef Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 12 Jan 2022 22:35:30 -0800 Subject: [PATCH 548/549] Add factory to download name (#3040) --- 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 ca257d93b4..f9ae3a4fc8 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -416,7 +416,7 @@ class DownloadBinaryRequestHandler(BaseHandler): if storage_json is None: self.send_error(404) return - filename = f"{storage_json.name}.bin" + filename = f"{storage_json.name}-factory.bin" path = storage_json.firmware_bin_path.replace( "firmware.bin", "firmware-factory.bin" ) From 2a8668ea60ea51a5906efac7c89e1c1baab1d23a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 12 Jan 2022 23:22:19 -0800 Subject: [PATCH 549/549] Bump dashboard to 20220113.2 (#3041) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b21218f511..05636da805 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.4 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20220113.1 +esphome-dashboard==20220113.2 aioesphomeapi==10.6.0 zeroconf==0.37.0