From 52d2f62a57a9471fd4ccffa9e7e6a1891c1d4890 Mon Sep 17 00:00:00 2001 From: Ohad Lutzky Date: Sun, 16 Jan 2022 22:14:45 +0000 Subject: [PATCH 001/238] Disable caching for binary download (#3054) --- esphome/dashboard/dashboard.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index f9ae3a4fc8..c68d037fe6 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -445,6 +445,7 @@ class DownloadBinaryRequestHandler(BaseHandler): self.set_header("Content-Type", "application/octet-stream") self.set_header("Content-Disposition", f'attachment; filename="{filename}"') + self.set_header("Cache-Control", "no-cache") if not Path(path).is_file(): self.send_error(404) return From f44fca0a4b930f208c04aabba16314d416dfcab8 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 16 Jan 2022 23:15:11 +0100 Subject: [PATCH 002/238] Rename post_build scripts to fix codeowners script (#3057) --- esphome/components/esp32/__init__.py | 2 +- .../components/esp32/{post_build.py => post_build.py.script} | 0 esphome/components/esp8266/__init__.py | 2 +- .../components/esp8266/{post_build.py => post_build.py.script} | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename esphome/components/esp32/{post_build.py => post_build.py.script} (100%) rename esphome/components/esp8266/{post_build.py => post_build.py.script} (100%) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 161803eaf4..8214886f8c 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -417,7 +417,7 @@ def copy_files(): ) dir = os.path.dirname(__file__) - post_build_file = os.path.join(dir, "post_build.py") + post_build_file = os.path.join(dir, "post_build.py.script") 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.script similarity index 100% rename from esphome/components/esp32/post_build.py rename to esphome/components/esp32/post_build.py.script diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 34a4a2fadb..7182042770 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -220,7 +220,7 @@ async def to_code(config): def copy_files(): dir = os.path.dirname(__file__) - post_build_file = os.path.join(dir, "post_build.py") + post_build_file = os.path.join(dir, "post_build.py.script") 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.script similarity index 100% rename from esphome/components/esp8266/post_build.py rename to esphome/components/esp8266/post_build.py.script From 1ea6f957bc8e33ccea31e27b2d251791a810dc94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Jan 2022 23:19:21 +0100 Subject: [PATCH 003/238] Bump pytest-asyncio from 0.16.0 to 0.17.0 (#3047) 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 4d5c40296f..8ec2a8c723 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -7,6 +7,6 @@ pre-commit pytest==6.2.5 pytest-cov==3.0.0 pytest-mock==3.6.1 -pytest-asyncio==0.16.0 +pytest-asyncio==0.17.0 asyncmock==0.4.2 hypothesis==5.49.0 From 89e7448007e6554ff232cc40ed7b3a60c483497a Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 16 Jan 2022 23:40:15 +0100 Subject: [PATCH 004/238] Remove deprecated attribute from virtual entity methods (#3056) --- .../components/binary_sensor/binary_sensor.h | 6 +++-- esphome/components/cover/cover.h | 6 ++++- esphome/components/sensor/sensor.h | 24 ++++++++++++------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index ecf68de74c..591f444387 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -74,8 +74,10 @@ 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") + /** Override this to set the default device class. + * + * @deprecated This method is deprecated, set the property during config validation instead. (2022.1) + */ virtual std::string device_class(); protected: diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 779e4a2a46..1b5d3a8fa1 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -169,7 +169,11 @@ 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") + + /** Override this to set the default device class. + * + * @deprecated This method is deprecated, set the property during config validation instead. (2022.1) + */ virtual std::string device_class(); optional restore_state_(); diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 794aecca95..d31fe9d834 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -150,20 +150,28 @@ class Sensor : public EntityBase { void internal_send_state_to_frontend(float state); protected: - /// Override this to set the default unit of measurement. - ESPDEPRECATED("unit_of_measurement() is deprecated, set property during config validation instead.", "2022.01") + /** Override this to set the default unit of measurement. + * + * @deprecated This method is deprecated, set the property during config validation instead. (2022.1) + */ 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") + /** Override this to set the default accuracy in decimals. + * + * @deprecated This method is deprecated, set the property during config validation instead. (2022.1) + */ 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") + /** Override this to set the default device class. + * + * @deprecated This method is deprecated, set the property during config validation instead. (2022.1) + */ 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") + /** Override this to set the default state class. + * + * @deprecated This method is deprecated, set the property during config validation instead. (2022.1) + */ virtual StateClass state_class(); // NOLINT uint32_t hash_base() override; From 09402fdb2268e352cfb7ed5ce7f854c54383a9b6 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 16 Jan 2022 23:40:27 +0100 Subject: [PATCH 005/238] Fix argument order in gitpod config file (#3058) --- .gitpod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitpod.yml b/.gitpod.yml index 2ff20a0366..e3f786a403 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -3,4 +3,4 @@ ports: onOpen: open-preview tasks: - before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup - command: python -m esphome config dashboard + command: python -m esphome dashboard config From 45ac577c4d5e8e25ddc9d4a6a4ded45539c3cde2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 17 Jan 2022 12:31:44 +1300 Subject: [PATCH 006/238] Add number setting to web_server/rest_api (#3055) --- esphome/components/web_server/web_server.cpp | 45 +++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 4cc77da256..7413af67c4 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -236,7 +236,18 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #ifdef USE_NUMBER for (auto *obj : App.get_numbers()) if (this->include_internal_ || !obj->is_internal()) - write_row(stream, obj, "number", ""); + write_row(stream, obj, "number", "", [](AsyncResponseStream &stream, EntityBase *obj) { + number::Number *number = (number::Number *) obj; + stream.print(R"(traits.get_min_value()); + stream.print(R"(" max=")"); + stream.print(number->traits.get_max_value()); + stream.print(R"(" step=")"); + stream.print(number->traits.get_step()); + stream.print(R"(" value=")"); + stream.print(number->state); + stream.print(R"("/>)"); + }); #endif #ifdef USE_SELECT @@ -652,8 +663,29 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM for (auto *obj : App.get_numbers()) { if (obj->get_object_id() != match.id) continue; - std::string data = this->number_json(obj, obj->state); - request->send(200, "text/json", data.c_str()); + + if (request->method() == HTTP_GET) { + std::string data = this->number_json(obj, obj->state); + request->send(200, "text/json", data.c_str()); + return; + } + + if (match.method != "set") { + request->send(404); + return; + } + + auto call = obj->make_call(); + + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + optional value_f = parse_number(value.c_str()); + if (value_f.has_value()) + call.set_value(*value_f); + } + + this->defer([call]() mutable { call.perform(); }); + request->send(200); return; } request->send(404); @@ -661,9 +693,8 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM std::string WebServer::number_json(number::Number *obj, float value) { return json::build_json([obj, value](JsonObject root) { root["id"] = "number-" + obj->get_object_id(); - char buffer[64]; - snprintf(buffer, sizeof(buffer), "%f", value); - root["state"] = buffer; + std::string state = str_sprintf("%f", value); + root["state"] = state; root["value"] = value; }); } @@ -769,7 +800,7 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { #endif #ifdef USE_NUMBER - if (request->method() == HTTP_GET && match.domain == "number") + if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "number") return true; #endif From 514204f0d4ab015ebfcb519da2b9f2dcf2e01c59 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 17 Jan 2022 11:44:18 -0800 Subject: [PATCH 007/238] bump dashboard to 20220116.0 (#3061) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 05636da805..9add417bdf 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.2 +esphome-dashboard==20220116.0 aioesphomeapi==10.6.0 zeroconf==0.37.0 From cdb4fa24878681533e411cf01962c72ad3e61079 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 17 Jan 2022 21:05:13 +0100 Subject: [PATCH 008/238] [modbus_controller] add missing skip_updates (#3063) --- esphome/components/modbus_controller/switch/__init__.py | 2 ++ esphome/components/modbus_controller/switch/modbus_switch.h | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/modbus_controller/switch/__init__.py b/esphome/components/modbus_controller/switch/__init__.py index 9858d45617..0dfbd83cb8 100644 --- a/esphome/components/modbus_controller/switch/__init__.py +++ b/esphome/components/modbus_controller/switch/__init__.py @@ -18,6 +18,7 @@ from ..const import ( CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, CONF_REGISTER_TYPE, + CONF_SKIP_UPDATES, CONF_USE_WRITE_MULTIPLE, CONF_WRITE_LAMBDA, ) @@ -53,6 +54,7 @@ async def to_code(config): config[CONF_ADDRESS], byte_offset, config[CONF_BITMASK], + config[CONF_SKIP_UPDATES], config[CONF_FORCE_NEW_RANGE], ) await cg.register_component(var, config) diff --git a/esphome/components/modbus_controller/switch/modbus_switch.h b/esphome/components/modbus_controller/switch/modbus_switch.h index 5ac2af01a1..6732c01eef 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.h +++ b/esphome/components/modbus_controller/switch/modbus_switch.h @@ -10,14 +10,14 @@ 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) + uint8_t skip_updates, 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->skip_updates = skip_updates; this->register_count = 1; if (register_type == ModbusRegisterType::HOLDING || register_type == ModbusRegisterType::COIL) { this->start_address += offset; From db21731b149a91d7f9465a85c88db6765f6552fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jan 2022 00:19:08 +0100 Subject: [PATCH 009/238] Bump pytest-asyncio from 0.17.0 to 0.17.2 (#3064) 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 8ec2a8c723..1203858c96 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -7,6 +7,6 @@ pre-commit pytest==6.2.5 pytest-cov==3.0.0 pytest-mock==3.6.1 -pytest-asyncio==0.17.0 +pytest-asyncio==0.17.2 asyncmock==0.4.2 hypothesis==5.49.0 From 737188ae504da2a404f52f3e1a0d81793f87b740 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 18 Jan 2022 02:29:57 +0100 Subject: [PATCH 010/238] Fail hard if no random bytes available for encryption (#3067) --- esphome/components/api/api_frame_helper.cpp | 8 +++++++- esphome/core/helpers.cpp | 7 +++---- esphome/core/helpers.h | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 094dd67e33..d9eadb2aaa 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -1,6 +1,7 @@ #include "api_frame_helper.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "proto.h" #include @@ -721,7 +722,12 @@ 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::random_bytes(reinterpret_cast(output), len); } +void noise_rand_bytes(void *output, size_t len) { + if (!esphome::random_bytes(reinterpret_cast(output), len)) { + ESP_LOGE(TAG, "Failed to acquire random bytes, rebooting!"); + arch_restart(); + } +} } #endif // USE_API_NOISE diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index e15e3a8ea3..5f29abe579 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -287,13 +287,12 @@ uint32_t random_uint32() { #endif } float random_float() { return static_cast(random_uint32()) / static_cast(UINT32_MAX); } -void random_bytes(uint8_t *data, size_t len) { +bool random_bytes(uint8_t *data, size_t len) { #ifdef USE_ESP32 esp_fill_random(data, len); + return true; #elif defined(USE_ESP8266) - if (os_get_random(data, len) != 0) { - ESP_LOGE(TAG, "Failed to generate random bytes!"); - } + return os_get_random(data, len) == 0; #else #error "No random source available for this configuration." #endif diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index f071b4a814..c9a27a2fab 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -311,7 +311,7 @@ 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); +bool random_bytes(uint8_t *data, size_t len); ///@} From cb5f793ede5fc24b3b5cf124072400a4821da667 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 20 Jan 2022 08:33:13 +1300 Subject: [PATCH 011/238] Add *.py.script files to distributions (#3074) --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 0fe80762b3..a3126404f2 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,4 +4,5 @@ include requirements.txt include esphome/dashboard/templates/*.html recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE recursive-include esphome *.cpp *.h *.tcc +recursive-include esphome *.py.script recursive-include esphome LICENSE.txt From 434ab65c160517c95161ced31768b613b53ed645 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Wed, 19 Jan 2022 21:19:24 +0100 Subject: [PATCH 012/238] [modbus_controller] fix incorrect start address for number write (#3073) --- .../components/modbus_controller/number/modbus_number.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index 5e977f5df4..a0e990d272 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -57,9 +57,11 @@ void ModbusNumber::control(float value) { // 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]); + // since offset is in bytes and a register is 16 bits we get the start by adding offset/2 + write_cmd = + ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]); } else { - write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, + write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, this->register_count, data); } // publish new value From 172507acb57cbfb4d6057e905207447cfb93e452 Mon Sep 17 00:00:00 2001 From: cwitting Date: Wed, 19 Jan 2022 22:53:52 +0100 Subject: [PATCH 013/238] Fix calibration parameter for bme680 humidity calculation (#3069) --- esphome/components/bme680/bme680.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/bme680/bme680.cpp b/esphome/components/bme680/bme680.cpp index 99e0b6f860..9856304c81 100644 --- a/esphome/components/bme680/bme680.cpp +++ b/esphome/components/bme680/bme680.cpp @@ -95,7 +95,7 @@ void BME680Component::setup() { this->calibration_.t3 = cal1[3]; this->calibration_.h1 = cal2[2] << 4 | (cal2[1] & 0x0F); - this->calibration_.h2 = cal2[0] << 4 | cal2[1]; + this->calibration_.h2 = cal2[0] << 4 | cal2[1] >> 4; this->calibration_.h3 = cal2[3]; this->calibration_.h4 = cal2[4]; this->calibration_.h5 = cal2[5]; From 1f8a1f0046b67b3067096a24f5d1935816dc1b8a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 20 Jan 2022 15:21:44 +1300 Subject: [PATCH 014/238] Bump improv library version (#3072) --- esphome/components/esp32_improv/__init__.py | 2 +- esphome/components/improv_serial/__init__.py | 2 +- .../improv_serial/improv_serial_component.cpp | 57 +++---------------- platformio.ini | 2 +- 4 files changed, 10 insertions(+), 53 deletions(-) diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 80c53f7c2a..26e586e4ff 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -56,7 +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_library("esphome/Improv", "1.1.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/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py index ed7c382a2f..4499bef1fd 100644 --- a/esphome/components/improv_serial/__init__.py +++ b/esphome/components/improv_serial/__init__.py @@ -30,4 +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") + cg.add_library("esphome/Improv", "1.1.0") diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index b4d1d88370..9f86c06e6f 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -111,58 +111,15 @@ std::vector ImprovSerialComponent::build_version_info_() { 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); + ESP_LOGV(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) - return true; - - if (at == 8 + data_len + 1) { - uint8_t checksum = 0x00; - for (size_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, false); - return this->parse_improv_payload_(command); - } - } - - // If we got here then the command coming is is improv, but not an RPC command - return false; + return improv::parse_improv_serial_byte( + at, byte, raw, [this](improv::ImprovCommand command) -> bool { return this->parse_improv_payload_(command); }, + [this](improv::Error error) -> void { + ESP_LOGW(TAG, "Error decoding Improv payload"); + this->set_error_(error); + }); } bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) { diff --git a/platformio.ini b/platformio.ini index 589624a71d..d45e58953b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -35,7 +35,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 + esphome/Improv@1.1.0 ; improv_serial / esp32_improv bblanchon/ArduinoJson@6.18.5 ; json build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE From 62f9736b1df45a829d5c80f2d986f0deed051325 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 20 Jan 2022 12:03:32 +0100 Subject: [PATCH 015/238] API: Expect a name for connections (#2533) --- esphome/components/api/api.proto | 3 +++ esphome/components/api/api_connection.cpp | 2 ++ esphome/components/api/api_frame_helper.cpp | 14 +++++++++++--- esphome/components/api/api_pb2.cpp | 9 +++++++++ esphome/components/api/api_pb2.h | 1 + 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index dca722dca5..9d43e22497 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -95,6 +95,9 @@ message HelloResponse { // and only exists for debugging/logging purposes. // For example "ESPHome v1.10.0 on ESP8266" string server_info = 3; + + // The name of the server (App.get_name()) + string name = 4; } // Message sent at the beginning of each connection to authenticate the client diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 1f629c2c85..20011c0954 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -766,6 +766,8 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { resp.api_version_major = 1; resp.api_version_minor = 6; resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; + resp.name = App.get_name(); + this->connection_state_ = ConnectionState::CONNECTED; return resp; } diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index d9eadb2aaa..9f0ab82a52 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -3,6 +3,7 @@ #include "esphome/core/log.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" +#include "esphome/core/application.h" #include "proto.h" #include @@ -302,9 +303,16 @@ APIError APINoiseFrameHelper::state_action_() { } if (state_ == State::SERVER_HELLO) { // send server hello - uint8_t msg[1]; - msg[0] = 0x01; // chosen proto - aerr = write_frame_(msg, 1); + std::vector msg; + // chosen proto + msg.push_back(0x01); + + // node name, terminated by null byte + const std::string &name = App.get_name(); + const uint8_t *name_ptr = reinterpret_cast(name.c_str()); + msg.insert(msg.end(), name_ptr, name_ptr + name.size() + 1); + + aerr = write_frame_(msg.data(), msg.size()); if (aerr != APIError::OK) return aerr; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 5b6853c276..4ecd727f29 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -319,6 +319,10 @@ bool HelloResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) this->server_info = value.as_string(); return true; } + case 4: { + this->name = value.as_string(); + return true; + } default: return false; } @@ -327,6 +331,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->api_version_major); buffer.encode_uint32(2, this->api_version_minor); buffer.encode_string(3, this->server_info); + buffer.encode_string(4, this->name); } #ifdef HAS_PROTO_MESSAGE_DUMP void HelloResponse::dump_to(std::string &out) const { @@ -345,6 +350,10 @@ void HelloResponse::dump_to(std::string &out) const { out.append(" server_info: "); out.append("'").append(this->server_info).append("'"); out.append("\n"); + + out.append(" name: "); + out.append("'").append(this->name).append("'"); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index e92b2fa4b6..48ecb5f682 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -147,6 +147,7 @@ class HelloResponse : public ProtoMessage { uint32_t api_version_major{0}; uint32_t api_version_minor{0}; std::string server_info{}; + std::string name{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; From ea11462e1e0f1c81330758e1fc97a4c77657bfd7 Mon Sep 17 00:00:00 2001 From: buxtronix Date: Thu, 20 Jan 2022 23:33:42 +1100 Subject: [PATCH 016/238] AM43: autoload "sensor" to avoid compile errors (#3077) --- esphome/components/am43/cover/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/am43/cover/__init__.py b/esphome/components/am43/cover/__init__.py index 1ab0edbe78..79eeb2eef3 100644 --- a/esphome/components/am43/cover/__init__.py +++ b/esphome/components/am43/cover/__init__.py @@ -5,7 +5,7 @@ from esphome.const import CONF_ID, CONF_PIN CODEOWNERS = ["@buxtronix"] DEPENDENCIES = ["ble_client"] -AUTO_LOAD = ["am43"] +AUTO_LOAD = ["am43", "sensor"] CONF_INVERT_POSITION = "invert_position" From 1c51cac5bac07103df61b0adcf3b579f11a5de4e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 21 Jan 2022 11:09:07 +1300 Subject: [PATCH 017/238] Add initial_run to regular lambda light effect (#3059) --- esphome/components/light/base_light_effects.h | 9 ++++++--- esphome/components/light/effects.py | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/esphome/components/light/base_light_effects.h b/esphome/components/light/base_light_effects.h index 5ab9f66ce4..a6ab299308 100644 --- a/esphome/components/light/base_light_effects.h +++ b/esphome/components/light/base_light_effects.h @@ -102,21 +102,24 @@ class RandomLightEffect : public LightEffect { class LambdaLightEffect : public LightEffect { public: - LambdaLightEffect(const std::string &name, std::function f, uint32_t update_interval) + LambdaLightEffect(const std::string &name, std::function f, uint32_t update_interval) : LightEffect(name), f_(std::move(f)), update_interval_(update_interval) {} + void start() override { this->initial_run_ = true; } void apply() override { const uint32_t now = millis(); if (now - this->last_run_ >= this->update_interval_) { this->last_run_ = now; - this->f_(); + this->f_(this->initial_run_); + this->initial_run_ = false; } } protected: - std::function f_; + std::function f_; uint32_t update_interval_; uint32_t last_run_{0}; + bool initial_run_; }; class AutomationLightEffect : public LightEffect { diff --git a/esphome/components/light/effects.py b/esphome/components/light/effects.py index 4b2209c833..a987e0fc96 100644 --- a/esphome/components/light/effects.py +++ b/esphome/components/light/effects.py @@ -141,7 +141,9 @@ def register_addressable_effect( }, ) async def lambda_effect_to_code(config, effect_id): - lambda_ = await cg.process_lambda(config[CONF_LAMBDA], [], return_type=cg.void) + lambda_ = await cg.process_lambda( + config[CONF_LAMBDA], [(bool, "initial_run")], return_type=cg.void + ) return cg.new_Pvariable( effect_id, config[CONF_NAME], lambda_, config[CONF_UPDATE_INTERVAL] ) From 045952939edd6d04b338b95bc8ab897f4ffa02c8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 21 Jan 2022 11:16:18 +1300 Subject: [PATCH 018/238] Support simple transparent pngs for display (#3035) --- esphome/components/display/display_buffer.cpp | 8 ++++++++ esphome/components/display/display_buffer.h | 7 ++++++- esphome/components/image/__init__.py | 12 ++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index b97fb4ae23..bdf299e8f1 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -233,6 +233,14 @@ void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color colo } } break; + case IMAGE_TYPE_TRANSPARENT_BINARY: + for (int img_x = 0; img_x < image->get_width(); img_x++) { + for (int img_y = 0; img_y < image->get_height(); img_y++) { + if (image->get_pixel(img_x, img_y)) + this->draw_pixel_at(x + img_x, y + img_y, color_on); + } + } + break; } } diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index c803180a2d..b275b43b0e 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -73,7 +73,12 @@ extern const Color COLOR_OFF; /// Turn the pixel ON. extern const Color COLOR_ON; -enum ImageType { IMAGE_TYPE_BINARY = 0, IMAGE_TYPE_GRAYSCALE = 1, IMAGE_TYPE_RGB24 = 2 }; +enum ImageType { + IMAGE_TYPE_BINARY = 0, + IMAGE_TYPE_GRAYSCALE = 1, + IMAGE_TYPE_RGB24 = 2, + IMAGE_TYPE_TRANSPARENT_BINARY = 3, +}; enum DisplayRotation { DISPLAY_ROTATION_0_DEGREES = 0, diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index a721263dff..70d77dfd14 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -24,6 +24,7 @@ IMAGE_TYPE = { "BINARY": ImageType.IMAGE_TYPE_BINARY, "GRAYSCALE": ImageType.IMAGE_TYPE_GRAYSCALE, "RGB24": ImageType.IMAGE_TYPE_RGB24, + "TRANSPARENT_BINARY": ImageType.IMAGE_TYPE_TRANSPARENT_BINARY, } Image_ = display.display_ns.class_("Image") @@ -99,6 +100,17 @@ async def to_code(config): pos = x + y * width8 data[pos // 8] |= 0x80 >> (pos % 8) + elif config[CONF_TYPE] == "TRANSPARENT_BINARY": + image = image.convert("RGBA") + width8 = ((width + 7) // 8) * 8 + data = [0 for _ in range(height * width8 // 8)] + for y in range(height): + for x in range(width): + if not image.getpixel((x, y))[3]: + continue + pos = x + y * width8 + data[pos // 8] |= 0x80 >> (pos % 8) + rhs = [HexInt(x) for x in data] prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) cg.new_Pvariable( From ec769ccf728725b91644d9c787229c05dfcbd45b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Jan 2022 00:09:12 +0100 Subject: [PATCH 019/238] Bump aioesphomeapi from 10.6.0 to 10.8.0 (#3081) 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 9add417bdf..2e21f66981 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.4 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 esphome-dashboard==20220116.0 -aioesphomeapi==10.6.0 +aioesphomeapi==10.8.0 zeroconf==0.37.0 # esp-idf requires this, but doesn't bundle it by default From 6f8c7d9ec42e1029f24d5c1e0049acb7db795176 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 21 Jan 2022 15:45:49 +1300 Subject: [PATCH 020/238] Add ektf2232 touchscreen support (#3027) --- CODEOWNERS | 1 + esphome/components/ektf2232/__init__.py | 80 +++++++++ .../ektf2232/binary_sensor/__init__.py | 59 ++++++ .../binary_sensor/ektf2232_binary_sensor.cpp | 19 ++ .../binary_sensor/ektf2232_binary_sensor.h | 27 +++ esphome/components/ektf2232/ektf2232.cpp | 168 ++++++++++++++++++ esphome/components/ektf2232/ektf2232.h | 76 ++++++++ tests/test5.yaml | 9 + 8 files changed, 439 insertions(+) create mode 100644 esphome/components/ektf2232/__init__.py create mode 100644 esphome/components/ektf2232/binary_sensor/__init__.py create mode 100644 esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.cpp create mode 100644 esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.h create mode 100644 esphome/components/ektf2232/ektf2232.cpp create mode 100644 esphome/components/ektf2232/ektf2232.h diff --git a/CODEOWNERS b/CODEOWNERS index 351d9f5fc9..bfa4a7e59f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -54,6 +54,7 @@ esphome/components/dfplayer/* @glmnet esphome/components/dht/* @OttoWinter esphome/components/ds1307/* @badbadc0ffee esphome/components/dsmr/* @glmnet @zuidwijk +esphome/components/ektf2232/* @jesserockz esphome/components/esp32/* @esphome/core esphome/components/esp32_ble/* @jesserockz esphome/components/esp32_ble_server/* @jesserockz diff --git a/esphome/components/ektf2232/__init__.py b/esphome/components/ektf2232/__init__.py new file mode 100644 index 0000000000..0427bda4fb --- /dev/null +++ b/esphome/components/ektf2232/__init__.py @@ -0,0 +1,80 @@ +import esphome.codegen as cg +import esphome.config_validation as cv + +from esphome import pins, automation +from esphome.components import i2c +from esphome.const import CONF_HEIGHT, CONF_ID, CONF_ROTATION, CONF_WIDTH + +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["i2c"] + +ektf2232_ns = cg.esphome_ns.namespace("ektf2232") +EKTF2232Touchscreen = ektf2232_ns.class_( + "EKTF2232Touchscreen", cg.Component, i2c.I2CDevice +) +TouchPoint = ektf2232_ns.struct("TouchPoint") +TouchListener = ektf2232_ns.class_("TouchListener") + +EKTF2232Rotation = ektf2232_ns.enum("EKTF2232Rotation") + +CONF_EKTF2232_ID = "ektf2232_id" +CONF_INTERRUPT_PIN = "interrupt_pin" +CONF_RTS_PIN = "rts_pin" +CONF_ON_TOUCH = "on_touch" + +ROTATIONS = { + 0: EKTF2232Rotation.ROTATE_0_DEGREES, + 90: EKTF2232Rotation.ROTATE_90_DEGREES, + 180: EKTF2232Rotation.ROTATE_180_DEGREES, + 270: EKTF2232Rotation.ROTATE_270_DEGREES, +} + + +def validate_rotation(value): + value = cv.string(value) + if value.endswith("°"): + value = value[:-1] + return cv.enum(ROTATIONS, int=True)(value) + + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(EKTF2232Touchscreen), + cv.Required(CONF_INTERRUPT_PIN): cv.All( + pins.internal_gpio_input_pin_schema + ), + cv.Required(CONF_RTS_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_HEIGHT, default=758): cv.int_, + cv.Optional(CONF_WIDTH, default=1024): cv.int_, + cv.Optional(CONF_ROTATION, default=0): validate_rotation, + cv.Optional(CONF_ON_TOUCH): automation.validate_automation(single=True), + } + ) + .extend(i2c.i2c_device_schema(0x15)) + .extend(cv.COMPONENT_SCHEMA) +) + + +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) + + interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) + cg.add(var.set_interrupt_pin(interrupt_pin)) + rts_pin = await cg.gpio_pin_expression(config[CONF_RTS_PIN]) + cg.add(var.set_rts_pin(rts_pin)) + + cg.add( + var.set_display_details( + config[CONF_WIDTH], + config[CONF_HEIGHT], + config[CONF_ROTATION], + ) + ) + + if CONF_ON_TOUCH in config: + await automation.build_automation( + var.get_touch_trigger(), [(TouchPoint, "touch")], config[CONF_ON_TOUCH] + ) diff --git a/esphome/components/ektf2232/binary_sensor/__init__.py b/esphome/components/ektf2232/binary_sensor/__init__.py new file mode 100644 index 0000000000..349c45b31c --- /dev/null +++ b/esphome/components/ektf2232/binary_sensor/__init__.py @@ -0,0 +1,59 @@ +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 ektf2232_ns, CONF_EKTF2232_ID, EKTF2232Touchscreen, TouchListener + +DEPENDENCIES = ["ektf2232"] + +EKTF2232Button = ektf2232_ns.class_( + "EKTF2232Button", binary_sensor.BinarySensor, TouchListener +) + +CONF_X_MIN = "x_min" +CONF_X_MAX = "x_max" +CONF_Y_MIN = "y_min" +CONF_Y_MAX = "y_max" + + +def validate_coords(config): + if ( + config[CONF_X_MAX] < config[CONF_X_MIN] + or config[CONF_Y_MAX] < config[CONF_Y_MIN] + ): + raise cv.Invalid( + f"{CONF_X_MAX} is less than {CONF_X_MIN} or {CONF_Y_MAX} is less than {CONF_Y_MIN}" + ) + return config + + +CONFIG_SCHEMA = cv.All( + binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(EKTF2232Button), + cv.GenerateID(CONF_EKTF2232_ID): cv.use_id(EKTF2232Touchscreen), + cv.Required(CONF_X_MIN): cv.int_range(min=0, max=2000), + cv.Required(CONF_X_MAX): cv.int_range(min=0, max=2000), + cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=2000), + cv.Required(CONF_Y_MAX): cv.int_range(min=0, max=2000), + } + ), + validate_coords, +) + + +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_EKTF2232_ID]) + cg.add( + var.set_area( + config[CONF_X_MIN], + config[CONF_X_MAX], + config[CONF_Y_MIN], + config[CONF_Y_MAX], + ) + ) + cg.add(hub.register_listener(var)) diff --git a/esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.cpp b/esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.cpp new file mode 100644 index 0000000000..a6fdf8b76c --- /dev/null +++ b/esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.cpp @@ -0,0 +1,19 @@ +#include "ektf2232_binary_sensor.h" + +namespace esphome { +namespace ektf2232 { + +void EKTF2232Button::touch(TouchPoint tp) { + bool touched = (tp.x >= this->x_min_ && tp.x <= this->x_max_ && tp.y >= this->y_min_ && tp.y <= this->y_max_); + + if (touched) { + this->publish_state(true); + } else { + release(); + } +} + +void EKTF2232Button::release() { this->publish_state(false); } + +} // namespace ektf2232 +} // namespace esphome diff --git a/esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.h b/esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.h new file mode 100644 index 0000000000..170dfcdebb --- /dev/null +++ b/esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.h @@ -0,0 +1,27 @@ +#pragma once + +#include "../ektf2232.h" +#include "esphome/components/binary_sensor/binary_sensor.h" + +namespace esphome { +namespace ektf2232 { + +class EKTF2232Button : public binary_sensor::BinarySensor, public TouchListener { + public: + /// Set the touch screen area where the button will detect the touch. + void set_area(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) { + this->x_min_ = x_min; + this->x_max_ = x_max; + this->y_min_ = y_min; + this->y_max_ = y_max; + } + + void touch(TouchPoint tp) override; + void release() override; + + protected: + int16_t x_min_, x_max_, y_min_, y_max_; +}; + +} // namespace ektf2232 +} // namespace esphome diff --git a/esphome/components/ektf2232/ektf2232.cpp b/esphome/components/ektf2232/ektf2232.cpp new file mode 100644 index 0000000000..da16dc3cfe --- /dev/null +++ b/esphome/components/ektf2232/ektf2232.cpp @@ -0,0 +1,168 @@ +#include "ektf2232.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ektf2232 { + +static const char *const TAG = "ektf2232"; + +static const uint8_t SOFT_RESET_CMD[4] = {0x77, 0x77, 0x77, 0x77}; +static const uint8_t HELLO[4] = {0x55, 0x55, 0x55, 0x55}; +static const uint8_t GET_X_RES[4] = {0x53, 0x60, 0x00, 0x00}; +static const uint8_t GET_Y_RES[4] = {0x53, 0x63, 0x00, 0x00}; +static const uint8_t GET_POWER_STATE_CMD[4] = {0x53, 0x50, 0x00, 0x01}; + +void EKTF2232TouchscreenStore::gpio_intr(EKTF2232TouchscreenStore *store) { store->touch = true; } + +void EKTF2232Touchscreen::setup() { + ESP_LOGCONFIG(TAG, "Setting up EKT2232 Touchscreen..."); + this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + this->interrupt_pin_->setup(); + + this->store_.pin = this->interrupt_pin_->to_isr(); + this->interrupt_pin_->attach_interrupt(EKTF2232TouchscreenStore::gpio_intr, &this->store_, + gpio::INTERRUPT_FALLING_EDGE); + + this->rts_pin_->setup(); + + this->hard_reset_(); + if (!this->soft_reset_()) { + ESP_LOGE(TAG, "Failed to soft reset EKT2232!"); + this->interrupt_pin_->detach_interrupt(); + this->mark_failed(); + return; + } + + // Get touch resolution + uint8_t received[4]; + this->write(GET_X_RES, 4); + if (this->read(received, 4)) { + ESP_LOGE(TAG, "Failed to read X resolution!"); + this->interrupt_pin_->detach_interrupt(); + this->mark_failed(); + return; + } + this->x_resolution_ = ((received[2])) | ((received[3] & 0xf0) << 4); + + this->write(GET_Y_RES, 4); + if (this->read(received, 4)) { + ESP_LOGE(TAG, "Failed to read Y resolution!"); + this->interrupt_pin_->detach_interrupt(); + this->mark_failed(); + return; + } + this->y_resolution_ = ((received[2])) | ((received[3] & 0xf0) << 4); + this->store_.touch = false; + + this->set_power_state(true); +} + +void EKTF2232Touchscreen::loop() { + if (!this->store_.touch) + return; + this->store_.touch = false; + + uint8_t touch_count = 0; + std::vector touches; + + uint8_t raw[8]; + this->read(raw, 8); + for (int i = 0; i < 8; i++) + if (raw[7] & (1 << i)) + touch_count++; + + if (touch_count == 0) { + for (auto *listener : this->touch_listeners_) + listener->release(); + return; + } + + touch_count = std::min(touch_count, 2); + + ESP_LOGV(TAG, "Touch count: %d", touch_count); + + for (int i = 0; i < touch_count; i++) { + uint8_t *d = raw + 1 + (i * 3); + uint32_t raw_x = (d[0] & 0xF0) << 4 | d[1]; + uint32_t raw_y = (d[0] & 0x0F) << 8 | d[2]; + + raw_x = raw_x * this->display_height_ - 1; + raw_y = raw_y * this->display_width_ - 1; + + TouchPoint tp; + switch (this->rotation_) { + case ROTATE_0_DEGREES: + tp.y = raw_x / this->x_resolution_; + tp.x = this->display_width_ - 1 - (raw_y / this->y_resolution_); + break; + case ROTATE_90_DEGREES: + tp.x = raw_x / this->x_resolution_; + tp.y = raw_y / this->y_resolution_; + break; + case ROTATE_180_DEGREES: + tp.y = this->display_height_ - 1 - (raw_x / this->x_resolution_); + tp.x = raw_y / this->y_resolution_; + break; + case ROTATE_270_DEGREES: + tp.x = this->display_height_ - 1 - (raw_x / this->x_resolution_); + tp.y = this->display_width_ - 1 - (raw_y / this->y_resolution_); + break; + } + + ESP_LOGV(TAG, "Touch %d: (x=%d, y=%d)", i, tp.x, tp.y); + this->touch_trigger_->trigger(tp); + for (auto *listener : this->touch_listeners_) + listener->touch(tp); + } +} + +void EKTF2232Touchscreen::set_power_state(bool enable) { + uint8_t data[] = {0x54, 0x50, 0x00, 0x01}; + data[1] |= (enable << 3); + this->write(data, 4); +} + +bool EKTF2232Touchscreen::get_power_state() { + uint8_t received[4]; + this->write(GET_POWER_STATE_CMD, 4); + this->store_.touch = false; + this->read(received, 4); + return (received[1] >> 3) & 1; +} + +void EKTF2232Touchscreen::hard_reset_() { + this->rts_pin_->digital_write(false); + delay(15); + this->rts_pin_->digital_write(true); + delay(15); +} + +bool EKTF2232Touchscreen::soft_reset_() { + auto err = this->write(SOFT_RESET_CMD, 4); + if (err != i2c::ERROR_OK) + return false; + + uint8_t received[4]; + uint16_t timeout = 1000; + while (!this->store_.touch && timeout > 0) { + delay(1); + timeout--; + } + if (timeout > 0) + this->store_.touch = true; + this->read(received, 4); + this->store_.touch = false; + + return !memcmp(received, HELLO, 4); +} + +void EKTF2232Touchscreen::dump_config() { + ESP_LOGCONFIG(TAG, "EKT2232 Touchscreen:"); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); + LOG_PIN(" RTS Pin: ", this->rts_pin_); +} + +} // namespace ektf2232 +} // namespace esphome diff --git a/esphome/components/ektf2232/ektf2232.h b/esphome/components/ektf2232/ektf2232.h new file mode 100644 index 0000000000..0d6fb7a699 --- /dev/null +++ b/esphome/components/ektf2232/ektf2232.h @@ -0,0 +1,76 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace ektf2232 { + +struct EKTF2232TouchscreenStore { + volatile bool touch; + ISRInternalGPIOPin pin; + + static void gpio_intr(EKTF2232TouchscreenStore *store); +}; + +struct TouchPoint { + uint16_t x; + uint16_t y; +}; + +class TouchListener { + public: + virtual void touch(TouchPoint tp) = 0; + virtual void release(); +}; + +enum EKTF2232Rotation : uint8_t { + ROTATE_0_DEGREES = 0, + ROTATE_90_DEGREES, + ROTATE_180_DEGREES, + ROTATE_270_DEGREES, +}; + +class EKTF2232Touchscreen : public Component, public i2c::I2CDevice { + public: + void setup() override; + void loop() override; + void dump_config() override; + + void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } + void set_rts_pin(GPIOPin *pin) { this->rts_pin_ = pin; } + + void set_display_details(uint16_t width, uint16_t height, EKTF2232Rotation rotation) { + this->display_width_ = width; + this->display_height_ = height; + this->rotation_ = rotation; + } + + void set_power_state(bool enable); + bool get_power_state(); + + Trigger *get_touch_trigger() const { return this->touch_trigger_; } + + void register_listener(TouchListener *listener) { this->touch_listeners_.push_back(listener); } + + protected: + void hard_reset_(); + bool soft_reset_(); + + InternalGPIOPin *interrupt_pin_; + GPIOPin *rts_pin_; + EKTF2232TouchscreenStore store_; + uint16_t x_resolution_; + uint16_t y_resolution_; + + uint16_t display_width_; + uint16_t display_height_; + EKTF2232Rotation rotation_; + Trigger *touch_trigger_ = new Trigger(); + std::vector touch_listeners_; +}; + +} // namespace ektf2232 +} // namespace esphome diff --git a/tests/test5.yaml b/tests/test5.yaml index d6acbf1e65..b13f20a9b2 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -199,3 +199,12 @@ script: count: 5 then: - logger.log: "looping!" + +ektf2232: + interrupt_pin: GPIO36 + rts_pin: GPIO5 + rotation: 90 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: ["touch.x", "touch.y"] From ef88f9923f348da34bb6afb74706a115be3dc345 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Fri, 21 Jan 2022 10:08:54 +0100 Subject: [PATCH 021/238] Implement IPv6 sockets for lwIP (#3015) Co-authored-by: Oxan van Leeuwen --- .../components/socket/lwip_raw_tcp_impl.cpp | 113 +++++++++++------- 1 file changed, 73 insertions(+), 40 deletions(-) diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index d57413c739..f5bb57bb93 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -28,7 +28,7 @@ static const char *const TAG = "socket.lwip"; class LWIPRawImpl : public Socket { public: - LWIPRawImpl(struct tcp_pcb *pcb) : pcb_(pcb) {} + LWIPRawImpl(sa_family_t family, struct tcp_pcb *pcb) : pcb_(pcb), family_(family) {} ~LWIPRawImpl() override { if (pcb_ != nullptr) { LWIP_LOG("tcp_abort(%p)", pcb_); @@ -73,10 +73,9 @@ class LWIPRawImpl : public Socket { } ip_addr_t ip; in_port_t port; - auto family = name->sa_family; #if LWIP_IPV6 - if (family == AF_INET) { - if (addrlen < sizeof(sockaddr_in6)) { + if (family_ == AF_INET) { + if (addrlen < sizeof(sockaddr_in)) { errno = EINVAL; return -1; } @@ -84,30 +83,31 @@ class LWIPRawImpl : public Socket { port = ntohs(addr4->sin_port); ip.type = IPADDR_TYPE_V4; ip.u_addr.ip4.addr = addr4->sin_addr.s_addr; - - } else if (family == AF_INET6) { - if (addrlen < sizeof(sockaddr_in)) { + LWIP_LOG("tcp_bind(%p ip=%s port=%u)", pcb_, ip4addr_ntoa(&ip.u_addr.ip4), port); + } else if (family_ == AF_INET6) { + if (addrlen < sizeof(sockaddr_in6)) { errno = EINVAL; return -1; } auto *addr6 = reinterpret_cast(name); port = ntohs(addr6->sin6_port); - ip.type = IPADDR_TYPE_V6; + ip.type = IPADDR_TYPE_ANY; memcpy(&ip.u_addr.ip6.addr, &addr6->sin6_addr.un.u8_addr, 16); + LWIP_LOG("tcp_bind(%p ip=%s port=%u)", pcb_, ip6addr_ntoa(&ip.u_addr.ip6), port); } else { errno = EINVAL; return -1; } #else - if (family != AF_INET) { + if (family_ != AF_INET) { errno = EINVAL; return -1; } auto *addr4 = reinterpret_cast(name); port = ntohs(addr4->sin_port); ip.addr = addr4->sin_addr.s_addr; -#endif LWIP_LOG("tcp_bind(%p ip=%u port=%u)", pcb_, ip.addr, port); +#endif err_t err = tcp_bind(pcb_, &ip, port); if (err == ERR_USE) { LWIP_LOG(" -> err ERR_USE"); @@ -178,26 +178,22 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return -1; } - if (*addrlen < sizeof(struct sockaddr_in)) { - errno = EINVAL; - return -1; - } - struct sockaddr_in *addr = reinterpret_cast(name); - addr->sin_family = AF_INET; - *addrlen = addr->sin_len = sizeof(struct sockaddr_in); - addr->sin_port = pcb_->remote_port; - addr->sin_addr.s_addr = pcb_->remote_ip.addr; - return 0; + return this->ip2sockaddr_(&pcb_->local_ip, pcb_->local_port, name, addrlen); } std::string getpeername() override { if (pcb_ == nullptr) { errno = ECONNRESET; return ""; } - char buffer[24]; - uint32_t ip4 = pcb_->remote_ip.addr; - snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 0) & 0xFF, (ip4 >> 8) & 0xFF, (ip4 >> 16) & 0xFF, - (ip4 >> 24) & 0xFF); + char buffer[50] = {}; + if (IP_IS_V4_VAL(pcb_->remote_ip)) { + inet_ntoa_r(pcb_->remote_ip, buffer, sizeof(buffer)); + } +#if LWIP_IPV6 + else if (IP_IS_V6_VAL(pcb_->remote_ip)) { + inet6_ntoa_r(pcb_->remote_ip, buffer, sizeof(buffer)); + } +#endif return std::string(buffer); } int getsockname(struct sockaddr *name, socklen_t *addrlen) override { @@ -209,26 +205,22 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return -1; } - if (*addrlen < sizeof(struct sockaddr_in)) { - errno = EINVAL; - return -1; - } - struct sockaddr_in *addr = reinterpret_cast(name); - addr->sin_family = AF_INET; - *addrlen = addr->sin_len = sizeof(struct sockaddr_in); - addr->sin_port = pcb_->local_port; - addr->sin_addr.s_addr = pcb_->local_ip.addr; - return 0; + return this->ip2sockaddr_(&pcb_->local_ip, pcb_->local_port, name, addrlen); } std::string getsockname() override { if (pcb_ == nullptr) { errno = ECONNRESET; return ""; } - char buffer[24]; - uint32_t ip4 = pcb_->local_ip.addr; - snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 0) & 0xFF, (ip4 >> 8) & 0xFF, (ip4 >> 16) & 0xFF, - (ip4 >> 24) & 0xFF); + char buffer[50] = {}; + if (IP_IS_V4_VAL(pcb_->local_ip)) { + inet_ntoa_r(pcb_->local_ip, buffer, sizeof(buffer)); + } +#if LWIP_IPV6 + else if (IP_IS_V6_VAL(pcb_->local_ip)) { + inet6_ntoa_r(pcb_->local_ip, buffer, sizeof(buffer)); + } +#endif return std::string(buffer); } int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { @@ -497,7 +489,7 @@ class LWIPRawImpl : public Socket { // nothing to do here, we just don't push it to the queue return ERR_OK; } - auto sock = make_unique(newpcb); + auto sock = make_unique(family_, newpcb); sock->init(); accepted_sockets_.push(std::move(sock)); return ERR_OK; @@ -549,6 +541,46 @@ class LWIPRawImpl : public Socket { } protected: + int ip2sockaddr_(ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen) { + if (family_ == AF_INET) { + if (*addrlen < sizeof(struct sockaddr_in)) { + errno = EINVAL; + return -1; + } + + struct sockaddr_in *addr = reinterpret_cast(name); + addr->sin_family = AF_INET; + *addrlen = addr->sin_len = sizeof(struct sockaddr_in); + addr->sin_port = port; + inet_addr_from_ip4addr(&addr->sin_addr, ip_2_ip4(ip)); + return 0; + } +#if LWIP_IPV6 + else if (family_ == AF_INET6) { + if (*addrlen < sizeof(struct sockaddr_in6)) { + errno = EINVAL; + return -1; + } + + struct sockaddr_in6 *addr = reinterpret_cast(name); + addr->sin6_family = AF_INET6; + *addrlen = addr->sin6_len = sizeof(struct sockaddr_in6); + addr->sin6_port = port; + + // AF_INET6 sockets are bound to IPv4 as well, so we may encounter IPv4 addresses that must be converted to IPv6. + if (IP_IS_V4(ip)) { + ip_addr_t mapped; + ip4_2_ipv4_mapped_ipv6(ip_2_ip6(&mapped), ip_2_ip4(ip)); + inet6_addr_from_ip6addr(&addr->sin6_addr, ip_2_ip6(&mapped)); + } else { + inet6_addr_from_ip6addr(&addr->sin6_addr, ip_2_ip6(ip)); + } + return 0; + } +#endif + return -1; + } + struct tcp_pcb *pcb_; std::queue> accepted_sockets_; bool rx_closed_ = false; @@ -557,13 +589,14 @@ class LWIPRawImpl : public Socket { // don't use lwip nodelay flag, it sometimes causes reconnect // instead use it for determining whether to call lwip_output bool nodelay_ = false; + sa_family_t family_ = 0; }; std::unique_ptr socket(int domain, int type, int protocol) { auto *pcb = tcp_new(); if (pcb == nullptr) return nullptr; - auto *sock = new LWIPRawImpl(pcb); // NOLINT(cppcoreguidelines-owning-memory) + auto *sock = new LWIPRawImpl((sa_family_t) domain, pcb); // NOLINT(cppcoreguidelines-owning-memory) sock->init(); return std::unique_ptr{sock}; } From 338ada5c9f61c5f3b9fcf0c7737ba8710ad3a4b0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 22 Jan 2022 19:33:15 +1300 Subject: [PATCH 022/238] Allow multiple configs for cd74hc4067 (#3085) --- esphome/components/cd74hc4067/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/cd74hc4067/__init__.py b/esphome/components/cd74hc4067/__init__.py index f8efdf4b2a..4fb15d1bf3 100644 --- a/esphome/components/cd74hc4067/__init__.py +++ b/esphome/components/cd74hc4067/__init__.py @@ -6,8 +6,9 @@ from esphome.const import ( CONF_ID, ) -CODEOWNERS = ["@asoehlke"] AUTO_LOAD = ["sensor", "voltage_sampler"] +CODEOWNERS = ["@asoehlke"] +MULTI_CONF = True cd74hc4067_ns = cg.esphome_ns.namespace("cd74hc4067") From f0b183a55276c512af1d06c2d41db04afc12b94f Mon Sep 17 00:00:00 2001 From: William Charlton <38622599+willwill2will54@users.noreply.github.com> Date: Sat, 22 Jan 2022 11:13:46 +0000 Subject: [PATCH 023/238] Wake-on-LAN button (#3030) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: William Charlton --- CODEOWNERS | 1 + esphome/components/wake_on_lan/__init__.py | 1 + esphome/components/wake_on_lan/button.py | 32 ++++++++++ .../components/wake_on_lan/wake_on_lan.cpp | 58 +++++++++++++++++++ esphome/components/wake_on_lan/wake_on_lan.h | 27 +++++++++ tests/component_tests/button/test_button.py | 46 +++++++++++++++ tests/component_tests/button/test_button.yaml | 21 +++++++ tests/test3.yaml | 6 ++ 8 files changed, 192 insertions(+) create mode 100644 esphome/components/wake_on_lan/__init__.py create mode 100644 esphome/components/wake_on_lan/button.py create mode 100644 esphome/components/wake_on_lan/wake_on_lan.cpp create mode 100644 esphome/components/wake_on_lan/wake_on_lan.h create mode 100644 tests/component_tests/button/test_button.py create mode 100644 tests/component_tests/button/test_button.yaml diff --git a/CODEOWNERS b/CODEOWNERS index bfa4a7e59f..56899be01a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -198,6 +198,7 @@ esphome/components/tuya/text_sensor/* @dentra esphome/components/uart/* @esphome/core esphome/components/ultrasonic/* @OttoWinter esphome/components/version/* @esphome/core +esphome/components/wake_on_lan/* @willwill2will54 esphome/components/web_server_base/* @OttoWinter esphome/components/whirlpool/* @glmnet esphome/components/xiaomi_lywsd03mmc/* @ahpohl diff --git a/esphome/components/wake_on_lan/__init__.py b/esphome/components/wake_on_lan/__init__.py new file mode 100644 index 0000000000..3548fb02f4 --- /dev/null +++ b/esphome/components/wake_on_lan/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@willwill2will54"] diff --git a/esphome/components/wake_on_lan/button.py b/esphome/components/wake_on_lan/button.py new file mode 100644 index 0000000000..2710eb3df9 --- /dev/null +++ b/esphome/components/wake_on_lan/button.py @@ -0,0 +1,32 @@ +import esphome.codegen as cg +from esphome.components import button +import esphome.config_validation as cv +from esphome.const import CONF_ID + +CONF_TARGET_MAC_ADDRESS = "target_mac_address" + +wake_on_lan_ns = cg.esphome_ns.namespace("wake_on_lan") + +WakeOnLanButton = wake_on_lan_ns.class_("WakeOnLanButton", button.Button, cg.Component) + +DEPENDENCIES = ["network"] + +CONFIG_SCHEMA = cv.All( + button.BUTTON_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( + cv.Schema( + { + cv.Required(CONF_TARGET_MAC_ADDRESS): cv.mac_address, + cv.GenerateID(): cv.declare_id(WakeOnLanButton), + } + ), + ), + cv.only_with_arduino, +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + + yield cg.add(var.set_macaddr(*config[CONF_TARGET_MAC_ADDRESS].parts)) + yield cg.register_component(var, config) + yield button.register_button(var, config) diff --git a/esphome/components/wake_on_lan/wake_on_lan.cpp b/esphome/components/wake_on_lan/wake_on_lan.cpp new file mode 100644 index 0000000000..893aa75895 --- /dev/null +++ b/esphome/components/wake_on_lan/wake_on_lan.cpp @@ -0,0 +1,58 @@ +#ifdef USE_ARDUINO + +#include "wake_on_lan.h" +#include "esphome/core/log.h" +#include "esphome/components/network/ip_address.h" +#include "esphome/components/network/util.h" + +namespace esphome { +namespace wake_on_lan { + +static const char *const TAG = "wake_on_lan.button"; +static const uint8_t PREFIX[6] = {255, 255, 255, 255, 255, 255}; + +void WakeOnLanButton::set_macaddr(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f) { + macaddr_[0] = a; + macaddr_[1] = b; + macaddr_[2] = c; + macaddr_[3] = d; + macaddr_[4] = e; + macaddr_[5] = f; +} + +void WakeOnLanButton::dump_config() { + LOG_BUTTON("", "Wake-on-LAN Button", this); + ESP_LOGCONFIG(TAG, " Target MAC address: %02X:%02X:%02X:%02X:%02X:%02X", macaddr_[0], macaddr_[1], macaddr_[2], + macaddr_[3], macaddr_[4], macaddr_[5]); +} + +void WakeOnLanButton::press_action() { + ESP_LOGI(TAG, "Sending Wake-on-LAN Packet..."); + bool begin_status = false; + bool end_status = false; + uint32_t interface = esphome::network::get_ip_address(); + IPAddress interface_ip = IPAddress(interface); + IPAddress broadcast = IPAddress(255, 255, 255, 255); +#ifdef USE_ESP8266 + begin_status = this->udp_client_.beginPacketMulticast(broadcast, 9, interface_ip, 128); +#endif +#ifdef USE_ESP32 + begin_status = this->udp_client_.beginPacket(broadcast, 9); +#endif + + if (begin_status) { + this->udp_client_.write(PREFIX, 6); + for (size_t i = 0; i < 16; i++) { + this->udp_client_.write(macaddr_, 6); + } + end_status = this->udp_client_.endPacket(); + } + if (!begin_status || end_status) { + ESP_LOGE(TAG, "Sending Wake-on-LAN Packet Failed!"); + } +} + +} // namespace wake_on_lan +} // namespace esphome + +#endif diff --git a/esphome/components/wake_on_lan/wake_on_lan.h b/esphome/components/wake_on_lan/wake_on_lan.h new file mode 100644 index 0000000000..72f900e3fa --- /dev/null +++ b/esphome/components/wake_on_lan/wake_on_lan.h @@ -0,0 +1,27 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "esphome/components/button/button.h" +#include "esphome/core/component.h" +#include "WiFiUdp.h" + +namespace esphome { +namespace wake_on_lan { + +class WakeOnLanButton : public button::Button, public Component { + public: + void set_macaddr(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f); + + void dump_config() override; + + protected: + WiFiUDP udp_client_{}; + void press_action() override; + uint8_t macaddr_[6]; +}; + +} // namespace wake_on_lan +} // namespace esphome + +#endif diff --git a/tests/component_tests/button/test_button.py b/tests/component_tests/button/test_button.py new file mode 100644 index 0000000000..c84715dcd8 --- /dev/null +++ b/tests/component_tests/button/test_button.py @@ -0,0 +1,46 @@ +"""Tests for the button component""" + + +def test_button_is_setup(generate_main): + """ + When the button is set in the yaml file if should be registered in main + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/button/test_button.yaml") + + # Then + assert "new wake_on_lan::WakeOnLanButton();" in main_cpp + assert "App.register_button" in main_cpp + assert "App.register_component" in main_cpp + + +def test_button_sets_mandatory_fields(generate_main): + """ + When the mandatory fields are set in the yaml, they should be set in main + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/button/test_button.yaml") + + # Then + assert 'wol_1->set_name("wol_test_1");' in main_cpp + assert "wol_2->set_macaddr(18, 52, 86, 120, 144, 171);" in main_cpp + + +def test_button_config_value_internal_set(generate_main): + """ + Test that the "internal" config value is correctly set + """ + # Given + + # When + main_cpp = generate_main( + "tests/component_tests/button/test_button.yaml" + ) + + # Then + assert "wol_1->set_internal(true);" in main_cpp + assert "wol_2->set_internal(false);" in main_cpp diff --git a/tests/component_tests/button/test_button.yaml b/tests/component_tests/button/test_button.yaml new file mode 100644 index 0000000000..3eac129e8c --- /dev/null +++ b/tests/component_tests/button/test_button.yaml @@ -0,0 +1,21 @@ +esphome: + name: test + platform: ESP8266 + board: d1_mini_lite + +wifi: + ssid: SomeNetwork + password: SomePassword + +button: + - platform: wake_on_lan + target_mac_address: 12:34:56:78:90:ab + name: wol_test_1 + id: wol_1 + internal: true + - platform: wake_on_lan + target_mac_address: 12:34:56:78:90:ab + name: wol_test_2 + id: wol_2 + internal: false + diff --git a/tests/test3.yaml b/tests/test3.yaml index 607d985704..d5df2ac3f2 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1350,6 +1350,12 @@ daly_bms: update_interval: 20s uart_id: uart1 +button: + - platform: wake_on_lan + target_mac_address: 12:34:56:78:90:ab + name: wol_test_1 + id: wol_1 + cd74hc4067: pin_s0: GPIO12 pin_s1: GPIO13 From f9a7f008436455c8c771426433af296ac27321a9 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Sun, 23 Jan 2022 06:41:58 +1100 Subject: [PATCH 024/238] Add restore_mode to fan component (#3051) --- esphome/components/fan/__init__.py | 16 +++++++++ esphome/components/fan/fan_state.cpp | 54 +++++++++++++++++++++++----- esphome/components/fan/fan_state.h | 15 ++++++++ 3 files changed, 76 insertions(+), 9 deletions(-) diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 52bec3b5b6..8dec2dee51 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -19,6 +19,7 @@ from esphome.const import ( CONF_ON_TURN_ON, CONF_TRIGGER_ID, CONF_DIRECTION, + CONF_RESTORE_MODE, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_helpers import setup_entity @@ -35,6 +36,16 @@ FAN_DIRECTION_ENUM = { "REVERSE": FanDirection.FAN_DIRECTION_REVERSE, } +FanRestoreMode = fan_ns.enum("FanRestoreMode") +RESTORE_MODES = { + "RESTORE_DEFAULT_OFF": FanRestoreMode.FAN_RESTORE_DEFAULT_OFF, + "RESTORE_DEFAULT_ON": FanRestoreMode.FAN_RESTORE_DEFAULT_ON, + "ALWAYS_OFF": FanRestoreMode.FAN_ALWAYS_OFF, + "ALWAYS_ON": FanRestoreMode.FAN_ALWAYS_ON, + "RESTORE_INVERTED_DEFAULT_OFF": FanRestoreMode.FAN_RESTORE_INVERTED_DEFAULT_OFF, + "RESTORE_INVERTED_DEFAULT_ON": FanRestoreMode.FAN_RESTORE_INVERTED_DEFAULT_ON, +} + # Actions TurnOnAction = fan_ns.class_("TurnOnAction", automation.Action) TurnOffAction = fan_ns.class_("TurnOffAction", automation.Action) @@ -51,6 +62,9 @@ FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.temp FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(FanState), + cv.Optional(CONF_RESTORE_MODE, default="restore_default_off"): cv.enum( + RESTORE_MODES, upper=True, space="_" + ), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent), cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All( cv.requires_component("mqtt"), cv.publish_topic @@ -92,6 +106,8 @@ FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).exte async def setup_fan_core_(var, config): await setup_entity(var, config) + cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) + 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/fan/fan_state.cpp b/esphome/components/fan/fan_state.cpp index 6ff4d3a833..7f9023f881 100644 --- a/esphome/components/fan/fan_state.cpp +++ b/esphome/components/fan/fan_state.cpp @@ -27,16 +27,52 @@ struct FanStateRTCState { }; void FanState::setup() { - this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); - FanStateRTCState recovered{}; - if (!this->rtc_.load(&recovered)) - return; - auto call = this->make_call(); - call.set_state(recovered.state); - call.set_speed(recovered.speed); - call.set_oscillating(recovered.oscillating); - call.set_direction(recovered.direction); + FanStateRTCState recovered{}; + + switch (this->restore_mode_) { + case FAN_RESTORE_DEFAULT_OFF: + case FAN_RESTORE_DEFAULT_ON: + case FAN_RESTORE_INVERTED_DEFAULT_OFF: + case FAN_RESTORE_INVERTED_DEFAULT_ON: + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); + if (!this->rtc_.load(&recovered)) { + if (this->restore_mode_ == FAN_RESTORE_DEFAULT_ON || this->restore_mode_ == FAN_RESTORE_INVERTED_DEFAULT_ON) { + call.set_state(true); + } else { + call.set_state(false); + } + } else { + if (this->restore_mode_ == FAN_RESTORE_INVERTED_DEFAULT_OFF || + this->restore_mode_ == FAN_RESTORE_INVERTED_DEFAULT_ON) { + call.set_state(!recovered.state); + } else { + call.set_state(recovered.state); + } + + call.set_speed(recovered.speed); + call.set_oscillating(recovered.oscillating); + call.set_direction(recovered.direction); + } + break; + case FAN_ALWAYS_OFF: + case FAN_ALWAYS_ON: + if (this->restore_mode_ == FAN_ALWAYS_OFF) { + call.set_state(false); + } else if (this->restore_mode_ == FAN_ALWAYS_ON) { + call.set_state(true); + } + + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); + if (this->rtc_.load(&recovered)) { + call.set_speed(recovered.speed); + call.set_oscillating(recovered.oscillating); + call.set_direction(recovered.direction); + } + + break; + } + call.perform(); } float FanState::get_setup_priority() const { return setup_priority::DATA - 1.0f; } diff --git a/esphome/components/fan/fan_state.h b/esphome/components/fan/fan_state.h index c5a6f59ac4..c0d9d64d2c 100644 --- a/esphome/components/fan/fan_state.h +++ b/esphome/components/fan/fan_state.h @@ -20,6 +20,15 @@ enum ESPDEPRECATED("FanSpeed is deprecated.", "2021.9") FanSpeed { /// Simple enum to represent the direction of a fan enum FanDirection { FAN_DIRECTION_FORWARD = 0, FAN_DIRECTION_REVERSE = 1 }; +enum FanRestoreMode { + FAN_RESTORE_DEFAULT_OFF, + FAN_RESTORE_DEFAULT_ON, + FAN_ALWAYS_OFF, + FAN_ALWAYS_ON, + FAN_RESTORE_INVERTED_DEFAULT_OFF, + FAN_RESTORE_INVERTED_DEFAULT_ON, +}; + class FanState; class FanStateCall { @@ -81,6 +90,9 @@ class FanState : public EntityBase, public Component { /// Set the traits of this fan (i.e. what features it supports). void set_traits(const FanTraits &traits); + /// Set the restore mode of this fan + void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } + /// The current ON/OFF state of the fan. bool state{false}; /// The current oscillation state of the fan. @@ -106,6 +118,9 @@ class FanState : public EntityBase, public Component { FanTraits traits_{}; CallbackManager state_callback_{}; ESPPreferenceObject rtc_; + + /// Restore mode of the fan. + FanRestoreMode restore_mode_; }; } // namespace fan From c6cbe2748e91734a5fe9e9d2329e09f7f2a89bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pl=C3=A1cido=20Revilla?= Date: Sat, 22 Jan 2022 12:04:36 -0800 Subject: [PATCH 025/238] Set the wrapped single light in light partition to internal (#3092) --- esphome/components/partition/light.py | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/partition/light.py b/esphome/components/partition/light.py index 822b7ac306..73cda2c926 100644 --- a/esphome/components/partition/light.py +++ b/esphome/components/partition/light.py @@ -104,7 +104,6 @@ async def to_code(config): ) light_state = cg.new_Pvariable(conf[CONF_LIGHT_ID], "", wrapper) await cg.register_component(light_state, conf) - cg.add(cg.App.register_light(light_state)) segments.append(AddressableSegment(light_state, 0, 1, False)) else: From a6a9ebfde250bea94717123bd15f63a3c21740eb Mon Sep 17 00:00:00 2001 From: Pavel Skuratovich Date: Sun, 23 Jan 2022 01:08:55 +0300 Subject: [PATCH 026/238] slow_pwm: allow to restart a cycle on state change (#3004) Co-authored-by: Oxan van Leeuwen --- esphome/components/slow_pwm/output.py | 7 +++++++ .../components/slow_pwm/slow_pwm_output.cpp | 21 +++++++++++++------ esphome/components/slow_pwm/slow_pwm_output.h | 9 ++++++-- tests/test3.yaml | 1 + 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/esphome/components/slow_pwm/output.py b/esphome/components/slow_pwm/output.py index 0ce1c9f9e2..9cce35254b 100644 --- a/esphome/components/slow_pwm/output.py +++ b/esphome/components/slow_pwm/output.py @@ -15,6 +15,7 @@ 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" +CONF_RESTART_CYCLE_ON_STATE_CHANGE = "restart_cycle_on_state_change" CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { @@ -37,6 +38,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( cv.positive_time_period_milliseconds, cv.Range(min=core.TimePeriod(milliseconds=100)), ), + cv.Optional(CONF_RESTART_CYCLE_ON_STATE_CHANGE, default=False): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA) @@ -63,3 +65,8 @@ async def to_code(config): ) cg.add(var.set_period(config[CONF_PERIOD])) + cg.add( + var.set_restart_cycle_on_state_change( + config[CONF_RESTART_CYCLE_ON_STATE_CHANGE] + ) + ) diff --git a/esphome/components/slow_pwm/slow_pwm_output.cpp b/esphome/components/slow_pwm/slow_pwm_output.cpp index 573adbe3dc..9cfeb54153 100644 --- a/esphome/components/slow_pwm/slow_pwm_output.cpp +++ b/esphome/components/slow_pwm/slow_pwm_output.cpp @@ -18,6 +18,12 @@ void SlowPWMOutput::set_output_state_(bool new_state) { this->pin_->digital_write(new_state); } if (new_state != current_state_) { + if (this->pin_) { + ESP_LOGV(TAG, "Switching output pin %s to %s", this->pin_->dump_summary().c_str(), ONOFF(new_state)); + } else { + ESP_LOGV(TAG, "Switching to %s", ONOFF(new_state)); + } + if (this->state_change_trigger_) { this->state_change_trigger_->trigger(new_state); } @@ -36,16 +42,12 @@ 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_; } - if (scaled_state > now - this->period_start_time_) { - this->set_output_state_(true); - } else { - this->set_output_state_(false); - } + this->set_output_state_(now < this->period_start_time_ + scaled_state); } void SlowPWMOutput::dump_config() { @@ -58,8 +60,15 @@ void SlowPWMOutput::dump_config() { if (this->turn_off_trigger_) ESP_LOGCONFIG(TAG, " Turn off automation configured"); ESP_LOGCONFIG(TAG, " Period: %d ms", this->period_); + ESP_LOGCONFIG(TAG, " Restart cycle on state change: %s", YESNO(this->restart_cycle_on_state_change_)); LOG_FLOAT_OUTPUT(this); } +void SlowPWMOutput::write_state(float state) { + this->state_ = state; + if (this->restart_cycle_on_state_change_) + this->period_start_time_ = millis(); +} + } // 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 d5c5883f25..be45736864 100644 --- a/esphome/components/slow_pwm/slow_pwm_output.h +++ b/esphome/components/slow_pwm/slow_pwm_output.h @@ -11,6 +11,10 @@ class SlowPWMOutput : public output::FloatOutput, public Component { public: void set_pin(GPIOPin *pin) { pin_ = pin; }; void set_period(unsigned int period) { period_ = period; }; + void set_restart_cycle_on_state_change(bool restart_cycle_on_state_change) { + restart_cycle_on_state_change_ = restart_cycle_on_state_change; + } + /// Initialize pin void setup() override; void dump_config() override; @@ -37,7 +41,7 @@ class SlowPWMOutput : public output::FloatOutput, public Component { protected: void loop() override; - void write_state(float state) override { state_ = state; } + void write_state(float state) override; /// turn on/off the configured output void set_output_state_(bool state); @@ -48,7 +52,8 @@ class SlowPWMOutput : public output::FloatOutput, public Component { float state_{0}; bool current_state_{false}; unsigned int period_start_time_{0}; - unsigned int period_{5000}; + unsigned int period_; + bool restart_cycle_on_state_change_; }; } // namespace slow_pwm diff --git a/tests/test3.yaml b/tests/test3.yaml index d5df2ac3f2..b22ec72226 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1148,6 +1148,7 @@ output: pin: GPIO5 id: my_slow_pwm period: 15s + restart_cycle_on_state_change: false - platform: sm2135 id: sm2135_0 channel: 0 From 7854522792e7fa492802bf8310591877b3a55165 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 23 Jan 2022 08:28:00 +0100 Subject: [PATCH 027/238] Enable readability-const-return-type check (#3099) --- .clang-tidy | 1 - esphome/components/climate/climate_traits.h | 6 +++--- esphome/components/graph/graph.h | 2 +- esphome/components/mqtt/mqtt_component.cpp | 4 ++-- esphome/components/mqtt/mqtt_component.h | 6 +++--- esphome/components/select/select.h | 2 +- 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index b40e606121..2465fad239 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -69,7 +69,6 @@ Checks: >- -mpi-*, -objc-*, -readability-braces-around-statements, - -readability-const-return-type, -readability-convert-member-functions-to-static, -readability-else-after-return, -readability-function-cognitive-complexity, diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index 903ce085d8..d113510eeb 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -65,7 +65,7 @@ class ClimateTraits { ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20") void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); } bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); } - const std::set get_supported_modes() const { return supported_modes_; } + std::set get_supported_modes() const { return supported_modes_; } void set_supports_action(bool supports_action) { supports_action_ = supports_action; } bool get_supports_action() const { return supports_action_; } @@ -93,7 +93,7 @@ class ClimateTraits { void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); } bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); } bool get_supports_fan_modes() const { return !supported_fan_modes_.empty() || !supported_custom_fan_modes_.empty(); } - const std::set get_supported_fan_modes() const { return supported_fan_modes_; } + std::set get_supported_fan_modes() const { return supported_fan_modes_; } void set_supported_custom_fan_modes(std::set supported_custom_fan_modes) { supported_custom_fan_modes_ = std::move(supported_custom_fan_modes); @@ -141,7 +141,7 @@ class ClimateTraits { } bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); } bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); } - const std::set get_supported_swing_modes() { return supported_swing_modes_; } + std::set get_supported_swing_modes() { return supported_swing_modes_; } float get_visual_min_temperature() const { return visual_min_temperature_; } void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; } diff --git a/esphome/components/graph/graph.h b/esphome/components/graph/graph.h index f935917c57..15d2d1c7c4 100644 --- a/esphome/components/graph/graph.h +++ b/esphome/components/graph/graph.h @@ -115,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() { return name_; } + std::string get_name() { return name_; } const HistoryData *get_tracedata() { return &data_; } protected: diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 62dbae3bcc..a477701cb9 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -27,13 +27,13 @@ std::string MQTTComponent::get_default_topic_for_(const std::string &suffix) con "/" + suffix; } -const std::string MQTTComponent::get_state_topic_() const { +std::string MQTTComponent::get_state_topic_() const { if (this->custom_state_topic_.empty()) return this->get_default_topic_for_("state"); return this->custom_state_topic_; } -const std::string MQTTComponent::get_command_topic_() const { +std::string MQTTComponent::get_command_topic_() const { if (this->custom_command_topic_.empty()) return this->get_default_topic_for_("command"); return this->custom_command_topic_; diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index e83523a712..55fb8d11c1 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -33,7 +33,7 @@ struct SendDiscoveryConfig { \ public: \ void set_custom_##name##_##type##_topic(const std::string &topic) { this->custom_##name##_##type##_topic_ = topic; } \ - const std::string get_##name##_##type##_topic() const { \ + std::string get_##name##_##type##_topic() const { \ if (this->custom_##name##_##type##_topic_.empty()) \ return this->get_default_topic_for_(#name "/" #type); \ return this->custom_##name##_##type##_topic_; \ @@ -171,10 +171,10 @@ class MQTTComponent : public Component { 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; + std::string get_state_topic_() const; /// Get the MQTT topic for listening to commands. - const std::string get_command_topic_() const; + std::string get_command_topic_() const; bool is_connected_() const; diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index 6113cca1fd..db655ea34e 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -38,7 +38,7 @@ class SelectCall { class SelectTraits { public: void set_options(std::vector options) { this->options_ = std::move(options); } - const std::vector get_options() const { return this->options_; } + std::vector get_options() const { return this->options_; } protected: std::vector options_; From a31700e16f2254cb827b7bb79cf37e6e659114ac Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 23 Jan 2022 08:29:58 +0100 Subject: [PATCH 028/238] Enable readability-qualified-auto check (#3095) --- .clang-tidy | 3 ++- .../airthings_wave_mini/airthings_wave_mini.cpp | 4 ++-- .../airthings_wave_plus/airthings_wave_plus.cpp | 4 ++-- esphome/components/am43/am43.cpp | 6 +++--- esphome/components/am43/cover/am43_cover.cpp | 10 +++++----- esphome/components/anova/anova.cpp | 6 +++--- esphome/components/ballu/ballu.cpp | 2 +- esphome/components/bl0940/bl0940.cpp | 2 +- esphome/components/ble_client/ble_client.cpp | 12 ++++++------ .../ble_client/output/ble_binary_output.cpp | 4 ++-- esphome/components/ble_client/sensor/ble_sensor.cpp | 4 ++-- esphome/components/canbus/canbus.cpp | 2 +- esphome/components/climate_ir_lg/climate_ir_lg.cpp | 2 +- esphome/components/coolix/coolix.cpp | 2 +- esphome/components/daikin/daikin.cpp | 2 +- esphome/components/dallas/dallas_component.cpp | 2 +- esphome/components/e131/e131.cpp | 2 +- .../e131/e131_addressable_light_effect.cpp | 4 ++-- esphome/components/e131/e131_packet.cpp | 2 +- .../components/fujitsu_general/fujitsu_general.cpp | 2 +- esphome/components/heatpumpir/ir_sender_esphome.cpp | 6 +++--- esphome/components/hitachi_ac344/hitachi_ac344.cpp | 2 +- esphome/components/hitachi_ac424/hitachi_ac424.cpp | 2 +- esphome/components/mdns/mdns_esp8266.cpp | 4 ++-- esphome/components/mitsubishi/mitsubishi.cpp | 2 +- .../modbus_controller/modbus_controller.cpp | 2 +- esphome/components/nextion/nextion.cpp | 2 +- esphome/components/rotary_encoder/rotary_encoder.cpp | 2 +- esphome/components/tcl112/tcl112.cpp | 2 +- esphome/components/teleinfo/teleinfo.cpp | 2 +- esphome/components/toshiba/toshiba.cpp | 6 +++--- esphome/components/tuya/tuya.cpp | 4 ++-- esphome/components/whirlpool/whirlpool.cpp | 2 +- esphome/components/yashima/yashima.cpp | 2 +- 34 files changed, 59 insertions(+), 58 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 2465fad239..81cfec277c 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -77,7 +77,6 @@ Checks: >- -readability-magic-numbers, -readability-make-member-function-const, -readability-named-parameter, - -readability-qualified-auto, -readability-redundant-access-specifiers, -readability-redundant-member-init, -readability-redundant-string-init, @@ -159,3 +158,5 @@ CheckOptions: value: 'lower_case' - key: readability-identifier-naming.VirtualMethodSuffix value: '' + - key: readability-qualified-auto.AddConstToQualified + value: 0 diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp index 6b6418f7e6..2e7a1fb024 100644 --- a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp @@ -24,7 +24,7 @@ void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt case ESP_GATTC_SEARCH_CMPL_EVT: { this->handle_ = 0; - auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_); + 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()); @@ -56,7 +56,7 @@ void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt } void AirthingsWaveMini::read_sensors_(uint8_t *raw_value, uint16_t value_len) { - auto value = (WaveMiniReadings *) raw_value; + auto *value = (WaveMiniReadings *) raw_value; if (sizeof(WaveMiniReadings) <= value_len) { this->humidity_sensor_->publish_state(value->humidity / 100.0f); diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index 79f2cb7741..02ed33b87a 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -24,7 +24,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt case ESP_GATTC_SEARCH_CMPL_EVT: { this->handle_ = 0; - auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_); + 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()); @@ -56,7 +56,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt } void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) { - auto value = (WavePlusReadings *) raw_value; + auto *value = (WavePlusReadings *) raw_value; if (sizeof(WavePlusReadings) <= value_len) { ESP_LOGD(TAG, "version = %d", value->version); diff --git a/esphome/components/am43/am43.cpp b/esphome/components/am43/am43.cpp index a62e3bb6df..ced2ff8196 100644 --- a/esphome/components/am43/am43.cpp +++ b/esphome/components/am43/am43.cpp @@ -39,7 +39,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i break; } case ESP_GATTC_SEARCH_CMPL_EVT: { - auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID); + auto *chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID); if (chr == nullptr) { if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) { ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", @@ -75,7 +75,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i if (this->current_sensor_ > 0) { if (this->illuminance_ != nullptr) { - auto packet = this->encoder_->get_light_level_request(); + auto *packet = this->encoder_->get_light_level_request(); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); @@ -99,7 +99,7 @@ void Am43::update() { } if (this->current_sensor_ == 0) { if (this->battery_ != nullptr) { - auto packet = this->encoder_->get_battery_level_request(); + auto *packet = this->encoder_->get_battery_level_request(); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp index 274c527760..afdbe65630 100644 --- a/esphome/components/am43/cover/am43_cover.cpp +++ b/esphome/components/am43/cover/am43_cover.cpp @@ -25,7 +25,7 @@ void Am43Component::setup() { void Am43Component::loop() { if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) { - auto packet = this->encoder_->get_send_pin_request(this->pin_); + auto *packet = this->encoder_->get_send_pin_request(this->pin_); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); @@ -51,7 +51,7 @@ void Am43Component::control(const CoverCall &call) { return; } if (call.get_stop()) { - auto packet = this->encoder_->get_stop_request(); + auto *packet = this->encoder_->get_stop_request(); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); @@ -63,7 +63,7 @@ void Am43Component::control(const CoverCall &call) { if (this->invert_position_) pos = 1 - pos; - auto packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100)); + auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100)); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); @@ -80,7 +80,7 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } case ESP_GATTC_SEARCH_CMPL_EVT: { - auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID); + auto *chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID); if (chr == nullptr) { if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) { ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", this->get_name().c_str()); @@ -120,7 +120,7 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ if (this->decoder_->has_pin_response()) { if (this->decoder_->pin_ok_) { ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str()); - auto packet = this->encoder_->get_position_request(); + auto *packet = this->encoder_->get_position_request(); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index 5d9afddc74..5dfafbd2c9 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -40,7 +40,7 @@ void Anova::control(const ClimateCall &call) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); } if (call.get_target_temperature().has_value()) { - auto pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature()); + auto *pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature()); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) @@ -57,7 +57,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ break; } case ESP_GATTC_SEARCH_CMPL_EVT: { - auto chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID); + auto *chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID); if (chr == nullptr) { ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str()); ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.", this->get_name().c_str()); @@ -133,7 +133,7 @@ void Anova::update() { return; if (this->current_request_ < 2) { - auto pkt = this->codec_->get_read_device_status_request(); + auto *pkt = this->codec_->get_read_device_status_request(); if (this->current_request_ == 0) this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c'); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, diff --git a/esphome/components/ballu/ballu.cpp b/esphome/components/ballu/ballu.cpp index e2703a79fb..96eefca5fb 100644 --- a/esphome/components/ballu/ballu.cpp +++ b/esphome/components/ballu/ballu.cpp @@ -97,7 +97,7 @@ void BalluClimate::transmit_state() { // Send code auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(38000); diff --git a/esphome/components/bl0940/bl0940.cpp b/esphome/components/bl0940/bl0940.cpp index 19672e98d0..ed193b23f3 100644 --- a/esphome/components/bl0940/bl0940.cpp +++ b/esphome/components/bl0940/bl0940.cpp @@ -65,7 +65,7 @@ void BL0940::update() { } void BL0940::setup() { - for (auto i : BL0940_INIT) { + for (auto *i : BL0940_INIT) { this->write_array(i, 6); delay(1); } diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 407f1a1d17..d17292b88f 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -167,7 +167,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - auto descr = this->get_config_descriptor(param->reg_for_notify.handle); + auto *descr = this->get_config_descriptor(param->reg_for_notify.handle); if (descr == nullptr) { ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle); break; @@ -252,7 +252,7 @@ float BLEClient::parse_char_value(uint8_t *value, uint16_t length) { } BLEService *BLEClient::get_service(espbt::ESPBTUUID uuid) { - for (auto svc : this->services_) + for (auto *svc : this->services_) if (svc->uuid == uuid) return svc; return nullptr; @@ -261,7 +261,7 @@ BLEService *BLEClient::get_service(espbt::ESPBTUUID uuid) { BLEService *BLEClient::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); } BLECharacteristic *BLEClient::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) { - auto svc = this->get_service(service); + auto *svc = this->get_service(service); if (svc == nullptr) return nullptr; return svc->get_characteristic(chr); @@ -293,10 +293,10 @@ BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) { } BLEDescriptor *BLEClient::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) { - auto svc = this->get_service(service); + auto *svc = this->get_service(service); if (svc == nullptr) return nullptr; - auto ch = svc->get_characteristic(chr); + auto *ch = svc->get_characteristic(chr); if (ch == nullptr) return nullptr; return ch->get_descriptor(descr); @@ -389,7 +389,7 @@ BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) { } void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) { - auto client = this->service->client; + 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) { diff --git a/esphome/components/ble_client/output/ble_binary_output.cpp b/esphome/components/ble_client/output/ble_binary_output.cpp index ff3711e842..7bf5ea4ead 100644 --- a/esphome/components/ble_client/output/ble_binary_output.cpp +++ b/esphome/components/ble_client/output/ble_binary_output.cpp @@ -32,7 +32,7 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i break; } - auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); + 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; @@ -54,7 +54,7 @@ void BLEBinaryOutput::write_state(bool state) { return; } - auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); + 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()); diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index 7a2e3ddc8b..f8eee43137 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -43,7 +43,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga } case ESP_GATTC_SEARCH_CMPL_EVT: { this->handle = 0; - auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); + auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); if (chr == nullptr) { this->status_set_warning(); this->publish_state(NAN); @@ -53,7 +53,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga } this->handle = chr->handle; if (this->descr_uuid_.get_uuid().len > 0) { - auto descr = chr->get_descriptor(this->descr_uuid_); + auto *descr = chr->get_descriptor(this->descr_uuid_); if (descr == nullptr) { this->status_set_warning(); this->publish_state(NAN); diff --git a/esphome/components/canbus/canbus.cpp b/esphome/components/canbus/canbus.cpp index b8b6b9e65f..731682c277 100644 --- a/esphome/components/canbus/canbus.cpp +++ b/esphome/components/canbus/canbus.cpp @@ -75,7 +75,7 @@ void Canbus::loop() { } // fire all triggers - for (auto trigger : this->triggers_) { + for (auto *trigger : this->triggers_) { if ((trigger->can_id_ == can_message.can_id) && (trigger->use_extended_id_ == can_message.use_extended_id)) { trigger->trigger(data); } diff --git a/esphome/components/climate_ir_lg/climate_ir_lg.cpp b/esphome/components/climate_ir_lg/climate_ir_lg.cpp index cbb1f7699b..31c7f22be5 100644 --- a/esphome/components/climate_ir_lg/climate_ir_lg.cpp +++ b/esphome/components/climate_ir_lg/climate_ir_lg.cpp @@ -175,7 +175,7 @@ void LgIrClimate::transmit_(uint32_t value) { ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02X", value); auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(38000); data->reserve(2 + BITS * 2u); diff --git a/esphome/components/coolix/coolix.cpp b/esphome/components/coolix/coolix.cpp index 76ec1627c2..c5d8cb07e9 100644 --- a/esphome/components/coolix/coolix.cpp +++ b/esphome/components/coolix/coolix.cpp @@ -104,7 +104,7 @@ void CoolixClimate::transmit_state() { ESP_LOGV(TAG, "Sending coolix code: 0x%06X", remote_state); auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); remote_base::CoolixProtocol().encode(data, remote_state); transmit.perform(); } diff --git a/esphome/components/daikin/daikin.cpp b/esphome/components/daikin/daikin.cpp index 83d0253691..820cf78b3f 100644 --- a/esphome/components/daikin/daikin.cpp +++ b/esphome/components/daikin/daikin.cpp @@ -23,7 +23,7 @@ void DaikinClimate::transmit_state() { } auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(DAIKIN_IR_FREQUENCY); data->mark(DAIKIN_HEADER_MARK); diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 3610e79447..fb38f57f8c 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -52,7 +52,7 @@ void DallasComponent::setup() { this->found_sensors_.push_back(address); } - for (auto sensor : this->sensors_) { + for (auto *sensor : this->sensors_) { if (sensor->get_index().has_value()) { if (*sensor->get_index() >= this->found_sensors_.size()) { this->status_set_error(); diff --git a/esphome/components/e131/e131.cpp b/esphome/components/e131/e131.cpp index 35510fe204..6d584687ce 100644 --- a/esphome/components/e131/e131.cpp +++ b/esphome/components/e131/e131.cpp @@ -97,7 +97,7 @@ bool E131Component::process_(int universe, const E131Packet &packet) { ESP_LOGV(TAG, "Received E1.31 packet for %d universe, with %d bytes", universe, packet.count); - for (auto light_effect : light_effects_) { + for (auto *light_effect : light_effects_) { handled = light_effect->process_(universe, packet) || handled; } diff --git a/esphome/components/e131/e131_addressable_light_effect.cpp b/esphome/components/e131/e131_addressable_light_effect.cpp index 371f3b9cbf..7a3e71808e 100644 --- a/esphome/components/e131/e131_addressable_light_effect.cpp +++ b/esphome/components/e131/e131_addressable_light_effect.cpp @@ -47,7 +47,7 @@ void E131AddressableLightEffect::apply(light::AddressableLight &it, const Color } bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet) { - auto it = get_addressable_(); + auto *it = get_addressable_(); // check if this is our universe and data are valid if (universe < first_universe_ || universe > get_last_universe()) @@ -57,7 +57,7 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet // limit amount of lights per universe and received int output_end = std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1)); - auto input_data = packet.values + 1; + auto *input_data = packet.values + 1; ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %d-%d.", get_name().c_str(), universe, output_offset, output_end); diff --git a/esphome/components/e131/e131_packet.cpp b/esphome/components/e131/e131_packet.cpp index b20eb9f666..f199d3574b 100644 --- a/esphome/components/e131/e131_packet.cpp +++ b/esphome/components/e131/e131_packet.cpp @@ -116,7 +116,7 @@ bool E131Component::packet_(const std::vector &data, int &universe, E13 if (data.size() < E131_MIN_PACKET_SIZE) return false; - auto sbuff = reinterpret_cast(&data[0]); + auto *sbuff = reinterpret_cast(&data[0]); if (memcmp(sbuff->acn_id, ACN_ID, sizeof(sbuff->acn_id)) != 0) return false; diff --git a/esphome/components/fujitsu_general/fujitsu_general.cpp b/esphome/components/fujitsu_general/fujitsu_general.cpp index 9e58f672c7..291af8c8cd 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.cpp +++ b/esphome/components/fujitsu_general/fujitsu_general.cpp @@ -207,7 +207,7 @@ void FujitsuGeneralClimate::transmit_(uint8_t const *message, uint8_t length) { ESP_LOGV(TAG, "Transmit message length %d", length); auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(FUJITSU_GENERAL_CARRIER_FREQUENCY); diff --git a/esphome/components/heatpumpir/ir_sender_esphome.cpp b/esphome/components/heatpumpir/ir_sender_esphome.cpp index 24c7933563..173d595119 100644 --- a/esphome/components/heatpumpir/ir_sender_esphome.cpp +++ b/esphome/components/heatpumpir/ir_sender_esphome.cpp @@ -6,20 +6,20 @@ namespace esphome { namespace heatpumpir { void IRSenderESPHome::setFrequency(int frequency) { // NOLINT(readability-identifier-naming) - auto data = transmit_.get_data(); + 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(); + 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(); + auto *data = transmit_.get_data(); data->space(space_length); } else { transmit_.perform(); diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.cpp b/esphome/components/hitachi_ac344/hitachi_ac344.cpp index 7702baf312..f8c3f1722d 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.cpp +++ b/esphome/components/hitachi_ac344/hitachi_ac344.cpp @@ -211,7 +211,7 @@ void HitachiClimate::transmit_state() { invert_byte_pairs(remote_state_ + 3, HITACHI_AC344_STATE_LENGTH - 3); auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(HITACHI_AC344_FREQ); uint8_t repeat = 0; diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.cpp b/esphome/components/hitachi_ac424/hitachi_ac424.cpp index 713bc0be25..a6f146c6c3 100644 --- a/esphome/components/hitachi_ac424/hitachi_ac424.cpp +++ b/esphome/components/hitachi_ac424/hitachi_ac424.cpp @@ -212,7 +212,7 @@ void HitachiClimate::transmit_state() { invert_byte_pairs(remote_state_ + 3, HITACHI_AC424_STATE_LENGTH - 3); auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(HITACHI_AC424_FREQ); uint8_t repeat = 0; diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index ff305f907a..b91f60cb6b 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -20,11 +20,11 @@ void MDNSComponent::setup() { // 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(); + auto *proto = service.proto.c_str(); while (*proto == '_') { proto++; } - auto service_type = service.service_type.c_str(); + auto *service_type = service.service_type.c_str(); while (*service_type == '_') { service_type++; } diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index 43397770d1..4d47b1ac14 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -54,7 +54,7 @@ void MitsubishiClimate::transmit_state() { } auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(38000); // repeat twice diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index d07a6d5335..014a355cae 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -99,7 +99,7 @@ void ModbusController::on_register_data(ModbusRegisterType register_type, uint16 // loop through all sensors with the same start address auto sensors = find_sensors_(register_type, start_address); - for (auto sensor : sensors) { + for (auto *sensor : sensors) { sensor->parse_and_publish(data); } } diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index fcb3885db9..709e2bffcd 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -688,7 +688,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 diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index aff8fc381c..c5227b41a7 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -104,7 +104,7 @@ void IRAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensorStore } if (rotation_dir != 0) { - auto first_zero = std::find(arg->rotation_events.begin(), arg->rotation_events.end(), 0); // find first zero + auto *first_zero = std::find(arg->rotation_events.begin(), arg->rotation_events.end(), 0); // find first zero if (first_zero == arg->rotation_events.begin() // are we at the start (first event this loop iteration) || std::signbit(*std::prev(first_zero)) != std::signbit(rotation_dir) // or is the last stored event the wrong direction diff --git a/esphome/components/tcl112/tcl112.cpp b/esphome/components/tcl112/tcl112.cpp index 5b938ba0c3..e05a07873e 100644 --- a/esphome/components/tcl112/tcl112.cpp +++ b/esphome/components/tcl112/tcl112.cpp @@ -120,7 +120,7 @@ void Tcl112Climate::transmit_state() { remote_state[13]); auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(38000); diff --git a/esphome/components/teleinfo/teleinfo.cpp b/esphome/components/teleinfo/teleinfo.cpp index d9f80134f4..4d617ae4e6 100644 --- a/esphome/components/teleinfo/teleinfo.cpp +++ b/esphome/components/teleinfo/teleinfo.cpp @@ -179,7 +179,7 @@ void TeleInfo::loop() { } } void TeleInfo::publish_value_(const std::string &tag, const std::string &val) { - for (auto element : teleinfo_listeners_) { + for (auto *element : teleinfo_listeners_) { if (tag != element->tag) continue; element->publish_val(val); diff --git a/esphome/components/toshiba/toshiba.cpp b/esphome/components/toshiba/toshiba.cpp index 975a149b52..a6af3b1015 100644 --- a/esphome/components/toshiba/toshiba.cpp +++ b/esphome/components/toshiba/toshiba.cpp @@ -197,7 +197,7 @@ void ToshibaClimate::transmit_generic_() { // Transmit auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); encode_(data, message, message_length, 1); @@ -210,7 +210,7 @@ void ToshibaClimate::transmit_rac_pt1411hwru_() { clamp(this->target_temperature, TOSHIBA_RAC_PT1411HWRU_TEMP_C_MIN, TOSHIBA_RAC_PT1411HWRU_TEMP_C_MAX); float temp_adjd = temperature - TOSHIBA_RAC_PT1411HWRU_TEMP_C_MIN; auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); // Byte 0: Header upper (0xB2) message[0] = RAC_PT1411HWRU_MESSAGE_HEADER0; @@ -357,7 +357,7 @@ void ToshibaClimate::transmit_rac_pt1411hwru_temp_(const bool cs_state, const bo uint8_t message[RAC_PT1411HWRU_MESSAGE_LENGTH] = {0}; float temperature = clamp(this->current_temperature, 0.0, TOSHIBA_RAC_PT1411HWRU_TEMP_C_MAX + 1); auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); // "Comfort Sense" feature notes // IR Code: 0xBA45 xxXX yyYY // xx: Temperature in °C diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 7ff8c66c44..50565fae0a 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -208,7 +208,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff #ifdef USE_TIME if (this->time_id_.has_value()) { this->send_local_time_(); - auto time_id = *this->time_id_; + auto *time_id = *this->time_id_; time_id->add_on_time_sync_callback([this] { this->send_local_time_(); }); } else { ESP_LOGW(TAG, "LOCAL_TIME_QUERY is not handled because time is not configured"); @@ -414,7 +414,7 @@ void Tuya::send_wifi_status_() { #ifdef USE_TIME void Tuya::send_local_time_() { std::vector payload; - auto time_id = *this->time_id_; + auto *time_id = *this->time_id_; time::ESPTime now = time_id->now(); if (now.is_valid()) { uint8_t year = now.year - 2000; diff --git a/esphome/components/whirlpool/whirlpool.cpp b/esphome/components/whirlpool/whirlpool.cpp index d705b42a8c..61ffffa192 100644 --- a/esphome/components/whirlpool/whirlpool.cpp +++ b/esphome/components/whirlpool/whirlpool.cpp @@ -120,7 +120,7 @@ void WhirlpoolClimate::transmit_state() { // Send code auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(38000); diff --git a/esphome/components/yashima/yashima.cpp b/esphome/components/yashima/yashima.cpp index 8d588127b0..9a6e85bca1 100644 --- a/esphome/components/yashima/yashima.cpp +++ b/esphome/components/yashima/yashima.cpp @@ -171,7 +171,7 @@ void YashimaClimate::transmit_state_() { remote_state[1] |= YASHIMA_TEMP_MAP_BYTE1[safecelsius - YASHIMA_TEMP_MIN]; auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(YASHIMA_CARRIER_FREQUENCY); From 7da12a878f341e92e481207bcb307451723ffaa4 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 23 Jan 2022 08:34:43 +0100 Subject: [PATCH 029/238] Enable readability-redundant-member-init check (#3097) --- .clang-tidy | 1 - esphome/components/esp32_touch/esp32_touch.cpp | 2 +- .../modbus_controller/binary_sensor/modbus_binarysensor.h | 3 +-- esphome/components/modbus_controller/modbus_controller.h | 2 +- esphome/components/modbus_controller/number/modbus_number.h | 3 +-- esphome/components/modbus_controller/output/modbus_output.h | 5 ++--- esphome/components/modbus_controller/sensor/modbus_sensor.h | 3 +-- esphome/components/modbus_controller/switch/modbus_switch.h | 3 +-- .../modbus_controller/text_sensor/modbus_textsensor.h | 3 +-- esphome/components/mqtt/mqtt_binary_sensor.cpp | 2 +- esphome/components/mqtt/mqtt_button.cpp | 2 +- esphome/components/mqtt/mqtt_fan.cpp | 2 +- esphome/components/mqtt/mqtt_light.cpp | 2 +- esphome/components/mqtt/mqtt_number.cpp | 2 +- esphome/components/mqtt/mqtt_select.cpp | 2 +- esphome/components/mqtt/mqtt_sensor.cpp | 2 +- esphome/components/mqtt/mqtt_switch.cpp | 2 +- esphome/components/mqtt/mqtt_text_sensor.cpp | 2 +- esphome/components/remote_base/remote_base.h | 2 +- esphome/components/sensor/filter.cpp | 3 +-- esphome/components/socket/bsd_sockets_impl.cpp | 2 +- esphome/core/component.cpp | 2 +- 22 files changed, 22 insertions(+), 30 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 81cfec277c..17360d2e5d 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -78,7 +78,6 @@ Checks: >- -readability-make-member-function-const, -readability-named-parameter, -readability-redundant-access-specifiers, - -readability-redundant-member-init, -readability-redundant-string-init, -readability-uppercase-literal-suffix, -readability-use-anyofallof, diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index b225ae1a8a..0e3d3d9fd5 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -162,7 +162,7 @@ void ESP32TouchComponent::on_shutdown() { } 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) {} + : touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {} } // namespace esp32_touch } // namespace esphome diff --git a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h index 21afbc7053..41f1f1b054 100644 --- a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h +++ b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h @@ -10,8 +10,7 @@ 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() { + uint8_t skip_updates, bool force_new_range) { this->register_type = register_type; this->start_address = start_address; this->offset = offset; diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 6dbabac71e..ada6843ca3 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -393,7 +393,7 @@ class ModbusCommandItem { class ModbusController : public PollingComponent, public modbus::ModbusDevice { public: - ModbusController(uint16_t throttle = 0) : modbus::ModbusDevice(), command_throttle_(throttle){}; + ModbusController(uint16_t throttle = 0) : command_throttle_(throttle){}; void dump_config() override; void loop() override; void setup() override; diff --git a/esphome/components/modbus_controller/number/modbus_number.h b/esphome/components/modbus_controller/number/modbus_number.h index c678cd00cc..2d6428c2c3 100644 --- a/esphome/components/modbus_controller/number/modbus_number.h +++ b/esphome/components/modbus_controller/number/modbus_number.h @@ -12,8 +12,7 @@ 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() { + uint8_t skip_updates, bool force_new_range) { this->register_type = ModbusRegisterType::HOLDING; this->start_address = start_address; this->offset = offset; diff --git a/esphome/components/modbus_controller/output/modbus_output.h b/esphome/components/modbus_controller/output/modbus_output.h index 6237805d24..f089775c0c 100644 --- a/esphome/components/modbus_controller/output/modbus_output.h +++ b/esphome/components/modbus_controller/output/modbus_output.h @@ -9,8 +9,7 @@ namespace modbus_controller { class ModbusFloatOutput : public output::FloatOutput, public Component, public SensorItem { public: - ModbusFloatOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type, int register_count) - : output::FloatOutput(), Component() { + ModbusFloatOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type, int register_count) { this->register_type = ModbusRegisterType::HOLDING; this->start_address = start_address; this->offset = offset; @@ -43,7 +42,7 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S class ModbusBinaryOutput : public output::BinaryOutput, public Component, public SensorItem { public: - ModbusBinaryOutput(uint16_t start_address, uint8_t offset) : output::BinaryOutput(), Component() { + ModbusBinaryOutput(uint16_t start_address, uint8_t offset) { this->register_type = ModbusRegisterType::COIL; this->start_address = start_address; this->bitmask = bitmask; diff --git a/esphome/components/modbus_controller/sensor/modbus_sensor.h b/esphome/components/modbus_controller/sensor/modbus_sensor.h index 37ea9d0dd0..ababcc33d2 100644 --- a/esphome/components/modbus_controller/sensor/modbus_sensor.h +++ b/esphome/components/modbus_controller/sensor/modbus_sensor.h @@ -10,8 +10,7 @@ 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() { + SensorValueType value_type, int register_count, uint8_t skip_updates, bool force_new_range) { this->register_type = register_type; this->start_address = start_address; this->offset = offset; diff --git a/esphome/components/modbus_controller/switch/modbus_switch.h b/esphome/components/modbus_controller/switch/modbus_switch.h index 6732c01eef..eccfc3b64a 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.h +++ b/esphome/components/modbus_controller/switch/modbus_switch.h @@ -10,8 +10,7 @@ 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, - uint8_t skip_updates, bool force_new_range) - : Component(), switch_::Switch() { + uint8_t skip_updates, bool force_new_range) { this->register_type = register_type; this->start_address = start_address; this->offset = offset; diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h index 3db4d94a45..571a43d9a9 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h @@ -12,8 +12,7 @@ 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() { + uint16_t response_bytes, RawEncoding encode, uint8_t skip_updates, bool force_new_range) { this->register_type = register_type; this->start_address = start_address; this->offset = offset; diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 0bf3b751fd..79e6989a8f 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -23,7 +23,7 @@ void MQTTBinarySensorComponent::dump_config() { LOG_MQTT_COMPONENT(true, false) } MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor *binary_sensor) - : MQTTComponent(), binary_sensor_(binary_sensor) { + : binary_sensor_(binary_sensor) { if (this->binary_sensor_->is_status_binary_sensor()) { this->set_custom_state_topic(mqtt::global_mqtt_client->get_availability().topic); } diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index 52df63093a..204f60fe67 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -13,7 +13,7 @@ static const char *const TAG = "mqtt.button"; using namespace esphome::button; -MQTTButtonComponent::MQTTButtonComponent(button::Button *button) : MQTTComponent(), button_(button) {} +MQTTButtonComponent::MQTTButtonComponent(button::Button *button) : button_(button) {} void MQTTButtonComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 0f2eb6535f..755f99d777 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -14,7 +14,7 @@ static const char *const TAG = "mqtt.fan"; using namespace esphome::fan; -MQTTFanComponent::MQTTFanComponent(FanState *state) : MQTTComponent(), state_(state) {} +MQTTFanComponent::MQTTFanComponent(FanState *state) : state_(state) {} FanState *MQTTFanComponent::get_state() const { return this->state_; } std::string MQTTFanComponent::component_type() const { return "fan"; } diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index ee1cc36af7..e2480acd62 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -28,7 +28,7 @@ void MQTTJSONLightComponent::setup() { this->state_->add_new_remote_values_callback([this, f]() { this->defer("send", f); }); } -MQTTJSONLightComponent::MQTTJSONLightComponent(LightState *state) : MQTTComponent(), state_(state) {} +MQTTJSONLightComponent::MQTTJSONLightComponent(LightState *state) : state_(state) {} bool MQTTJSONLightComponent::publish_state_() { return this->publish_json(this->get_state_topic_(), diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 73d37f7cd3..7018792283 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -13,7 +13,7 @@ static const char *const TAG = "mqtt.number"; using namespace esphome::number; -MQTTNumberComponent::MQTTNumberComponent(Number *number) : MQTTComponent(), number_(number) {} +MQTTNumberComponent::MQTTNumberComponent(Number *number) : number_(number) {} void MQTTNumberComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) { diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index cb4c9c9052..7ecbf9425e 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -13,7 +13,7 @@ static const char *const TAG = "mqtt.select"; using namespace esphome::select; -MQTTSelectComponent::MQTTSelectComponent(Select *select) : MQTTComponent(), select_(select) {} +MQTTSelectComponent::MQTTSelectComponent(Select *select) : select_(select) {} void MQTTSelectComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) { diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 303aa0e753..4946cfb924 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -17,7 +17,7 @@ static const char *const TAG = "mqtt.sensor"; using namespace esphome::sensor; -MQTTSensorComponent::MQTTSensorComponent(Sensor *sensor) : MQTTComponent(), sensor_(sensor) {} +MQTTSensorComponent::MQTTSensorComponent(Sensor *sensor) : sensor_(sensor) {} void MQTTSensorComponent::setup() { this->sensor_->add_on_state_callback([this](float state) { this->publish_state(state); }); diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index 2e91f8e502..3fd578825a 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -13,7 +13,7 @@ static const char *const TAG = "mqtt.switch"; using namespace esphome::switch_; -MQTTSwitchComponent::MQTTSwitchComponent(switch_::Switch *a_switch) : MQTTComponent(), switch_(a_switch) {} +MQTTSwitchComponent::MQTTSwitchComponent(switch_::Switch *a_switch) : switch_(a_switch) {} void MQTTSwitchComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index 010364e221..d0d3174bfe 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -11,7 +11,7 @@ static const char *const TAG = "mqtt.text_sensor"; using namespace esphome::text_sensor; -MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : MQTTComponent(), sensor_(sensor) {} +MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {} void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { config.command_topic = false; } diff --git a/esphome/components/remote_base/remote_base.h b/esphome/components/remote_base/remote_base.h index e1af41274e..3c76da84e3 100644 --- a/esphome/components/remote_base/remote_base.h +++ b/esphome/components/remote_base/remote_base.h @@ -284,7 +284,7 @@ class RemoteReceiverBinarySensorBase : public binary_sensor::BinarySensorInitial public Component, public RemoteReceiverListener { public: - explicit RemoteReceiverBinarySensorBase() : BinarySensorInitiallyOff() {} + explicit RemoteReceiverBinarySensorBase() {} void dump_config() override; virtual bool matches(RemoteReceiveData src) = 0; bool on_receive(RemoteReceiveData src) override { diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index 7a8a557273..7e7153196c 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -291,8 +291,7 @@ optional FilterOutValueFilter::new_value(float value) { } // ThrottleFilter -ThrottleFilter::ThrottleFilter(uint32_t min_time_between_inputs) - : Filter(), min_time_between_inputs_(min_time_between_inputs) {} +ThrottleFilter::ThrottleFilter(uint32_t min_time_between_inputs) : min_time_between_inputs_(min_time_between_inputs) {} optional ThrottleFilter::new_value(float value) { const uint32_t now = millis(); if (this->last_input_ == 0 || now - this->last_input_ >= min_time_between_inputs_) { diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index 1db24973e7..49bda7683c 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -35,7 +35,7 @@ std::string format_sockaddr(const struct sockaddr_storage &storage) { class BSDSocketImpl : public Socket { public: - BSDSocketImpl(int fd) : Socket(), fd_(fd) {} + BSDSocketImpl(int fd) : fd_(fd) {} ~BSDSocketImpl() override { if (!closed_) { close(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 591c9943b5..5cb063cbec 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -177,7 +177,7 @@ bool Component::has_overridden_loop() const { return loop_overridden || call_loop_overridden; } -PollingComponent::PollingComponent(uint32_t update_interval) : Component(), update_interval_(update_interval) {} +PollingComponent::PollingComponent(uint32_t update_interval) : update_interval_(update_interval) {} void PollingComponent::call_setup() { // Let the polling component subclass setup their HW. From b2430097f2878b02b3540709d75ec870700a413e Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 23 Jan 2022 08:39:07 +0100 Subject: [PATCH 030/238] Enable readability-named-parameter check (#3098) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .clang-tidy | 1 - esphome/components/anova/anova.h | 2 +- esphome/components/api/user_services.h | 2 +- esphome/components/hm3301/hm3301.h | 4 ++-- .../components/kalman_combinator/kalman_combinator.cpp | 2 +- .../components/modbus_controller/modbus_controller.cpp | 2 +- .../modbus_controller/number/modbus_number.h | 1 - esphome/components/sim800l/sim800l.h | 4 ++-- esphome/components/tm1651/tm1651.h | 10 +++++----- esphome/components/wifi/wifi_component.h | 2 +- 10 files changed, 14 insertions(+), 16 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 17360d2e5d..2f245a8675 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -76,7 +76,6 @@ Checks: >- -readability-isolate-declaration, -readability-magic-numbers, -readability-make-member-function-const, - -readability-named-parameter, -readability-redundant-access-specifiers, -readability-redundant-string-init, -readability-uppercase-literal-suffix, diff --git a/esphome/components/anova/anova.h b/esphome/components/anova/anova.h index 4f8f0d0ee2..3d1394980a 100644 --- a/esphome/components/anova/anova.h +++ b/esphome/components/anova/anova.h @@ -36,7 +36,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode traits.set_visual_temperature_step(0.1); return traits; } - void set_unit_of_measurement(const char *); + void set_unit_of_measurement(const char *unit); protected: std::unique_ptr codec_; diff --git a/esphome/components/api/user_services.h b/esphome/components/api/user_services.h index 1f9ffc5914..df6f6924aa 100644 --- a/esphome/components/api/user_services.h +++ b/esphome/components/api/user_services.h @@ -52,7 +52,7 @@ template class UserServiceBase : public UserServiceDescriptor { protected: virtual void execute(Ts... x) = 0; - template void execute_(std::vector args, seq) { + template void execute_(std::vector args, seq type) { this->execute((get_execute_arg_value(args[S]))...); } diff --git a/esphome/components/hm3301/hm3301.h b/esphome/components/hm3301/hm3301.h index e13ffa466e..bccdd1d35b 100644 --- a/esphome/components/hm3301/hm3301.h +++ b/esphome/components/hm3301/hm3301.h @@ -44,8 +44,8 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { AQICalculatorType aqi_calc_type_; AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory(); - bool validate_checksum_(const uint8_t *); - uint16_t get_sensor_value_(const uint8_t *, uint8_t); + bool validate_checksum_(const uint8_t *data); + uint16_t get_sensor_value_(const uint8_t *data, uint8_t i); }; } // namespace hm3301 diff --git a/esphome/components/kalman_combinator/kalman_combinator.cpp b/esphome/components/kalman_combinator/kalman_combinator.cpp index d55f26126f..50d8f03a93 100644 --- a/esphome/components/kalman_combinator/kalman_combinator.cpp +++ b/esphome/components/kalman_combinator/kalman_combinator.cpp @@ -28,7 +28,7 @@ void KalmanCombinatorComponent::add_source(Sensor *sensor, std::functionadd_source(sensor, std::function{[stddev](float) -> float { return stddev; }}); + this->add_source(sensor, std::function{[stddev](float x) -> float { return stddev; }}); } void KalmanCombinatorComponent::update_variance_() { diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 014a355cae..8b6482b1dc 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -440,7 +440,7 @@ ModbusCommandItem ModbusCommandItem::create_custom_command( cmd.modbusdevice = modbusdevice; cmd.function_code = ModbusFunctionCode::CUSTOM; if (handler == nullptr) { - cmd.on_data_func = [](ModbusRegisterType, uint16_t, const std::vector &data) { + cmd.on_data_func = [](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { ESP_LOGI(TAG, "Custom Command sent"); }; } else { diff --git a/esphome/components/modbus_controller/number/modbus_number.h b/esphome/components/modbus_controller/number/modbus_number.h index 2d6428c2c3..0c525d9c89 100644 --- a/esphome/components/modbus_controller/number/modbus_number.h +++ b/esphome/components/modbus_controller/number/modbus_number.h @@ -26,7 +26,6 @@ class ModbusNumber : public number::Number, public Component, public SensorItem 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; } diff --git a/esphome/components/sim800l/sim800l.h b/esphome/components/sim800l/sim800l.h index 21e9ac4a50..4f738b0a8c 100644 --- a/esphome/components/sim800l/sim800l.h +++ b/esphome/components/sim800l/sim800l.h @@ -49,8 +49,8 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { void dial(const std::string &recipient); protected: - void send_cmd_(const std::string &); - void parse_cmd_(std::string); + void send_cmd_(const std::string &message); + void parse_cmd_(std::string message); std::string sender_; char read_buffer_[SIM800L_READ_BUFFER_LENGTH]; diff --git a/esphome/components/tm1651/tm1651.h b/esphome/components/tm1651/tm1651.h index 72849bc8eb..eb65ed186d 100644 --- a/esphome/components/tm1651/tm1651.h +++ b/esphome/components/tm1651/tm1651.h @@ -21,9 +21,9 @@ class TM1651Display : public Component { void setup() override; void dump_config() override; - void set_level_percent(uint8_t); - void set_level(uint8_t); - void set_brightness(uint8_t); + void set_level_percent(uint8_t new_level); + void set_level(uint8_t new_level); + void set_brightness(uint8_t new_brightness); void turn_on(); void turn_off(); @@ -39,8 +39,8 @@ class TM1651Display : public Component { void repaint_(); - uint8_t calculate_level_(uint8_t); - uint8_t calculate_brightness_(uint8_t); + uint8_t calculate_level_(uint8_t new_level); + uint8_t calculate_brightness_(uint8_t new_brightness); }; template class SetLevelPercentAction : public Action, public Parented { diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index d04f15695d..118de3a7a3 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -299,7 +299,7 @@ class WiFiComponent : public Component { void wifi_scan_done_callback_(); #endif #ifdef USE_ESP_IDF - void wifi_process_event_(IDFWiFiEvent *); + void wifi_process_event_(IDFWiFiEvent *data); #endif std::string use_address_; From 97681d142e9b0e98c1dcc3088bca81eb901086b2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 23 Jan 2022 08:47:22 +0100 Subject: [PATCH 031/238] Enable readability-redundant-access-specifiers check (#3096) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .clang-tidy | 1 - esphome/components/adalight/adalight_light_effect.h | 2 -- esphome/components/e131/e131.h | 3 --- esphome/components/e131/e131_addressable_light_effect.h | 4 ---- .../components/esp32_camera_web_server/camera_web_server.h | 1 - .../modbus_controller/text_sensor/modbus_textsensor.h | 1 - esphome/components/mqtt/mqtt_component.h | 1 - esphome/components/wled/wled_light_effect.h | 2 -- 8 files changed, 15 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 2f245a8675..f784cd65ec 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -76,7 +76,6 @@ Checks: >- -readability-isolate-declaration, -readability-magic-numbers, -readability-make-member-function-const, - -readability-redundant-access-specifiers, -readability-redundant-string-init, -readability-uppercase-literal-suffix, -readability-use-anyofallof, diff --git a/esphome/components/adalight/adalight_light_effect.h b/esphome/components/adalight/adalight_light_effect.h index b757191864..72faf44269 100644 --- a/esphome/components/adalight/adalight_light_effect.h +++ b/esphome/components/adalight/adalight_light_effect.h @@ -13,7 +13,6 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U public: AdalightLightEffect(const std::string &name); - public: void start() override; void stop() override; void apply(light::AddressableLight &it, const Color ¤t_color) override; @@ -30,7 +29,6 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U void blank_all_leds_(light::AddressableLight &it); Frame parse_frame_(light::AddressableLight &it); - protected: uint32_t last_ack_{0}; uint32_t last_byte_{0}; uint32_t last_reset_{0}; diff --git a/esphome/components/e131/e131.h b/esphome/components/e131/e131.h index 3819e522a5..648cfb4585 100644 --- a/esphome/components/e131/e131.h +++ b/esphome/components/e131/e131.h @@ -33,11 +33,9 @@ class E131Component : public esphome::Component { void loop() override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } - public: void add_effect(E131AddressableLightEffect *light_effect); void remove_effect(E131AddressableLightEffect *light_effect); - public: void set_method(E131ListenMethod listen_method) { this->listen_method_ = listen_method; } protected: @@ -47,7 +45,6 @@ class E131Component : public esphome::Component { void join_(int universe); void leave_(int universe); - protected: E131ListenMethod listen_method_{E131_MULTICAST}; std::unique_ptr udp_; std::set light_effects_; diff --git a/esphome/components/e131/e131_addressable_light_effect.h b/esphome/components/e131/e131_addressable_light_effect.h index e78f6bb0e0..b3e481e43b 100644 --- a/esphome/components/e131/e131_addressable_light_effect.h +++ b/esphome/components/e131/e131_addressable_light_effect.h @@ -17,19 +17,16 @@ class E131AddressableLightEffect : public light::AddressableLightEffect { public: E131AddressableLightEffect(const std::string &name); - public: void start() override; void stop() override; void apply(light::AddressableLight &it, const Color ¤t_color) override; - public: int get_data_per_universe() const; int get_lights_per_universe() const; int get_first_universe() const; int get_last_universe() const; int get_universe_count() const; - public: void set_first_universe(int universe) { this->first_universe_ = universe; } void set_channels(E131LightChannels channels) { this->channels_ = channels; } void set_e131(E131Component *e131) { this->e131_ = e131; } @@ -37,7 +34,6 @@ class E131AddressableLightEffect : public light::AddressableLightEffect { protected: bool process_(int universe, const E131Packet &packet); - protected: int first_universe_{0}; int last_universe_{0}; E131LightChannels channels_{E131_RGB}; diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.h b/esphome/components/esp32_camera_web_server/camera_web_server.h index df30a43ed2..509ca81592 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.h +++ b/esphome/components/esp32_camera_web_server/camera_web_server.h @@ -36,7 +36,6 @@ class CameraWebServer : public Component { 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_; diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h index 571a43d9a9..c52c6cd072 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h @@ -35,7 +35,6 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi protected: optional transform_func_{nullopt}; - protected: RawEncoding encode_; }; diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 55fb8d11c1..994ab952c2 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -186,7 +186,6 @@ class MQTTComponent : public Component { /// Generate the Home Assistant MQTT discovery object id by automatically transforming the friendly name. std::string get_default_object_id_() const; - protected: std::string custom_state_topic_{}; std::string custom_command_topic_{}; bool retain_{true}; diff --git a/esphome/components/wled/wled_light_effect.h b/esphome/components/wled/wled_light_effect.h index f0021ca978..8f239276d7 100644 --- a/esphome/components/wled/wled_light_effect.h +++ b/esphome/components/wled/wled_light_effect.h @@ -17,7 +17,6 @@ class WLEDLightEffect : public light::AddressableLightEffect { public: WLEDLightEffect(const std::string &name); - public: void start() override; void stop() override; void apply(light::AddressableLight &it, const Color ¤t_color) override; @@ -32,7 +31,6 @@ class WLEDLightEffect : public light::AddressableLightEffect { bool parse_drgbw_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size); bool parse_dnrgb_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size); - protected: uint16_t port_{0}; std::unique_ptr udp_; uint32_t blank_at_{0}; From 8187a4bce915e293902f5fb8127cc39454589b28 Mon Sep 17 00:00:00 2001 From: VitaliyKurokhtin Date: Sun, 23 Jan 2022 00:05:37 -0800 Subject: [PATCH 032/238] Command retain option for MQTT component (#3078) --- esphome/components/mqtt/__init__.py | 3 + esphome/components/mqtt/mqtt_component.cpp | 3 + esphome/components/mqtt/mqtt_component.h | 3 + esphome/components/mqtt/mqtt_const.h | 2 + esphome/config_validation.py | 2 + esphome/const.py | 1 + tests/test1.yaml | 579 +++++++++++---------- 7 files changed, 304 insertions(+), 289 deletions(-) diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 755b0c685c..a7e14bd4a6 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -11,6 +11,7 @@ from esphome.const import ( CONF_BROKER, CONF_CLIENT_ID, CONF_COMMAND_TOPIC, + CONF_COMMAND_RETAIN, CONF_DISCOVERY, CONF_DISCOVERY_PREFIX, CONF_DISCOVERY_RETAIN, @@ -392,6 +393,8 @@ async def register_mqtt_component(var, config): cg.add(var.set_custom_state_topic(config[CONF_STATE_TOPIC])) if CONF_COMMAND_TOPIC in config: cg.add(var.set_custom_command_topic(config[CONF_COMMAND_TOPIC])) + if CONF_COMMAND_RETAIN in config: + cg.add(var.set_command_retain(config[CONF_COMMAND_RETAIN])) if CONF_AVAILABILITY in config: availability = config[CONF_AVAILABILITY] if not availability: diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index a477701cb9..341ac50e37 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -92,6 +92,8 @@ bool MQTTComponent::send_discovery_() { root[MQTT_STATE_TOPIC] = this->get_state_topic_(); if (config.command_topic) root[MQTT_COMMAND_TOPIC] = this->get_command_topic_(); + if (this->command_retain_) + root[MQTT_COMMAND_RETAIN] = true; if (this->availability_ == nullptr) { if (!global_mqtt_client->get_availability().topic.empty()) { @@ -165,6 +167,7 @@ void MQTTComponent::set_custom_state_topic(const std::string &custom_state_topic void MQTTComponent::set_custom_command_topic(const std::string &custom_command_topic) { this->custom_command_topic_ = custom_command_topic; } +void MQTTComponent::set_command_retain(bool command_retain) { this->command_retain_ = command_retain; } void MQTTComponent::set_availability(std::string topic, std::string payload_available, std::string payload_not_available) { diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 994ab952c2..16a00cfdde 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -91,6 +91,8 @@ class MQTTComponent : public Component { void set_custom_state_topic(const std::string &custom_state_topic); /// Set a custom command topic. Set to "" for default behavior. void set_custom_command_topic(const std::string &custom_command_topic); + /// Set whether command message should be retained. + void set_command_retain(bool command_retain); /// MQTT_COMPONENT setup priority. float get_setup_priority() const override; @@ -188,6 +190,7 @@ class MQTTComponent : public Component { std::string custom_state_topic_{}; std::string custom_command_topic_{}; + bool command_retain_{false}; bool retain_{true}; bool discovery_enabled_{true}; std::unique_ptr availability_; diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h index 8134a6b53e..52ca0ed7c0 100644 --- a/esphome/components/mqtt/mqtt_const.h +++ b/esphome/components/mqtt/mqtt_const.h @@ -45,6 +45,7 @@ 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_RETAIN = "ret"; 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"; @@ -297,6 +298,7 @@ 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_RETAIN = "retain"; 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"; diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 8df74ba861..7b68681ed2 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -17,6 +17,7 @@ from esphome.const import ( ALLOWED_NAME_CHARS, CONF_AVAILABILITY, CONF_COMMAND_TOPIC, + CONF_COMMAND_RETAIN, CONF_DISABLED_BY_DEFAULT, CONF_DISCOVERY, CONF_ENTITY_CATEGORY, @@ -1591,6 +1592,7 @@ MQTT_COMPONENT_SCHEMA = Schema( MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend( { Optional(CONF_COMMAND_TOPIC): All(requires_component("mqtt"), subscribe_topic), + Optional(CONF_COMMAND_RETAIN): All(requires_component("mqtt"), boolean), } ) diff --git a/esphome/const.py b/esphome/const.py index 8dbfd8d1dc..0c8e53380e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -106,6 +106,7 @@ CONF_COLOR_MODE = "color_mode" CONF_COLOR_TEMPERATURE = "color_temperature" CONF_COLORS = "colors" CONF_COMMAND = "command" +CONF_COMMAND_RETAIN = "command_retain" CONF_COMMAND_TOPIC = "command_topic" CONF_COMMENT = "comment" CONF_COMMIT = "commit" diff --git a/tests/test1.yaml b/tests/test1.yaml index d3351e3b12..0ded069638 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -29,7 +29,7 @@ esphome: range_from: 0 range_to: 100 red: 100% - green: !lambda 'return 255;' + green: !lambda "return 255;" blue: 0% white: 100% - http_request.get: @@ -43,18 +43,18 @@ esphome: json: key: !lambda |- return id(${textname}_text).state; - greeting: 'Hello World' + greeting: "Hello World" - http_request.send: method: PUT url: https://esphome.io headers: Content-Type: application/json - body: 'Some data' + body: "Some data" verify_ssl: false on_response: then: - logger.log: - format: 'Response status: %d' + format: "Response status: %d" args: - status_code build_path: build/test1 @@ -65,12 +65,12 @@ packages: wifi: networks: - - ssid: 'MySSID' - password: 'password1' - - ssid: 'MySSID2' - password: '' + - ssid: "MySSID" + password: "password1" + - ssid: "MySSID2" + password: "" channel: 14 - bssid: 'A1:63:95:47:D3:1D' + bssid: "A1:63:95:47:D3:1D" manual_ip: static_ip: 192.168.178.230 gateway: 192.168.178.1 @@ -89,10 +89,10 @@ http_request: timeout: 10s mqtt: - broker: '192.168.178.84' + broker: "192.168.178.84" port: 1883 - username: 'debug' - password: 'debug' + username: "debug" + password: "debug" client_id: someclient use_abbreviations: false discovery: True @@ -153,7 +153,7 @@ mqtt: return effect; - light.control: id: ${roomname}_lights - brightness: !lambda 'return id(${roomname}_lights).current_values.get_brightness() + 0.5;' + brightness: !lambda "return id(${roomname}_lights).current_values.get_brightness() + 0.5;" - light.dim_relative: id: ${roomname}_lights relative_brightness: 5% @@ -215,7 +215,7 @@ uart: ota: safe_mode: True - password: 'superlongpasswordthatnoonewillknow' + password: "superlongpasswordthatnoonewillknow" port: 3286 reboot_timeout: 2min num_attempts: 5 @@ -252,7 +252,7 @@ web_server: js_url: https://esphome.io/_static/webserver-v1.min.js power_supply: - id: 'atx_power_supply' + id: "atx_power_supply" enable_time: 20ms keep_on_time: 10s pin: @@ -294,22 +294,22 @@ ble_client: then: - switch.turn_on: ble1_status mcp23s08: - - id: 'mcp23s08_hub' + - id: "mcp23s08_hub" cs_pin: GPIO12 deviceaddress: 0 mcp23s17: - - id: 'mcp23s17_hub' + - id: "mcp23s17_hub" cs_pin: GPIO12 deviceaddress: 1 sensor: - platform: ble_client ble_client_id: ble_foo - name: 'Green iTag btn' - service_uuid: 'ffe0' - characteristic_uuid: 'ffe1' - descriptor_uuid: 'ffe2' + name: "Green iTag btn" + service_uuid: "ffe0" + characteristic_uuid: "ffe1" + descriptor_uuid: "ffe2" notify: true update_interval: never lambda: |- @@ -321,11 +321,11 @@ sensor: ESP_LOGD("green_btn", "Button was pressed, val%f", x); - platform: adc pin: A0 - name: 'Living Room Brightness' - update_interval: '1:01' + name: "Living Room Brightness" + update_interval: "1:01" attenuation: 2.5db - unit_of_measurement: '°C' - icon: 'mdi:water-percent' + unit_of_measurement: "°C" + icon: "mdi:water-percent" accuracy_decimals: 5 expire_after: 120s setup_priority: -100 @@ -390,9 +390,9 @@ sensor: ESP_LOGD("main", "Got raw value %f", x); - logger.log: level: DEBUG - format: 'Got raw value %f' - args: ['x'] - - logger.log: 'Got raw value NAN' + format: "Got raw value %f" + args: ["x"] + - logger.log: "Got raw value NAN" - mqtt.publish: topic: some/topic payload: Hello @@ -401,7 +401,7 @@ sensor: - platform: esp32_hall name: ESP32 Hall Sensor - platform: ads1115 - multiplexer: 'A0_A1' + multiplexer: "A0_A1" gain: 1.024 id: ${sensorname}_sensor filters: @@ -412,48 +412,48 @@ sensor: cs_pin: 5 phase_a: voltage: - name: 'EMON Line Voltage A' + name: "EMON Line Voltage A" current: - name: 'EMON CT1 Current' + name: "EMON CT1 Current" power: - name: 'EMON Active Power CT1' + name: "EMON Active Power CT1" reactive_power: - name: 'EMON Reactive Power CT1' + name: "EMON Reactive Power CT1" power_factor: - name: 'EMON Power Factor CT1' + name: "EMON Power Factor CT1" gain_voltage: 7305 gain_ct: 27961 phase_b: current: - name: 'EMON CT2 Current' + name: "EMON CT2 Current" power: - name: 'EMON Active Power CT2' + name: "EMON Active Power CT2" reactive_power: - name: 'EMON Reactive Power CT2' + name: "EMON Reactive Power CT2" power_factor: - name: 'EMON Power Factor CT2' + name: "EMON Power Factor CT2" gain_voltage: 7305 gain_ct: 27961 phase_c: current: - name: 'EMON CT3 Current' + name: "EMON CT3 Current" power: - name: 'EMON Active Power CT3' + name: "EMON Active Power CT3" reactive_power: - name: 'EMON Reactive Power CT3' + name: "EMON Reactive Power CT3" power_factor: - name: 'EMON Power Factor CT3' + name: "EMON Power Factor CT3" gain_voltage: 7305 gain_ct: 27961 frequency: - name: 'EMON Line Frequency' + name: "EMON Line Frequency" chip_temperature: - name: 'EMON Chip Temp A' + name: "EMON Chip Temp A" line_frequency: 60Hz current_phases: 3 gain_pga: 2X - platform: bh1750 - name: 'Living Room Brightness 3' + name: "Living Room Brightness 3" internal: true address: 0x23 resolution: 1.0 @@ -465,13 +465,13 @@ sensor: i2c_id: i2c_bus - platform: bme280 temperature: - name: 'Outside Temperature' + name: "Outside Temperature" oversampling: 16x pressure: - name: 'Outside Pressure' + name: "Outside Pressure" oversampling: none humidity: - name: 'Outside Humidity' + name: "Outside Humidity" oversampling: 8x address: 0x77 iir_filter: 16x @@ -479,14 +479,14 @@ sensor: i2c_id: i2c_bus - platform: bme680 temperature: - name: 'Outside Temperature' + name: "Outside Temperature" oversampling: 16x pressure: - name: 'Outside Pressure' + name: "Outside Pressure" humidity: - name: 'Outside Humidity' + name: "Outside Humidity" gas_resistance: - name: 'Outside Gas Sensor' + name: "Outside Gas Sensor" address: 0x77 heater: temperature: 320 @@ -495,9 +495,9 @@ sensor: i2c_id: i2c_bus - platform: bmp085 temperature: - name: 'Outside Temperature' + name: "Outside Temperature" pressure: - name: 'Outside Pressure' + name: "Outside Pressure" filters: - lambda: >- return x / powf(1.0 - (x / 44330.0), 5.255); @@ -505,47 +505,47 @@ sensor: i2c_id: i2c_bus - platform: bmp280 temperature: - name: 'Outside Temperature' + name: "Outside Temperature" oversampling: 16x pressure: - name: 'Outside Pressure' + name: "Outside Pressure" address: 0x77 update_interval: 15s iir_filter: 16x i2c_id: i2c_bus - platform: dallas address: 0x1C0000031EDD2A28 - name: 'Living Room Temperature' + name: "Living Room Temperature" resolution: 9 - platform: dallas index: 1 - name: 'Living Room Temperature 2' + name: "Living Room Temperature 2" - platform: dht pin: GPIO26 temperature: - name: 'Living Room Temperature 3' + name: "Living Room Temperature 3" humidity: - name: 'Living Room Humidity 3' + name: "Living Room Humidity 3" model: AM2302 update_interval: 15s - platform: dht12 temperature: - name: 'Living Room Temperature 4' + name: "Living Room Temperature 4" humidity: - name: 'Living Room Humidity 4' + name: "Living Room Humidity 4" update_interval: 15s i2c_id: i2c_bus - platform: duty_cycle pin: GPIO25 name: Duty Cycle Sensor - platform: esp32_hall - name: 'ESP32 Hall Sensor' + name: "ESP32 Hall Sensor" update_interval: 15s - platform: hdc1080 temperature: - name: 'Living Room Temperature 5' + name: "Living Room Temperature 5" humidity: - name: 'Living Room Pressure 5' + name: "Living Room Pressure 5" update_interval: 15s i2c_id: i2c_bus - platform: hlw8012 @@ -553,14 +553,14 @@ sensor: cf_pin: 14 cf1_pin: 13 current: - name: 'HLW8012 Current' + name: "HLW8012 Current" voltage: - name: 'HLW8012 Voltage' + name: "HLW8012 Voltage" power: - name: 'HLW8012 Power' + name: "HLW8012 Power" id: hlw8012_power energy: - name: 'HLW8012 Energy' + name: "HLW8012 Energy" id: hlw8012_energy update_interval: 15s current_resistor: 0.001 ohm @@ -570,26 +570,26 @@ sensor: model: hlw8012 - platform: total_daily_energy power_id: hlw8012_power - name: 'HLW8012 Total Daily Energy' + name: "HLW8012 Total Daily Energy" - platform: integration sensor: hlw8012_power - name: 'Integration Sensor' + name: "Integration Sensor" time_unit: s - platform: integration sensor: hlw8012_power - name: 'Integration Sensor lazy' + name: "Integration Sensor lazy" time_unit: s min_save_interval: 60s - platform: hmc5883l address: 0x68 field_strength_x: - name: 'HMC5883L Field Strength X' + name: "HMC5883L Field Strength X" field_strength_y: - name: 'HMC5883L Field Strength Y' + name: "HMC5883L Field Strength Y" field_strength_z: - name: 'HMC5883L Field Strength Z' + name: "HMC5883L Field Strength Z" heading: - name: 'HMC5883L Heading' + name: "HMC5883L Heading" range: 130uT oversampling: 8x update_interval: 15s @@ -597,19 +597,19 @@ sensor: - platform: qmc5883l address: 0x0D field_strength_x: - name: 'QMC5883L Field Strength X' + name: "QMC5883L Field Strength X" field_strength_y: - name: 'QMC5883L Field Strength Y' + name: "QMC5883L Field Strength Y" field_strength_z: - name: 'QMC5883L Field Strength Z' + name: "QMC5883L Field Strength Z" heading: - name: 'QMC5883L Heading' + name: "QMC5883L Heading" range: 800uT oversampling: 256x update_interval: 15s i2c_id: i2c_bus - platform: hx711 - name: 'HX711 Value' + name: "HX711 Value" dout_pin: GPIO23 clk_pin: GPIO25 gain: 128 @@ -618,13 +618,13 @@ sensor: address: 0x40 shunt_resistance: 0.1 ohm current: - name: 'INA219 Current' + name: "INA219 Current" power: - name: 'INA219 Power' + name: "INA219 Power" bus_voltage: - name: 'INA219 Bus Voltage' + name: "INA219 Bus Voltage" shunt_voltage: - name: 'INA219 Shunt Voltage' + name: "INA219 Shunt Voltage" max_voltage: 32.0V max_current: 3.2A update_interval: 15s @@ -633,13 +633,13 @@ sensor: address: 0x40 shunt_resistance: 0.1 ohm current: - name: 'INA226 Current' + name: "INA226 Current" power: - name: 'INA226 Power' + name: "INA226 Power" bus_voltage: - name: 'INA226 Bus Voltage' + name: "INA226 Bus Voltage" shunt_voltage: - name: 'INA226 Shunt Voltage' + name: "INA226 Shunt Voltage" max_current: 3.2A update_interval: 15s i2c_id: i2c_bus @@ -648,13 +648,13 @@ sensor: channel_1: shunt_resistance: 0.1 ohm current: - name: 'INA3221 Channel 1 Current' + name: "INA3221 Channel 1 Current" power: - name: 'INA3221 Channel 1 Power' + name: "INA3221 Channel 1 Power" bus_voltage: - name: 'INA3221 Channel 1 Bus Voltage' + name: "INA3221 Channel 1 Bus Voltage" shunt_voltage: - name: 'INA3221 Channel 1 Shunt Voltage' + name: "INA3221 Channel 1 Shunt Voltage" update_interval: 15s i2c_id: i2c_bus - platform: kalman_combinator @@ -668,62 +668,62 @@ sensor: error: 1.5 - platform: htu21d temperature: - name: 'Living Room Temperature 6' + name: "Living Room Temperature 6" humidity: - name: 'Living Room Humidity 6' + name: "Living Room Humidity 6" update_interval: 15s i2c_id: i2c_bus - platform: max6675 - name: 'Living Room Temperature' + name: "Living Room Temperature" cs_pin: GPIO23 update_interval: 15s - platform: max31855 - name: 'Den Temperature' + name: "Den Temperature" cs_pin: GPIO23 update_interval: 15s reference_temperature: - name: 'MAX31855 Internal Temperature' + name: "MAX31855 Internal Temperature" - platform: max31856 - name: 'BBQ Temperature' + name: "BBQ Temperature" cs_pin: GPIO17 update_interval: 15s mains_filter: 50Hz - platform: max31865 - name: 'Water Tank Temperature' + name: "Water Tank Temperature" cs_pin: GPIO23 update_interval: 15s - reference_resistance: '430 Ω' - rtd_nominal_resistance: '100 Ω' + reference_resistance: "430 Ω" + rtd_nominal_resistance: "100 Ω" - platform: mhz19 uart_id: uart0 co2: - name: 'MH-Z19 CO2 Value' + name: "MH-Z19 CO2 Value" temperature: - name: 'MH-Z19 Temperature' + name: "MH-Z19 Temperature" update_interval: 15s automatic_baseline_calibration: false - platform: mpu6050 address: 0x68 accel_x: - name: 'MPU6050 Accel X' + name: "MPU6050 Accel X" accel_y: - name: 'MPU6050 Accel Y' + name: "MPU6050 Accel Y" accel_z: - name: 'MPU6050 Accel z' + name: "MPU6050 Accel z" gyro_x: - name: 'MPU6050 Gyro X' + name: "MPU6050 Gyro X" gyro_y: - name: 'MPU6050 Gyro Y' + name: "MPU6050 Gyro Y" gyro_z: - name: 'MPU6050 Gyro z' + name: "MPU6050 Gyro z" temperature: - name: 'MPU6050 Temperature' + name: "MPU6050 Temperature" i2c_id: i2c_bus - platform: ms5611 temperature: - name: 'Outside Temperature' + name: "Outside Temperature" pressure: - name: 'Outside Pressure' + name: "Outside Pressure" address: 0x77 update_interval: 15s i2c_id: i2c_bus @@ -750,7 +750,7 @@ sensor: standard_units: True i2c_id: i2c_bus - platform: pulse_counter - name: 'Pulse Counter' + name: "Pulse Counter" pin: GPIO12 count_mode: rising_edge: INCREMENT @@ -758,7 +758,7 @@ sensor: internal_filter: 13us update_interval: 15s - platform: pulse_meter - name: 'Pulse Meter' + name: "Pulse Meter" id: pulse_meter_sensor pin: GPIO12 internal_filter: 100ms @@ -768,9 +768,9 @@ sensor: id: pulse_meter_sensor value: 12345 total: - name: 'Pulse Meter Total' + name: "Pulse Meter Total" - platform: rotary_encoder - name: 'Rotary Encoder' + name: "Rotary Encoder" id: rotary_encoder1 pin_a: GPIO23 pin_b: GPIO25 @@ -788,51 +788,51 @@ sensor: value: 10 - sensor.rotary_encoder.set_value: id: rotary_encoder1 - value: !lambda 'return -1;' + value: !lambda "return -1;" on_clockwise: - - logger.log: 'Clockwise' + - logger.log: "Clockwise" on_anticlockwise: - - logger.log: 'Anticlockwise' + - logger.log: "Anticlockwise" - platform: pulse_width name: Pulse Width pin: GPIO12 - platform: sm300d2 uart_id: uart0 co2: - name: 'SM300D2 CO2 Value' + name: "SM300D2 CO2 Value" formaldehyde: - name: 'SM300D2 Formaldehyde Value' + name: "SM300D2 Formaldehyde Value" tvoc: - name: 'SM300D2 TVOC Value' + name: "SM300D2 TVOC Value" pm_2_5: - name: 'SM300D2 PM2.5 Value' + name: "SM300D2 PM2.5 Value" pm_10_0: - name: 'SM300D2 PM10 Value' + name: "SM300D2 PM10 Value" temperature: - name: 'SM300D2 Temperature Value' + name: "SM300D2 Temperature Value" humidity: - name: 'SM300D2 Humidity Value' + name: "SM300D2 Humidity Value" update_interval: 60s - platform: sht3xd temperature: - name: 'Living Room Temperature 8' + name: "Living Room Temperature 8" humidity: - name: 'Living Room Humidity 8' + name: "Living Room Humidity 8" address: 0x44 i2c_id: i2c_bus update_interval: 15s - platform: sts3x - name: 'Living Room Temperature 9' + name: "Living Room Temperature 9" address: 0x4A i2c_id: i2c_bus - platform: scd30 co2: - name: 'Living Room CO2 9' + name: "Living Room CO2 9" temperature: id: scd30_temperature - name: 'Living Room Temperature 9' + name: "Living Room Temperature 9" humidity: - name: 'Living Room Humidity 9' + name: "Living Room Humidity 9" address: 0x61 update_interval: 15s automatic_self_calibration: true @@ -856,63 +856,63 @@ sensor: i2c_id: i2c_bus - platform: sgp30 eco2: - name: 'Workshop eCO2' + name: "Workshop eCO2" accuracy_decimals: 1 tvoc: - name: 'Workshop TVOC' + name: "Workshop TVOC" accuracy_decimals: 1 address: 0x58 update_interval: 5s i2c_id: i2c_bus - platform: sps30 pm_1_0: - name: 'Workshop PM <1µm Weight concentration' - id: 'workshop_PM_1_0' + name: "Workshop PM <1µm Weight concentration" + id: "workshop_PM_1_0" pm_2_5: - name: 'Workshop PM <2.5µm Weight concentration' - id: 'workshop_PM_2_5' + name: "Workshop PM <2.5µm Weight concentration" + id: "workshop_PM_2_5" pm_4_0: - name: 'Workshop PM <4µm Weight concentration' - id: 'workshop_PM_4_0' + name: "Workshop PM <4µm Weight concentration" + id: "workshop_PM_4_0" pm_10_0: - name: 'Workshop PM <10µm Weight concentration' - id: 'workshop_PM_10_0' + name: "Workshop PM <10µm Weight concentration" + id: "workshop_PM_10_0" pmc_0_5: - name: 'Workshop PM <0.5µm Number concentration' - id: 'workshop_PMC_0_5' + name: "Workshop PM <0.5µm Number concentration" + id: "workshop_PMC_0_5" pmc_1_0: - name: 'Workshop PM <1µm Number concentration' - id: 'workshop_PMC_1_0' + name: "Workshop PM <1µm Number concentration" + id: "workshop_PMC_1_0" pmc_2_5: - name: 'Workshop PM <2.5µm Number concentration' - id: 'workshop_PMC_2_5' + name: "Workshop PM <2.5µm Number concentration" + id: "workshop_PMC_2_5" pmc_4_0: - name: 'Workshop PM <4µm Number concentration' - id: 'workshop_PMC_4_0' + name: "Workshop PM <4µm Number concentration" + id: "workshop_PMC_4_0" pmc_10_0: - name: 'Workshop PM <10µm Number concentration' - id: 'workshop_PMC_10_0' + name: "Workshop PM <10µm Number concentration" + id: "workshop_PMC_10_0" address: 0x69 update_interval: 10s i2c_id: i2c_bus - platform: sht4x temperature: - name: 'SHT4X Temperature' + name: "SHT4X Temperature" humidity: - name: 'SHT4X Humidity' + name: "SHT4X Humidity" address: 0x44 update_interval: 15s i2c_id: i2c_bus - platform: shtcx temperature: - name: 'Living Room Temperature 10' + name: "Living Room Temperature 10" humidity: - name: 'Living Room Humidity 10' + name: "Living Room Humidity 10" address: 0x70 update_interval: 15s i2c_id: i2c_bus - platform: template - name: 'Template Sensor' + name: "Template Sensor" state_class: measurement id: template_sensor lambda: |- @@ -928,9 +928,9 @@ sensor: state: 43.0 - sensor.template.publish: id: template_sensor - state: !lambda 'return NAN;' + state: !lambda "return NAN;" - platform: tsl2561 - name: 'TSL2561 Ambient Light' + name: "TSL2561 Ambient Light" address: 0x39 update_interval: 15s is_cs_package: true @@ -946,7 +946,7 @@ sensor: visible: name: "tsl2591 visible" id: tsl2591_vis - unit_of_measurement: 'pH' + unit_of_measurement: "pH" infrared: name: "tsl2591 infrared" id: tsl2591_ir @@ -962,17 +962,17 @@ sensor: echo_pin: number: GPIO23 inverted: true - name: 'Ultrasonic Sensor' + name: "Ultrasonic Sensor" timeout: 5.5m id: ultrasonic_sensor1 - platform: uptime name: Uptime Sensor - platform: wifi_signal - name: 'WiFi Signal Sensor' + name: "WiFi Signal Sensor" update_interval: 15s - platform: mqtt_subscribe - name: 'MQTT Subscribe Sensor 1' - topic: 'mqtt/topic' + name: "MQTT Subscribe Sensor 1" + topic: "mqtt/topic" id: the_sensor qos: 2 on_value: @@ -984,9 +984,9 @@ sensor: - platform: sds011 uart_id: uart0 pm_2_5: - name: 'SDS011 PM2.5' + name: "SDS011 PM2.5" pm_10_0: - name: 'SDS011 PM10.0' + name: "SDS011 PM10.0" update_interval: 5min rx_only: false - platform: ccs811 @@ -999,9 +999,9 @@ sensor: i2c_id: i2c_bus - platform: tx20 wind_speed: - name: 'Windspeed' + name: "Windspeed" wind_direction_degrees: - name: 'Winddirection Degrees' + name: "Winddirection Degrees" pin: number: GPIO04 mode: INPUT @@ -1009,30 +1009,30 @@ sensor: clock_pin: GPIO5 data_pin: GPIO4 co2: - name: 'ZyAura CO2' + name: "ZyAura CO2" temperature: - name: 'ZyAura Temperature' + name: "ZyAura Temperature" humidity: - name: 'ZyAura Humidity' + name: "ZyAura Humidity" - platform: as3935 lightning_energy: - name: 'Lightning Energy' + name: "Lightning Energy" distance: - name: 'Distance Storm' + name: "Distance Storm" - platform: tmp117 - name: 'TMP117 Temperature' + name: "TMP117 Temperature" update_interval: 5s i2c_id: i2c_bus - platform: hm3301 pm_1_0: - name: 'PM1.0' + name: "PM1.0" pm_2_5: - name: 'PM2.5' + name: "PM2.5" pm_10_0: - name: 'PM10.0' + name: "PM10.0" aqi: - name: 'AQI' - calculation_type: 'CAQI' + name: "AQI" + calculation_type: "CAQI" i2c_id: i2c_bus - platform: teleinfo tag_name: "HCHC" @@ -1041,13 +1041,13 @@ sensor: icon: mdi:flash teleinfo_id: myteleinfo - platform: mcp9808 - name: 'MCP9808 Temperature' + name: "MCP9808 Temperature" update_interval: 15s i2c_id: i2c_bus - platform: ezo id: ph_ezo address: 99 - unit_of_measurement: 'pH' + unit_of_measurement: "pH" i2c_id: i2c_bus - platform: sdp3x name: "HVAC Filter Pressure drop" @@ -1089,7 +1089,7 @@ esp32_touch: binary_sensor: - platform: gpio - name: 'MCP23S08 Pin #1' + name: "MCP23S08 Pin #1" pin: mcp23xxx: mcp23s08_hub # Use pin number 1 @@ -1098,7 +1098,7 @@ binary_sensor: mode: INPUT_PULLUP inverted: False - platform: gpio - name: 'MCP23S17 Pin #1' + name: "MCP23S17 Pin #1" pin: mcp23xxx: mcp23s17_hub # Use pin number 1 @@ -1107,7 +1107,7 @@ binary_sensor: mode: INPUT_PULLUP inverted: False - platform: gpio - name: 'MCP23S17 Pin #1 with interrupt' + name: "MCP23S17 Pin #1 with interrupt" pin: mcp23xxx: mcp23s17_hub # Use pin number 1 @@ -1118,7 +1118,7 @@ binary_sensor: interrupt: FALLING - platform: gpio pin: GPIO9 - name: 'Living Room Window' + name: "Living Room Window" device_class: window filters: - invert: @@ -1158,7 +1158,7 @@ binary_sensor: - OFF for at least 0.2s then: - logger.log: - format: 'Multi Clicked TWO' + format: "Multi Clicked TWO" level: warn - timing: - OFF for 1s to 2s @@ -1166,30 +1166,30 @@ binary_sensor: - OFF for at least 0.5s then: - logger.log: - format: 'Multi Clicked LONG SINGLE' + format: "Multi Clicked LONG SINGLE" level: warn - timing: - ON for at most 1s - OFF for at least 0.5s then: - logger.log: - format: 'Multi Clicked SINGLE' + format: "Multi Clicked SINGLE" level: warn id: binary_sensor1 - platform: gpio pin: number: GPIO9 mode: INPUT_PULLUP - name: 'Living Room Window 2' + name: "Living Room Window 2" - platform: status - name: 'Living Room Status' + name: "Living Room Status" - platform: esp32_touch - name: 'ESP32 Touch Pad GPIO27' + name: "ESP32 Touch Pad GPIO27" pin: GPIO27 threshold: 1000 id: btn_left - platform: template - name: 'Garage Door Open' + name: "Garage Door Open" id: garage_door lambda: |- if (isnan(id(${sensorname}_sensor).state)) { @@ -1213,37 +1213,37 @@ binary_sensor: frequency: 500.0Hz - output.ledc.set_frequency: id: gpio_19 - frequency: !lambda 'return 500.0;' + frequency: !lambda "return 500.0;" - platform: pn532 pn532_id: pn532_bs uid: 74-10-37-94 - name: 'PN532 NFC Tag' + name: "PN532 NFC Tag" - platform: rdm6300 uid: 7616525 - name: 'RDM6300 NFC Tag' + name: "RDM6300 NFC Tag" - platform: gpio - name: 'PCF binary sensor' + name: "PCF binary sensor" pin: pcf8574: pcf8574_hub number: 1 mode: INPUT inverted: True - platform: gpio - name: 'MCP21 binary sensor' + name: "MCP21 binary sensor" pin: mcp23xxx: mcp23017_hub number: 1 mode: INPUT inverted: True - platform: gpio - name: 'MCP22 binary sensor' + name: "MCP22 binary sensor" pin: mcp23xxx: mcp23008_hub number: 7 mode: INPUT_PULLUP inverted: False - platform: gpio - name: 'MCP23 binary sensor' + name: "MCP23 binary sensor" pin: mcp23016: mcp23016_hub number: 7 @@ -1251,7 +1251,7 @@ binary_sensor: inverted: False - platform: remote_receiver - name: 'Raw Remote Receiver Test' + name: "Raw Remote Receiver Test" raw: code: [ @@ -1292,7 +1292,7 @@ binary_sensor: 1709, ] - platform: as3935 - name: 'Storm Alert' + name: "Storm Alert" pca9685: frequency: 500 @@ -1356,39 +1356,39 @@ output: - platform: tlc59208f id: tlc_0 channel: 0 - tlc59208f_id: 'tlc59208f_1' + tlc59208f_id: "tlc59208f_1" - platform: tlc59208f id: tlc_1 channel: 1 - tlc59208f_id: 'tlc59208f_1' + tlc59208f_id: "tlc59208f_1" - platform: tlc59208f id: tlc_2 channel: 2 - tlc59208f_id: 'tlc59208f_1' + tlc59208f_id: "tlc59208f_1" - platform: tlc59208f id: tlc_3 channel: 0 - tlc59208f_id: 'tlc59208f_2' + tlc59208f_id: "tlc59208f_2" - platform: tlc59208f id: tlc_4 channel: 1 - tlc59208f_id: 'tlc59208f_2' + tlc59208f_id: "tlc59208f_2" - platform: tlc59208f id: tlc_5 channel: 2 - tlc59208f_id: 'tlc59208f_2' + tlc59208f_id: "tlc59208f_2" - platform: tlc59208f id: tlc_6 channel: 0 - tlc59208f_id: 'tlc59208f_3' + tlc59208f_id: "tlc59208f_3" - platform: tlc59208f id: tlc_7 channel: 1 - tlc59208f_id: 'tlc59208f_3' + tlc59208f_id: "tlc59208f_3" - platform: tlc59208f id: tlc_8 channel: 2 - tlc59208f_id: 'tlc59208f_3' + tlc59208f_id: "tlc59208f_3" - platform: gpio id: id2 pin: @@ -1454,12 +1454,12 @@ e131: light: - platform: binary - name: 'Desk Lamp' + name: "Desk Lamp" output: gpio_26 effects: - strobe: - strobe: - name: 'My Strobe' + name: "My Strobe" colors: - state: True duration: 250ms @@ -1474,7 +1474,7 @@ light: id: livingroom_lights state: yes - platform: monochromatic - name: 'Kitchen Lights' + name: "Kitchen Lights" id: kitchen output: gpio_19 gamma_correct: 2.8 @@ -1483,7 +1483,7 @@ light: - strobe: - flicker: - flicker: - name: 'My Flicker' + name: "My Flicker" alpha: 98% intensity: 1.5% - lambda: @@ -1495,20 +1495,20 @@ light: if (state == 4) state = 0; - platform: rgb - name: 'Living Room Lights' + name: "Living Room Lights" id: ${roomname}_lights red: pca_0 green: pca_1 blue: pca_2 - platform: rgbw - name: 'Living Room Lights 2' + name: "Living Room Lights 2" red: pca_3 green: pca_4 blue: pca_5 white: pca_6 color_interlock: true - platform: rgbww - name: 'Living Room Lights 2' + name: "Living Room Lights 2" red: pca_3 green: pca_4 blue: pca_5 @@ -1518,7 +1518,7 @@ light: warm_white_color_temperature: 500 mireds color_interlock: true - platform: rgbct - name: 'Living Room Lights 2' + name: "Living Room Lights 2" red: pca_3 green: pca_4 blue: pca_5 @@ -1528,14 +1528,14 @@ light: warm_white_color_temperature: 500 mireds color_interlock: true - platform: cwww - name: 'Living Room Lights 2' + name: "Living Room Lights 2" cold_white: pca_6 warm_white: pca_6 cold_white_color_temperature: 153 mireds warm_white_color_temperature: 500 mireds constant_brightness: true - platform: color_temperature - name: 'Living Room Lights 2' + name: "Living Room Lights 2" color_temperature: pca_6 brightness: pca_6 cold_white_color_temperature: 153 mireds @@ -1549,7 +1549,7 @@ light: max_refresh_rate: 20ms power_supply: atx_power_supply color_correct: [75%, 100%, 50%] - name: 'FastLED WS2811 Light' + name: "FastLED WS2811 Light" effects: - addressable_color_wipe: - addressable_color_wipe: @@ -1592,7 +1592,7 @@ light: update_interval: 16ms intensity: 5% - addressable_lambda: - name: 'Test For Custom Lambda Effect' + name: "Test For Custom Lambda Effect" lambda: |- if (initial_run) { it[0] = current_color; @@ -1628,10 +1628,10 @@ light: data_rate: 2MHz num_leds: 60 rgb_order: BRG - name: 'FastLED SPI Light' + name: "FastLED SPI Light" - platform: neopixelbus id: addr3 - name: 'Neopixelbus Light' + name: "Neopixelbus Light" gamma_correct: 2.8 color_correct: [0.0, 0.0, 0.0, 0.0] default_transition_length: 10s @@ -1647,7 +1647,7 @@ light: num_leds: 60 pin: GPIO23 - platform: partition - name: 'Partition Light' + name: "Partition Light" segments: - id: addr1 from: 0 @@ -1677,7 +1677,7 @@ climate: 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 + 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 @@ -1803,7 +1803,7 @@ switch: remote_transmitter.transmit_midea: code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF] - platform: gpio - name: 'MCP23S08 Pin #0' + name: "MCP23S08 Pin #0" pin: mcp23xxx: mcp23s08_hub # Use pin number 0 @@ -1811,7 +1811,7 @@ switch: mode: OUTPUT inverted: False - platform: gpio - name: 'MCP23S17 Pin #0' + name: "MCP23S17 Pin #0" pin: mcp23xxx: mcp23s17_hub # Use pin number 0 @@ -1820,10 +1820,11 @@ switch: inverted: False - platform: gpio pin: GPIO25 - name: 'Living Room Dehumidifier' - icon: 'mdi:restart' + name: "Living Room Dehumidifier" + icon: "mdi:restart" inverted: True command_topic: custom_command_topic + command_retain: true restore_mode: ALWAYS_OFF - platform: template name: JVC Off @@ -1885,14 +1886,14 @@ switch: name: RC Switch Raw turn_on_action: remote_transmitter.transmit_rc_switch_raw: - code: '00101001100111110101xxxx' + code: "00101001100111110101xxxx" protocol: 1 - platform: template name: RC Switch Type A turn_on_action: remote_transmitter.transmit_rc_switch_type_a: - group: '11001' - device: '01000' + group: "11001" + device: "01000" state: True protocol: pulse_length: 175 @@ -1911,7 +1912,7 @@ switch: name: RC Switch Type C turn_on_action: remote_transmitter.transmit_rc_switch_type_c: - family: 'a' + family: "a" group: 1 device: 2 state: True @@ -1919,7 +1920,7 @@ switch: name: RC Switch Type D turn_on_action: remote_transmitter.transmit_rc_switch_type_d: - group: 'a' + group: "a" device: 2 state: True - platform: template @@ -1945,16 +1946,16 @@ switch: level: 50% - output.set_level: id: gpio_19 - level: !lambda 'return 0.5;' + level: !lambda "return 0.5;" - output.set_level: id: dac_output level: 50% - output.set_level: id: dac_output - level: !lambda 'return 0.5;' + level: !lambda "return 0.5;" - output.set_level: id: mcp4725_dac_output - level: !lambda 'return 0.5;' + level: !lambda "return 0.5;" turn_off_action: - switch.turn_on: living_room_lights_off restore_state: False @@ -1963,16 +1964,16 @@ switch: id: livingroom_lights state: yes - platform: restart - name: 'Living Room Restart' + name: "Living Room Restart" - platform: safe_mode - name: 'Living Room Restart (Safe Mode)' + name: "Living Room Restart (Safe Mode)" - platform: shutdown - name: 'Living Room Shutdown' + name: "Living Room Shutdown" - platform: output - name: 'Generic Output' + name: "Generic Output" output: pca_6 - platform: template - name: 'Template Switch' + name: "Template Switch" id: my_switch lambda: |- if (id(binary_sensor1).state) { @@ -1995,18 +1996,18 @@ switch: on_turn_off: - switch.template.publish: id: my_switch - state: !lambda 'return false;' + state: !lambda "return false;" - platform: uart uart_id: uart0 - name: 'UART String Output' - data: 'DataToSend' + name: "UART String Output" + data: "DataToSend" - platform: uart uart_id: uart0 - name: 'UART Bytes Output' + name: "UART Bytes Output" data: [0xDE, 0xAD, 0xBE, 0xEF] - platform: uart uart_id: uart0 - name: 'UART Recurring Output' + name: "UART Recurring Output" data: [0xDE, 0xAD, 0xBE, 0xEF] send_every: 1s - platform: template @@ -2027,7 +2028,7 @@ switch: position: 0 - platform: gpio - name: 'SN74HC595 Pin #0' + name: "SN74HC595 Pin #0" pin: sn74hc595: sn74hc595_hub # Use pin number 0 @@ -2040,7 +2041,7 @@ switch: fan: - platform: binary output: gpio_26 - name: 'Living Room Fan 1' + name: "Living Room Fan 1" oscillation_output: gpio_19 direction_output: gpio_26 - platform: speed @@ -2048,7 +2049,7 @@ fan: icon: mdi:weather-windy output: pca_6 speed_count: 10 - name: 'Living Room Fan 2' + name: "Living Room Fan 2" oscillation_output: gpio_19 direction_output: gpio_26 oscillation_state_topic: oscillation/state/topic @@ -2084,7 +2085,7 @@ interval: id: display1 page_id: page1 then: - - logger.log: 'Seeing page 1' + - logger.log: "Seeing page 1" color: - id: kbx_red @@ -2145,7 +2146,7 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1306_i2c - model: 'SSD1306_128X64' + model: "SSD1306_128X64" reset_pin: GPIO23 address: 0x3C id: display1 @@ -2165,28 +2166,28 @@ display: ESP_LOGD("display", "1 -> 2"); i2c_id: i2c_bus - platform: ssd1306_spi - model: 'SSD1306 128x64' + model: "SSD1306 128x64" cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1322_spi - model: 'SSD1322 256x64' + model: "SSD1322 256x64" cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1325_spi - model: 'SSD1325 128x64' + model: "SSD1325 128x64" cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1327_i2c - model: 'SSD1327 128X128' + model: "SSD1327 128X128" reset_pin: GPIO23 address: 0x3D id: display1327 @@ -2200,7 +2201,7 @@ display: // Nothing i2c_id: i2c_bus - platform: ssd1327_spi - model: 'SSD1327 128x128' + model: "SSD1327 128x128" cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 @@ -2213,7 +2214,7 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1351_spi - model: 'SSD1351 128x128' + model: "SSD1351 128x128" cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 @@ -2235,7 +2236,7 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: st7735 - model: 'INITR_BLACKTAB' + model: "INITR_BLACKTAB" cs_pin: GPIO5 dc_pin: GPIO16 reset_pin: GPIO23 @@ -2293,13 +2294,13 @@ pn532_spi: ESP_LOGD("main", "Found tag %s", x.c_str()); - mqtt.publish: topic: the/topic - payload: !lambda 'return x;' + payload: !lambda "return x;" on_tag_removed: - lambda: |- ESP_LOGD("main", "Removed tag %s", x.c_str()); - mqtt.publish: topic: the/topic - payload: !lambda 'return x;' + payload: !lambda "return x;" pn532_i2c: i2c_id: i2c_bus @@ -2338,7 +2339,7 @@ time: - 1.pool.ntp.org - 192.168.178.1 on_time: - cron: '/30 0-30,30/5 * ? JAN-DEC MON,SAT-SUN,TUE-FRI' + cron: "/30 0-30,30/5 * ? JAN-DEC MON,SAT-SUN,TUE-FRI" then: - lambda: 'ESP_LOGD("main", "time");' - platform: gps @@ -2356,7 +2357,7 @@ time: cover: - platform: template - name: 'Template Cover' + name: "Template Cover" id: template_cover lambda: |- if (id(binary_sensor1).state) { @@ -2373,7 +2374,7 @@ cover: has_position: yes position_state_topic: position/state/topic position_command_topic: position/command/topic - tilt_lambda: !lambda 'return 0.5;' + tilt_lambda: !lambda "return 0.5;" tilt_state_topic: tilt/state/topic tilt_command_topic: tilt/command/topic on_open: @@ -2383,7 +2384,7 @@ cover: then: - lambda: 'ESP_LOGD("cover", "closed");' - platform: am43 - name: 'Test AM43' + name: "Test AM43" id: am43_test ble_client_id: ble_foo icon: mdi:blinds @@ -2402,24 +2403,24 @@ tca9548a: i2c_id: multiplex0_chan0 pcf8574: - - id: 'pcf8574_hub' + - id: "pcf8574_hub" address: 0x21 pcf8575: False i2c_id: i2c_bus mcp23017: - - id: 'mcp23017_hub' - open_drain_interrupt: 'true' + - id: "mcp23017_hub" + open_drain_interrupt: "true" i2c_id: i2c_bus mcp23008: - - id: 'mcp23008_hub' + - id: "mcp23008_hub" address: 0x22 - open_drain_interrupt: 'true' + open_drain_interrupt: "true" i2c_id: i2c_bus mcp23016: - - id: 'mcp23016_hub' + - id: "mcp23016_hub" address: 0x23 i2c_id: i2c_bus @@ -2437,15 +2438,15 @@ globals: - id: glob_int type: int restore_value: yes - initial_value: '0' + initial_value: "0" - id: glob_float type: float restore_value: yes - initial_value: '0.0f' + initial_value: "0.0f" - id: glob_bool type: bool restore_value: no - initial_value: 'true' + initial_value: "true" - id: glob_string type: std::string restore_value: no @@ -2453,12 +2454,12 @@ globals: - id: glob_bool_processed type: bool restore_value: no - initial_value: 'false' + initial_value: "false" text_sensor: - platform: mqtt_subscribe - name: 'MQTT Subscribe Text' - topic: 'the/topic' + name: "MQTT Subscribe Text" + topic: "the/topic" qos: 2 on_value: - text_sensor.template.publish: @@ -2470,7 +2471,7 @@ text_sensor: return "Hello World2"; - globals.set: id: glob_int - value: '0' + value: "0" - canbus.send: canbus_id: mcp2515_can can_id: 23 @@ -2484,17 +2485,17 @@ text_sensor: id: ${textname}_text - platform: wifi_info scan_results: - name: 'Scan Results' + name: "Scan Results" ip_address: - name: 'IP Address' + name: "IP Address" ssid: - name: 'SSID' + name: "SSID" bssid: - name: 'BSSID' + name: "BSSID" mac_address: - name: 'Mac Address' + name: "Mac Address" - platform: version - name: 'ESPHome Version No Timestamp' + name: "ESPHome Version No Timestamp" hide_timestamp: True - platform: teleinfo tag_name: "OPTARIF" @@ -2502,7 +2503,7 @@ text_sensor: teleinfo_id: myteleinfo sn74hc595: - - id: 'sn74hc595_hub' + - id: "sn74hc595_hub" data_pin: GPIO21 clock_pin: GPIO23 latch_pin: GPIO22 @@ -2528,7 +2529,7 @@ canbus: then: - if: condition: - lambda: 'return x[0] == 0x11;' + lambda: "return x[0] == 0x11;" then: light.toggle: ${roomname}_lights - platform: esp32_can @@ -2547,7 +2548,7 @@ canbus: then: - if: condition: - lambda: 'return x[0] == 0x11;' + lambda: "return x[0] == 0x11;" then: light.toggle: ${roomname}_lights From 2a84db7f850e49994852e410487bbc45c01bfe80 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 23 Jan 2022 10:21:54 +0100 Subject: [PATCH 033/238] Refactor fan platform to resemble climate/cover platforms (#2848) Co-authored-by: Oxan van Leeuwen Co-authored-by: rob-deutsch Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/api/api_connection.cpp | 6 +- esphome/components/api/api_connection.h | 4 +- esphome/components/api/api_server.cpp | 2 +- esphome/components/api/api_server.h | 2 +- esphome/components/api/list_entities.cpp | 2 +- esphome/components/api/list_entities.h | 2 +- esphome/components/api/subscribe_state.cpp | 2 +- esphome/components/api/subscribe_state.h | 2 +- esphome/components/api/util.h | 2 +- esphome/components/binary/fan/__init__.py | 5 +- esphome/components/binary/fan/binary_fan.cpp | 74 +++----- esphome/components/binary/fan/binary_fan.h | 17 +- esphome/components/demo/__init__.py | 5 +- esphome/components/demo/demo_fan.h | 24 ++- esphome/components/fan/__init__.py | 39 ++-- esphome/components/fan/automation.cpp | 10 - esphome/components/fan/automation.h | 30 +-- esphome/components/fan/fan.cpp | 175 ++++++++++++++++++ esphome/components/fan/fan.h | 154 +++++++++++++++ esphome/components/fan/fan_helpers.h | 3 +- esphome/components/fan/fan_state.cpp | 111 +---------- esphome/components/fan/fan_state.h | 114 ++---------- esphome/components/hbridge/fan/__init__.py | 3 +- .../components/hbridge/fan/hbridge_fan.cpp | 53 +++--- esphome/components/hbridge/fan/hbridge_fan.h | 17 +- esphome/components/mqtt/mqtt_fan.cpp | 4 +- esphome/components/mqtt/mqtt_fan.h | 6 +- esphome/components/output/binary_output.h | 8 + .../prometheus/prometheus_handler.cpp | 2 +- .../prometheus/prometheus_handler.h | 2 +- esphome/components/speed/fan/__init__.py | 8 +- esphome/components/speed/fan/speed_fan.cpp | 78 +++----- esphome/components/speed/fan/speed_fan.h | 15 +- esphome/components/tuya/fan/__init__.py | 8 +- esphome/components/tuya/fan/tuya_fan.cpp | 71 ++++--- esphome/components/tuya/fan/tuya_fan.h | 16 +- esphome/components/web_server/web_server.cpp | 8 +- esphome/components/web_server/web_server.h | 4 +- esphome/core/application.h | 8 +- esphome/core/controller.h | 2 +- script/ci-custom.py | 1 + 41 files changed, 593 insertions(+), 506 deletions(-) delete mode 100644 esphome/components/fan/automation.cpp create mode 100644 esphome/components/fan/fan.cpp create mode 100644 esphome/components/fan/fan.h diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 20011c0954..c4cc9d18f8 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -255,7 +255,7 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) { // 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) { +bool APIConnection::send_fan_state(fan::Fan *fan) { if (!this->state_subscription_) return false; @@ -273,7 +273,7 @@ bool APIConnection::send_fan_state(fan::FanState *fan) { resp.direction = static_cast(fan->direction); return this->send_fan_state_response(resp); } -bool APIConnection::send_fan_info(fan::FanState *fan) { +bool APIConnection::send_fan_info(fan::Fan *fan) { auto traits = fan->get_traits(); ListEntitiesFanResponse msg; msg.key = fan->get_object_id_hash(); @@ -290,7 +290,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) { return this->send_list_entities_fan_response(msg); } void APIConnection::fan_command(const FanCommandRequest &msg) { - fan::FanState *fan = App.get_fan_by_key(msg.key); + fan::Fan *fan = App.get_fan_by_key(msg.key); if (fan == nullptr) return; diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 72697b5911..c1f520c83b 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -32,8 +32,8 @@ class APIConnection : public APIServerConnection { void cover_command(const CoverCommandRequest &msg) override; #endif #ifdef USE_FAN - bool send_fan_state(fan::FanState *fan); - bool send_fan_info(fan::FanState *fan); + bool send_fan_state(fan::Fan *fan); + bool send_fan_info(fan::Fan *fan); void fan_command(const FanCommandRequest &msg) override; #endif #ifdef USE_LIGHT diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 25081a809a..c857763f95 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -188,7 +188,7 @@ void APIServer::on_cover_update(cover::Cover *obj) { #endif #ifdef USE_FAN -void APIServer::on_fan_update(fan::FanState *obj) { +void APIServer::on_fan_update(fan::Fan *obj) { if (obj->is_internal()) return; for (auto &c : this->clients_) diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 056d9f54f2..23b01df375 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -44,7 +44,7 @@ class APIServer : public Component, public Controller { void on_cover_update(cover::Cover *obj) override; #endif #ifdef USE_FAN - void on_fan_update(fan::FanState *obj) override; + void on_fan_update(fan::Fan *obj) override; #endif #ifdef USE_LIGHT void on_light_update(light::LightState *obj) override; diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index cb97df8ca1..35a590a828 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -16,7 +16,7 @@ bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_ bool ListEntitiesIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_info(cover); } #endif #ifdef USE_FAN -bool ListEntitiesIterator::on_fan(fan::FanState *fan) { return this->client_->send_fan_info(fan); } +bool ListEntitiesIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_info(fan); } #endif #ifdef USE_LIGHT bool ListEntitiesIterator::on_light(light::LightState *light) { return this->client_->send_light_info(light); } diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index 714edaa91f..81b814676a 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -19,7 +19,7 @@ class ListEntitiesIterator : public ComponentIterator { bool on_cover(cover::Cover *cover) override; #endif #ifdef USE_FAN - bool on_fan(fan::FanState *fan) override; + bool on_fan(fan::Fan *fan) override; #endif #ifdef USE_LIGHT bool on_light(light::LightState *light) override; diff --git a/esphome/components/api/subscribe_state.cpp b/esphome/components/api/subscribe_state.cpp index 1b8453f233..07b3913ff7 100644 --- a/esphome/components/api/subscribe_state.cpp +++ b/esphome/components/api/subscribe_state.cpp @@ -14,7 +14,7 @@ bool InitialStateIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_ bool InitialStateIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_state(cover); } #endif #ifdef USE_FAN -bool InitialStateIterator::on_fan(fan::FanState *fan) { return this->client_->send_fan_state(fan); } +bool InitialStateIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_state(fan); } #endif #ifdef USE_LIGHT bool InitialStateIterator::on_light(light::LightState *light) { return this->client_->send_light_state(light); } diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index d3f2d3aa45..4b83b5e793 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -20,7 +20,7 @@ class InitialStateIterator : public ComponentIterator { bool on_cover(cover::Cover *cover) override; #endif #ifdef USE_FAN - bool on_fan(fan::FanState *fan) override; + bool on_fan(fan::Fan *fan) override; #endif #ifdef USE_LIGHT bool on_light(light::LightState *light) override; diff --git a/esphome/components/api/util.h b/esphome/components/api/util.h index 7849b3e028..f329867a4e 100644 --- a/esphome/components/api/util.h +++ b/esphome/components/api/util.h @@ -27,7 +27,7 @@ class ComponentIterator { virtual bool on_cover(cover::Cover *cover) = 0; #endif #ifdef USE_FAN - virtual bool on_fan(fan::FanState *fan) = 0; + virtual bool on_fan(fan::Fan *fan) = 0; #endif #ifdef USE_LIGHT virtual bool on_light(light::LightState *light) = 0; diff --git a/esphome/components/binary/fan/__init__.py b/esphome/components/binary/fan/__init__.py index e6c8d9bfe9..6edfa885c9 100644 --- a/esphome/components/binary/fan/__init__.py +++ b/esphome/components/binary/fan/__init__.py @@ -9,7 +9,7 @@ from esphome.const import ( ) from .. import binary_ns -BinaryFan = binary_ns.class_("BinaryFan", cg.Component) +BinaryFan = binary_ns.class_("BinaryFan", fan.Fan, cg.Component) CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( { @@ -24,9 +24,8 @@ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( async def to_code(config): var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) await cg.register_component(var, config) + await fan.register_fan(var, config) - fan_ = await fan.create_fan_state(config) - cg.add(var.set_fan(fan_)) output_ = await cg.get_variable(config[CONF_OUTPUT]) cg.add(var.set_output(output_)) diff --git a/esphome/components/binary/fan/binary_fan.cpp b/esphome/components/binary/fan/binary_fan.cpp index 2201fe576e..a2f75242de 100644 --- a/esphome/components/binary/fan/binary_fan.cpp +++ b/esphome/components/binary/fan/binary_fan.cpp @@ -6,59 +6,35 @@ namespace binary { static const char *const TAG = "binary.fan"; -void binary::BinaryFan::dump_config() { - ESP_LOGCONFIG(TAG, "Fan '%s':", this->fan_->get_name().c_str()); - if (this->fan_->get_traits().supports_oscillation()) { - ESP_LOGCONFIG(TAG, " Oscillation: YES"); - } - if (this->fan_->get_traits().supports_direction()) { - ESP_LOGCONFIG(TAG, " Direction: YES"); - } -} void BinaryFan::setup() { - auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr, 0); - this->fan_->set_traits(traits); - this->fan_->add_on_state_callback([this]() { this->next_update_ = true; }); -} -void BinaryFan::loop() { - if (!this->next_update_) { - return; - } - this->next_update_ = false; - - { - bool enable = this->fan_->state; - if (enable) - this->output_->turn_on(); - else - this->output_->turn_off(); - ESP_LOGD(TAG, "Setting binary state: %s", ONOFF(enable)); - } - - if (this->oscillating_ != nullptr) { - bool enable = this->fan_->oscillating; - if (enable) { - this->oscillating_->turn_on(); - } else { - this->oscillating_->turn_off(); - } - ESP_LOGD(TAG, "Setting oscillation: %s", ONOFF(enable)); - } - - if (this->direction_ != nullptr) { - bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE; - if (enable) { - this->direction_->turn_on(); - } else { - this->direction_->turn_off(); - } - ESP_LOGD(TAG, "Setting reverse direction: %s", ONOFF(enable)); + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->apply(*this); + this->write_state_(); } } +void BinaryFan::dump_config() { LOG_FAN("", "Binary Fan", this); } +fan::FanTraits BinaryFan::get_traits() { + return fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr, 0); +} +void BinaryFan::control(const fan::FanCall &call) { + if (call.get_state().has_value()) + this->state = *call.get_state(); + if (call.get_oscillating().has_value()) + this->oscillating = *call.get_oscillating(); + if (call.get_direction().has_value()) + this->direction = *call.get_direction(); -// We need a higher priority than the FanState component to make sure that the traits are set -// when that component sets itself up. -float BinaryFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; } + this->write_state_(); + this->publish_state(); +} +void BinaryFan::write_state_() { + this->output_->set_state(this->state); + if (this->oscillating_ != nullptr) + this->oscillating_->set_state(this->oscillating); + if (this->direction_ != nullptr) + this->direction_->set_state(this->direction == fan::FanDirection::REVERSE); +} } // namespace binary } // namespace esphome diff --git a/esphome/components/binary/fan/binary_fan.h b/esphome/components/binary/fan/binary_fan.h index 93294b8dee..16bce2e6af 100644 --- a/esphome/components/binary/fan/binary_fan.h +++ b/esphome/components/binary/fan/binary_fan.h @@ -2,28 +2,29 @@ #include "esphome/core/component.h" #include "esphome/components/output/binary_output.h" -#include "esphome/components/fan/fan_state.h" +#include "esphome/components/fan/fan.h" namespace esphome { namespace binary { -class BinaryFan : public Component { +class BinaryFan : public Component, public fan::Fan { public: - void set_fan(fan::FanState *fan) { fan_ = fan; } - void set_output(output::BinaryOutput *output) { output_ = output; } void setup() override; - void loop() override; void dump_config() override; - float get_setup_priority() const override; + + void set_output(output::BinaryOutput *output) { this->output_ = output; } void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; } void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; } + fan::FanTraits get_traits() override; + protected: - fan::FanState *fan_; + void control(const fan::FanCall &call) override; + void write_state_(); + output::BinaryOutput *output_; output::BinaryOutput *oscillating_{nullptr}; output::BinaryOutput *direction_{nullptr}; - bool next_update_{true}; }; } // namespace binary diff --git a/esphome/components/demo/__init__.py b/esphome/components/demo/__init__.py index fae8a2b07d..6b4a55aac9 100644 --- a/esphome/components/demo/__init__.py +++ b/esphome/components/demo/__init__.py @@ -67,7 +67,7 @@ DemoClimate = demo_ns.class_("DemoClimate", climate.Climate, cg.Component) DemoClimateType = demo_ns.enum("DemoClimateType", is_class=True) DemoCover = demo_ns.class_("DemoCover", cover.Cover, cg.Component) DemoCoverType = demo_ns.enum("DemoCoverType", is_class=True) -DemoFan = demo_ns.class_("DemoFan", cg.Component) +DemoFan = demo_ns.class_("DemoFan", fan.Fan, cg.Component) DemoFanType = demo_ns.enum("DemoFanType", is_class=True) DemoLight = demo_ns.class_("DemoLight", light.LightOutput, cg.Component) DemoLightType = demo_ns.enum("DemoLightType", is_class=True) @@ -411,8 +411,7 @@ async def to_code(config): for conf in config[CONF_FANS]: var = cg.new_Pvariable(conf[CONF_OUTPUT_ID]) await cg.register_component(var, conf) - fan_ = await fan.create_fan_state(conf) - cg.add(var.set_fan(fan_)) + await fan.register_fan(var, conf) cg.add(var.set_type(conf[CONF_TYPE])) for conf in config[CONF_LIGHTS]: diff --git a/esphome/components/demo/demo_fan.h b/esphome/components/demo/demo_fan.h index e926f68edb..09edc4e0b7 100644 --- a/esphome/components/demo/demo_fan.h +++ b/esphome/components/demo/demo_fan.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/components/fan/fan_state.h" +#include "esphome/components/fan/fan.h" namespace esphome { namespace demo { @@ -13,11 +13,10 @@ enum class DemoFanType { TYPE_4, }; -class DemoFan : public Component { +class DemoFan : public fan::Fan, public Component { public: void set_type(DemoFanType type) { type_ = type; } - void set_fan(fan::FanState *fan) { fan_ = fan; } - void setup() override { + fan::FanTraits get_traits() override { fan::FanTraits traits{}; // oscillation @@ -43,10 +42,23 @@ class DemoFan : public Component { break; } - this->fan_->set_traits(traits); + return traits; + } + + protected: + void control(const fan::FanCall &call) override { + if (call.get_state().has_value()) + this->state = *call.get_state(); + if (call.get_oscillating().has_value()) + this->oscillating = *call.get_oscillating(); + if (call.get_speed().has_value()) + this->speed = *call.get_speed(); + if (call.get_direction().has_value()) + this->direction = *call.get_direction(); + + this->publish_state(); } - fan::FanState *fan_; DemoFanType type_; }; diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 8dec2dee51..eb67bbcbd7 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -27,23 +27,24 @@ from esphome.cpp_helpers import setup_entity IS_PLATFORM_COMPONENT = True fan_ns = cg.esphome_ns.namespace("fan") -FanState = fan_ns.class_("FanState", cg.EntityBase, cg.Component) -MakeFan = cg.Application.struct("MakeFan") +Fan = fan_ns.class_("Fan", cg.EntityBase) +FanState = fan_ns.class_("Fan", Fan, cg.Component) -FanDirection = fan_ns.enum("FanDirection") +FanDirection = fan_ns.enum("FanDirection", is_class=True) FAN_DIRECTION_ENUM = { - "FORWARD": FanDirection.FAN_DIRECTION_FORWARD, - "REVERSE": FanDirection.FAN_DIRECTION_REVERSE, + "FORWARD": FanDirection.FORWARD, + "REVERSE": FanDirection.REVERSE, } -FanRestoreMode = fan_ns.enum("FanRestoreMode") +FanRestoreMode = fan_ns.enum("FanRestoreMode", is_class=True) RESTORE_MODES = { - "RESTORE_DEFAULT_OFF": FanRestoreMode.FAN_RESTORE_DEFAULT_OFF, - "RESTORE_DEFAULT_ON": FanRestoreMode.FAN_RESTORE_DEFAULT_ON, - "ALWAYS_OFF": FanRestoreMode.FAN_ALWAYS_OFF, - "ALWAYS_ON": FanRestoreMode.FAN_ALWAYS_ON, - "RESTORE_INVERTED_DEFAULT_OFF": FanRestoreMode.FAN_RESTORE_INVERTED_DEFAULT_OFF, - "RESTORE_INVERTED_DEFAULT_ON": FanRestoreMode.FAN_RESTORE_INVERTED_DEFAULT_ON, + "NO_RESTORE": FanRestoreMode.NO_RESTORE, + "ALWAYS_OFF": FanRestoreMode.ALWAYS_OFF, + "ALWAYS_ON": FanRestoreMode.ALWAYS_ON, + "RESTORE_DEFAULT_OFF": FanRestoreMode.RESTORE_DEFAULT_OFF, + "RESTORE_DEFAULT_ON": FanRestoreMode.RESTORE_DEFAULT_ON, + "RESTORE_INVERTED_DEFAULT_OFF": FanRestoreMode.RESTORE_INVERTED_DEFAULT_OFF, + "RESTORE_INVERTED_DEFAULT_ON": FanRestoreMode.RESTORE_INVERTED_DEFAULT_ON, } # Actions @@ -61,8 +62,8 @@ FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.temp FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { - cv.GenerateID(): cv.declare_id(FanState), - cv.Optional(CONF_RESTORE_MODE, default="restore_default_off"): cv.enum( + cv.GenerateID(): cv.declare_id(Fan), + cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_OFF"): cv.enum( RESTORE_MODES, upper=True, space="_" ), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent), @@ -158,19 +159,19 @@ async def register_fan(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) cg.add(cg.App.register_fan(var)) - await cg.register_component(var, config) await setup_fan_core_(var, config) async def create_fan_state(config): var = cg.new_Pvariable(config[CONF_ID]) await register_fan(var, config) + await cg.register_component(var, config) return var FAN_ACTION_SCHEMA = maybe_simple_id( { - cv.Required(CONF_ID): cv.use_id(FanState), + cv.Required(CONF_ID): cv.use_id(Fan), } ) @@ -192,7 +193,7 @@ async def fan_turn_off_to_code(config, action_id, template_arg, args): TurnOnAction, maybe_simple_id( { - cv.Required(CONF_ID): cv.use_id(FanState), + cv.Required(CONF_ID): cv.use_id(Fan), cv.Optional(CONF_OSCILLATING): cv.templatable(cv.boolean), cv.Optional(CONF_SPEED): cv.templatable(cv.int_range(1)), cv.Optional(CONF_DIRECTION): cv.templatable( @@ -227,7 +228,7 @@ async def fan_cycle_speed_to_code(config, action_id, template_arg, args): FanIsOnCondition, automation.maybe_simple_id( { - cv.Required(CONF_ID): cv.use_id(FanState), + cv.Required(CONF_ID): cv.use_id(Fan), } ), ) @@ -236,7 +237,7 @@ async def fan_cycle_speed_to_code(config, action_id, template_arg, args): FanIsOffCondition, automation.maybe_simple_id( { - cv.Required(CONF_ID): cv.use_id(FanState), + cv.Required(CONF_ID): cv.use_id(Fan), } ), ) diff --git a/esphome/components/fan/automation.cpp b/esphome/components/fan/automation.cpp deleted file mode 100644 index 79e583fc57..0000000000 --- a/esphome/components/fan/automation.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "automation.h" -#include "esphome/core/log.h" - -namespace esphome { -namespace fan { - -static const char *const TAG = "fan.automation"; - -} // namespace fan -} // namespace esphome diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index 608f772b75..23fb70a95b 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -9,7 +9,7 @@ namespace fan { template class TurnOnAction : public Action { public: - explicit TurnOnAction(FanState *state) : state_(state) {} + explicit TurnOnAction(Fan *state) : state_(state) {} TEMPLATABLE_VALUE(bool, oscillating) TEMPLATABLE_VALUE(int, speed) @@ -29,30 +29,30 @@ template class TurnOnAction : public Action { call.perform(); } - FanState *state_; + Fan *state_; }; template class TurnOffAction : public Action { public: - explicit TurnOffAction(FanState *state) : state_(state) {} + explicit TurnOffAction(Fan *state) : state_(state) {} void play(Ts... x) override { this->state_->turn_off().perform(); } - FanState *state_; + Fan *state_; }; template class ToggleAction : public Action { public: - explicit ToggleAction(FanState *state) : state_(state) {} + explicit ToggleAction(Fan *state) : state_(state) {} void play(Ts... x) override { this->state_->toggle().perform(); } - FanState *state_; + Fan *state_; }; template class CycleSpeedAction : public Action { public: - explicit CycleSpeedAction(FanState *state) : state_(state) {} + explicit CycleSpeedAction(Fan *state) : state_(state) {} void play(Ts... x) override { // check to see if fan supports speeds and is on @@ -83,29 +83,29 @@ template class CycleSpeedAction : public Action { } } - FanState *state_; + Fan *state_; }; template class FanIsOnCondition : public Condition { public: - explicit FanIsOnCondition(FanState *state) : state_(state) {} + explicit FanIsOnCondition(Fan *state) : state_(state) {} bool check(Ts... x) override { return this->state_->state; } protected: - FanState *state_; + Fan *state_; }; template class FanIsOffCondition : public Condition { public: - explicit FanIsOffCondition(FanState *state) : state_(state) {} + explicit FanIsOffCondition(Fan *state) : state_(state) {} bool check(Ts... x) override { return !this->state_->state; } protected: - FanState *state_; + Fan *state_; }; class FanTurnOnTrigger : public Trigger<> { public: - FanTurnOnTrigger(FanState *state) { + FanTurnOnTrigger(Fan *state) { state->add_on_state_callback([this, state]() { auto is_on = state->state; auto should_trigger = is_on && !this->last_on_; @@ -123,7 +123,7 @@ class FanTurnOnTrigger : public Trigger<> { class FanTurnOffTrigger : public Trigger<> { public: - FanTurnOffTrigger(FanState *state) { + FanTurnOffTrigger(Fan *state) { state->add_on_state_callback([this, state]() { auto is_on = state->state; auto should_trigger = !is_on && this->last_on_; @@ -141,7 +141,7 @@ class FanTurnOffTrigger : public Trigger<> { class FanSpeedSetTrigger : public Trigger<> { public: - FanSpeedSetTrigger(FanState *state) { + FanSpeedSetTrigger(Fan *state) { state->add_on_state_callback([this, state]() { auto speed = state->speed; auto should_trigger = speed != !this->last_speed_; diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp new file mode 100644 index 0000000000..5f9660f6d6 --- /dev/null +++ b/esphome/components/fan/fan.cpp @@ -0,0 +1,175 @@ +#include "fan.h" +#include "fan_helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace fan { + +static const char *const TAG = "fan"; + +const LogString *fan_direction_to_string(FanDirection direction) { + switch (direction) { + case FanDirection::FORWARD: + return LOG_STR("FORWARD"); + case FanDirection::REVERSE: + return LOG_STR("REVERSE"); + default: + return LOG_STR("UNKNOWN"); + } +} + +void FanCall::perform() { + ESP_LOGD(TAG, "'%s' - Setting:", this->parent_.get_name().c_str()); + this->validate_(); + if (this->binary_state_.has_value()) + ESP_LOGD(TAG, " State: %s", ONOFF(*this->binary_state_)); + if (this->oscillating_.has_value()) + ESP_LOGD(TAG, " Oscillating: %s", YESNO(*this->oscillating_)); + if (this->speed_.has_value()) + ESP_LOGD(TAG, " Speed: %d", *this->speed_); + if (this->direction_.has_value()) + ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_))); + + this->parent_.control(*this); +} +void FanCall::validate_() { + auto traits = this->parent_.get_traits(); + + if (this->speed_.has_value()) + this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count()); + + if (this->binary_state_.has_value() && *this->binary_state_) { + // when turning on, if current speed is zero, set speed to 100% + if (traits.supports_speed() && !this->parent_.state && this->parent_.speed == 0) { + this->speed_ = traits.supported_speed_count(); + } + } + + if (this->oscillating_.has_value() && !traits.supports_oscillation()) { + ESP_LOGW(TAG, "'%s' - This fan does not support oscillation!", this->parent_.get_name().c_str()); + this->oscillating_.reset(); + } + + if (this->speed_.has_value() && !traits.supports_speed()) { + ESP_LOGW(TAG, "'%s' - This fan does not support speeds!", this->parent_.get_name().c_str()); + this->speed_.reset(); + } + + if (this->direction_.has_value() && !traits.supports_direction()) { + ESP_LOGW(TAG, "'%s' - This fan does not support directions!", this->parent_.get_name().c_str()); + this->direction_.reset(); + } +} + +// This whole method is deprecated, don't warn about usage of deprecated methods inside of it. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +FanCall &FanCall::set_speed(const char *legacy_speed) { + const auto supported_speed_count = this->parent_.get_traits().supported_speed_count(); + if (strcasecmp(legacy_speed, "low") == 0) { + this->set_speed(fan::speed_enum_to_level(FAN_SPEED_LOW, supported_speed_count)); + } else if (strcasecmp(legacy_speed, "medium") == 0) { + this->set_speed(fan::speed_enum_to_level(FAN_SPEED_MEDIUM, supported_speed_count)); + } else if (strcasecmp(legacy_speed, "high") == 0) { + this->set_speed(fan::speed_enum_to_level(FAN_SPEED_HIGH, supported_speed_count)); + } + return *this; +} +#pragma GCC diagnostic pop + +FanCall FanRestoreState::to_call(Fan &fan) { + auto call = fan.make_call(); + call.set_state(this->state); + call.set_oscillating(this->oscillating); + call.set_speed(this->speed); + call.set_direction(this->direction); + return call; +} +void FanRestoreState::apply(Fan &fan) { + fan.state = this->state; + fan.oscillating = this->oscillating; + fan.speed = this->speed; + fan.direction = this->direction; + fan.publish_state(); +} + +Fan::Fan() : EntityBase("") {} +Fan::Fan(const std::string &name) : EntityBase(name) {} + +FanCall Fan::turn_on() { return this->make_call().set_state(true); } +FanCall Fan::turn_off() { return this->make_call().set_state(false); } +FanCall Fan::toggle() { return this->make_call().set_state(!this->state); } +FanCall Fan::make_call() { return FanCall(*this); } + +void Fan::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } +void Fan::publish_state() { + auto traits = this->get_traits(); + + ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str()); + ESP_LOGD(TAG, " State: %s", ONOFF(this->state)); + if (traits.supports_speed()) + ESP_LOGD(TAG, " Speed: %d", this->speed); + if (traits.supports_oscillation()) + ESP_LOGD(TAG, " Oscillating: %s", YESNO(this->oscillating)); + if (traits.supports_direction()) + ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction))); + + this->state_callback_.call(); + this->save_state_(); +} + +// Random 32-bit value, change this every time the layout of the FanRestoreState struct changes. +constexpr uint32_t RESTORE_STATE_VERSION = 0x71700ABA; +optional Fan::restore_state_() { + FanRestoreState recovered{}; + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash() ^ RESTORE_STATE_VERSION); + bool restored = this->rtc_.load(&recovered); + + switch (this->restore_mode_) { + case FanRestoreMode::NO_RESTORE: + return {}; + case FanRestoreMode::ALWAYS_OFF: + recovered.state = false; + return recovered; + case FanRestoreMode::ALWAYS_ON: + recovered.state = true; + return recovered; + case FanRestoreMode::RESTORE_DEFAULT_OFF: + recovered.state = restored ? recovered.state : false; + return recovered; + case FanRestoreMode::RESTORE_DEFAULT_ON: + recovered.state = restored ? recovered.state : true; + return recovered; + case FanRestoreMode::RESTORE_INVERTED_DEFAULT_OFF: + recovered.state = restored ? !recovered.state : false; + return recovered; + case FanRestoreMode::RESTORE_INVERTED_DEFAULT_ON: + recovered.state = restored ? !recovered.state : true; + return recovered; + } + + return {}; +} +void Fan::save_state_() { + FanRestoreState state{}; + state.state = this->state; + state.oscillating = this->oscillating; + state.speed = this->speed; + state.direction = this->direction; + this->rtc_.save(&state); +} + +void Fan::dump_traits_(const char *tag, const char *prefix) { + if (this->get_traits().supports_speed()) { + ESP_LOGCONFIG(tag, "%s Speed: YES", prefix); + ESP_LOGCONFIG(tag, "%s Speed count: %d", prefix, this->get_traits().supported_speed_count()); + } + if (this->get_traits().supports_oscillation()) + ESP_LOGCONFIG(tag, "%s Oscillation: YES", prefix); + if (this->get_traits().supports_direction()) + ESP_LOGCONFIG(tag, "%s Direction: YES", prefix); +} +uint32_t Fan::hash_base() { return 418001110UL; } + +} // namespace fan +} // namespace esphome diff --git a/esphome/components/fan/fan.h b/esphome/components/fan/fan.h new file mode 100644 index 0000000000..cafb5843d1 --- /dev/null +++ b/esphome/components/fan/fan.h @@ -0,0 +1,154 @@ +#pragma once + +#include "esphome/core/entity_base.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/optional.h" +#include "esphome/core/preferences.h" +#include "fan_traits.h" + +namespace esphome { +namespace fan { + +#define LOG_FAN(prefix, type, obj) \ + if ((obj) != nullptr) { \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ + (obj)->dump_traits_(TAG, prefix); \ + } + +/// Simple enum to represent the speed of a fan. - DEPRECATED - Will be deleted soon +enum ESPDEPRECATED("FanSpeed is deprecated.", "2021.9") FanSpeed { + FAN_SPEED_LOW = 0, ///< The fan is running on low speed. + FAN_SPEED_MEDIUM = 1, ///< The fan is running on medium speed. + FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed. +}; + +/// Simple enum to represent the direction of a fan. +enum class FanDirection { FORWARD = 0, REVERSE = 1 }; + +/// Restore mode of a fan. +enum class FanRestoreMode { + NO_RESTORE, + ALWAYS_OFF, + ALWAYS_ON, + RESTORE_DEFAULT_OFF, + RESTORE_DEFAULT_ON, + RESTORE_INVERTED_DEFAULT_OFF, + RESTORE_INVERTED_DEFAULT_ON, +}; + +const LogString *fan_direction_to_string(FanDirection direction); + +class Fan; + +class FanCall { + public: + explicit FanCall(Fan &parent) : parent_(parent) {} + + FanCall &set_state(bool binary_state) { + this->binary_state_ = binary_state; + return *this; + } + FanCall &set_state(optional binary_state) { + this->binary_state_ = binary_state; + return *this; + } + optional get_state() const { return this->binary_state_; } + FanCall &set_oscillating(bool oscillating) { + this->oscillating_ = oscillating; + return *this; + } + FanCall &set_oscillating(optional oscillating) { + this->oscillating_ = oscillating; + return *this; + } + optional get_oscillating() const { return this->oscillating_; } + FanCall &set_speed(int speed) { + this->speed_ = speed; + return *this; + } + ESPDEPRECATED("set_speed() with string argument is deprecated, use integer argument instead.", "2021.9") + FanCall &set_speed(const char *legacy_speed); + optional get_speed() const { return this->speed_; } + FanCall &set_direction(FanDirection direction) { + this->direction_ = direction; + return *this; + } + FanCall &set_direction(optional direction) { + this->direction_ = direction; + return *this; + } + optional get_direction() const { return this->direction_; } + + void perform(); + + protected: + void validate_(); + + Fan &parent_; + optional binary_state_; + optional oscillating_; + optional speed_; + optional direction_{}; +}; + +struct FanRestoreState { + bool state; + int speed; + bool oscillating; + FanDirection direction; + + /// Convert this struct to a fan call that can be performed. + FanCall to_call(Fan &fan); + /// Apply these settings to the fan. + void apply(Fan &fan); +} __attribute__((packed)); + +class Fan : public EntityBase { + public: + Fan(); + /// Construct the fan with name. + explicit Fan(const std::string &name); + + /// The current on/off state of the fan. + bool state{false}; + /// The current oscillation state of the fan. + bool oscillating{false}; + /// The current fan speed level + int speed{0}; + /// The current direction of the fan + FanDirection direction{FanDirection::FORWARD}; + + FanCall turn_on(); + FanCall turn_off(); + FanCall toggle(); + FanCall make_call(); + + /// Register a callback that will be called each time the state changes. + void add_on_state_callback(std::function &&callback); + + void publish_state(); + + virtual FanTraits get_traits() = 0; + + /// Set the restore mode of this fan. + void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } + + protected: + friend FanCall; + + virtual void control(const FanCall &call) = 0; + + optional restore_state_(); + void save_state_(); + + void dump_traits_(const char *tag, const char *prefix); + uint32_t hash_base() override; + + CallbackManager state_callback_{}; + ESPPreferenceObject rtc_; + FanRestoreMode restore_mode_; +}; + +} // namespace fan +} // namespace esphome diff --git a/esphome/components/fan/fan_helpers.h b/esphome/components/fan/fan_helpers.h index 009505601e..8e8e3859bd 100644 --- a/esphome/components/fan/fan_helpers.h +++ b/esphome/components/fan/fan_helpers.h @@ -1,5 +1,6 @@ #pragma once -#include "fan_state.h" + +#include "fan.h" namespace esphome { namespace fan { diff --git a/esphome/components/fan/fan_state.cpp b/esphome/components/fan/fan_state.cpp index 7f9023f881..7c1658fb2e 100644 --- a/esphome/components/fan/fan_state.cpp +++ b/esphome/components/fan/fan_state.cpp @@ -1,121 +1,16 @@ #include "fan_state.h" -#include "fan_helpers.h" -#include "esphome/core/log.h" namespace esphome { namespace fan { static const char *const TAG = "fan"; -const FanTraits &FanState::get_traits() const { return this->traits_; } -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) : EntityBase(name) {} - -FanStateCall FanState::turn_on() { return this->make_call().set_state(true); } -FanStateCall FanState::turn_off() { return this->make_call().set_state(false); } -FanStateCall FanState::toggle() { return this->make_call().set_state(!this->state); } -FanStateCall FanState::make_call() { return FanStateCall(this); } - -struct FanStateRTCState { - bool state; - int speed; - bool oscillating; - FanDirection direction; -}; - void FanState::setup() { - auto call = this->make_call(); - FanStateRTCState recovered{}; - - switch (this->restore_mode_) { - case FAN_RESTORE_DEFAULT_OFF: - case FAN_RESTORE_DEFAULT_ON: - case FAN_RESTORE_INVERTED_DEFAULT_OFF: - case FAN_RESTORE_INVERTED_DEFAULT_ON: - this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); - if (!this->rtc_.load(&recovered)) { - if (this->restore_mode_ == FAN_RESTORE_DEFAULT_ON || this->restore_mode_ == FAN_RESTORE_INVERTED_DEFAULT_ON) { - call.set_state(true); - } else { - call.set_state(false); - } - } else { - if (this->restore_mode_ == FAN_RESTORE_INVERTED_DEFAULT_OFF || - this->restore_mode_ == FAN_RESTORE_INVERTED_DEFAULT_ON) { - call.set_state(!recovered.state); - } else { - call.set_state(recovered.state); - } - - call.set_speed(recovered.speed); - call.set_oscillating(recovered.oscillating); - call.set_direction(recovered.direction); - } - break; - case FAN_ALWAYS_OFF: - case FAN_ALWAYS_ON: - if (this->restore_mode_ == FAN_ALWAYS_OFF) { - call.set_state(false); - } else if (this->restore_mode_ == FAN_ALWAYS_ON) { - call.set_state(true); - } - - this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); - if (this->rtc_.load(&recovered)) { - call.set_speed(recovered.speed); - call.set_oscillating(recovered.oscillating); - call.set_direction(recovered.direction); - } - - break; - } - - call.perform(); + auto restore = this->restore_state_(); + if (restore) + restore->to_call(*this).perform(); } float FanState::get_setup_priority() const { return setup_priority::DATA - 1.0f; } -uint32_t FanState::hash_base() { return 418001110UL; } - -void FanStateCall::perform() const { - if (this->binary_state_.has_value()) { - this->state_->state = *this->binary_state_; - } - if (this->oscillating_.has_value()) { - this->state_->oscillating = *this->oscillating_; - } - if (this->direction_.has_value()) { - this->state_->direction = *this->direction_; - } - if (this->speed_.has_value()) { - const int speed_count = this->state_->get_traits().supported_speed_count(); - this->state_->speed = clamp(*this->speed_, 1, speed_count); - } - - FanStateRTCState saved{}; - saved.state = this->state_->state; - saved.speed = this->state_->speed; - saved.oscillating = this->state_->oscillating; - saved.direction = this->state_->direction; - this->state_->rtc_.save(&saved); - - 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) { - this->set_speed(fan::speed_enum_to_level(FAN_SPEED_LOW, supported_speed_count)); - } else if (strcasecmp(legacy_speed, "medium") == 0) { - this->set_speed(fan::speed_enum_to_level(FAN_SPEED_MEDIUM, supported_speed_count)); - } else if (strcasecmp(legacy_speed, "high") == 0) { - this->set_speed(fan::speed_enum_to_level(FAN_SPEED_HIGH, supported_speed_count)); - } - return *this; -} } // namespace fan } // namespace esphome diff --git a/esphome/components/fan/fan_state.h b/esphome/components/fan/fan_state.h index c0d9d64d2c..044ee59736 100644 --- a/esphome/components/fan/fan_state.h +++ b/esphome/components/fan/fan_state.h @@ -1,126 +1,34 @@ #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" -#include "fan_traits.h" +#include "fan.h" namespace esphome { namespace fan { -/// Simple enum to represent the speed of a fan. - DEPRECATED - Will be deleted soon -enum ESPDEPRECATED("FanSpeed is deprecated.", "2021.9") FanSpeed { - FAN_SPEED_LOW = 0, ///< The fan is running on low speed. - FAN_SPEED_MEDIUM = 1, ///< The fan is running on medium speed. - FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed. +enum ESPDEPRECATED("LegacyFanDirection members are deprecated, use FanDirection instead.", + "2022.2") LegacyFanDirection { + FAN_DIRECTION_FORWARD = 0, + FAN_DIRECTION_REVERSE = 1 }; -/// Simple enum to represent the direction of a fan -enum FanDirection { FAN_DIRECTION_FORWARD = 0, FAN_DIRECTION_REVERSE = 1 }; - -enum FanRestoreMode { - FAN_RESTORE_DEFAULT_OFF, - FAN_RESTORE_DEFAULT_ON, - FAN_ALWAYS_OFF, - FAN_ALWAYS_ON, - FAN_RESTORE_INVERTED_DEFAULT_OFF, - FAN_RESTORE_INVERTED_DEFAULT_ON, -}; - -class FanState; - -class FanStateCall { - public: - explicit FanStateCall(FanState *state) : state_(state) {} - - FanStateCall &set_state(bool binary_state) { - this->binary_state_ = binary_state; - return *this; - } - FanStateCall &set_state(optional binary_state) { - this->binary_state_ = binary_state; - return *this; - } - FanStateCall &set_oscillating(bool oscillating) { - this->oscillating_ = oscillating; - return *this; - } - FanStateCall &set_oscillating(optional oscillating) { - this->oscillating_ = oscillating; - return *this; - } - FanStateCall &set_speed(int speed) { - this->speed_ = speed; - return *this; - } - ESPDEPRECATED("set_speed() with string argument is deprecated, use integer argument instead.", "2021.9") - FanStateCall &set_speed(const char *legacy_speed); - FanStateCall &set_direction(FanDirection direction) { - this->direction_ = direction; - return *this; - } - FanStateCall &set_direction(optional direction) { - this->direction_ = direction; - return *this; - } - - void perform() const; - - protected: - FanState *const state_; - optional binary_state_; - optional oscillating_; - optional speed_; - optional direction_{}; -}; - -class FanState : public EntityBase, public Component { +class ESPDEPRECATED("FanState is deprecated, use Fan instead.", "2022.2") FanState : public Fan, public Component { public: FanState() = default; - /// Construct the fan state with name. - explicit FanState(const std::string &name); + explicit FanState(const std::string &name) : Fan(name) {} - /// Register a callback that will be called each time the state changes. - void add_on_state_callback(std::function &&callback); - - /// Get the traits of this fan (i.e. what features it supports). - const FanTraits &get_traits() const; + /// Get the traits of this fan. + FanTraits get_traits() override { return this->traits_; } /// Set the traits of this fan (i.e. what features it supports). - void set_traits(const FanTraits &traits); - - /// Set the restore mode of this fan - void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } - - /// The current ON/OFF state of the fan. - bool state{false}; - /// The current oscillation state of the fan. - bool oscillating{false}; - /// The current fan speed level - int speed{}; - /// The current direction of the fan - FanDirection direction{FAN_DIRECTION_FORWARD}; - - FanStateCall turn_on(); - FanStateCall turn_off(); - FanStateCall toggle(); - FanStateCall make_call(); + void set_traits(const FanTraits &traits) { this->traits_ = traits; } void setup() override; float get_setup_priority() const override; protected: - friend FanStateCall; - - uint32_t hash_base() override; + void control(const FanCall &call) override { this->publish_state(); } FanTraits traits_{}; - CallbackManager state_callback_{}; - ESPPreferenceObject rtc_; - - /// Restore mode of the fan. - FanRestoreMode restore_mode_; }; } // namespace fan diff --git a/esphome/components/hbridge/fan/__init__.py b/esphome/components/hbridge/fan/__init__.py index b169978acd..421883a1ff 100644 --- a/esphome/components/hbridge/fan/__init__.py +++ b/esphome/components/hbridge/fan/__init__.py @@ -17,7 +17,7 @@ from .. import hbridge_ns CODEOWNERS = ["@WeekendWarrior"] -HBridgeFan = hbridge_ns.class_("HBridgeFan", fan.FanState) +HBridgeFan = hbridge_ns.class_("HBridgeFan", cg.Component, fan.Fan) DecayMode = hbridge_ns.enum("DecayMode") DECAY_MODE_OPTIONS = { @@ -59,6 +59,7 @@ async def to_code(config): config[CONF_SPEED_COUNT], config[CONF_DECAY_MODE], ) + await cg.register_component(var, config) await fan.register_fan(var, config) pin_a_ = await cg.get_variable(config[CONF_PIN_A]) cg.add(var.set_pin_a(pin_a_)) diff --git a/esphome/components/hbridge/fan/hbridge_fan.cpp b/esphome/components/hbridge/fan/hbridge_fan.cpp index a4e5429ff4..52d2b3d8b7 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.cpp +++ b/esphome/components/hbridge/fan/hbridge_fan.cpp @@ -22,47 +22,49 @@ void HBridgeFan::set_hbridge_levels_(float a_level, float b_level, float enable) ESP_LOGD(TAG, "Setting speed: a: %.2f, b: %.2f, enable: %.2f", a_level, b_level, enable); } -fan::FanStateCall HBridgeFan::brake() { +fan::FanCall HBridgeFan::brake() { ESP_LOGD(TAG, "Braking"); (this->enable_ == nullptr) ? this->set_hbridge_levels_(1.0f, 1.0f) : this->set_hbridge_levels_(1.0f, 1.0f, 1.0f); return this->make_call().set_state(false); } +void HBridgeFan::setup() { + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->apply(*this); + this->write_state_(); + } +} void HBridgeFan::dump_config() { - ESP_LOGCONFIG(TAG, "Fan '%s':", this->get_name().c_str()); - if (this->get_traits().supports_oscillation()) { - ESP_LOGCONFIG(TAG, " Oscillation: YES"); - } - if (this->get_traits().supports_direction()) { - ESP_LOGCONFIG(TAG, " Direction: YES"); - } + LOG_FAN("", "H-Bridge Fan", this); if (this->decay_mode_ == DECAY_MODE_SLOW) { ESP_LOGCONFIG(TAG, " Decay Mode: Slow"); } else { ESP_LOGCONFIG(TAG, " Decay Mode: Fast"); } } -void HBridgeFan::setup() { - auto traits = fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_); - this->set_traits(traits); - this->add_on_state_callback([this]() { this->next_update_ = true; }); +fan::FanTraits HBridgeFan::get_traits() { + return fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_); } -void HBridgeFan::loop() { - if (!this->next_update_) { - return; - } - this->next_update_ = false; +void HBridgeFan::control(const fan::FanCall &call) { + if (call.get_state().has_value()) + this->state = *call.get_state(); + if (call.get_speed().has_value()) + this->speed = *call.get_speed(); + if (call.get_oscillating().has_value()) + this->oscillating = *call.get_oscillating(); + if (call.get_direction().has_value()) + this->direction = *call.get_direction(); - float speed = 0.0f; - if (this->state) { - speed = static_cast(this->speed) / static_cast(this->speed_count_); - } + this->write_state_(); + this->publish_state(); +} +void HBridgeFan::write_state_() { + float speed = this->state ? static_cast(this->speed) / static_cast(this->speed_count_) : 0.0f; if (speed == 0.0f) { // off means idle (this->enable_ == nullptr) ? this->set_hbridge_levels_(speed, speed) : this->set_hbridge_levels_(speed, speed, speed); - return; - } - if (this->direction == fan::FAN_DIRECTION_FORWARD) { + } else if (this->direction == fan::FanDirection::FORWARD) { if (this->decay_mode_ == DECAY_MODE_SLOW) { (this->enable_ == nullptr) ? this->set_hbridge_levels_(1.0f - speed, 1.0f) : this->set_hbridge_levels_(1.0f - speed, 1.0f, 1.0f); @@ -79,6 +81,9 @@ void HBridgeFan::loop() { : this->set_hbridge_levels_(1.0f, 0.0f, speed); } } + + if (this->oscillating_ != nullptr) + this->oscillating_->set_state(this->oscillating); } } // namespace hbridge diff --git a/esphome/components/hbridge/fan/hbridge_fan.h b/esphome/components/hbridge/fan/hbridge_fan.h index 984318c8d6..4389b97ccb 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.h +++ b/esphome/components/hbridge/fan/hbridge_fan.h @@ -3,7 +3,7 @@ #include "esphome/core/automation.h" #include "esphome/components/output/binary_output.h" #include "esphome/components/output/float_output.h" -#include "esphome/components/fan/fan_state.h" +#include "esphome/components/fan/fan.h" namespace esphome { namespace hbridge { @@ -13,7 +13,7 @@ enum DecayMode { DECAY_MODE_FAST = 1, }; -class HBridgeFan : public fan::FanState { +class HBridgeFan : public Component, public fan::Fan { public: HBridgeFan(int speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {} @@ -22,25 +22,22 @@ class HBridgeFan : public fan::FanState { void set_enable_pin(output::FloatOutput *enable) { enable_ = enable; } void setup() override; - void loop() override; void dump_config() override; - float get_setup_priority() const override { return setup_priority::HARDWARE; } + fan::FanTraits get_traits() override; - fan::FanStateCall brake(); - - int get_speed_count() { return this->speed_count_; } - // update Hbridge without a triggered FanState change, eg. for acceleration/deceleration ramping - void internal_update() { this->next_update_ = true; } + fan::FanCall brake(); protected: output::FloatOutput *pin_a_; output::FloatOutput *pin_b_; output::FloatOutput *enable_{nullptr}; output::BinaryOutput *oscillating_{nullptr}; - bool next_update_{true}; int speed_count_{}; DecayMode decay_mode_{DECAY_MODE_SLOW}; + void control(const fan::FanCall &call) override; + void write_state_(); + void set_hbridge_levels_(float a_level, float b_level); void set_hbridge_levels_(float a_level, float b_level, float enable); }; diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 755f99d777..e4d867843c 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -14,9 +14,9 @@ static const char *const TAG = "mqtt.fan"; using namespace esphome::fan; -MQTTFanComponent::MQTTFanComponent(FanState *state) : state_(state) {} +MQTTFanComponent::MQTTFanComponent(Fan *state) : state_(state) {} -FanState *MQTTFanComponent::get_state() const { return this->state_; } +Fan *MQTTFanComponent::get_state() const { return this->state_; } std::string MQTTFanComponent::component_type() const { return "fan"; } const EntityBase *MQTTFanComponent::get_entity() const { return this->state_; } diff --git a/esphome/components/mqtt/mqtt_fan.h b/esphome/components/mqtt/mqtt_fan.h index 9d15a6cd0e..12286b9f01 100644 --- a/esphome/components/mqtt/mqtt_fan.h +++ b/esphome/components/mqtt/mqtt_fan.h @@ -13,7 +13,7 @@ namespace mqtt { class MQTTFanComponent : public mqtt::MQTTComponent { public: - explicit MQTTFanComponent(fan::FanState *state); + explicit MQTTFanComponent(fan::Fan *state); MQTT_COMPONENT_CUSTOM_TOPIC(oscillation, command) MQTT_COMPONENT_CUSTOM_TOPIC(oscillation, state) @@ -37,12 +37,12 @@ class MQTTFanComponent : public mqtt::MQTTComponent { /// 'fan' component type for discovery. std::string component_type() const override; - fan::FanState *get_state() const; + fan::Fan *get_state() const; protected: const EntityBase *get_entity() const override; - fan::FanState *state_; + fan::Fan *state_; }; } // namespace mqtt diff --git a/esphome/components/output/binary_output.h b/esphome/components/output/binary_output.h index 2697b23616..993ed9e8ac 100644 --- a/esphome/components/output/binary_output.h +++ b/esphome/components/output/binary_output.h @@ -30,6 +30,14 @@ class BinaryOutput { void set_power_supply(power_supply::PowerSupply *power_supply) { this->power_.set_parent(power_supply); } #endif + /// Enable or disable this binary output. + virtual void set_state(bool state) { + if (state) + this->turn_on(); + else + this->turn_off(); + } + /// Enable this binary output. virtual void turn_on() { #ifdef USE_POWER_SUPPLY diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index 618c866d5b..e65729b184 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -127,7 +127,7 @@ void PrometheusHandler::fan_type_(AsyncResponseStream *stream) { stream->print(F("#TYPE esphome_fan_speed GAUGE\n")); stream->print(F("#TYPE esphome_fan_oscillation GAUGE\n")); } -void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::FanState *obj) { +void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj) { if (obj->is_internal()) return; stream->print(F("esphome_fan_failed{id=\"")); diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index 5076883ba6..82e3fe28e0 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -52,7 +52,7 @@ class PrometheusHandler : public AsyncWebHandler, public Component { /// Return the type for prometheus void fan_type_(AsyncResponseStream *stream); /// Return the sensor state as prometheus data point - void fan_row_(AsyncResponseStream *stream, fan::FanState *obj); + void fan_row_(AsyncResponseStream *stream, fan::Fan *obj); #endif #ifdef USE_LIGHT diff --git a/esphome/components/speed/fan/__init__.py b/esphome/components/speed/fan/__init__.py index 0ee31c76a0..978e68d1e9 100644 --- a/esphome/components/speed/fan/__init__.py +++ b/esphome/components/speed/fan/__init__.py @@ -11,7 +11,7 @@ from esphome.const import ( ) from .. import speed_ns -SpeedFan = speed_ns.class_("SpeedFan", cg.Component) +SpeedFan = speed_ns.class_("SpeedFan", cg.Component, fan.Fan) CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( { @@ -29,11 +29,9 @@ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( async def to_code(config): output_ = await cg.get_variable(config[CONF_OUTPUT]) - state = await fan.create_fan_state(config) - var = cg.new_Pvariable( - config[CONF_OUTPUT_ID], state, output_, config[CONF_SPEED_COUNT] - ) + var = cg.new_Pvariable(config[CONF_OUTPUT_ID], output_, config[CONF_SPEED_COUNT]) await cg.register_component(var, config) + await fan.register_fan(var, config) if CONF_OSCILLATION_OUTPUT in config: oscillation_output = await cg.get_variable(config[CONF_OSCILLATION_OUTPUT]) diff --git a/esphome/components/speed/fan/speed_fan.cpp b/esphome/components/speed/fan/speed_fan.cpp index cb10db4ed4..9ed201982a 100644 --- a/esphome/components/speed/fan/speed_fan.cpp +++ b/esphome/components/speed/fan/speed_fan.cpp @@ -7,59 +7,39 @@ namespace speed { static const char *const TAG = "speed.fan"; -void SpeedFan::dump_config() { - ESP_LOGCONFIG(TAG, "Fan '%s':", this->fan_->get_name().c_str()); - if (this->fan_->get_traits().supports_oscillation()) { - ESP_LOGCONFIG(TAG, " Oscillation: YES"); - } - if (this->fan_->get_traits().supports_direction()) { - ESP_LOGCONFIG(TAG, " Direction: YES"); - } -} void SpeedFan::setup() { - auto traits = fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr, this->speed_count_); - this->fan_->set_traits(traits); - this->fan_->add_on_state_callback([this]() { this->next_update_ = true; }); -} -void SpeedFan::loop() { - if (!this->next_update_) { - return; - } - this->next_update_ = false; - - { - float speed = 0.0f; - if (this->fan_->state) { - speed = static_cast(this->fan_->speed) / static_cast(this->speed_count_); - } - ESP_LOGD(TAG, "Setting speed: %.2f", speed); - this->output_->set_level(speed); - } - - if (this->oscillating_ != nullptr) { - bool enable = this->fan_->oscillating; - if (enable) { - this->oscillating_->turn_on(); - } else { - this->oscillating_->turn_off(); - } - ESP_LOGD(TAG, "Setting oscillation: %s", ONOFF(enable)); - } - - if (this->direction_ != nullptr) { - bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE; - if (enable) { - this->direction_->turn_on(); - } else { - this->direction_->turn_off(); - } - ESP_LOGD(TAG, "Setting reverse direction: %s", ONOFF(enable)); + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->apply(*this); + this->write_state_(); } } +void SpeedFan::dump_config() { LOG_FAN("", "Speed Fan", this); } +fan::FanTraits SpeedFan::get_traits() { + return fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr, this->speed_count_); +} +void SpeedFan::control(const fan::FanCall &call) { + if (call.get_state().has_value()) + this->state = *call.get_state(); + if (call.get_speed().has_value()) + this->speed = *call.get_speed(); + if (call.get_oscillating().has_value()) + this->oscillating = *call.get_oscillating(); + if (call.get_direction().has_value()) + this->direction = *call.get_direction(); -// We need a higher priority than the FanState component to make sure that the traits are set -// when that component sets itself up. -float SpeedFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; } + this->write_state_(); + this->publish_state(); +} +void SpeedFan::write_state_() { + float speed = this->state ? static_cast(this->speed) / static_cast(this->speed_count_) : 0.0f; + this->output_->set_level(speed); + + if (this->oscillating_ != nullptr) + this->oscillating_->set_state(this->oscillating); + if (this->direction_ != nullptr) + this->direction_->set_state(this->direction == fan::FanDirection::REVERSE); +} } // namespace speed } // namespace esphome diff --git a/esphome/components/speed/fan/speed_fan.h b/esphome/components/speed/fan/speed_fan.h index 6b7fa0b0f2..1fad53813a 100644 --- a/esphome/components/speed/fan/speed_fan.h +++ b/esphome/components/speed/fan/speed_fan.h @@ -3,28 +3,27 @@ #include "esphome/core/component.h" #include "esphome/components/output/binary_output.h" #include "esphome/components/output/float_output.h" -#include "esphome/components/fan/fan_state.h" +#include "esphome/components/fan/fan.h" namespace esphome { namespace speed { -class SpeedFan : public Component { +class SpeedFan : public Component, public fan::Fan { public: - SpeedFan(fan::FanState *fan, output::FloatOutput *output, int speed_count) - : fan_(fan), output_(output), speed_count_(speed_count) {} + SpeedFan(output::FloatOutput *output, int speed_count) : output_(output), speed_count_(speed_count) {} void setup() override; - void loop() override; void dump_config() override; - float get_setup_priority() const override; void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; } void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; } + fan::FanTraits get_traits() override; protected: - fan::FanState *fan_; + void control(const fan::FanCall &call) override; + void write_state_(); + output::FloatOutput *output_; output::BinaryOutput *oscillating_{nullptr}; output::BinaryOutput *direction_{nullptr}; - bool next_update_{true}; int speed_count_{}; }; diff --git a/esphome/components/tuya/fan/__init__.py b/esphome/components/tuya/fan/__init__.py index 6d660e6d29..4832fd8638 100644 --- a/esphome/components/tuya/fan/__init__.py +++ b/esphome/components/tuya/fan/__init__.py @@ -10,7 +10,7 @@ CONF_SPEED_DATAPOINT = "speed_datapoint" CONF_OSCILLATION_DATAPOINT = "oscillation_datapoint" CONF_DIRECTION_DATAPOINT = "direction_datapoint" -TuyaFan = tuya_ns.class_("TuyaFan", cg.Component) +TuyaFan = tuya_ns.class_("TuyaFan", cg.Component, fan.Fan) CONFIG_SCHEMA = cv.All( fan.FAN_SCHEMA.extend( @@ -30,12 +30,10 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): parent = await cg.get_variable(config[CONF_TUYA_ID]) - state = await fan.create_fan_state(config) - var = cg.new_Pvariable( - config[CONF_OUTPUT_ID], parent, state, config[CONF_SPEED_COUNT] - ) + var = cg.new_Pvariable(config[CONF_OUTPUT_ID], parent, config[CONF_SPEED_COUNT]) await cg.register_component(var, config) + await fan.register_fan(var, config) if CONF_SPEED_DATAPOINT in config: cg.add(var.set_speed_id(config[CONF_SPEED_DATAPOINT])) diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index d0c8809564..019b504deb 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -8,52 +8,48 @@ namespace tuya { static const char *const TAG = "tuya.fan"; void TuyaFan::setup() { - auto traits = fan::FanTraits(this->oscillation_id_.has_value(), this->speed_id_.has_value(), - this->direction_id_.has_value(), this->speed_count_); - this->fan_->set_traits(traits); - if (this->speed_id_.has_value()) { this->parent_->register_listener(*this->speed_id_, [this](const TuyaDatapoint &datapoint) { ESP_LOGV(TAG, "MCU reported speed of: %d", datapoint.value_enum); - auto call = this->fan_->make_call(); - if (datapoint.value_enum < this->speed_count_) - call.set_speed(datapoint.value_enum + 1); - else - ESP_LOGCONFIG(TAG, "Speed has invalid value %d", datapoint.value_enum); - call.perform(); + if (datapoint.value_enum >= this->speed_count_) { + ESP_LOGE(TAG, "Speed has invalid value %d", datapoint.value_enum); + } else { + this->speed = datapoint.value_enum + 1; + this->publish_state(); + } }); } if (this->switch_id_.has_value()) { this->parent_->register_listener(*this->switch_id_, [this](const TuyaDatapoint &datapoint) { ESP_LOGV(TAG, "MCU reported switch is: %s", ONOFF(datapoint.value_bool)); - auto call = this->fan_->make_call(); - call.set_state(datapoint.value_bool); - call.perform(); + this->state = datapoint.value_bool; + this->publish_state(); }); } if (this->oscillation_id_.has_value()) { this->parent_->register_listener(*this->oscillation_id_, [this](const TuyaDatapoint &datapoint) { ESP_LOGV(TAG, "MCU reported oscillation is: %s", ONOFF(datapoint.value_bool)); - auto call = this->fan_->make_call(); - call.set_oscillating(datapoint.value_bool); - call.perform(); + this->oscillating = datapoint.value_bool; + this->publish_state(); }); } if (this->direction_id_.has_value()) { this->parent_->register_listener(*this->direction_id_, [this](const TuyaDatapoint &datapoint) { - auto call = this->fan_->make_call(); - call.set_direction(datapoint.value_bool ? fan::FAN_DIRECTION_REVERSE : fan::FAN_DIRECTION_FORWARD); - call.perform(); ESP_LOGD(TAG, "MCU reported reverse direction is: %s", ONOFF(datapoint.value_bool)); + this->direction = datapoint.value_bool ? fan::FanDirection::REVERSE : fan::FanDirection::FORWARD; + this->publish_state(); }); } - this->fan_->add_on_state_callback([this]() { this->write_state(); }); + this->parent_->add_on_initialized_callback([this]() { + auto restored = this->restore_state_(); + if (restored) + restored->to_call(*this).perform(); + }); } void TuyaFan::dump_config() { - ESP_LOGCONFIG(TAG, "Tuya Fan:"); - ESP_LOGCONFIG(TAG, " Speed count %d", this->speed_count_); + LOG_FAN("", "Tuya Fan", this); if (this->speed_id_.has_value()) ESP_LOGCONFIG(TAG, " Speed has datapoint ID %u", *this->speed_id_); if (this->switch_id_.has_value()) @@ -64,29 +60,26 @@ void TuyaFan::dump_config() { ESP_LOGCONFIG(TAG, " Direction has datapoint ID %u", *this->direction_id_); } -void TuyaFan::write_state() { - if (this->switch_id_.has_value()) { - ESP_LOGV(TAG, "Setting switch: %s", ONOFF(this->fan_->state)); - this->parent_->set_boolean_datapoint_value(*this->switch_id_, this->fan_->state); +fan::FanTraits TuyaFan::get_traits() { + return fan::FanTraits(this->oscillation_id_.has_value(), this->speed_id_.has_value(), this->direction_id_.has_value(), + this->speed_count_); +} + +void TuyaFan::control(const fan::FanCall &call) { + if (this->switch_id_.has_value() && call.get_state().has_value()) { + this->parent_->set_boolean_datapoint_value(*this->switch_id_, *call.get_state()); } - if (this->oscillation_id_.has_value()) { - ESP_LOGV(TAG, "Setting oscillating: %s", ONOFF(this->fan_->oscillating)); - this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, this->fan_->oscillating); + if (this->oscillation_id_.has_value() && call.get_oscillating().has_value()) { + this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, *call.get_oscillating()); } - if (this->direction_id_.has_value()) { - bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE; - ESP_LOGV(TAG, "Setting reverse direction: %s", ONOFF(enable)); + if (this->direction_id_.has_value() && call.get_direction().has_value()) { + bool enable = *call.get_direction() == fan::FanDirection::REVERSE; 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); - this->parent_->set_enum_datapoint_value(*this->speed_id_, this->fan_->speed - 1); + if (this->speed_id_.has_value() && call.get_speed().has_value()) { + this->parent_->set_enum_datapoint_value(*this->speed_id_, *call.get_speed() - 1); } } -// We need a higher priority than the FanState component to make sure that the traits are set -// when that component sets itself up. -float TuyaFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; } - } // namespace tuya } // namespace esphome diff --git a/esphome/components/tuya/fan/tuya_fan.h b/esphome/components/tuya/fan/tuya_fan.h index e96770d8c3..4aba1e1c07 100644 --- a/esphome/components/tuya/fan/tuya_fan.h +++ b/esphome/components/tuya/fan/tuya_fan.h @@ -2,35 +2,31 @@ #include "esphome/core/component.h" #include "esphome/components/tuya/tuya.h" -#include "esphome/components/fan/fan_state.h" +#include "esphome/components/fan/fan.h" namespace esphome { namespace tuya { -class TuyaFan : public Component { +class TuyaFan : public Component, public fan::Fan { public: - TuyaFan(Tuya *parent, fan::FanState *fan, int speed_count) : parent_(parent), fan_(fan), speed_count_(speed_count) {} + TuyaFan(Tuya *parent, int speed_count) : parent_(parent), speed_count_(speed_count) {} void setup() override; - float get_setup_priority() const override; void dump_config() override; void set_speed_id(uint8_t speed_id) { this->speed_id_ = speed_id; } void set_switch_id(uint8_t switch_id) { this->switch_id_ = switch_id; } void set_oscillation_id(uint8_t oscillation_id) { this->oscillation_id_ = oscillation_id; } void set_direction_id(uint8_t direction_id) { this->direction_id_ = direction_id; } - void write_state(); + + fan::FanTraits get_traits() override; protected: - void update_speed_(uint32_t value); - void update_switch_(uint32_t value); - void update_oscillation_(uint32_t value); - void update_direction_(uint32_t value); + void control(const fan::FanCall &call) override; Tuya *parent_; optional speed_id_{}; optional switch_id_{}; optional oscillation_id_{}; optional direction_id_{}; - fan::FanState *fan_; int speed_count_{}; }; diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 7413af67c4..a7dbcd4b85 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -440,8 +440,8 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con #endif #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) { +void WebServer::on_fan_update(fan::Fan *obj) { this->events_.send(this->fan_json(obj).c_str(), "state"); } +std::string WebServer::fan_json(fan::Fan *obj) { return json::build_json([obj](JsonObject root) { root["id"] = "fan-" + obj->get_object_id(); root["state"] = obj->state ? "ON" : "OFF"; @@ -470,7 +470,7 @@ 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()) { + for (fan::Fan *obj : App.get_fans()) { if (obj->get_object_id() != match.id) continue; @@ -516,7 +516,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc return; } } - this->defer([call]() { call.perform(); }); + this->defer([call]() mutable { call.perform(); }); request->send(200); } else if (match.method == "turn_off") { this->defer([obj]() { obj->turn_off().perform(); }); diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 8edb4237a2..afd4f1d4b5 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -128,13 +128,13 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_FAN - void on_fan_update(fan::FanState *obj) override; + void on_fan_update(fan::Fan *obj) override; /// Handle a fan request under '/fan//'. void handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the fan state as a JSON string. - std::string fan_json(fan::FanState *obj); + std::string fan_json(fan::Fan *obj); #endif #ifdef USE_LIGHT diff --git a/esphome/core/application.h b/esphome/core/application.h index 2a20793c19..2598a2f4a4 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -81,7 +81,7 @@ class Application { #endif #ifdef USE_FAN - void register_fan(fan::FanState *state) { this->fans_.push_back(state); } + void register_fan(fan::Fan *state) { this->fans_.push_back(state); } #endif #ifdef USE_COVER @@ -204,8 +204,8 @@ class Application { } #endif #ifdef USE_FAN - const std::vector &get_fans() { return this->fans_; } - fan::FanState *get_fan_by_key(uint32_t key, bool include_internal = false) { + const std::vector &get_fans() { return this->fans_; } + fan::Fan *get_fan_by_key(uint32_t key, bool include_internal = false) { for (auto *obj : this->fans_) if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; @@ -288,7 +288,7 @@ class Application { std::vector text_sensors_{}; #endif #ifdef USE_FAN - std::vector fans_{}; + std::vector fans_{}; #endif #ifdef USE_COVER std::vector covers_{}; diff --git a/esphome/core/controller.h b/esphome/core/controller.h index 0c3722855c..49750d1cc4 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -44,7 +44,7 @@ class Controller { virtual void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state){}; #endif #ifdef USE_FAN - virtual void on_fan_update(fan::FanState *obj){}; + virtual void on_fan_update(fan::Fan *obj){}; #endif #ifdef USE_LIGHT virtual void on_light_update(light::LightState *obj){}; diff --git a/script/ci-custom.py b/script/ci-custom.py index 52ac4025ca..2beba3483d 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -594,6 +594,7 @@ def lint_inclusive_language(fname, match): "esphome/components/binary_sensor/binary_sensor.h", "esphome/components/cover/cover.h", "esphome/components/display/display_buffer.h", + "esphome/components/fan/fan.h", "esphome/components/i2c/i2c.h", "esphome/components/mqtt/mqtt_component.h", "esphome/components/number/number.h", From c2ee0f0864d57babb12a8e4d0cf9603007f8a4fa Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 24 Jan 2022 00:34:38 +0100 Subject: [PATCH 034/238] Rename WEBSERVER_PORT define to USE_WEBSERVER_PORT (#3102) --- esphome/components/api/api_connection.cpp | 2 +- .../components/esp32_improv/esp32_improv_component.cpp | 2 +- .../components/improv_serial/improv_serial_component.cpp | 2 +- esphome/components/mdns/mdns_component.cpp | 8 ++++---- esphome/components/web_server/__init__.py | 2 +- esphome/core/defines.h | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index c4cc9d18f8..fc10fb103b 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -805,7 +805,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { resp.project_version = ESPHOME_PROJECT_VERSION; #endif #ifdef USE_WEBSERVER - resp.webserver_port = WEBSERVER_PORT; + resp.webserver_port = USE_WEBSERVER_PORT; #endif return resp; } diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 788e7a9460..956934abc1 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -128,7 +128,7 @@ void ESP32ImprovComponent::loop() { 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); + std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); urls.push_back(webserver_url); #endif std::vector data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls); diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index 9f86c06e6f..b55855abf9 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -95,7 +95,7 @@ std::vector ImprovSerialComponent::build_rpc_settings_response_(improv: 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); + std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); urls.push_back(webserver_url); #endif std::vector data = improv::build_rpc_response(command, urls, false); diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 915c640b06..168eaf3ae1 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -16,8 +16,8 @@ namespace mdns { static const char *const TAG = "mdns"; -#ifndef WEBSERVER_PORT -#define WEBSERVER_PORT 80 // NOLINT +#ifndef USE_WEBSERVER_PORT +#define USE_WEBSERVER_PORT 80 // NOLINT #endif void MDNSComponent::compile_records_() { @@ -63,7 +63,7 @@ void MDNSComponent::compile_records_() { MDNSService service{}; service.service_type = "_prometheus-http"; service.proto = "_tcp"; - service.port = WEBSERVER_PORT; + service.port = USE_WEBSERVER_PORT; this->services_.push_back(service); } #endif @@ -74,7 +74,7 @@ void MDNSComponent::compile_records_() { MDNSService service{}; service.service_type = "_http"; service.proto = "_tcp"; - service.port = WEBSERVER_PORT; + service.port = USE_WEBSERVER_PORT; service.txt_records.push_back({"version", ESPHOME_VERSION}); this->services_.push_back(service); } diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 62d5ec6f14..1e5f341717 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -66,8 +66,8 @@ async def to_code(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") + cg.add_define("USE_WEBSERVER_PORT", config[CONF_PORT]) 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])) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index a74755f651..cb30c0cf66 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -47,8 +47,8 @@ #define USE_MQTT #define USE_PROMETHEUS #define USE_WEBSERVER +#define USE_WEBSERVER_PORT 80 // NOLINT #define USE_WIFI_WPA2_EAP -#define WEBSERVER_PORT 80 // NOLINT #endif // ESP32-specific feature flags From f2d677d51a76358a142dcadcc3cb46787be9b907 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 24 Jan 2022 04:03:34 +0100 Subject: [PATCH 035/238] Fix path to extra_scripts in platformio.ini (#3093) --- platformio.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index d45e58953b..c271c8092a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -89,7 +89,7 @@ build_flags = ${common:arduino.build_flags} -DUSE_ESP8266 -DUSE_ESP8266_FRAMEWORK_ARDUINO -extra_scripts = post:esphome/components/esp8266/post_build.py +extra_scripts = post:esphome/components/esp8266/post_build.py.script ; This are common settings for the ESP32 (all variants) using Arduino. [common:esp32-arduino] @@ -108,7 +108,7 @@ build_flags = ${common:arduino.build_flags} -DUSE_ESP32 -DUSE_ESP32_FRAMEWORK_ARDUINO -extra_scripts = post:esphome/components/esp32/post_build.py +extra_scripts = post:esphome/components/esp32/post_build.py.script ; This are common settings for the ESP32 (all variants) using IDF. [common:esp32-idf] @@ -127,7 +127,7 @@ build_flags = -Wno-nonnull-compare -DUSE_ESP32 -DUSE_ESP32_FRAMEWORK_ESP_IDF -extra_scripts = post:esphome/components/esp32/post_build.py +extra_scripts = post:esphome/components/esp32/post_build.py.script ; All the actual environments are defined below. [env:esp8266-arduino] From cdda648360d2e7a86c3d7677155c3f2707596740 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 24 Jan 2022 10:34:34 +0100 Subject: [PATCH 036/238] Generate ARDUINO_VERSION_CODE in Python code (#3101) Co-authored-by: Otto winter --- esphome/components/esp32/__init__.py | 16 ++++++ esphome/components/esp8266/__init__.py | 7 ++- .../components/esp8266_pwm/esp8266_pwm.cpp | 5 +- esphome/components/esp8266_pwm/output.py | 23 ++++---- .../components/http_request/http_request.cpp | 8 +-- esphome/components/neopixelbus/light.py | 4 ++ .../neopixelbus/neopixelbus_light.h | 4 -- esphome/components/nextion/nextion_upload.cpp | 14 ++--- esphome/components/wifi/wifi_component.h | 3 +- .../wifi/wifi_component_esp8266.cpp | 12 ++--- esphome/core/defines.h | 8 +++ esphome/core/log.h | 7 +-- esphome/core/macros.h | 54 +------------------ esphome/writer.py | 1 + platformio.ini | 3 -- 15 files changed, 71 insertions(+), 98 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 8214886f8c..cec72e1b77 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -293,6 +293,8 @@ async def to_code(config): cg.add_platformio_option("lib_ldf_mode", "off") + framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] + conf = config[CONF_FRAMEWORK] cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) @@ -335,6 +337,13 @@ async def to_code(config): "CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE", False ) + cg.add_define( + "USE_ESP_IDF_VERSION_CODE", + cg.RawExpression( + f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})" + ), + ) + elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: cg.add_platformio_option("framework", "arduino") cg.add_build_flag("-DUSE_ARDUINO") @@ -346,6 +355,13 @@ async def to_code(config): cg.add_platformio_option("board_build.partitions", "partitions.csv") + cg.add_define( + "USE_ARDUINO_VERSION_CODE", + cg.RawExpression( + f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})" + ), + ) + ARDUINO_PARTITIONS_CSV = """\ nvs, data, nvs, 0x009000, 0x005000, diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 7182042770..9b1f091432 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -198,10 +198,15 @@ async def to_code(config): cg.add_platformio_option("board_build.flash_mode", config[CONF_BOARD_FLASH_MODE]) + ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] + cg.add_define( + "USE_ARDUINO_VERSION_CODE", + cg.RawExpression(f"VERSION_CODE({ver.major}, {ver.minor}, {ver.patch})"), + ) + if config[CONF_BOARD] in ESP8266_FLASH_SIZES: flash_size = ESP8266_FLASH_SIZES[config[CONF_BOARD]] ld_scripts = ESP8266_LD_SCRIPTS[flash_size] - ver = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] if ver <= cv.Version(2, 3, 0): # No ld script support diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.cpp b/esphome/components/esp8266_pwm/esp8266_pwm.cpp index e472edf2a7..8b3d8613b0 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.cpp +++ b/esphome/components/esp8266_pwm/esp8266_pwm.cpp @@ -2,13 +2,10 @@ #include "esp8266_pwm.h" #include "esphome/core/macros.h" +#include "esphome/core/defines.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" -#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) -#error ESP8266 PWM requires at least arduino_version 2.4.0 -#endif - #include namespace esphome { diff --git a/esphome/components/esp8266_pwm/output.py b/esphome/components/esp8266_pwm/output.py index 3d52e5af16..7feee79ff2 100644 --- a/esphome/components/esp8266_pwm/output.py +++ b/esphome/components/esp8266_pwm/output.py @@ -23,15 +23,20 @@ ESP8266PWM = esp8266_pwm_ns.class_("ESP8266PWM", output.FloatOutput, cg.Componen SetFrequencyAction = esp8266_pwm_ns.class_("SetFrequencyAction", automation.Action) validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6)) -CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( - { - cv.Required(CONF_ID): cv.declare_id(ESP8266PWM), - cv.Required(CONF_PIN): cv.All( - pins.internal_gpio_output_pin_schema, valid_pwm_pin - ), - cv.Optional(CONF_FREQUENCY, default="1kHz"): validate_frequency, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.All( + output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(ESP8266PWM), + cv.Required(CONF_PIN): cv.All( + pins.internal_gpio_output_pin_schema, valid_pwm_pin + ), + cv.Optional(CONF_FREQUENCY, default="1kHz"): validate_frequency, + } + ).extend(cv.COMPONENT_SCHEMA), + cv.require_framework_version( + esp8266_arduino=cv.Version(2, 4, 0), + ), +) async def to_code(config): diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index a80d095835..0fd9c6a4ae 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -1,7 +1,7 @@ #ifdef USE_ARDUINO #include "http_request.h" -#include "esphome/core/macros.h" +#include "esphome/core/defines.h" #include "esphome/core/log.h" #include "esphome/components/network/util.h" @@ -42,12 +42,12 @@ void HttpRequestComponent::send(const std::vector begin_status = this->client_.begin(url); #endif #ifdef USE_ESP8266 -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) this->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); -#elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) +#elif USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) this->client_.setFollowRedirects(true); #endif -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) this->client_.setRedirectLimit(3); #endif begin_status = this->client_.begin(*this->get_wifi_client_(), url); diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index 6bb1bc8f99..722e6f5b06 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -169,6 +169,10 @@ def _validate_method(value): CONFIG_SCHEMA = cv.All( cv.only_with_arduino, + cv.require_framework_version( + esp8266_arduino=cv.Version(2, 4, 0), + esp32_arduino=cv.Version(0, 0, 0), + ), light.ADDRESSABLE_LIGHT_SCHEMA.extend( { cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(NeoPixelBusLightOutputBase), diff --git a/esphome/components/neopixelbus/neopixelbus_light.h b/esphome/components/neopixelbus/neopixelbus_light.h index 34e10f2cfe..5233886075 100644 --- a/esphome/components/neopixelbus/neopixelbus_light.h +++ b/esphome/components/neopixelbus/neopixelbus_light.h @@ -9,10 +9,6 @@ #include "esphome/components/light/light_output.h" #include "esphome/components/light/addressable_light.h" -#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) -#error The NeoPixelBus library requires at least arduino_version 2.4.x -#endif - #include "NeoPixelBus.h" namespace esphome { diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index 1b60034bd1..9e6884398c 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -3,7 +3,7 @@ #ifdef USE_NEXTION_TFT_UPLOAD #include "esphome/core/application.h" -#include "esphome/core/macros.h" +#include "esphome/core/defines.h" #include "esphome/core/util.h" #include "esphome/core/log.h" #include "esphome/components/network/util.h" @@ -32,12 +32,12 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { range_end = this->tft_size_; #ifdef USE_ESP8266 -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); -#elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) +#elif USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) http->setFollowRedirects(true); #endif -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) http->setRedirectLimit(3); #endif #endif @@ -148,12 +148,12 @@ void Nextion::upload_tft() { begin_status = http.begin(this->tft_url_.c_str()); #endif #ifdef USE_ESP8266 -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); -#elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) +#elif USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) http.setFollowRedirects(true); #endif -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) http.setRedirectLimit(3); #endif begin_status = http.begin(*this->get_wifi_client_(), this->tft_url_.c_str()); diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 118de3a7a3..d032382dc8 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -1,6 +1,5 @@ #pragma once -#include "esphome/core/macros.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/automation.h" @@ -18,7 +17,7 @@ #include #include -#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) +#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) extern "C" { #include }; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 2021773209..de4253fe41 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -1,5 +1,5 @@ #include "wifi_component.h" -#include "esphome/core/macros.h" +#include "esphome/core/defines.h" #ifdef USE_ESP8266 @@ -20,7 +20,7 @@ extern "C" { #if LWIP_IPV6 #include "lwip/netif.h" // struct netif #endif -#if ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) #include "LwipDhcpServer.h" #define wifi_softap_set_dhcps_lease(lease) dhcpSoftAP.set_dhcps_lease(lease) #define wifi_softap_set_dhcps_lease_time(time) dhcpSoftAP.set_dhcps_lease_time(time) @@ -238,7 +238,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { conf.bssid_set = 0; } -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) if (ap.get_password().empty()) { conf.threshold.authmode = AUTH_OPEN; } else { @@ -528,7 +528,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { ESP_LOGVV(TAG, "Event: AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi); break; } -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) case EVENT_OPMODE_CHANGED: { auto it = event->event_info.opmode_changed; ESP_LOGV(TAG, "Event: Changed Mode old=%s new=%s", LOG_STR_ARG(get_op_mode_str(it.old_opmode)), @@ -614,7 +614,7 @@ bool WiFiComponent::wifi_scan_start_() { config.bssid = nullptr; config.channel = 0; config.show_hidden = 1; -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) config.scan_type = WIFI_SCAN_TYPE_ACTIVE; if (first_scan) { config.scan_time.active.min = 100; @@ -693,7 +693,7 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { return false; } -#if ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) dhcpSoftAP.begin(&info); #endif diff --git a/esphome/core/defines.h b/esphome/core/defines.h index cb30c0cf66..ded98054b6 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -5,6 +5,8 @@ // // This file is only used by static analyzers and IDEs. +#include "esphome/core/macros.h" + // Informative flags #define ESPHOME_BOARD "dummy_board" #define ESPHOME_PROJECT_NAME "dummy project" @@ -60,13 +62,19 @@ #define USE_SOCKET_IMPL_BSD_SOCKETS #ifdef USE_ARDUINO +#define USE_ARDUINO_VERSION_CODE VERSION_CODE(1, 0, 6) #define USE_ETHERNET #endif + +#ifdef USE_ESP_IDF +#define USE_ARDUINO_VERSION_CODE VERSION_CODE(4, 3, 0) +#endif #endif // ESP8266-specific feature flags #ifdef USE_ESP8266 #define USE_ADC_SENSOR_VCC +#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 0, 2) #define USE_ESP8266_PREFERENCES_FLASH #define USE_HTTP_REQUEST_ESP8266_HTTPS #define USE_SOCKET_IMPL_LWIP_TCP diff --git a/esphome/core/log.h b/esphome/core/log.h index 1e93ed4219..b1b1cf9115 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -6,10 +6,9 @@ #ifdef USE_STORE_LOG_STR_IN_FLASH #include "WString.h" +#include "esphome/core/defines.h" // for USE_ARDUINO_VERSION_CODE #endif -#include "esphome/core/macros.h" - // Include ESP-IDF/Arduino based logging methods here so they don't undefine ours later #if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) #include @@ -19,8 +18,6 @@ #include #endif -#include "esphome/core/macros.h" - namespace esphome { #define ESPHOME_LOG_LEVEL_NONE 0 @@ -176,7 +173,7 @@ struct LogString; #include -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 0) #define LOG_STR_ARG(s) ((PGM_P)(s)) #else // Pre-Arduino 2.5, we can't pass a PSTR() to printf(). Emulate support by copying the message to a diff --git a/esphome/core/macros.h b/esphome/core/macros.h index b0027a276c..70ceaf58f4 100644 --- a/esphome/core/macros.h +++ b/esphome/core/macros.h @@ -1,56 +1,4 @@ #pragma once +// Helper macro to define a version code, whos evalue can be compared against other version codes. #define VERSION_CODE(major, minor, patch) ((major) << 16 | (minor) << 8 | (patch)) - -#if defined(USE_ESP8266) - -#include -#if defined(ARDUINO_ESP8266_MAJOR) && defined(ARDUINO_ESP8266_MINOR) && defined(ARDUINO_ESP8266_REVISION) // v3.0.1+ -#define ARDUINO_VERSION_CODE VERSION_CODE(ARDUINO_ESP8266_MAJOR, ARDUINO_ESP8266_MINOR, ARDUINO_ESP8266_REVISION) -#elif ARDUINO_ESP8266_GIT_VER == 0xefb0341a // version defines were screwed up in v3.0.0 -#define ARDUINO_VERSION_CODE VERSION_CODE(3, 0, 0) -#elif defined(ARDUINO_ESP8266_RELEASE_2_7_4) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 4) -#elif defined(ARDUINO_ESP8266_RELEASE_2_7_3) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 3) -#elif defined(ARDUINO_ESP8266_RELEASE_2_7_2) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 2) -#elif defined(ARDUINO_ESP8266_RELEASE_2_7_1) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 1) -#elif defined(ARDUINO_ESP8266_RELEASE_2_7_0) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 0) -#elif defined(ARDUINO_ESP8266_RELEASE_2_6_3) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 6, 3) -#elif defined(ARDUINO_ESP8266_RELEASE_2_6_2) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 6, 2) -#elif defined(ARDUINO_ESP8266_RELEASE_2_6_1) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 6, 1) -#elif defined(ARDUINO_ESP8266_RELEASE_2_5_2) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 5, 2) -#elif defined(ARDUINO_ESP8266_RELEASE_2_5_1) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 5, 1) -#elif defined(ARDUINO_ESP8266_RELEASE_2_5_0) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 5, 0) -#elif defined(ARDUINO_ESP8266_RELEASE_2_4_2) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 4, 2) -#elif defined(ARDUINO_ESP8266_RELEASE_2_4_1) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 4, 1) -#elif defined(ARDUINO_ESP8266_RELEASE_2_4_0) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 4, 0) -#elif defined(ARDUINO_ESP8266_RELEASE_2_3_0) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 3, 0) -#else -#warning "Could not determine Arduino framework version, update esphome/core/macros.h!" -#endif - -#elif defined(USE_ESP32_FRAMEWORK_ARDUINO) - -#if defined(IDF_VER) // identifies v2, needed since v1 doesn't have the esp_arduino_version.h header -#include -#define ARDUINO_VERSION_CODE \ - VERSION_CODE(ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATH) -#else -#define ARDUINO_VERSION_CODE VERSION_CODE(1, 0, 0) // there are no defines identifying minor/patch version -#endif - -#endif diff --git a/esphome/writer.py b/esphome/writer.py index 89a074683a..31b47e243e 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -202,6 +202,7 @@ def write_platformio_project(): DEFINES_H_FORMAT = ESPHOME_H_FORMAT = """\ #pragma once +#include "esphome/core/macros.h" {} """ VERSION_H_FORMAT = """\ diff --git a/platformio.ini b/platformio.ini index c271c8092a..e2081a3ff6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -74,7 +74,6 @@ build_flags = ; 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 platform_packages = platformio/framework-arduinoespressif8266 @ ~3.30002.0 @@ -94,7 +93,6 @@ extra_scripts = post:esphome/components/esp8266/post_build.py.script ; 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 platform = platformio/espressif32 @ 3.3.2 platform_packages = platformio/framework-arduinoespressif32 @ ~3.10006.0 @@ -113,7 +111,6 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script ; 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 platform = platformio/espressif32 @ 3.3.2 platform_packages = platformio/framework-espidf @ ~3.40300.0 From 2f462679941b7c63cb5f77afe6ca7ceaa6a1a96e Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 24 Jan 2022 20:10:27 +0100 Subject: [PATCH 037/238] Add cv.require_esphome_version helper (#3103) --- esphome/config_validation.py | 15 ++++++++++++++- esphome/util.py | 8 ++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 7b68681ed2..2e7a4e5677 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -63,7 +63,7 @@ from esphome.jsonschema import ( jschema_registry, jschema_typed, ) - +from esphome.util import parse_esphome_version from esphome.voluptuous_schema import _Schema from esphome.yaml_util import make_data_base @@ -1742,6 +1742,19 @@ def require_framework_version( return validator +def require_esphome_version(year, month, patch): + def validator(value): + esphome_version = parse_esphome_version() + if esphome_version < (year, month, patch): + requires_version = f"{year}.{month}.{patch}" + raise Invalid( + f"This component requires at least ESPHome version {requires_version}" + ) + return value + + return validator + + @contextmanager def suppress_invalid(): try: diff --git a/esphome/util.py b/esphome/util.py index b2ba0c22c3..9975f5fc72 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -1,3 +1,4 @@ +import typing from typing import Union, List import collections @@ -242,6 +243,13 @@ def is_dev_esphome_version(): return "dev" in const.__version__ +def parse_esphome_version() -> typing.Tuple[int, int, int]: + match = re.match(r"^(\d+).(\d+).(\d+)(-dev\d*|b\d*)?$", const.__version__) + if match is None: + raise ValueError(f"Failed to parse ESPHome version '{const.__version__}'") + return int(match.group(1)), int(match.group(2)), int(match.group(3)) + + # Custom OrderedDict with nicer repr method for debugging class OrderedDict(collections.OrderedDict): def __repr__(self): From 7cb6729fa7ab611463eae249f7ef6c441f4ad2be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Jan 2022 08:37:35 +1300 Subject: [PATCH 038/238] Bump aioesphomeapi from 10.8.0 to 10.8.1 (#3110) 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 2e21f66981..2cbebb11f5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.4 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 esphome-dashboard==20220116.0 -aioesphomeapi==10.8.0 +aioesphomeapi==10.8.1 zeroconf==0.37.0 # esp-idf requires this, but doesn't bundle it by default From 6b27f2d2cff6c451e5e1920ed60c302f0856ecb0 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 24 Jan 2022 20:44:20 +0100 Subject: [PATCH 039/238] Remove unused polling_component_schema from modbus number (#3108) --- .../modbus_controller/number/__init__.py | 6 +-- tests/test5.yaml | 46 ++++++++++++++++--- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py index 4ad6601fee..56ec734315 100644 --- a/esphome/components/modbus_controller/number/__init__.py +++ b/esphome/components/modbus_controller/number/__init__.py @@ -58,8 +58,7 @@ def validate_modbus_number(config): CONFIG_SCHEMA = cv.All( - number.NUMBER_SCHEMA.extend(ModbusItemBaseSchema) - .extend( + number.NUMBER_SCHEMA.extend(ModbusItemBaseSchema).extend( { cv.GenerateID(): cv.declare_id(ModbusNumber), cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), @@ -72,8 +71,7 @@ CONFIG_SCHEMA = cv.All( 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")), + ), validate_min_max, validate_modbus_number, ) diff --git a/tests/test5.yaml b/tests/test5.yaml index b13f20a9b2..55786050b3 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -14,8 +14,8 @@ esp32: wifi: networks: - - ssid: 'MySSID' - password: 'password1' + - ssid: "MySSID" + password: "password1" manual_ip: static_ip: 192.168.1.23 gateway: 192.168.1.1 @@ -39,7 +39,6 @@ uart: i2c: - modbus: uart_id: uart1 flow_control_pin: 5 @@ -50,13 +49,20 @@ modbus_controller: address: 0x2 modbus_id: mod_bus1 - binary_sensor: - platform: gpio pin: GPIO0 id: io0_button icon: mdi:gesture-tap-button + - platform: modbus_controller + modbus_controller_id: modbus_controller_test + id: modbus_binsensortest + register_type: read + address: 0x3200 + bitmask: 0x80 #(bit 8) + lambda: !lambda "{ return x ;}" + tlc5947: data_pin: GPIO12 clock_pin: GPIO14 @@ -75,6 +81,14 @@ output: - platform: mcp47a1 id: output_mcp47a1 + - platform: modbus_controller + modbus_controller_id: modbus_controller_test + id: modbus_output_test + lambda: |- + return x * 1.0 ; + address: 0x9001 + value_type: U_WORD + demo: esp32_ble: @@ -104,9 +118,20 @@ number: max_value: 100 min_value: 0 step: 5 - unit_of_measurement: '%' + unit_of_measurement: "%" mode: slider + - id: modbus_numbertest + platform: modbus_controller + modbus_controller_id: modbus_controller_test + name: "ModbusNumber" + address: 0x9002 + value_type: U_WORD + lambda: "return x * 1.0; " + write_lambda: |- + return x * 1.0 ; + multiply: 1.0 + select: - platform: template name: My template select @@ -170,8 +195,7 @@ sensor: name: "SelecEM2M Maximum Demand Apparent Power" disabled_by_default: true - - id: battery_voltage - name: "Battery voltage2" + - id: modbus_sensortest platform: modbus_controller modbus_controller_id: modbus_controller_test address: 0x331A @@ -200,6 +224,14 @@ script: then: - logger.log: "looping!" +switch: + - platform: modbus_controller + modbus_controller_id: modbus_controller_test + id: modbus_switch_test + register_type: coil + address: 2 + bitmask: 1 + ektf2232: interrupt_pin: GPIO36 rts_pin: GPIO5 From 80d03a631e3bccc055d76cc642a6f8993f141df4 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 24 Jan 2022 20:56:36 +0100 Subject: [PATCH 040/238] Force braces around multi-line statements (#3094) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .clang-tidy | 3 +- esphome/components/ac_dimmer/ac_dimmer.cpp | 14 +++-- esphome/components/adc/adc_sensor.cpp | 5 +- esphome/components/am2320/am2320.cpp | 6 +- esphome/components/am43/am43.cpp | 3 +- esphome/components/am43/cover/am43_cover.cpp | 5 +- esphome/components/anova/anova.cpp | 3 +- esphome/components/apds9960/apds9960.cpp | 3 +- esphome/components/api/api_connection.cpp | 8 ++- esphome/components/api/api_server.cpp | 3 +- esphome/components/api/proto.h | 15 +++-- esphome/components/as3935/as3935.cpp | 5 +- esphome/components/ballu/ballu.cpp | 4 +- .../bang_bang/bang_bang_climate.cpp | 23 ++++--- esphome/components/ble_client/ble_client.cpp | 21 ++++--- esphome/components/ble_client/ble_client.h | 3 +- esphome/components/bme280/bme280.cpp | 6 +- esphome/components/bme680/bme680.cpp | 5 +- esphome/components/bmp280/bmp280.cpp | 6 +- esphome/components/ccs811/ccs811.cpp | 9 +-- .../climate_ir_lg/climate_ir_lg.cpp | 15 ++--- esphome/components/coolix/coolix.cpp | 23 ++++--- esphome/components/daikin/daikin.cpp | 13 ++-- esphome/components/dallas/esp_one_wire.cpp | 11 ++-- esphome/components/dht/dht.cpp | 10 +-- esphome/components/display/display_buffer.cpp | 13 ++-- esphome/components/ektf2232/ektf2232.cpp | 3 +- .../esp32_ble_server/ble_characteristic.cpp | 30 +++++---- .../esp32_ble_server/ble_service.cpp | 3 +- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 11 ++-- .../camera_web_server.cpp | 5 +- esphome/components/esp8266/preferences.cpp | 6 +- esphome/components/ezo/ezo.cpp | 3 +- .../fingerprint_grow/fingerprint_grow.cpp | 5 +- .../components/gpio/switch/gpio_switch.cpp | 10 +-- .../hitachi_ac344/hitachi_ac344.cpp | 21 ++++--- .../hitachi_ac424/hitachi_ac424.cpp | 21 ++++--- .../homeassistant_binary_sensor.cpp | 5 +- esphome/components/i2c/i2c_bus_arduino.cpp | 10 +-- esphome/components/i2c/i2c_bus_esp_idf.cpp | 5 +- esphome/components/inkplate6/inkplate.cpp | 9 +-- esphome/components/inkplate6/inkplate.h | 10 +-- esphome/components/light/light_call.cpp | 8 ++- esphome/components/light/light_state.cpp | 10 +-- esphome/components/max7219/max7219.cpp | 8 ++- .../components/max7219digit/max7219digit.cpp | 5 +- esphome/components/mcp23016/mcp23016.cpp | 5 +- .../mcp23x08_base/mcp23x08_base.cpp | 5 +- .../mcp23x17_base/mcp23x17_base.cpp | 5 +- esphome/components/midea/air_conditioner.cpp | 10 +-- esphome/components/midea_ir/midea_ir.cpp | 5 +- esphome/components/mitsubishi/mitsubishi.cpp | 3 +- .../binary_sensor/modbus_binarysensor.h | 5 +- .../modbus_controller/modbus_controller.h | 5 +- esphome/components/mqtt/mqtt_client.cpp | 8 ++- esphome/components/mqtt/mqtt_climate.cpp | 3 +- esphome/components/nextion/nextion.cpp | 7 ++- esphome/components/ota/ota_component.cpp | 5 +- esphome/components/output/binary_output.h | 5 +- .../output/switch/output_switch.cpp | 5 +- .../components/partition/light_partition.h | 5 +- esphome/components/pid/pid_climate.cpp | 16 ++--- esphome/components/pipsolar/pipsolar.cpp | 5 +- esphome/components/rc522/rc522.cpp | 14 +++-- .../remote_base/coolix_protocol.cpp | 5 +- .../components/remote_base/dish_protocol.cpp | 10 +-- .../components/remote_base/jvc_protocol.cpp | 5 +- .../components/remote_base/lg_protocol.cpp | 5 +- .../components/remote_base/midea_protocol.cpp | 11 ++-- .../components/remote_base/nec_protocol.cpp | 10 +-- .../components/remote_base/nexa_protocol.cpp | 25 +++++--- .../remote_base/panasonic_protocol.cpp | 10 +-- .../remote_base/pioneer_protocol.cpp | 30 +++++---- esphome/components/remote_base/raw_protocol.h | 5 +- .../remote_base/rc_switch_protocol.cpp | 10 +-- .../remote_base/samsung_protocol.cpp | 5 +- .../components/remote_base/sony_protocol.cpp | 5 +- .../remote_base/toshiba_ac_protocol.cpp | 15 +++-- .../remote_receiver_esp8266.cpp | 3 +- .../remote_transmitter_esp8266.cpp | 5 +- .../resistance/resistance_sensor.cpp | 10 +-- esphome/components/rtttl/rtttl.cpp | 5 +- esphome/components/scd30/scd30.cpp | 10 +-- esphome/components/scd4x/scd4x.cpp | 10 +-- esphome/components/sdp3x/sdp3x.cpp | 5 +- esphome/components/sensor/filter.cpp | 20 +++--- esphome/components/servo/servo.cpp | 5 +- esphome/components/sgp30/sgp30.cpp | 13 ++-- esphome/components/sgp40/sgp40.cpp | 15 +++-- esphome/components/sht3xd/sht3xd.cpp | 10 +-- esphome/components/shtcx/shtcx.cpp | 10 +-- .../components/socket/bsd_sockets_impl.cpp | 6 +- esphome/components/spi/spi.cpp | 5 +- esphome/components/sps30/sps30.cpp | 10 +-- .../components/ssd1306_base/ssd1306_base.cpp | 10 +-- .../components/ssd1325_base/ssd1325_base.cpp | 33 +++++----- .../components/ssd1331_base/ssd1331_base.cpp | 3 +- .../components/ssd1351_base/ssd1351_base.cpp | 10 +-- esphome/components/sts3x/sts3x.cpp | 10 +-- esphome/components/sun/sun.h | 5 +- .../sun/text_sensor/sun_text_sensor.h | 5 +- esphome/components/sx1509/sx1509.cpp | 10 +-- esphome/components/tcl112/tcl112.cpp | 14 +++-- .../template/number/template_number.cpp | 5 +- esphome/components/text_sensor/filter.cpp | 3 +- .../thermostat/thermostat_climate.cpp | 51 +++++++++------ esphome/components/tm1637/tm1637.cpp | 5 +- esphome/components/toshiba/toshiba.cpp | 5 +- esphome/components/tuya/light/tuya_light.cpp | 15 +++-- esphome/components/tuya/tuya.cpp | 27 ++++---- .../uart/uart_component_esp32_arduino.cpp | 10 +-- .../uart/uart_component_esp8266.cpp | 19 +++--- .../uart/uart_component_esp_idf.cpp | 9 +-- esphome/components/vl53l0x/vl53l0x_sensor.cpp | 5 +- esphome/components/vl53l0x/vl53l0x_sensor.h | 12 ++-- .../waveshare_epaper/waveshare_epaper.cpp | 30 +++++---- esphome/components/web_server/web_server.cpp | 63 ++++++++++++------- esphome/components/whirlpool/whirlpool.cpp | 4 +- esphome/components/wifi/wifi_component.cpp | 9 ++- esphome/components/wifi/wifi_component.h | 9 ++- .../wifi/wifi_component_esp_idf.cpp | 9 +-- .../xiaomi_miscale/xiaomi_miscale.cpp | 12 ++-- esphome/components/yashima/yashima.cpp | 3 +- esphome/core/helpers.cpp | 23 ++++--- esphome/core/scheduler.cpp | 14 +++-- 125 files changed, 770 insertions(+), 504 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index f784cd65ec..c9b77b5720 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -68,7 +68,6 @@ Checks: >- -modernize-use-nodiscard, -mpi-*, -objc-*, - -readability-braces-around-statements, -readability-convert-member-functions-to-static, -readability-else-after-return, -readability-function-cognitive-complexity, @@ -109,6 +108,8 @@ CheckOptions: value: 'make_unique' - key: modernize-make-unique.MakeSmartPtrFunctionHeader value: 'esphome/core/helpers.h' + - key: readability-braces-around-statements.ShortStatementLines + value: 2 - key: readability-identifier-naming.LocalVariableCase value: 'lower_case' - key: readability-identifier-naming.ClassCase diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index a7f1e6f3a9..e9af828a9d 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -52,10 +52,10 @@ uint32_t IRAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) { this->gate_pin.digital_write(false); } - if (time_since_zc < this->enable_time_us) + if (time_since_zc < this->enable_time_us) { // Next event is enable, return time until that event return this->enable_time_us - time_since_zc; - else if (time_since_zc < disable_time_us) { + } else if (time_since_zc < disable_time_us) { // Next event is disable, return time until that event return this->disable_time_us - time_since_zc; } @@ -74,9 +74,10 @@ uint32_t IRAM_ATTR HOT timer_interrupt() { uint32_t min_dt_us = 1000; uint32_t now = micros(); for (auto *dimmer : all_dimmers) { - if (dimmer == nullptr) + if (dimmer == nullptr) { // no more dimmers break; + } uint32_t res = dimmer->timer_intr(now); if (res != 0 && res < min_dt_us) min_dt_us = res; @@ -212,12 +213,13 @@ void AcDimmer::dump_config() { LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_); ESP_LOGCONFIG(TAG, " Min Power: %.1f%%", this->store_.min_power / 10.0f); ESP_LOGCONFIG(TAG, " Init with half cycle: %s", YESNO(this->init_with_half_cycle_)); - if (method_ == DIM_METHOD_LEADING_PULSE) + if (method_ == DIM_METHOD_LEADING_PULSE) { ESP_LOGCONFIG(TAG, " Method: leading pulse"); - else if (method_ == DIM_METHOD_LEADING) + } else if (method_ == DIM_METHOD_LEADING) { ESP_LOGCONFIG(TAG, " Method: leading"); - else + } else { ESP_LOGCONFIG(TAG, " Method: trailing"); + } LOG_FLOAT_OUTPUT(this); ESP_LOGV(TAG, " Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2); diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 0a439f8b8d..c3e5221bf7 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -65,9 +65,9 @@ void ADCSensor::dump_config() { #ifdef USE_ESP32 LOG_PIN(" Pin: ", pin_); - if (autorange_) + if (autorange_) { ESP_LOGCONFIG(TAG, " Attenuation: auto"); - else + } else { switch (this->attenuation_) { case ADC_ATTEN_DB_0: ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); @@ -84,6 +84,7 @@ void ADCSensor::dump_config() { default: // This is to satisfy the unused ADC_ATTEN_MAX break; } + } #endif // USE_ESP32 LOG_UPDATE_INTERVAL(this); } diff --git a/esphome/components/am2320/am2320.cpp b/esphome/components/am2320/am2320.cpp index c06a2a34d7..8ab48a348e 100644 --- a/esphome/components/am2320/am2320.cpp +++ b/esphome/components/am2320/am2320.cpp @@ -19,12 +19,14 @@ uint16_t crc_16(uint8_t *ptr, uint8_t length) { //------------------------------ while (length--) { crc ^= *ptr++; - for (i = 0; i < 8; i++) + for (i = 0; i < 8; i++) { if ((crc & 0x01) != 0) { crc >>= 1; crc ^= 0xA001; - } else + } else { crc >>= 1; + } + } } return crc; } diff --git a/esphome/components/am43/am43.cpp b/esphome/components/am43/am43.cpp index ced2ff8196..1c4bad64c3 100644 --- a/esphome/components/am43/am43.cpp +++ b/esphome/components/am43/am43.cpp @@ -79,9 +79,10 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); - if (status) + if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + } } this->current_sensor_ = 0; } diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp index afdbe65630..39089e73c0 100644 --- a/esphome/components/am43/cover/am43_cover.cpp +++ b/esphome/components/am43/cover/am43_cover.cpp @@ -30,10 +30,11 @@ void Am43Component::loop() { esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str()); - if (status) + if (status) { ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status); - else + } else { this->logged_in_ = true; + } } } diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index 5dfafbd2c9..39b1187caf 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -114,9 +114,10 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); - if (status) + if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + } } } break; diff --git a/esphome/components/apds9960/apds9960.cpp b/esphome/components/apds9960/apds9960.cpp index 9ee873ac64..5ba3afbacc 100644 --- a/esphome/components/apds9960/apds9960.cpp +++ b/esphome/components/apds9960/apds9960.cpp @@ -225,9 +225,10 @@ void APDS9960::read_gesture_data_() { uint8_t fifo_level; APDS9960_WARNING_CHECK(this->read_byte(0xAE, &fifo_level), "Reading FIFO level failed."); - if (fifo_level == 0) + if (fifo_level == 0) { // no data to process return; + } APDS9960_WARNING_CHECK(fifo_level <= 32, "FIFO level has invalid value.") diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index fc10fb103b..8a106dc39c 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -469,10 +469,11 @@ void APIConnection::switch_command(const SwitchCommandRequest &msg) { if (a_switch == nullptr) return; - if (msg.state) + if (msg.state) { a_switch->turn_on(); - else + } else { a_switch->turn_off(); + } } #endif @@ -810,10 +811,11 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { return resp; } void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) { - for (auto &it : this->parent_->get_state_subs()) + for (auto &it : this->parent_->get_state_subs()) { if (it.entity_id == msg.entity_id && it.attribute.value() == msg.attribute) { it.callback(msg.state); } + } } void APIConnection::execute_service(const ExecuteServiceRequest &msg) { bool found = false; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index c857763f95..3614c7d593 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -80,9 +80,10 @@ void APIServer::setup() { 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_) + for (auto &c : this->clients_) { if (!c->remove_) c->send_camera_state(image); + } }); } #endif diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index edbc916b01..38fd98b489 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -55,17 +55,19 @@ class ProtoVarInt { } int32_t as_sint32() const { // with ZigZag encoding - if (this->value_ & 1) + if (this->value_ & 1) { return static_cast(~(this->value_ >> 1)); - else + } else { return static_cast(this->value_ >> 1); + } } int64_t as_sint64() const { // with ZigZag encoding - if (this->value_ & 1) + if (this->value_ & 1) { return static_cast(~(this->value_ >> 1)); - else + } else { return static_cast(this->value_ >> 1); + } } void encode(std::vector &out) { uint32_t val = this->value_; @@ -220,10 +222,11 @@ class ProtoWriteBuffer { } void encode_sint32(uint32_t field_id, int32_t value, bool force = false) { uint32_t uvalue; - if (value < 0) + if (value < 0) { uvalue = ~(value << 1); - else + } else { uvalue = value << 1; + } this->encode_uint32(field_id, uvalue, force); } template void encode_message(uint32_t field_id, const C &value, bool force = false) { diff --git a/esphome/components/as3935/as3935.cpp b/esphome/components/as3935/as3935.cpp index 1cc400bb7b..b36856218a 100644 --- a/esphome/components/as3935/as3935.cpp +++ b/esphome/components/as3935/as3935.cpp @@ -58,10 +58,11 @@ void AS3935Component::loop() { void AS3935Component::write_indoor(bool indoor) { ESP_LOGV(TAG, "Setting indoor to %d", indoor); - if (indoor) + if (indoor) { this->write_register(AFE_GAIN, GAIN_MASK, INDOOR, 1); - else + } else { this->write_register(AFE_GAIN, GAIN_MASK, OUTDOOR, 1); + } } // REG0x01, bits[3:0], manufacturer default: 0010 (2). // This setting determines the threshold for events that trigger the diff --git a/esphome/components/ballu/ballu.cpp b/esphome/components/ballu/ballu.cpp index 96eefca5fb..b33ad11c1f 100644 --- a/esphome/components/ballu/ballu.cpp +++ b/esphome/components/ballu/ballu.cpp @@ -130,10 +130,10 @@ bool BalluClimate::on_receive(remote_base::RemoteReceiveData data) { for (int i = 0; i < BALLU_STATE_LENGTH; i++) { // Read bit for (int j = 0; j < 8; j++) { - if (data.expect_item(BALLU_BIT_MARK, BALLU_ONE_SPACE)) + if (data.expect_item(BALLU_BIT_MARK, BALLU_ONE_SPACE)) { remote_state[i] |= 1 << j; - else if (!data.expect_item(BALLU_BIT_MARK, BALLU_ZERO_SPACE)) { + } else if (!data.expect_item(BALLU_BIT_MARK, BALLU_ZERO_SPACE)) { ESP_LOGV(TAG, "Byte %d bit %d fail", i, j); return false; } diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index 4a95f8c339..20cb87025a 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -21,12 +21,13 @@ void BangBangClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles those for us - if (supports_cool_ && supports_heat_) + if (supports_cool_ && supports_heat_) { this->mode = climate::CLIMATE_MODE_HEAT_COOL; - else if (supports_cool_) + } else if (supports_cool_) { this->mode = climate::CLIMATE_MODE_COOL; - else if (supports_heat_) + } else if (supports_heat_) { this->mode = climate::CLIMATE_MODE_HEAT; + } this->change_away_(false); } } @@ -56,11 +57,12 @@ climate::ClimateTraits BangBangClimate::traits() { if (supports_cool_ && supports_heat_) traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL); traits.set_supports_two_point_target_temperature(true); - if (supports_away_) + if (supports_away_) { traits.set_supported_presets({ climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_AWAY, }); + } traits.set_supports_action(true); return traits; } @@ -82,17 +84,19 @@ void BangBangClimate::compute_state_() { if (too_cold) { // 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)) + (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_HEAT)) { target_action = climate::CLIMATE_ACTION_HEATING; - else + } else { target_action = climate::CLIMATE_ACTION_IDLE; + } } else if (too_hot) { // 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)) + (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_COOL)) { target_action = climate::CLIMATE_ACTION_COOLING; - else + } else { target_action = climate::CLIMATE_ACTION_IDLE; + } } else { // neither too hot nor too cold -> in range if (this->supports_cool_ && this->supports_heat_ && this->mode == climate::CLIMATE_MODE_HEAT_COOL) { @@ -107,9 +111,10 @@ void BangBangClimate::compute_state_() { this->switch_to_action_(target_action); } void BangBangClimate::switch_to_action_(climate::ClimateAction action) { - if (action == this->action) + if (action == this->action) { // already in target mode return; + } if ((action == climate::CLIMATE_ACTION_OFF && this->action == climate::CLIMATE_ACTION_IDLE) || (action == climate::CLIMATE_ACTION_IDLE && this->action == climate::CLIMATE_ACTION_OFF)) { diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index d17292b88f..89981e8174 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -252,9 +252,10 @@ float BLEClient::parse_char_value(uint8_t *value, uint16_t length) { } BLEService *BLEClient::get_service(espbt::ESPBTUUID uuid) { - for (auto *svc : this->services_) + for (auto *svc : this->services_) { if (svc->uuid == uuid) return svc; + } return nullptr; } @@ -272,19 +273,24 @@ BLECharacteristic *BLEClient::get_characteristic(uint16_t service, uint16_t chr) } BLEDescriptor *BLEClient::get_config_descriptor(uint16_t handle) { - for (auto &svc : this->services_) - for (auto &chr : svc->characteristics) - if (chr->handle == handle) - for (auto &desc : chr->descriptors) + for (auto &svc : this->services_) { + for (auto &chr : svc->characteristics) { + if (chr->handle == handle) { + for (auto &desc : chr->descriptors) { if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902)) return desc; + } + } + } + } return nullptr; } BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) { - for (auto &chr : this->characteristics) + for (auto &chr : this->characteristics) { if (chr->uuid == uuid) return chr; + } return nullptr; } @@ -379,9 +385,10 @@ void BLECharacteristic::parse_descriptors() { } BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) { - for (auto &desc : this->descriptors) + for (auto &desc : this->descriptors) { if (desc->uuid == uuid) return desc; + } return nullptr; } BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) { diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index 5680b69f72..c173f7a995 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -126,9 +126,10 @@ class BLEClient : public espbt::ESPBTClient, public Component { bool all_nodes_established_() { if (this->state() != espbt::ClientState::ESTABLISHED) return false; - for (auto &node : nodes_) + for (auto &node : nodes_) { if (node->node_state != espbt::ClientState::ESTABLISHED) return false; + } return true; } diff --git a/esphome/components/bme280/bme280.cpp b/esphome/components/bme280/bme280.cpp index d3a228328b..fcb293afa0 100644 --- a/esphome/components/bme280/bme280.cpp +++ b/esphome/components/bme280/bme280.cpp @@ -214,9 +214,10 @@ void BME280Component::update() { 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) + if (adc == 0x80000) { // temperature was disabled return NAN; + } const int32_t t1 = this->calibration_.t1; const int32_t t2 = this->calibration_.t2; @@ -233,9 +234,10 @@ float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) { 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) + if (adc == 0x80000) { // pressure was disabled return NAN; + } const int64_t p1 = this->calibration_.p1; const int64_t p2 = this->calibration_.p2; const int64_t p3 = this->calibration_.p3; diff --git a/esphome/components/bme680/bme680.cpp b/esphome/components/bme680/bme680.cpp index 9856304c81..64770ac0d5 100644 --- a/esphome/components/bme680/bme680.cpp +++ b/esphome/components/bme680/bme680.cpp @@ -420,10 +420,11 @@ float BME680Component::calc_humidity_(uint16_t raw_humidity) { calc_hum = var2 + (var3 + var4 * temp_comp) * var2 * var2; - if (calc_hum > 100.0f) + if (calc_hum > 100.0f) { calc_hum = 100.0f; - else if (calc_hum < 0.0f) + } else if (calc_hum < 0.0f) { calc_hum = 0.0f; + } return calc_hum; } diff --git a/esphome/components/bmp280/bmp280.cpp b/esphome/components/bmp280/bmp280.cpp index b4348e8a74..bda34e6c2b 100644 --- a/esphome/components/bmp280/bmp280.cpp +++ b/esphome/components/bmp280/bmp280.cpp @@ -161,9 +161,10 @@ float BMP280Component::read_temperature_(int32_t *t_fine) { return NAN; int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); adc >>= 4; - if (adc == 0x80000) + if (adc == 0x80000) { // temperature was disabled return NAN; + } const int32_t t1 = this->calibration_.t1; const int32_t t2 = this->calibration_.t2; @@ -183,9 +184,10 @@ float BMP280Component::read_pressure_(int32_t t_fine) { return NAN; int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); adc >>= 4; - if (adc == 0x80000) + if (adc == 0x80000) { // pressure was disabled return NAN; + } const int64_t p1 = this->calibration_.p1; const int64_t p2 = this->calibration_.p2; const int64_t p3 = this->calibration_.p3; diff --git a/esphome/components/ccs811/ccs811.cpp b/esphome/components/ccs811/ccs811.cpp index f8cee79c55..5c60989afa 100644 --- a/esphome/components/ccs811/ccs811.cpp +++ b/esphome/components/ccs811/ccs811.cpp @@ -39,14 +39,15 @@ void CCS811Component::setup() { // set MEAS_MODE (page 5) uint8_t meas_mode = 0; uint32_t interval = this->get_update_interval(); - if (interval >= 60 * 1000) + if (interval >= 60 * 1000) { meas_mode = 3 << 4; // sensor takes a reading every 60 seconds - else if (interval >= 10 * 1000) + } else if (interval >= 10 * 1000) { meas_mode = 2 << 4; // sensor takes a reading every 10 seconds - else if (interval >= 1 * 1000) + } else if (interval >= 1 * 1000) { meas_mode = 1 << 4; // sensor takes a reading every second - else + } else { meas_mode = 4 << 4; // sensor takes a reading every 250ms + } CHECKED_IO(this->write_byte(0x01, meas_mode)) diff --git a/esphome/components/climate_ir_lg/climate_ir_lg.cpp b/esphome/components/climate_ir_lg/climate_ir_lg.cpp index 31c7f22be5..a41aad1bd0 100644 --- a/esphome/components/climate_ir_lg/climate_ir_lg.cpp +++ b/esphome/components/climate_ir_lg/climate_ir_lg.cpp @@ -137,11 +137,11 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { this->swing_mode = this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; } else { - if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) + if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) { this->mode = climate::CLIMATE_MODE_HEAT_COOL; - else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) + } else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) { this->mode = climate::CLIMATE_MODE_DRY; - else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) { + } else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) { this->mode = climate::CLIMATE_MODE_HEAT; } else { this->mode = climate::CLIMATE_MODE_COOL; @@ -156,14 +156,15 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { this->fan_mode = climate::CLIMATE_FAN_AUTO; } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT || this->mode == climate::CLIMATE_MODE_DRY) { - if ((remote_state & FAN_MASK) == FAN_AUTO) + if ((remote_state & FAN_MASK) == FAN_AUTO) { this->fan_mode = climate::CLIMATE_FAN_AUTO; - else if ((remote_state & FAN_MASK) == FAN_MIN) + } else if ((remote_state & FAN_MASK) == FAN_MIN) { this->fan_mode = climate::CLIMATE_FAN_LOW; - else if ((remote_state & FAN_MASK) == FAN_MED) + } else if ((remote_state & FAN_MASK) == FAN_MED) { this->fan_mode = climate::CLIMATE_FAN_MEDIUM; - else if ((remote_state & FAN_MASK) == FAN_MAX) + } else if ((remote_state & FAN_MASK) == FAN_MAX) { this->fan_mode = climate::CLIMATE_FAN_HIGH; + } } } this->publish_state(); diff --git a/esphome/components/coolix/coolix.cpp b/esphome/components/coolix/coolix.cpp index c5d8cb07e9..738fd8d00d 100644 --- a/esphome/components/coolix/coolix.cpp +++ b/esphome/components/coolix/coolix.cpp @@ -125,34 +125,37 @@ bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteRecei 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) + if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT) { parent->mode = climate::CLIMATE_MODE_HEAT; - else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO) + } else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO) { 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) + } else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) { + if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY) { parent->mode = climate::CLIMATE_MODE_DRY; - else + } else { parent->mode = climate::CLIMATE_MODE_FAN_ONLY; + } } else parent->mode = climate::CLIMATE_MODE_COOL; // Fan Speed if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || parent->mode == climate::CLIMATE_MODE_HEAT_COOL || - parent->mode == climate::CLIMATE_MODE_DRY) + parent->mode == climate::CLIMATE_MODE_DRY) { parent->fan_mode = climate::CLIMATE_FAN_AUTO; - else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN) + } else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN) { parent->fan_mode = climate::CLIMATE_FAN_LOW; - else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED) + } else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED) { parent->fan_mode = climate::CLIMATE_FAN_MEDIUM; - else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX) + } else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX) { 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++) + for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++) { if (COOLIX_TEMP_MAP[i] == temperature_code) parent->target_temperature = i + COOLIX_TEMP_MIN; + } } parent->publish_state(); diff --git a/esphome/components/daikin/daikin.cpp b/esphome/components/daikin/daikin.cpp index 820cf78b3f..bb8587fbeb 100644 --- a/esphome/components/daikin/daikin.cpp +++ b/esphome/components/daikin/daikin.cpp @@ -175,14 +175,15 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) { } uint8_t fan_mode = frame[8]; uint8_t swing_mode = frame[9]; - if (fan_mode & 0xF && swing_mode & 0xF) + if (fan_mode & 0xF && swing_mode & 0xF) { this->swing_mode = climate::CLIMATE_SWING_BOTH; - else if (fan_mode & 0xF) + } else if (fan_mode & 0xF) { this->swing_mode = climate::CLIMATE_SWING_VERTICAL; - else if (swing_mode & 0xF) + } else if (swing_mode & 0xF) { this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; - else + } else { this->swing_mode = climate::CLIMATE_SWING_OFF; + } switch (fan_mode & 0xF0) { case DAIKIN_FAN_1: case DAIKIN_FAN_2: @@ -212,9 +213,9 @@ bool DaikinClimate::on_receive(remote_base::RemoteReceiveData data) { for (uint8_t pos = 0; pos < DAIKIN_STATE_FRAME_SIZE; pos++) { uint8_t byte = 0; for (int8_t bit = 0; bit < 8; bit++) { - if (data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE)) + if (data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE)) { byte |= 1 << bit; - else if (!data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE)) { + } else if (!data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE)) { return false; } } diff --git a/esphome/components/dallas/esp_one_wire.cpp b/esphome/components/dallas/esp_one_wire.cpp index a0ab10f8a4..6dc085a0bf 100644 --- a/esphome/components/dallas/esp_one_wire.cpp +++ b/esphome/components/dallas/esp_one_wire.cpp @@ -145,9 +145,10 @@ uint64_t ESPOneWire::search() { // read its complement bool cmp_id_bit = this->read_bit(); - if (id_bit && cmp_id_bit) + if (id_bit && cmp_id_bit) { // No devices participating in search break; + } bool branch; @@ -170,12 +171,13 @@ uint64_t ESPOneWire::search() { } } - if (branch) + if (branch) { // set bit this->rom_number8_()[rom_byte_number] |= rom_byte_mask; - else + } else { // clear bit this->rom_number8_()[rom_byte_number] &= ~rom_byte_mask; + } // choose/announce branch this->write_bit(branch); @@ -190,9 +192,10 @@ uint64_t ESPOneWire::search() { if (id_bit_number >= 65) { this->last_discrepancy_ = last_zero; - if (this->last_discrepancy_ == 0) + if (this->last_discrepancy_ == 0) { // we're at root and have no choices left, so this was the last one. this->last_device_flag_ = true; + } search_result = true; } diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index 2a4ccf1529..c70b227330 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -114,10 +114,11 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r // Wait for rising edge while (!this->pin_->digital_read()) { if (micros() - start_time > 90) { - if (i < 0) + if (i < 0) { error_code = 1; - else + } else { error_code = 2; + } break; } } @@ -130,10 +131,11 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r // Wait for falling edge while (this->pin_->digital_read()) { if ((end_time = micros()) - start_time > 90) { - if (i < 0) + if (i < 0) { error_code = 3; - else + } else { error_code = 4; + } break; } } diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index bdf299e8f1..ac878b01e1 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -176,9 +176,10 @@ void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]); if (!font->get_glyphs().empty()) { uint8_t glyph_width = font->get_glyphs()[0].glyph_data_->width; - for (int glyph_x = 0; glyph_x < glyph_width; glyph_x++) + for (int glyph_x = 0; glyph_x < glyph_width; glyph_x++) { for (int glyph_y = 0; glyph_y < height; glyph_y++) this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color); + } x_at += glyph_width; } @@ -425,10 +426,11 @@ int Font::match_next_glyph(const char *str, int *match_length) { int hi = this->glyphs_.size() - 1; while (lo != hi) { int mid = (lo + hi + 1) / 2; - if (this->glyphs_[mid].compare_to(str)) + if (this->glyphs_[mid].compare_to(str)) { lo = mid; - else + } else { hi = mid - 1; + } } *match_length = this->glyphs_[lo].match_length(str); if (*match_length <= 0) @@ -454,10 +456,11 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in } const Glyph &glyph = this->glyphs_[glyph_n]; - if (!has_char) + if (!has_char) { min_x = glyph.glyph_data_->offset_x; - else + } else { min_x = std::min(min_x, x + glyph.glyph_data_->offset_x); + } x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x; i += match_length; diff --git a/esphome/components/ektf2232/ektf2232.cpp b/esphome/components/ektf2232/ektf2232.cpp index da16dc3cfe..f96928a8de 100644 --- a/esphome/components/ektf2232/ektf2232.cpp +++ b/esphome/components/ektf2232/ektf2232.cpp @@ -68,9 +68,10 @@ void EKTF2232Touchscreen::loop() { uint8_t raw[8]; this->read(raw, 8); - for (int i = 0; i < 8; i++) + for (int i = 0; i < 8; i++) { if (raw[7] & (1 << i)) touch_count++; + } if (touch_count == 0) { for (auto *listener : this->touch_listeners_) diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index fae8c13934..df822ac0b9 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -147,40 +147,46 @@ bool BLECharacteristic::is_failed() { } void BLECharacteristic::set_broadcast_property(bool value) { - if (value) + if (value) { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST); - else + } else { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); + } } void BLECharacteristic::set_indicate_property(bool value) { - if (value) + if (value) { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE); - else + } else { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); + } } void BLECharacteristic::set_notify_property(bool value) { - if (value) + if (value) { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY); - else + } else { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); + } } void BLECharacteristic::set_read_property(bool value) { - if (value) + if (value) { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ); - else + } else { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ); + } } void BLECharacteristic::set_write_property(bool value) { - if (value) + if (value) { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE); - else + } else { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE); + } } void BLECharacteristic::set_write_no_response_property(bool value) { - if (value) + if (value) { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); - else + } else { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + } } void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, diff --git a/esphome/components/esp32_ble_server/ble_service.cpp b/esphome/components/esp32_ble_server/ble_service.cpp index 281164f0f5..4fcd2e3e79 100644 --- a/esphome/components/esp32_ble_server/ble_service.cpp +++ b/esphome/components/esp32_ble_server/ble_service.cpp @@ -18,9 +18,10 @@ BLEService::~BLEService() { } BLECharacteristic *BLEService::get_characteristic(ESPBTUUID uuid) { - for (auto *chr : this->characteristics_) + for (auto *chr : this->characteristics_) { if (chr->get_uuid() == uuid) return chr; + } return nullptr; } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 084dab4c84..7614e33979 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -58,11 +58,12 @@ void ESP32BLETracker::setup() { void ESP32BLETracker::loop() { BLEEvent *ble_event = this->ble_events_.pop(); while (ble_event != nullptr) { - if (ble_event->type_) + 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); - else + } else { 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(); } @@ -89,11 +90,12 @@ void ESP32BLETracker::loop() { device.parse_scan_rst(this->scan_result_buffer_[i]); bool found = false; - for (auto *listener : this->listeners_) + for (auto *listener : this->listeners_) { if (listener->parse_device(device)) found = true; + } - for (auto *client : this->clients_) + for (auto *client : this->clients_) { if (client->parse_device(device)) { found = true; if (client->state() == ClientState::DISCOVERED) { @@ -103,6 +105,7 @@ void ESP32BLETracker::loop() { } } } + } if (!found) { this->print_bt_device_info(device); 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 39b110bc85..3210989ff5 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.cpp +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp @@ -87,10 +87,11 @@ void CameraWebServer::on_shutdown() { void CameraWebServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 Camera Web Server:"); ESP_LOGCONFIG(TAG, " Port: %d", this->port_); - if (this->mode_ == STREAM) + if (this->mode_ == STREAM) { ESP_LOGCONFIG(TAG, " Mode: stream"); - else + } else { ESP_LOGCONFIG(TAG, " Mode: snapshot"); + } if (this->is_failed()) { ESP_LOGE(TAG, " Setup Failed"); diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index a8f8bd0d41..0e42cea576 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -98,16 +98,18 @@ static bool load_from_flash(size_t offset, uint32_t *data, size_t len) { } static bool save_to_rtc(size_t offset, const uint32_t *data, size_t len) { - for (uint32_t i = 0; i < len; i++) + for (uint32_t i = 0; i < len; i++) { if (!esp_rtc_user_mem_write(offset + i, data[i])) return false; + } return true; } static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) { - for (uint32_t i = 0; i < len; i++) + for (uint32_t i = 0; i < len; i++) { if (!esp_rtc_user_mem_read(offset + i, &data[i])) return false; + } return true; } diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index 2ee5782ff6..12f88a0f66 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -75,9 +75,10 @@ void EZOSensor::loop() { return; // some sensors return multiple comma-separated values, terminate string after first one - for (size_t i = 1; i < sizeof(buf) - 1; i++) + for (size_t i = 1; i < sizeof(buf) - 1; i++) { if (buf[i] == ',') buf[i] = '\0'; + } float val = parse_number((char *) &buf[1]).value_or(0); this->publish_state(val); diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.cpp b/esphome/components/fingerprint_grow/fingerprint_grow.cpp index be17e29de3..3b8c52fea2 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.cpp +++ b/esphome/components/fingerprint_grow/fingerprint_grow.cpp @@ -278,10 +278,11 @@ void FingerprintGrowComponent::delete_all_fingerprints() { void FingerprintGrowComponent::led_control(bool state) { ESP_LOGD(TAG, "Setting LED"); - if (state) + if (state) { this->data_ = {LED_ON}; - else + } else { this->data_ = {LED_OFF}; + } switch (this->send_command_()) { case OK: ESP_LOGD(TAG, "LED set"); diff --git a/esphome/components/gpio/switch/gpio_switch.cpp b/esphome/components/gpio/switch/gpio_switch.cpp index 56e0087eae..714f2ea6d8 100644 --- a/esphome/components/gpio/switch/gpio_switch.cpp +++ b/esphome/components/gpio/switch/gpio_switch.cpp @@ -33,16 +33,18 @@ void GPIOSwitch::setup() { } // write state before setup - if (initial_state) + if (initial_state) { this->turn_on(); - else + } else { this->turn_off(); + } this->pin_->setup(); // write after setup again for other IOs - if (initial_state) + if (initial_state) { this->turn_on(); - else + } else { this->turn_off(); + } } void GPIOSwitch::dump_config() { LOG_SWITCH("", "GPIO Switch", this); diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.cpp b/esphome/components/hitachi_ac344/hitachi_ac344.cpp index f8c3f1722d..7b93b00503 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.cpp +++ b/esphome/components/hitachi_ac344/hitachi_ac344.cpp @@ -19,10 +19,11 @@ void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, con void set_bit(uint8_t *const data, const uint8_t position, const bool on) { uint8_t mask = 1 << position; - if (on) + if (on) { *data |= mask; - else + } else { *data &= ~mask; + } } uint8_t *invert_byte_pairs(uint8_t *ptr, const uint16_t length) { @@ -69,10 +70,11 @@ void HitachiClimate::set_temp_(uint8_t celsius, bool set_previous) { temp = std::min(celsius, HITACHI_AC344_TEMP_MAX); temp = std::max(temp, HITACHI_AC344_TEMP_MIN); set_bits(&remote_state_[HITACHI_AC344_TEMP_BYTE], HITACHI_AC344_TEMP_OFFSET, HITACHI_AC344_TEMP_SIZE, temp); - if (previous_temp_ > temp) + if (previous_temp_ > temp) { set_button_(HITACHI_AC344_BUTTON_TEMP_DOWN); - else if (previous_temp_ < temp) + } else if (previous_temp_ < temp) { set_button_(HITACHI_AC344_BUTTON_TEMP_UP); + } if (set_previous) previous_temp_ = temp; } @@ -110,11 +112,12 @@ void HitachiClimate::set_fan_(uint8_t speed) { void HitachiClimate::set_swing_v_toggle_(bool on) { uint8_t button = get_button_(); // Get the current button value. - if (on) - button = HITACHI_AC344_BUTTON_SWINGV; // Set the button to SwingV. - else if (button == HITACHI_AC344_BUTTON_SWINGV) // Asked to unset it + if (on) { + button = HITACHI_AC344_BUTTON_SWINGV; // Set the button to SwingV. + } else if (button == HITACHI_AC344_BUTTON_SWINGV) { // Asked to unset it // It was set previous, so use Power as a default button = HITACHI_AC344_BUTTON_POWER; + } set_button_(button); } @@ -320,9 +323,9 @@ bool HitachiClimate::on_receive(remote_base::RemoteReceiveData data) { for (uint8_t pos = 0; pos < HITACHI_AC344_STATE_LENGTH; pos++) { // Read bit for (int8_t bit = 0; bit < 8; bit++) { - if (data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ONE_SPACE)) + if (data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ONE_SPACE)) { recv_state[pos] |= 1 << bit; - else if (!data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ZERO_SPACE)) { + } else if (!data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ZERO_SPACE)) { ESP_LOGVV(TAG, "Byte %d bit %d fail", pos, bit); return false; } diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.cpp b/esphome/components/hitachi_ac424/hitachi_ac424.cpp index a6f146c6c3..65cfaa4175 100644 --- a/esphome/components/hitachi_ac424/hitachi_ac424.cpp +++ b/esphome/components/hitachi_ac424/hitachi_ac424.cpp @@ -19,10 +19,11 @@ void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, con void set_bit(uint8_t *const data, const uint8_t position, const bool on) { uint8_t mask = 1 << position; - if (on) + if (on) { *data |= mask; - else + } else { *data &= ~mask; + } } uint8_t *invert_byte_pairs(uint8_t *ptr, const uint16_t length) { @@ -69,10 +70,11 @@ void HitachiClimate::set_temp_(uint8_t celsius, bool set_previous) { temp = std::min(celsius, HITACHI_AC424_TEMP_MAX); temp = std::max(temp, HITACHI_AC424_TEMP_MIN); set_bits(&remote_state_[HITACHI_AC424_TEMP_BYTE], HITACHI_AC424_TEMP_OFFSET, HITACHI_AC424_TEMP_SIZE, temp); - if (previous_temp_ > temp) + if (previous_temp_ > temp) { set_button_(HITACHI_AC424_BUTTON_TEMP_DOWN); - else if (previous_temp_ < temp) + } else if (previous_temp_ < temp) { set_button_(HITACHI_AC424_BUTTON_TEMP_UP); + } if (set_previous) previous_temp_ = temp; } @@ -110,11 +112,12 @@ void HitachiClimate::set_fan_(uint8_t speed) { void HitachiClimate::set_swing_v_toggle_(bool on) { uint8_t button = get_button_(); // Get the current button value. - if (on) - button = HITACHI_AC424_BUTTON_SWINGV; // Set the button to SwingV. - else if (button == HITACHI_AC424_BUTTON_SWINGV) // Asked to unset it + if (on) { + button = HITACHI_AC424_BUTTON_SWINGV; // Set the button to SwingV. + } else if (button == HITACHI_AC424_BUTTON_SWINGV) { // Asked to unset it // It was set previous, so use Power as a default button = HITACHI_AC424_BUTTON_POWER; + } set_button_(button); } @@ -321,9 +324,9 @@ bool HitachiClimate::on_receive(remote_base::RemoteReceiveData data) { for (uint8_t pos = 0; pos < HITACHI_AC424_STATE_LENGTH; pos++) { // Read bit for (int8_t bit = 0; bit < 8; bit++) { - if (data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ONE_SPACE)) + if (data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ONE_SPACE)) { recv_state[pos] |= 1 << bit; - else if (!data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ZERO_SPACE)) { + } else if (!data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ZERO_SPACE)) { ESP_LOGVV(TAG, "Byte %d bit %d fail", pos, bit); return false; } diff --git a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp index cea02f072a..a36fcb204a 100644 --- a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp +++ b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp @@ -25,10 +25,11 @@ void HomeassistantBinarySensor::setup() { } else { ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_.c_str(), ONOFF(new_state)); } - if (this->initial_) + if (this->initial_) { this->publish_initial_state(new_state); - else + } else { this->publish_state(new_state); + } break; } this->initial_ = false; diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index b605928692..693b869bf7 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -16,10 +16,11 @@ void ArduinoI2CBus::setup() { #ifdef USE_ESP32 static uint8_t next_bus_num = 0; - if (next_bus_num == 0) + if (next_bus_num == 0) { wire_ = &Wire; - else + } else { wire_ = new TwoWire(next_bus_num); // NOLINT(cppcoreguidelines-owning-memory) + } next_bus_num++; #else wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer) @@ -55,10 +56,11 @@ void ArduinoI2CBus::dump_config() { ESP_LOGI(TAG, "Found no i2c devices!"); } else { for (const auto &s : scan_results_) { - if (s.second) + if (s.second) { ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first); - else + } else { ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first); + } } } } diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 109c3f890d..606583fd7c 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -65,10 +65,11 @@ void IDFI2CBus::dump_config() { ESP_LOGI(TAG, "Found no i2c devices!"); } else { for (const auto &s : scan_results_) { - if (s.second) + if (s.second) { ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first); - else + } else { ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first); + } } } } diff --git a/esphome/components/inkplate6/inkplate.cpp b/esphome/components/inkplate6/inkplate.cpp index e62e594a49..8b7890feb7 100644 --- a/esphome/components/inkplate6/inkplate.cpp +++ b/esphome/components/inkplate6/inkplate.cpp @@ -548,14 +548,15 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { eink_on_(); uint8_t data = 0; - if (c == 0) // White + if (c == 0) { // White data = 0b10101010; - else if (c == 1) // Black + } else if (c == 1) { // Black data = 0b01010101; - else if (c == 2) // Discharge + } else if (c == 2) { // Discharge data = 0b00000000; - else if (c == 3) // Skip + } else if (c == 3) { // Skip data = 0b11111111; + } uint32_t send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | (((data & 0b11100000) >> 5) << 25); diff --git a/esphome/components/inkplate6/inkplate.h b/esphome/components/inkplate6/inkplate.h index 2dac12a0c4..6cbb24805d 100644 --- a/esphome/components/inkplate6/inkplate.h +++ b/esphome/components/inkplate6/inkplate.h @@ -109,18 +109,20 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public void pins_as_outputs_(); int get_width_internal() override { - if (this->model_ == INKPLATE_6) + if (this->model_ == INKPLATE_6) { return 800; - else if (this->model_ == INKPLATE_10) + } else if (this->model_ == INKPLATE_10) { return 1200; + } return 0; } int get_height_internal() override { - if (this->model_ == INKPLATE_6) + if (this->model_ == INKPLATE_6) { return 600; - else if (this->model_ == INKPLATE_10) + } else if (this->model_ == INKPLATE_10) { return 825; + } return 0; } diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 3f1b8aef30..fb4e45b3b6 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -98,10 +98,11 @@ void LightCall::perform() { // EFFECT auto effect = this->effect_; const char *effect_s; - if (effect == 0u) + if (effect == 0u) { effect_s = "None"; - else + } else { effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str(); + } if (this->publish_) { ESP_LOGD(TAG, " Effect: '%s'", effect_s); @@ -445,9 +446,10 @@ std::set LightCall::get_suitable_color_modes_() { }; auto key = KEY(has_white, has_ct, has_cwww, has_rgb); - for (auto &item : lookup_table) + for (auto &item : lookup_table) { if (std::get<0>(item) == key) return std::get<1>(item); + } // This happens if there are conflicting flags given. return {}; diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 5f16585c36..151bc58a1c 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -145,10 +145,11 @@ void LightState::publish_state() { this->remote_values_callback_.call(); } LightOutput *LightState::get_output() const { return this->output_; } std::string LightState::get_effect_name() { - if (this->active_effect_index_ > 0) + if (this->active_effect_index_ > 0) { return this->effects_[this->active_effect_index_ - 1]->get_name(); - else + } else { return "None"; + } } void LightState::add_new_remote_values_callback(std::function &&send_callback) { @@ -219,10 +220,11 @@ void LightState::start_effect_(uint32_t effect_index) { effect->start_internal(); } LightEffect *LightState::get_active_effect_() { - if (this->active_effect_index_ == 0) + if (this->active_effect_index_ == 0) { return nullptr; - else + } else { return this->effects_[this->active_effect_index_ - 1]; + } } void LightState::stop_effect_() { auto *effect = this->get_active_effect_(); diff --git a/esphome/components/max7219/max7219.cpp b/esphome/components/max7219/max7219.cpp index 960ac58071..b50a78eb96 100644 --- a/esphome/components/max7219/max7219.cpp +++ b/esphome/components/max7219/max7219.cpp @@ -143,11 +143,13 @@ void MAX7219Component::dump_config() { void MAX7219Component::display() { for (uint8_t i = 0; i < 8; i++) { this->enable(); - for (uint8_t j = 0; j < this->num_chips_; j++) - if (reverse_) + for (uint8_t j = 0; j < this->num_chips_; j++) { + if (reverse_) { this->send_byte_(8 - i, buffer_[(num_chips_ - j - 1) * 8 + i]); - else + } else { this->send_byte_(8 - i, buffer_[j * 8 + i]); + } + } this->disable(); } } diff --git a/esphome/components/max7219digit/max7219digit.cpp b/esphome/components/max7219digit/max7219digit.cpp index 2368c17448..1b9ae230f7 100644 --- a/esphome/components/max7219digit/max7219digit.cpp +++ b/esphome/components/max7219digit/max7219digit.cpp @@ -253,10 +253,11 @@ void MAX7219Component::send_char(uint8_t chip, uint8_t data) { void MAX7219Component::send64pixels(uint8_t chip, const uint8_t pixels[8]) { for (uint8_t col = 0; col < 8; col++) { // RUN THIS LOOP 8 times until column is 7 this->enable(); // start sending by enabling SPI - for (uint8_t i = 0; i < chip; i++) // send extra NOPs to push the pixels out to extra displays + for (uint8_t i = 0; i < chip; i++) { // send extra NOPs to push the pixels out to extra displays this->send_byte_(MAX7219_REGISTER_NOOP, MAX7219_REGISTER_NOOP); // run this loop unit the matching chip is reached - uint8_t b = 0; // rotate pixels 90 degrees -- set byte to 0 + } + uint8_t b = 0; // rotate pixels 90 degrees -- set byte to 0 if (this->orientation_ == 0) { for (uint8_t i = 0; i < 8; i++) { // run this loop 8 times for all the pixels[8] received diff --git a/esphome/components/mcp23016/mcp23016.cpp b/esphome/components/mcp23016/mcp23016.cpp index a8df4e1745..9787da6faa 100644 --- a/esphome/components/mcp23016/mcp23016.cpp +++ b/esphome/components/mcp23016/mcp23016.cpp @@ -62,10 +62,11 @@ void MCP23016::update_reg_(uint8_t pin, bool pin_value, uint8_t reg_addr) { this->read_reg_(reg_addr, ®_value); } - if (pin_value) + if (pin_value) { reg_value |= 1 << bit; - else + } else { reg_value &= ~(1 << bit); + } this->write_reg_(reg_addr, reg_value); diff --git a/esphome/components/mcp23x08_base/mcp23x08_base.cpp b/esphome/components/mcp23x08_base/mcp23x08_base.cpp index 2137b36921..0c20e902c4 100644 --- a/esphome/components/mcp23x08_base/mcp23x08_base.cpp +++ b/esphome/components/mcp23x08_base/mcp23x08_base.cpp @@ -67,10 +67,11 @@ void MCP23X08Base::update_reg(uint8_t pin, bool pin_value, uint8_t reg_addr) { this->read_reg(reg_addr, ®_value); } - if (pin_value) + if (pin_value) { reg_value |= 1 << bit; - else + } else { reg_value &= ~(1 << bit); + } this->write_reg(reg_addr, reg_value); diff --git a/esphome/components/mcp23x17_base/mcp23x17_base.cpp b/esphome/components/mcp23x17_base/mcp23x17_base.cpp index 744f2fbe9c..99064f8880 100644 --- a/esphome/components/mcp23x17_base/mcp23x17_base.cpp +++ b/esphome/components/mcp23x17_base/mcp23x17_base.cpp @@ -70,10 +70,11 @@ void MCP23X17Base::update_reg(uint8_t pin, bool pin_value, uint8_t reg_addr) { this->read_reg(reg_addr, ®_value); } - if (pin_value) + if (pin_value) { reg_value |= 1 << bit; - else + } else { reg_value &= ~(1 << bit); + } this->write_reg(reg_addr, reg_value); diff --git a/esphome/components/midea/air_conditioner.cpp b/esphome/components/midea/air_conditioner.cpp index dd48f640a2..1ad5ade53d 100644 --- a/esphome/components/midea/air_conditioner.cpp +++ b/esphome/components/midea/air_conditioner.cpp @@ -59,14 +59,16 @@ void AirConditioner::control(const ClimateCall &call) { ctrl.swingMode = Converters::to_midea_swing_mode(call.get_swing_mode().value()); if (call.get_mode().has_value()) ctrl.mode = Converters::to_midea_mode(call.get_mode().value()); - if (call.get_preset().has_value()) + if (call.get_preset().has_value()) { ctrl.preset = Converters::to_midea_preset(call.get_preset().value()); - else if (call.get_custom_preset().has_value()) + } else if (call.get_custom_preset().has_value()) { ctrl.preset = Converters::to_midea_preset(call.get_custom_preset().value()); - if (call.get_fan_mode().has_value()) + } + if (call.get_fan_mode().has_value()) { ctrl.fanMode = Converters::to_midea_fan_mode(call.get_fan_mode().value()); - else if (call.get_custom_fan_mode().has_value()) + } else if (call.get_custom_fan_mode().has_value()) { ctrl.fanMode = Converters::to_midea_fan_mode(call.get_custom_fan_mode().value()); + } this->base_.control(ctrl); } diff --git a/esphome/components/midea_ir/midea_ir.cpp b/esphome/components/midea_ir/midea_ir.cpp index 5e507cbbb0..aa5e2b46f5 100644 --- a/esphome/components/midea_ir/midea_ir.cpp +++ b/esphome/components/midea_ir/midea_ir.cpp @@ -172,10 +172,11 @@ bool MideaIR::on_midea_(const MideaData &data) { this->target_temperature = status.get_temp(); this->mode = status.get_mode(); this->fan_mode = status.get_fan_mode(); - if (status.get_sleep_preset()) + if (status.get_sleep_preset()) { this->preset = climate::CLIMATE_PRESET_SLEEP; - else if (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; } diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index 4d47b1ac14..99ca6d1cc5 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -63,12 +63,13 @@ void MitsubishiClimate::transmit_state() { data->mark(MITSUBISHI_HEADER_MARK); data->space(MITSUBISHI_HEADER_SPACE); // Data - for (uint8_t i : remote_state) + for (uint8_t i : remote_state) { for (uint8_t j = 0; j < 8; j++) { data->mark(MITSUBISHI_BIT_MARK); bool bit = i & (1 << j); data->space(bit ? MITSUBISHI_ONE_SPACE : MITSUBISHI_ZERO_SPACE); } + } // Footer if (r == 0) { data->mark(MITSUBISHI_BIT_MARK); diff --git a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h index 41f1f1b054..3a8e175c26 100644 --- a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h +++ b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h @@ -19,10 +19,11 @@ class ModbusBinarySensor : public Component, public binary_sensor::BinarySensor, this->skip_updates = skip_updates; this->force_new_range = force_new_range; - if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) + if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) { this->register_count = offset + 1; - else + } else { this->register_count = 1; + } } void parse_and_publish(const std::vector &data) override; diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index ada6843ca3..88e2bba9ad 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -230,10 +230,11 @@ class SensorItem { void set_custom_data(const std::vector &data) { custom_data = data; } size_t virtual get_register_size() const { - if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) + if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) { return 1; - else // if CONF_RESPONSE_BYTES is used override the default + } 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; } diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index de25c5b2e3..67063d4c72 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -373,10 +373,11 @@ void MQTTClientComponent::unsubscribe(const std::string &topic) { auto it = subscriptions_.begin(); while (it != subscriptions_.end()) { - if (it->topic == topic) + if (it->topic == topic) { it = subscriptions_.erase(it); - else + } else { ++it; + } } } @@ -484,9 +485,10 @@ void MQTTClientComponent::on_message(const std::string &topic, const std::string // in an ISR. this->defer([this, topic, payload]() { #endif - for (auto &subscription : this->subscriptions_) + for (auto &subscription : this->subscriptions_) { if (topic_match(topic.c_str(), subscription.topic.c_str())) subscription.callback(topic, payload); + } #ifdef USE_ESP8266 }); #endif diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index f6ef3a5e8f..7c3c414b3a 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -299,7 +299,7 @@ bool MQTTClimateComponent::publish_state_() { if (traits.get_supports_fan_modes()) { std::string payload; - if (this->device_->fan_mode.has_value()) + if (this->device_->fan_mode.has_value()) { switch (this->device_->fan_mode.value()) { case CLIMATE_FAN_ON: payload = "on"; @@ -329,6 +329,7 @@ bool MQTTClimateComponent::publish_state_() { payload = "diffuse"; break; } + } if (this->device_->custom_fan_mode.has_value()) payload = this->device_->custom_fan_mode.value(); if (!this->publish(this->get_fan_mode_state_topic(), payload)) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 709e2bffcd..46c063e5ee 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -737,9 +737,10 @@ void Nextion::process_nextion_commands_() { 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) + if (this->nextion_queue_[i]->queue_time == 0) { ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\" queue_time 0", component->get_queue_type_string().c_str(), component->get_variable_name().c_str()); + } if (component->get_variable_name() == "sleep_wake") { this->is_sleeping_ = false; @@ -873,9 +874,9 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool while ((timeout == 0 && this->available()) || millis() - start <= timeout) { this->read_byte(&c); - if (c == 0xFF) + if (c == 0xFF) { nr_of_ff_bytes++; - else { + } else { nr_of_ff_bytes = 0; ff_flag = false; } diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 92256eb1b6..c05f2aa546 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -452,10 +452,11 @@ bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_ bool is_manual_safe_mode = this->safe_mode_rtc_value_ == esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC; - if (is_manual_safe_mode) + if (is_manual_safe_mode) { ESP_LOGI(TAG, "Safe mode has been entered manually"); - else + } 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(); diff --git a/esphome/components/output/binary_output.h b/esphome/components/output/binary_output.h index 993ed9e8ac..7a15bc7b51 100644 --- a/esphome/components/output/binary_output.h +++ b/esphome/components/output/binary_output.h @@ -32,10 +32,11 @@ class BinaryOutput { /// Enable or disable this binary output. virtual void set_state(bool state) { - if (state) + if (state) { this->turn_on(); - else + } else { this->turn_off(); + } } /// Enable this binary output. diff --git a/esphome/components/output/switch/output_switch.cpp b/esphome/components/output/switch/output_switch.cpp index 3691896cbe..ec9c8afc01 100644 --- a/esphome/components/output/switch/output_switch.cpp +++ b/esphome/components/output/switch/output_switch.cpp @@ -30,10 +30,11 @@ void OutputSwitch::setup() { break; } - if (initial_state) + if (initial_state) { this->turn_on(); - else + } else { this->turn_off(); + } } void OutputSwitch::write_state(bool state) { if (state) { diff --git a/esphome/components/partition/light_partition.h b/esphome/components/partition/light_partition.h index f74001cf75..5790fd2b39 100644 --- a/esphome/components/partition/light_partition.h +++ b/esphome/components/partition/light_partition.h @@ -78,10 +78,11 @@ class PartitionLightOutput : public light::AddressableLight { int32_t seg_off = index - seg.get_dst_offset(); // offset within the src int32_t src_off; - if (seg.is_reversed()) + if (seg.is_reversed()) { src_off = seg.get_src_offset() + seg.get_size() - seg_off - 1; - else + } else { src_off = seg.get_src_offset() + seg_off; + } auto view = (*seg.get_src())[src_off]; view.raw_set_color_correction(&this->correction_); diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index f5c7792782..3f8377c720 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -20,12 +20,13 @@ void PIDClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles those for us - if (supports_heat_() && supports_cool_()) + if (supports_heat_() && supports_cool_()) { this->mode = climate::CLIMATE_MODE_HEAT_COOL; - else if (supports_cool_()) + } else if (supports_cool_()) { this->mode = climate::CLIMATE_MODE_COOL; - else if (supports_heat_()) + } else if (supports_heat_()) { this->mode = climate::CLIMATE_MODE_HEAT; + } this->target_temperature = this->default_target_temperature_; } } @@ -83,14 +84,15 @@ void PIDClimate::write_output_(float value) { // Update action variable for user feedback what's happening climate::ClimateAction new_action; - if (this->supports_cool_() && value < 0) + if (this->supports_cool_() && value < 0) { new_action = climate::CLIMATE_ACTION_COOLING; - else if (this->supports_heat_() && value > 0) + } else if (this->supports_heat_() && value > 0) { new_action = climate::CLIMATE_ACTION_HEATING; - else if (this->mode == climate::CLIMATE_MODE_OFF) + } else if (this->mode == climate::CLIMATE_MODE_OFF) { new_action = climate::CLIMATE_ACTION_OFF; - else + } else { new_action = climate::CLIMATE_ACTION_IDLE; + } if (new_action != this->action) { this->action = new_action; diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index 13a08bbd16..c1935509f0 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -908,10 +908,11 @@ uint16_t Pipsolar::crc_xmodem_update_(uint16_t crc, uint8_t data) { int i; crc = crc ^ ((uint16_t) data << 8); for (i = 0; i < 8; i++) { - if (crc & 0x8000) + if (crc & 0x8000) { crc = (crc << 1) ^ 0x1021; //(polynomial = 0x1021) - else + } else { crc <<= 1; + } } return crc; } diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp index d203b3ce8f..5bfeb40156 100644 --- a/esphome/components/rc522/rc522.cpp +++ b/esphome/components/rc522/rc522.cpp @@ -139,10 +139,11 @@ void RC522::loop() { StatusCode status = STATUS_ERROR; // For lint passing. TODO: refactor this if (awaiting_comm_) { - if (state_ == STATE_SELECT_SERIAL_DONE) + if (state_ == STATE_SELECT_SERIAL_DONE) { status = await_crc_(); - else + } else { status = await_transceive_(); + } if (status == STATUS_WAITING) { return; @@ -210,11 +211,12 @@ void RC522::loop() { } case STATE_READ_SERIAL_DONE: { if (status != STATUS_OK || back_length_ != 3) { - if (status == STATUS_TIMEOUT) + if (status == STATUS_TIMEOUT) { ESP_LOGV(TAG, "STATE_READ_SERIAL_DONE -> TIMEOUT (no tag present) %d", status); - else + } else { ESP_LOGW(TAG, "Unexpected response. Read status is %d. Read bytes: %d (%s)", status, back_length_, format_buffer(buffer_, 9).c_str()); + } state_ = STATE_DONE; uid_idx_ = 0; @@ -476,9 +478,9 @@ RC522::StatusCode RC522::await_crc_() { bool RC522BinarySensor::process(std::vector &data) { bool result = true; - if (data.size() != this->uid_.size()) + if (data.size() != this->uid_.size()) { result = false; - else { + } else { for (size_t i = 0; i < data.size(); i++) { if (data[i] != this->uid_[i]) { result = false; diff --git a/esphome/components/remote_base/coolix_protocol.cpp b/esphome/components/remote_base/coolix_protocol.cpp index 3e6e7e185a..252b6f0e91 100644 --- a/esphome/components/remote_base/coolix_protocol.cpp +++ b/esphome/components/remote_base/coolix_protocol.cpp @@ -51,10 +51,11 @@ static bool decode_data(RemoteReceiveData &src, CoolixData &dst) { 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)) + if (src.expect_space(BIT_ONE_SPACE_US)) { data |= mask; - else if (!src.expect_space(BIT_ZERO_SPACE_US)) + } else if (!src.expect_space(BIT_ZERO_SPACE_US)) { return false; + } } // Check for inverse byte for (uint32_t mask = 1 << 7; mask; mask >>= 1) { diff --git a/esphome/components/remote_base/dish_protocol.cpp b/esphome/components/remote_base/dish_protocol.cpp index 1257e22a45..47bfdc5c58 100644 --- a/esphome/components/remote_base/dish_protocol.cpp +++ b/esphome/components/remote_base/dish_protocol.cpp @@ -24,18 +24,20 @@ void DishProtocol::encode(RemoteTransmitData *dst, const DishData &data) { for (uint i = 0; i < 4; i++) { // COMMAND (function, in MSB) for (uint8_t mask = 1UL << 5; mask; mask >>= 1) { - if (data.command & mask) + if (data.command & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } // ADDRESS (unit code, in LSB) for (uint8_t mask = 1UL; mask < 1UL << 4; mask <<= 1) { - if ((data.address - 1) & mask) + if ((data.address - 1) & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } // PADDING for (uint j = 0; j < 6; j++) diff --git a/esphome/components/remote_base/jvc_protocol.cpp b/esphome/components/remote_base/jvc_protocol.cpp index f43a28bdc5..169b1d00bf 100644 --- a/esphome/components/remote_base/jvc_protocol.cpp +++ b/esphome/components/remote_base/jvc_protocol.cpp @@ -20,10 +20,11 @@ void JVCProtocol::encode(RemoteTransmitData *dst, const JVCData &data) { dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) { - if (data.data & mask) + if (data.data & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } dst->mark(BIT_HIGH_US); diff --git a/esphome/components/remote_base/lg_protocol.cpp b/esphome/components/remote_base/lg_protocol.cpp index a3e7f9828b..8040b0f3fc 100644 --- a/esphome/components/remote_base/lg_protocol.cpp +++ b/esphome/components/remote_base/lg_protocol.cpp @@ -19,10 +19,11 @@ void LGProtocol::encode(RemoteTransmitData *dst, const LGData &data) { dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint32_t mask = 1UL << (data.nbits - 1); mask != 0; mask >>= 1) { - if (data.data & mask) + if (data.data & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } dst->mark(BIT_HIGH_US); diff --git a/esphome/components/remote_base/midea_protocol.cpp b/esphome/components/remote_base/midea_protocol.cpp index bf67429001..f619d201bc 100644 --- a/esphome/components/remote_base/midea_protocol.cpp +++ b/esphome/components/remote_base/midea_protocol.cpp @@ -31,14 +31,16 @@ void MideaProtocol::encode(RemoteTransmitData *dst, const MideaData &src) { dst->set_carrier_frequency(38000); 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 (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 (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); } @@ -48,10 +50,11 @@ static bool decode_data(RemoteReceiveData &src, MideaData &dst) { 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)) + if (src.expect_space(BIT_ONE_SPACE_US)) { data |= mask; - else if (!src.expect_space(BIT_ZERO_SPACE_US)) + } else if (!src.expect_space(BIT_ZERO_SPACE_US)) { return false; + } } dst[idx] = data; } diff --git a/esphome/components/remote_base/nec_protocol.cpp b/esphome/components/remote_base/nec_protocol.cpp index 47b4d676dd..ee3fec5992 100644 --- a/esphome/components/remote_base/nec_protocol.cpp +++ b/esphome/components/remote_base/nec_protocol.cpp @@ -18,17 +18,19 @@ void NECProtocol::encode(RemoteTransmitData *dst, const NECData &data) { dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint16_t mask = 1; mask; mask <<= 1) { - if (data.address & mask) + if (data.address & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } for (uint16_t mask = 1; mask; mask <<= 1) { - if (data.command & mask) + if (data.command & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } dst->mark(BIT_HIGH_US); diff --git a/esphome/components/remote_base/nexa_protocol.cpp b/esphome/components/remote_base/nexa_protocol.cpp index 814b46135a..dbdc835fe3 100644 --- a/esphome/components/remote_base/nexa_protocol.cpp +++ b/esphome/components/remote_base/nexa_protocol.cpp @@ -41,43 +41,48 @@ void NexaProtocol::encode(RemoteTransmitData *dst, const NexaData &data) { // Device (26 bits) for (int16_t i = 26 - 1; i >= 0; i--) { - if (data.device & (1 << i)) + if (data.device & (1 << i)) { this->one(dst); - else + } else { this->zero(dst); + } } // Group (1 bit) - if (data.group != 0) + if (data.group != 0) { this->one(dst); - else + } 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) + } else if (data.state == 1) { this->one(dst); - else + } else { this->zero(dst); + } // Channel (4 bits) for (int16_t i = 4 - 1; i >= 0; i--) { - if (data.channel & (1 << i)) + if (data.channel & (1 << i)) { this->one(dst); - else + } 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)) + if (data.level & (1 << i)) { this->one(dst); - else + } else { this->zero(dst); + } } } diff --git a/esphome/components/remote_base/panasonic_protocol.cpp b/esphome/components/remote_base/panasonic_protocol.cpp index fd4f7c4bf7..fe1060e935 100644 --- a/esphome/components/remote_base/panasonic_protocol.cpp +++ b/esphome/components/remote_base/panasonic_protocol.cpp @@ -19,17 +19,19 @@ void PanasonicProtocol::encode(RemoteTransmitData *dst, const PanasonicData &dat uint32_t mask; for (mask = 1UL << 15; mask != 0; mask >>= 1) { - if (data.address & mask) + if (data.address & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } for (mask = 1UL << 31; mask != 0; mask >>= 1) { - if (data.command & mask) + if (data.command & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } dst->mark(BIT_HIGH_US); } diff --git a/esphome/components/remote_base/pioneer_protocol.cpp b/esphome/components/remote_base/pioneer_protocol.cpp index 74a3998f11..5c10eab48d 100644 --- a/esphome/components/remote_base/pioneer_protocol.cpp +++ b/esphome/components/remote_base/pioneer_protocol.cpp @@ -42,26 +42,29 @@ void PioneerProtocol::encode(RemoteTransmitData *dst, const PioneerData &data) { command1 = (command1 << 8) | ((~command1) & 0xff); command2 = (command2 << 8) | ((~command2) & 0xff); - if (data.rc_code_2 == 0) + if (data.rc_code_2 == 0) { dst->reserve(68); - else + } else { dst->reserve((68 * 2) + 1); + } dst->set_carrier_frequency(40000); dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint32_t mask = 1UL << 15; mask; mask >>= 1) { - if (address1 & mask) + if (address1 & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } for (uint32_t mask = 1UL << 15; mask; mask >>= 1) { - if (command1 & mask) + if (command1 & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } dst->mark(BIT_HIGH_US); @@ -70,17 +73,19 @@ void PioneerProtocol::encode(RemoteTransmitData *dst, const PioneerData &data) { dst->space(TRAILER_SPACE_US); dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint32_t mask = 1UL << 15; mask; mask >>= 1) { - if (address2 & mask) + if (address2 & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } for (uint32_t mask = 1UL << 15; mask; mask >>= 1) { - if (command2 & mask) + if (command2 & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } dst->mark(BIT_HIGH_US); @@ -140,10 +145,11 @@ optional PioneerProtocol::decode(RemoteReceiveData src) { return data; } void PioneerProtocol::dump(const PioneerData &data) { - if (data.rc_code_2 == 0) + if (data.rc_code_2 == 0) { ESP_LOGD(TAG, "Received Pioneer: rc_code_X=0x%04X", data.rc_code_1); - else + } else { ESP_LOGD(TAG, "Received Pioneer: rc_code_1=0x%04X, rc_code_2=0x%04X", data.rc_code_1, data.rc_code_2); + } } } // namespace remote_base diff --git a/esphome/components/remote_base/raw_protocol.h b/esphome/components/remote_base/raw_protocol.h index 1d9f1c5acc..054f02ff7c 100644 --- a/esphome/components/remote_base/raw_protocol.h +++ b/esphome/components/remote_base/raw_protocol.h @@ -50,10 +50,11 @@ template class RawAction : public RemoteTransmitterActionBasecode_static_ != nullptr) { for (size_t i = 0; i < this->code_static_len_; i++) { auto val = this->code_static_[i]; - if (val < 0) + if (val < 0) { dst->space(static_cast(-val)); - else + } else { dst->mark(static_cast(val)); + } } } else { dst->set_data(this->code_func_(x...)); diff --git a/esphome/components/remote_base/rc_switch_protocol.cpp b/esphome/components/remote_base/rc_switch_protocol.cpp index 1dc094d552..b353f0254e 100644 --- a/esphome/components/remote_base/rc_switch_protocol.cpp +++ b/esphome/components/remote_base/rc_switch_protocol.cpp @@ -57,10 +57,11 @@ void RCSwitchBase::transmit(RemoteTransmitData *dst, uint64_t code, uint8_t len) dst->set_carrier_frequency(0); this->sync(dst); for (int16_t i = len - 1; i >= 0; i--) { - if (code & ((uint64_t) 1 << i)) + if (code & ((uint64_t) 1 << i)) { this->one(dst); - else + } else { this->zero(dst); + } } } @@ -148,10 +149,11 @@ void RCSwitchBase::simple_code_to_tristate(uint16_t code, uint8_t nbits, uint64_ *out_code = 0; for (int8_t i = nbits - 1; i >= 0; i--) { *out_code <<= 2; - if (code & (1 << i)) + if (code & (1 << i)) { *out_code |= 0b01; - else + } else { *out_code |= 0b00; + } } } void RCSwitchBase::type_a_code(uint8_t switch_group, uint8_t switch_device, bool state, uint64_t *out_code, diff --git a/esphome/components/remote_base/samsung_protocol.cpp b/esphome/components/remote_base/samsung_protocol.cpp index 4571f332b3..20e8285a03 100644 --- a/esphome/components/remote_base/samsung_protocol.cpp +++ b/esphome/components/remote_base/samsung_protocol.cpp @@ -22,10 +22,11 @@ void SamsungProtocol::encode(RemoteTransmitData *dst, const SamsungData &data) { dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint8_t bit = data.nbits; bit > 0; bit--) { - if ((data.data >> (bit - 1)) & 1) + if ((data.data >> (bit - 1)) & 1) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } dst->item(FOOTER_HIGH_US, FOOTER_LOW_US); diff --git a/esphome/components/remote_base/sony_protocol.cpp b/esphome/components/remote_base/sony_protocol.cpp index 3bb643266a..8634cf7d61 100644 --- a/esphome/components/remote_base/sony_protocol.cpp +++ b/esphome/components/remote_base/sony_protocol.cpp @@ -19,10 +19,11 @@ void SonyProtocol::encode(RemoteTransmitData *dst, const SonyData &data) { dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint32_t mask = 1UL << (data.nbits - 1); mask != 0; mask >>= 1) { - if (data.data & mask) + if (data.data & mask) { dst->item(BIT_ONE_HIGH_US, BIT_LOW_US); - else + } else { dst->item(BIT_ZERO_HIGH_US, BIT_LOW_US); + } } } optional SonyProtocol::decode(RemoteReceiveData src) { diff --git a/esphome/components/remote_base/toshiba_ac_protocol.cpp b/esphome/components/remote_base/toshiba_ac_protocol.cpp index bd1d2a8f5b..1a19f534f8 100644 --- a/esphome/components/remote_base/toshiba_ac_protocol.cpp +++ b/esphome/components/remote_base/toshiba_ac_protocol.cpp @@ -24,10 +24,11 @@ void ToshibaAcProtocol::encode(RemoteTransmitData *dst, const ToshibaAcData &dat dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint8_t bit = 48; bit > 0; bit--) { dst->mark(BIT_HIGH_US); - if ((data.rc_code_1 >> (bit - 1)) & 1) + if ((data.rc_code_1 >> (bit - 1)) & 1) { dst->space(BIT_ONE_LOW_US); - else + } else { dst->space(BIT_ZERO_LOW_US); + } } dst->item(FOOTER_HIGH_US, FOOTER_LOW_US); } @@ -36,10 +37,11 @@ void ToshibaAcProtocol::encode(RemoteTransmitData *dst, const ToshibaAcData &dat dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint8_t bit = 48; bit > 0; bit--) { dst->mark(BIT_HIGH_US); - if ((data.rc_code_2 >> (bit - 1)) & 1) + if ((data.rc_code_2 >> (bit - 1)) & 1) { dst->space(BIT_ONE_LOW_US); - else + } else { dst->space(BIT_ZERO_LOW_US); + } } dst->item(FOOTER_HIGH_US, FOOTER_LOW_US); } @@ -102,10 +104,11 @@ optional ToshibaAcProtocol::decode(RemoteReceiveData src) { } void ToshibaAcProtocol::dump(const ToshibaAcData &data) { - if (data.rc_code_2 != 0) + if (data.rc_code_2 != 0) { ESP_LOGD(TAG, "Received Toshiba AC: rc_code_1=0x%" PRIX64 ", rc_code_2=0x%" PRIX64, data.rc_code_1, data.rc_code_2); - else + } else { ESP_LOGD(TAG, "Received Toshiba AC: rc_code_1=0x%" PRIX64, data.rc_code_1); + } } } // namespace remote_base diff --git a/esphome/components/remote_receiver/remote_receiver_esp8266.cpp b/esphome/components/remote_receiver/remote_receiver_esp8266.cpp index cf2c15402e..8700fcf0bb 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp8266.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp8266.cpp @@ -79,9 +79,10 @@ void RemoteReceiverComponent::loop() { if (dist <= 1) return; const uint32_t now = micros(); - if (now - s.buffer[write_at] < this->idle_us_) + if (now - s.buffer[write_at] < this->idle_us_) { // The last change was fewer than the configured idle time ago. return; + } ESP_LOGVV(TAG, "read_at=%u write_at=%u dist=%u now=%u end=%u", s.buffer_read_at, write_at, dist, now, s.buffer[write_at]); diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp index 39752cac5b..1c0eb94e61 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp @@ -35,10 +35,11 @@ void RemoteTransmitterComponent::calculate_on_off_time_(uint32_t carrier_frequen void RemoteTransmitterComponent::await_target_time_() { const uint32_t current_time = micros(); - if (this->target_time_ == 0) + if (this->target_time_ == 0) { this->target_time_ = current_time; - else if (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) { diff --git a/esphome/components/resistance/resistance_sensor.cpp b/esphome/components/resistance/resistance_sensor.cpp index 4d3dfa5928..e13959fd37 100644 --- a/esphome/components/resistance/resistance_sensor.cpp +++ b/esphome/components/resistance/resistance_sensor.cpp @@ -20,16 +20,18 @@ void ResistanceSensor::process_(float value) { float res = 0; switch (this->configuration_) { case UPSTREAM: - if (value == 0.0f) + if (value == 0.0f) { res = NAN; - else + } else { res = (this->reference_voltage_ - value) / value; + } break; case DOWNSTREAM: - if (value == this->reference_voltage_) + if (value == this->reference_voltage_) { res = NAN; - else + } else { res = value / (this->reference_voltage_ - value); + } break; } diff --git a/esphome/components/rtttl/rtttl.cpp b/esphome/components/rtttl/rtttl.cpp index c76d4a89b0..6274e69ba3 100644 --- a/esphome/components/rtttl/rtttl.cpp +++ b/esphome/components/rtttl/rtttl.cpp @@ -104,10 +104,11 @@ void Rtttl::loop() { // first, get note duration, if available uint8_t num = this->get_integer_(); - if (num) + if (num) { note_duration_ = wholenote_ / num; - else + } else { note_duration_ = wholenote_ / default_duration_; // we will need to check if we are a dotted note after + } uint8_t note; diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index 272ee75e30..8603072bd5 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -229,18 +229,20 @@ uint8_t SCD30Component::sht_crc_(uint8_t data1, uint8_t data2) { crc ^= data1; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } crc ^= data2; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } return crc; diff --git a/esphome/components/scd4x/scd4x.cpp b/esphome/components/scd4x/scd4x.cpp index eacb39edf1..4bd512394f 100644 --- a/esphome/components/scd4x/scd4x.cpp +++ b/esphome/components/scd4x/scd4x.cpp @@ -233,18 +233,20 @@ uint8_t SCD4XComponent::sht_crc_(uint8_t data1, uint8_t data2) { crc ^= data1; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } crc ^= data2; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } return crc; diff --git a/esphome/components/sdp3x/sdp3x.cpp b/esphome/components/sdp3x/sdp3x.cpp index 107ed2902f..eb1543f2c2 100644 --- a/esphome/components/sdp3x/sdp3x.cpp +++ b/esphome/components/sdp3x/sdp3x.cpp @@ -138,10 +138,11 @@ bool SDP3XComponent::check_crc_(const uint8_t data[], uint8_t size, uint8_t chec for (int i = 0; i < size; i++) { crc ^= (data[i]); for (uint8_t bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x31; - else + } else { crc = (crc << 1); + } } } diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index 7e7153196c..49d2c648b0 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -176,10 +176,11 @@ optional SlidingWindowMovingAverageFilter::new_value(float value) { this->sum_ += value; } float average; - if (this->queue_.empty()) + if (this->queue_.empty()) { average = 0.0f; - else + } else { average = this->sum_ / this->queue_.size(); + } ESP_LOGVV(TAG, "SlidingWindowMovingAverageFilter(%p)::new_value(%f) -> %f", this, value, average); if (++this->send_at_ % this->send_every_ == 0) { @@ -203,10 +204,11 @@ ExponentialMovingAverageFilter::ExponentialMovingAverageFilter(float alpha, size : send_every_(send_every), send_at_(send_every - 1), alpha_(alpha) {} optional ExponentialMovingAverageFilter::new_value(float value) { if (!std::isnan(value)) { - if (this->first_value_) + if (this->first_value_) { this->accumulator_ = value; - else + } else { this->accumulator_ = (this->alpha_ * value) + (1.0f - this->alpha_) * this->accumulator_; + } this->first_value_ = false; } @@ -274,19 +276,21 @@ FilterOutValueFilter::FilterOutValueFilter(float value_to_filter_out) : value_to optional FilterOutValueFilter::new_value(float value) { if (std::isnan(this->value_to_filter_out_)) { - if (std::isnan(value)) + if (std::isnan(value)) { return {}; - else + } else { return value; + } } else { int8_t accuracy = this->parent_->get_accuracy_decimals(); float accuracy_mult = powf(10.0f, accuracy); float rounded_filter_out = roundf(accuracy_mult * this->value_to_filter_out_); float rounded_value = roundf(accuracy_mult * value); - if (rounded_filter_out == rounded_value) + if (rounded_filter_out == rounded_value) { return {}; - else + } else { return value; + } } } diff --git a/esphome/components/servo/servo.cpp b/esphome/components/servo/servo.cpp index 2e1ba587a2..78fc45c679 100644 --- a/esphome/components/servo/servo.cpp +++ b/esphome/components/servo/servo.cpp @@ -65,10 +65,11 @@ void Servo::write(float value) { void Servo::internal_write(float value) { value = clamp(value, -1.0f, 1.0f); float level; - if (value < 0.0) + if (value < 0.0) { level = lerp(-value, this->idle_level_, this->min_level_); - else + } else { level = lerp(value, this->idle_level_, this->max_level_); + } this->output_->set_level(level); if (this->target_value_ == this->current_value_) { this->save_level_(level); diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 4157fd55cf..b55097fcd0 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -220,9 +220,10 @@ void SGP30Component::write_iaq_baseline_(uint16_t eco2_baseline, uint16_t tvoc_b data[6] = sht_crc_(data[4], data[5]); if (!this->write_bytes(SGP30_CMD_SET_IAQ_BASELINE >> 8, data, 7)) { ESP_LOGE(TAG, "Error applying eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", eco2_baseline, tvoc_baseline); - } else + } else { ESP_LOGI(TAG, "Initial baselines applied successfully! eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", eco2_baseline, tvoc_baseline); + } } void SGP30Component::dump_config() { @@ -315,18 +316,20 @@ uint8_t SGP30Component::sht_crc_(uint8_t data1, uint8_t data2) { crc ^= data1; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } crc ^= data2; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } return crc; diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index da6659c90f..829c00a218 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -229,10 +229,11 @@ uint8_t SGP40Component::generate_crc_(const uint8_t *data, uint8_t datalen) { for (uint8_t i = 0; i < datalen; i++) { crc ^= data[i]; for (uint8_t b = 0; b < 8; b++) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ SGP40_CRC8_POLYNOMIAL; - else + } else { crc <<= 1; + } } } return crc; @@ -303,18 +304,20 @@ uint8_t SGP40Component::sht_crc_(uint8_t data1, uint8_t data2) { crc ^= data1; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } crc ^= data2; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } return crc; diff --git a/esphome/components/sht3xd/sht3xd.cpp b/esphome/components/sht3xd/sht3xd.cpp index 56a43d5161..e7981b64cf 100644 --- a/esphome/components/sht3xd/sht3xd.cpp +++ b/esphome/components/sht3xd/sht3xd.cpp @@ -82,18 +82,20 @@ uint8_t sht_crc(uint8_t data1, uint8_t data2) { crc ^= data1; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } crc ^= data2; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } return crc; diff --git a/esphome/components/shtcx/shtcx.cpp b/esphome/components/shtcx/shtcx.cpp index f2fb6bd5c3..867c26df1d 100644 --- a/esphome/components/shtcx/shtcx.cpp +++ b/esphome/components/shtcx/shtcx.cpp @@ -112,18 +112,20 @@ uint8_t sht_crc(uint8_t data1, uint8_t data2) { crc ^= data1; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } crc ^= data2; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } return crc; diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index 49bda7683c..6636bcb3eb 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -88,9 +88,10 @@ class BSDSocketImpl : public Socket { 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 (ret != 0) { // if we already read some don't return an error break; + } return err; } ret += err; @@ -115,9 +116,10 @@ class BSDSocketImpl : public Socket { 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 (ret != 0) { // if we already wrote some don't return an error break; + } return err; } ret += err; diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index d427e2c91b..95280f23b9 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -129,10 +129,11 @@ uint8_t HOT SPIComponent::transfer_(uint8_t data) { for (uint8_t i = 0; i < 8; i++) { uint8_t shift; - if (BIT_ORDER == BIT_ORDER_MSB_FIRST) + if (BIT_ORDER == BIT_ORDER_MSB_FIRST) { shift = 7 - i; - else + } else { shift = i; + } if (CLOCK_PHASE == CLOCK_PHASE_LEADING) { // sampling on leading edge diff --git a/esphome/components/sps30/sps30.cpp b/esphome/components/sps30/sps30.cpp index 6160120564..2bd7bcb458 100644 --- a/esphome/components/sps30/sps30.cpp +++ b/esphome/components/sps30/sps30.cpp @@ -216,18 +216,20 @@ uint8_t SPS30Component::sht_crc_(uint8_t data1, uint8_t data2) { crc ^= data1; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } crc ^= data2; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } return crc; diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index 5ff220fce9..2ba990637e 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -66,10 +66,11 @@ void SSD1306::setup() { if (!this->is_ssd1305_()) { // Enable charge pump (0x8D) this->command(SSD1306_COMMAND_CHARGE_PUMP); - if (this->external_vcc_) + if (this->external_vcc_) { this->command(0x10); - else + } else { this->command(0x14); + } } // Set addressing mode to horizontal (0x20) @@ -105,10 +106,11 @@ void SSD1306::setup() { // Pre-charge period (0xD9) this->command(SSD1306_COMMAND_SET_PRE_CHARGE); - if (this->external_vcc_) + if (this->external_vcc_) { this->command(0x22); - else + } else { this->command(0xF1); + } // Set V_COM (0xDB) this->command(SSD1306_COMMAND_SET_VCOM_DETECT); diff --git a/esphome/components/ssd1325_base/ssd1325_base.cpp b/esphome/components/ssd1325_base/ssd1325_base.cpp index 60e46f573f..711f9e5197 100644 --- a/esphome/components/ssd1325_base/ssd1325_base.cpp +++ b/esphome/components/ssd1325_base/ssd1325_base.cpp @@ -51,24 +51,27 @@ void SSD1325::setup() { this->command(SSD1325_SETCLOCK); // set osc division this->command(0xF1); // 145 this->command(SSD1325_SETMULTIPLEX); // multiplex ratio - if (this->model_ == SSD1327_MODEL_128_128) + if (this->model_ == SSD1327_MODEL_128_128) { this->command(0x7f); // duty = height - 1 - else - this->command(0x3f); // duty = 1/64 + } else { + this->command(0x3f); // duty = 1/64 + } this->command(SSD1325_SETOFFSET); // set display offset - if (this->model_ == SSD1327_MODEL_128_128) + if (this->model_ == SSD1327_MODEL_128_128) { this->command(0x00); // 0 - else - this->command(0x4C); // 76 + } else { + this->command(0x4C); // 76 + } this->command(SSD1325_SETSTARTLINE); // set start line this->command(0x00); // ... this->command(SSD1325_MASTERCONFIG); // Set Master Config DC/DC Converter this->command(0x02); this->command(SSD1325_SETREMAP); // set segment remapping - if (this->model_ == SSD1327_MODEL_128_128) + if (this->model_ == SSD1327_MODEL_128_128) { this->command(0x53); // COM bottom-up, split odd/even, enable column and nibble remapping - else - this->command(0x50); // COM bottom-up, split odd/even + } else { + this->command(0x50); // COM bottom-up, split odd/even + } this->command(SSD1325_SETCURRENT + 0x2); // Set Full Current Range this->command(SSD1325_SETGRAYTABLE); // gamma ~2.2 @@ -122,10 +125,11 @@ void SSD1325::display() { this->command(0x3F); // set column end address this->command(SSD1325_SETROWADDR); // set row address this->command(0x00); // set row start address - if (this->model_ == SSD1327_MODEL_128_128) + if (this->model_ == SSD1327_MODEL_128_128) { this->command(127); // set last row - else + } else { this->command(63); // set last row + } this->write_display_data(); } @@ -135,12 +139,13 @@ void SSD1325::update() { } void SSD1325::set_brightness(float brightness) { // validation - if (brightness > 1) + if (brightness > 1) { this->brightness_ = 1.0; - else if (brightness < 0) + } else if (brightness < 0) { this->brightness_ = 0; - else + } else { this->brightness_ = brightness; + } // now write the new brightness level to the display this->command(SSD1325_SETCONTRAST); this->command(int(SSD1325_MAX_CONTRAST * (this->brightness_))); diff --git a/esphome/components/ssd1331_base/ssd1331_base.cpp b/esphome/components/ssd1331_base/ssd1331_base.cpp index 88764c3d90..14f94dee4f 100644 --- a/esphome/components/ssd1331_base/ssd1331_base.cpp +++ b/esphome/components/ssd1331_base/ssd1331_base.cpp @@ -129,12 +129,13 @@ void HOT SSD1331::draw_absolute_pixel_internal(int x, int y, Color color) { } void SSD1331::fill(Color color) { const uint32_t color565 = display::ColorUtil::color_to_565(color); - for (uint32_t i = 0; i < this->get_buffer_length_(); i++) + for (uint32_t i = 0; i < this->get_buffer_length_(); i++) { if (i & 1) { this->buffer_[i] = color565 & 0xff; } else { this->buffer_[i] = (color565 >> 8) & 0xff; } + } } void SSD1331::init_reset_() { if (this->reset_pin_ != nullptr) { diff --git a/esphome/components/ssd1351_base/ssd1351_base.cpp b/esphome/components/ssd1351_base/ssd1351_base.cpp index f26cd7c697..036a6a0e82 100644 --- a/esphome/components/ssd1351_base/ssd1351_base.cpp +++ b/esphome/components/ssd1351_base/ssd1351_base.cpp @@ -105,12 +105,13 @@ void SSD1351::update() { } void SSD1351::set_brightness(float brightness) { // validation - if (brightness > 1) + if (brightness > 1) { this->brightness_ = 1.0; - else if (brightness < 0) + } else if (brightness < 0) { this->brightness_ = 0; - else + } else { this->brightness_ = brightness; + } // now write the new brightness level to the display this->command(SSD1351_CONTRASTMASTER); this->data(int(SSD1351_MAX_CONTRAST * (this->brightness_))); @@ -157,12 +158,13 @@ void HOT SSD1351::draw_absolute_pixel_internal(int x, int y, Color color) { } void SSD1351::fill(Color color) { const uint32_t color565 = display::ColorUtil::color_to_565(color); - for (uint32_t i = 0; i < this->get_buffer_length_(); i++) + for (uint32_t i = 0; i < this->get_buffer_length_(); i++) { if (i & 1) { this->buffer_[i] = color565 & 0xff; } else { this->buffer_[i] = (color565 >> 8) & 0xff; } + } } void SSD1351::init_reset_() { if (this->reset_pin_ != nullptr) { diff --git a/esphome/components/sts3x/sts3x.cpp b/esphome/components/sts3x/sts3x.cpp index b1ecbc98f8..ce166f2055 100644 --- a/esphome/components/sts3x/sts3x.cpp +++ b/esphome/components/sts3x/sts3x.cpp @@ -78,18 +78,20 @@ uint8_t sts3x_crc(uint8_t data1, uint8_t data2) { crc ^= data1; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } crc ^= data2; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } return crc; diff --git a/esphome/components/sun/sun.h b/esphome/components/sun/sun.h index 0a2e6bcf97..efc6a1ab0a 100644 --- a/esphome/components/sun/sun.h +++ b/esphome/components/sun/sun.h @@ -110,10 +110,11 @@ template class SunCondition : public Condition, public Pa bool check(Ts... x) override { double elevation = this->elevation_.value(x...); double current = this->parent_->elevation(); - if (this->above_) + if (this->above_) { return current > elevation; - else + } else { return current < elevation; + } } protected: diff --git a/esphome/components/sun/text_sensor/sun_text_sensor.h b/esphome/components/sun/text_sensor/sun_text_sensor.h index e4f5beca9c..ad01d64ff1 100644 --- a/esphome/components/sun/text_sensor/sun_text_sensor.h +++ b/esphome/components/sun/text_sensor/sun_text_sensor.h @@ -16,10 +16,11 @@ class SunTextSensor : public text_sensor::TextSensor, public PollingComponent { void update() override { optional res; - if (this->sunrise_) + if (this->sunrise_) { res = this->parent_->sunrise(this->elevation_); - else + } else { res = this->parent_->sunset(this->elevation_); + } if (!res) { this->publish_state(""); return; diff --git a/esphome/components/sx1509/sx1509.cpp b/esphome/components/sx1509/sx1509.cpp index 9095dfeffa..16540d0dbf 100644 --- a/esphome/components/sx1509/sx1509.cpp +++ b/esphome/components/sx1509/sx1509.cpp @@ -64,10 +64,11 @@ void SX1509Component::digital_write(uint8_t pin, bool bit_value) { // If the pin is an output, write high/low uint16_t temp_reg_data = 0; this->read_byte_16(REG_DATA_B, &temp_reg_data); - if (bit_value) + if (bit_value) { temp_reg_data |= (1 << pin); - else + } else { temp_reg_data &= ~(1 << pin); + } this->write_byte_16(REG_DATA_B, temp_reg_data); } else { // Otherwise the pin is an input, pull-up/down @@ -94,10 +95,11 @@ void SX1509Component::digital_write(uint8_t pin, bool bit_value) { void SX1509Component::pin_mode(uint8_t pin, gpio::Flags flags) { this->read_byte_16(REG_DIR_B, &this->ddr_mask_); - if (flags == gpio::FLAG_OUTPUT) + if (flags == gpio::FLAG_OUTPUT) { this->ddr_mask_ &= ~(1 << pin); - else + } else { this->ddr_mask_ |= (1 << pin); + } this->write_byte_16(REG_DIR_B, this->ddr_mask_); if (flags & gpio::FLAG_PULLUP) diff --git a/esphome/components/tcl112/tcl112.cpp b/esphome/components/tcl112/tcl112.cpp index e05a07873e..a88e8e96a7 100644 --- a/esphome/components/tcl112/tcl112.cpp +++ b/esphome/components/tcl112/tcl112.cpp @@ -79,11 +79,12 @@ void Tcl112Climate::transmit_state() { safecelsius = std::min(safecelsius, TCL112_TEMP_MAX); // Convert to integer nr. of half degrees. auto half_degrees = static_cast(safecelsius * 2); - if (half_degrees & 1) // Do we have a half degree celsius? + if (half_degrees & 1) { // Do we have a half degree celsius? remote_state[12] |= TCL112_HALF_DEGREE; // Add 0.5 degrees - else + } else { remote_state[12] &= ~TCL112_HALF_DEGREE; // Clear the half degree. - remote_state[7] &= 0xF0; // Clear temp bits. + } + remote_state[7] &= 0xF0; // Clear temp bits. remote_state[7] |= ((uint8_t) TCL112_TEMP_MAX - half_degrees / 2); // Set fan @@ -128,12 +129,13 @@ void Tcl112Climate::transmit_state() { data->mark(TCL112_HEADER_MARK); data->space(TCL112_HEADER_SPACE); // Data - for (uint8_t i : remote_state) + for (uint8_t i : remote_state) { for (uint8_t j = 0; j < 8; j++) { data->mark(TCL112_BIT_MARK); bool bit = i & (1 << j); data->space(bit ? TCL112_ONE_SPACE : TCL112_ZERO_SPACE); } + } // Footer data->mark(TCL112_BIT_MARK); data->space(TCL112_GAP); @@ -153,9 +155,9 @@ bool Tcl112Climate::on_receive(remote_base::RemoteReceiveData data) { for (int i = 0; i < TCL112_STATE_LENGTH; i++) { // Read bit for (int j = 0; j < 8; j++) { - if (data.expect_item(TCL112_BIT_MARK, TCL112_ONE_SPACE)) + if (data.expect_item(TCL112_BIT_MARK, TCL112_ONE_SPACE)) { remote_state[i] |= 1 << j; - else if (!data.expect_item(TCL112_BIT_MARK, TCL112_ZERO_SPACE)) { + } else if (!data.expect_item(TCL112_BIT_MARK, TCL112_ZERO_SPACE)) { ESP_LOGVV(TAG, "Byte %d bit %d fail", i, j); return false; } diff --git a/esphome/components/template/number/template_number.cpp b/esphome/components/template/number/template_number.cpp index a5b015c44d..aaf5b27a71 100644 --- a/esphome/components/template/number/template_number.cpp +++ b/esphome/components/template/number/template_number.cpp @@ -16,10 +16,11 @@ void TemplateNumber::setup() { } else { this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); if (!this->pref_.load(&value)) { - if (!std::isnan(this->initial_value_)) + if (!std::isnan(this->initial_value_)) { value = this->initial_value_; - else + } else { value = this->traits.get_min_value(); + } } } this->publish_state(value); diff --git a/esphome/components/text_sensor/filter.cpp b/esphome/components/text_sensor/filter.cpp index c6cbcd7c1e..ba77913d1b 100644 --- a/esphome/components/text_sensor/filter.cpp +++ b/esphome/components/text_sensor/filter.cpp @@ -64,9 +64,10 @@ optional PrependFilter::new_value(std::string value) { return this- // Substitute optional SubstituteFilter::new_value(std::string value) { std::size_t pos; - for (size_t 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/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index ce15c53bbe..760525e2cd 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -129,9 +129,10 @@ void ThermostatClimate::validate_target_temperature_low() { this->target_temperature_low = this->get_traits().get_visual_min_temperature(); // target_temperature_low must not be greater than the visual maximum minus set_point_minimum_differential_ if (this->target_temperature_low > - this->get_traits().get_visual_max_temperature() - this->set_point_minimum_differential_) + this->get_traits().get_visual_max_temperature() - this->set_point_minimum_differential_) { this->target_temperature_low = this->get_traits().get_visual_max_temperature() - this->set_point_minimum_differential_; + } // if target_temperature_low is set greater than target_temperature_high, move up target_temperature_high if (this->target_temperature_low > this->target_temperature_high - this->set_point_minimum_differential_) this->target_temperature_high = this->target_temperature_low + this->set_point_minimum_differential_; @@ -147,9 +148,10 @@ void ThermostatClimate::validate_target_temperature_high() { this->target_temperature_high = this->get_traits().get_visual_max_temperature(); // target_temperature_high must not be lower than the visual minimum plus set_point_minimum_differential_ if (this->target_temperature_high < - this->get_traits().get_visual_min_temperature() + this->set_point_minimum_differential_) + this->get_traits().get_visual_min_temperature() + this->set_point_minimum_differential_) { this->target_temperature_high = this->get_traits().get_visual_min_temperature() + this->set_point_minimum_differential_; + } // if target_temperature_high is set less than target_temperature_low, move down target_temperature_low if (this->target_temperature_high < this->target_temperature_low + this->set_point_minimum_differential_) this->target_temperature_low = this->target_temperature_high - this->set_point_minimum_differential_; @@ -348,9 +350,10 @@ climate::ClimateAction ThermostatClimate::compute_supplemental_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_) + if ((action == this->action) && this->setup_complete_) { // already in target mode return; + } if (((action == climate::CLIMATE_ACTION_OFF && this->action == climate::CLIMATE_ACTION_IDLE) || (action == climate::CLIMATE_ACTION_IDLE && this->action == climate::CLIMATE_ACTION_OFF)) && @@ -373,10 +376,11 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu if (this->action == climate::CLIMATE_ACTION_COOLING) this->start_timer_(thermostat::TIMER_COOLING_OFF); if (this->action == climate::CLIMATE_ACTION_FAN) { - if (this->supports_fan_only_action_uses_fan_mode_timer_) + if (this->supports_fan_only_action_uses_fan_mode_timer_) { this->start_timer_(thermostat::TIMER_FAN_MODE); - else + } else { this->start_timer_(thermostat::TIMER_FANNING_OFF); + } } if (this->action == climate::CLIMATE_ACTION_HEATING) this->start_timer_(thermostat::TIMER_HEATING_OFF); @@ -415,10 +419,11 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu break; case climate::CLIMATE_ACTION_FAN: if (this->fanning_action_ready_()) { - if (this->supports_fan_only_action_uses_fan_mode_timer_) + if (this->supports_fan_only_action_uses_fan_mode_timer_) { this->start_timer_(thermostat::TIMER_FAN_MODE); - else + } else { this->start_timer_(thermostat::TIMER_FANNING_ON); + } trig = this->fan_only_action_trigger_; ESP_LOGVV(TAG, "Switching to FAN_ONLY action"); action_ready = true; @@ -461,9 +466,10 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu void ThermostatClimate::switch_to_supplemental_action_(climate::ClimateAction action) { // setup_complete_ helps us ensure an action is called immediately after boot - if ((action == this->supplemental_action_) && this->setup_complete_) + if ((action == this->supplemental_action_) && this->setup_complete_) { // already in target mode return; + } switch (action) { case climate::CLIMATE_ACTION_OFF: @@ -515,9 +521,10 @@ void ThermostatClimate::trigger_supplemental_action_() { 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_) + if ((fan_mode == this->prev_fan_mode_) && this->setup_complete_) { // already in target mode return; + } this->fan_mode = fan_mode; if (publish_state) @@ -582,9 +589,10 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bo 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_) + if ((mode == this->prev_mode_) && this->setup_complete_) { // already in target mode return; + } if (this->prev_mode_trigger_ != nullptr) { this->prev_mode_trigger_->stop_action(); @@ -627,9 +635,10 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_ 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_) + if ((swing_mode == this->prev_swing_mode_) && this->setup_complete_) { // already in target mode return; + } if (this->prev_swing_mode_trigger_ != nullptr) { this->prev_swing_mode_trigger_->stop_action(); @@ -1107,16 +1116,18 @@ Trigger<> *ThermostatClimate::get_temperature_change_trigger() const { return th void ThermostatClimate::dump_config() { LOG_CLIMATE("", "Thermostat", this); if (this->supports_heat_) { - if (this->supports_two_points_) + if (this->supports_two_points_) { ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C", this->normal_config_.default_temperature_low); - else + } else { ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C", this->normal_config_.default_temperature); + } } if ((this->supports_cool_) || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) { - if (this->supports_two_points_) + if (this->supports_two_points_) { ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C", this->normal_config_.default_temperature_high); - else + } else { ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C", this->normal_config_.default_temperature); + } } if (this->supports_two_points_) ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_); @@ -1186,18 +1197,20 @@ void ThermostatClimate::dump_config() { ESP_LOGCONFIG(TAG, " Supports AWAY mode: %s", YESNO(this->supports_away_)); if (this->supports_away_) { if (this->supports_heat_) { - if (this->supports_two_points_) + if (this->supports_two_points_) { ESP_LOGCONFIG(TAG, " Away Default Target Temperature Low: %.1f°C", this->away_config_.default_temperature_low); - else + } else { ESP_LOGCONFIG(TAG, " Away Default Target Temperature Low: %.1f°C", this->away_config_.default_temperature); + } } if ((this->supports_cool_) || (this->supports_fan_only_)) { - if (this->supports_two_points_) + if (this->supports_two_points_) { ESP_LOGCONFIG(TAG, " Away Default Target Temperature High: %.1f°C", this->away_config_.default_temperature_high); - else + } else { ESP_LOGCONFIG(TAG, " Away Default Target Temperature High: %.1f°C", this->away_config_.default_temperature); + } } } } diff --git a/esphome/components/tm1637/tm1637.cpp b/esphome/components/tm1637/tm1637.cpp index a21d2d438d..44f0a841b8 100644 --- a/esphome/components/tm1637/tm1637.cpp +++ b/esphome/components/tm1637/tm1637.cpp @@ -202,10 +202,11 @@ bool TM1637Display::send_byte_(uint8_t b) { this->bit_delay_(); // Set data bit - if (data & 0x01) + if (data & 0x01) { this->dio_pin_->pin_mode(gpio::FLAG_INPUT); - else + } else { this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT); + } this->bit_delay_(); diff --git a/esphome/components/toshiba/toshiba.cpp b/esphome/components/toshiba/toshiba.cpp index a6af3b1015..c7a5b72852 100644 --- a/esphome/components/toshiba/toshiba.cpp +++ b/esphome/components/toshiba/toshiba.cpp @@ -542,10 +542,11 @@ bool ToshibaClimate::on_receive(remote_base::RemoteReceiveData data) { // case RAC_PT1411HWRU_MODE_DRY: case RAC_PT1411HWRU_MODE_FAN: - if ((message[4] >> 4) == RAC_PT1411HWRU_TEMPERATURE_FAN_ONLY) + if ((message[4] >> 4) == RAC_PT1411HWRU_TEMPERATURE_FAN_ONLY) { this->mode = climate::CLIMATE_MODE_FAN_ONLY; - else + } else { this->mode = climate::CLIMATE_MODE_DRY; + } break; case RAC_PT1411HWRU_MODE_HEAT: diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index ecd3802839..7facc52946 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -71,31 +71,34 @@ 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()) + if (this->rgb_id_.has_value()) { ESP_LOGCONFIG(TAG, " RGB has datapoint ID %u", *this->rgb_id_); - else if (this->hsv_id_.has_value()) + } 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() || this->hsv_id_.has_value()) { - if (this->color_interlock_) + if (this->color_interlock_) { traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::COLOR_TEMPERATURE}); - else + } 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() || this->hsv_id_.has_value()) { if (this->dimmer_id_.has_value()) { - if (this->color_interlock_) + if (this->color_interlock_) { traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::WHITE}); - else + } 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()) { diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 50565fae0a..f2dceed33f 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -33,20 +33,21 @@ void Tuya::dump_config() { return; } for (auto &info : this->datapoints_) { - if (info.type == TuyaDatapointType::RAW) + if (info.type == TuyaDatapointType::RAW) { ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, format_hex_pretty(info.value_raw).c_str()); - else if (info.type == TuyaDatapointType::BOOLEAN) + } 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) + } else if (info.type == TuyaDatapointType::INTEGER) { ESP_LOGCONFIG(TAG, " Datapoint %u: int value (value: %d)", info.id, info.value_int); - else if (info.type == TuyaDatapointType::STRING) + } else if (info.type == TuyaDatapointType::STRING) { ESP_LOGCONFIG(TAG, " Datapoint %u: string value (value: %s)", info.id, info.value_string.c_str()); - else if (info.type == TuyaDatapointType::ENUM) + } else if (info.type == TuyaDatapointType::ENUM) { ESP_LOGCONFIG(TAG, " Datapoint %u: enum (value: %d)", info.id, info.value_enum); - else if (info.type == TuyaDatapointType::BITMASK) + } else if (info.type == TuyaDatapointType::BITMASK) { ESP_LOGCONFIG(TAG, " Datapoint %u: bitmask (value: %x)", info.id, info.value_bitmask); - else + } else { ESP_LOGCONFIG(TAG, " Datapoint %u: unknown", info.id); + } } if ((this->gpio_status_ != -1) || (this->gpio_reset_ != -1)) { ESP_LOGCONFIG(TAG, " GPIO Configuration: status: pin %d, reset: pin %d (not supported)", this->gpio_status_, @@ -80,9 +81,10 @@ bool Tuya::validate_message_() { // Byte 4: LENGTH1 // Byte 5: LENGTH2 - if (at <= 5) + if (at <= 5) { // no validation for these fields return true; + } uint16_t length = (uint16_t(data[4]) << 8) | (uint16_t(data[5])); @@ -318,9 +320,10 @@ void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) { } // Run through listeners - for (auto &listener : this->listeners_) + for (auto &listener : this->listeners_) { if (listener.datapoint_id == datapoint.id) listener.on_datapoint(datapoint); + } } void Tuya::send_raw_command_(TuyaCommand command) { @@ -488,9 +491,10 @@ void Tuya::force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t valu } optional Tuya::get_datapoint_(uint8_t datapoint_id) { - for (auto &datapoint : this->datapoints_) + for (auto &datapoint : this->datapoints_) { if (datapoint.id == datapoint_id) return datapoint; + } return {}; } @@ -578,9 +582,10 @@ void Tuya::register_listener(uint8_t datapoint_id, const std::functionlisteners_.push_back(listener); // Run through existing datapoints - for (auto &datapoint : this->datapoints_) + for (auto &datapoint : this->datapoints_) { if (datapoint.id == datapoint_id) func(datapoint); + } } TuyaInitState Tuya::get_init_state() { return this->init_state_; } diff --git a/esphome/components/uart/uart_component_esp32_arduino.cpp b/esphome/components/uart/uart_component_esp32_arduino.cpp index 95cdde4a43..a67e5354fb 100644 --- a/esphome/components/uart/uart_component_esp32_arduino.cpp +++ b/esphome/components/uart/uart_component_esp32_arduino.cpp @@ -41,10 +41,11 @@ uint32_t ESP32ArduinoUARTComponent::get_config() { * tick_ref_always_on:27 select the clock.1:apb clock:ref_tick */ - if (this->parity_ == UART_CONFIG_PARITY_EVEN) + if (this->parity_ == UART_CONFIG_PARITY_EVEN) { config |= UART_PARITY_EVEN | UART_PARITY_ENABLE; - else if (this->parity_ == UART_CONFIG_PARITY_ODD) + } else if (this->parity_ == UART_CONFIG_PARITY_ODD) { config |= UART_PARITY_ODD | UART_PARITY_ENABLE; + } switch (this->data_bits_) { case 5: @@ -61,10 +62,11 @@ uint32_t ESP32ArduinoUARTComponent::get_config() { break; } - if (this->stop_bits_ == 1) + if (this->stop_bits_ == 1) { config |= UART_NB_STOP_BIT_1; - else + } else { config |= UART_NB_STOP_BIT_2; + } config |= UART_TICK_APB_CLOCK; diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index 370adad779..529108f439 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -18,12 +18,13 @@ bool ESP8266UartComponent::serial0_in_use = false; // NOLINT(cppcoreguidelines- uint32_t ESP8266UartComponent::get_config() { uint32_t config = 0; - if (this->parity_ == UART_CONFIG_PARITY_NONE) + if (this->parity_ == UART_CONFIG_PARITY_NONE) { config |= UART_PARITY_NONE; - else if (this->parity_ == UART_CONFIG_PARITY_EVEN) + } else if (this->parity_ == UART_CONFIG_PARITY_EVEN) { config |= UART_PARITY_EVEN; - else if (this->parity_ == UART_CONFIG_PARITY_ODD) + } else if (this->parity_ == UART_CONFIG_PARITY_ODD) { config |= UART_PARITY_ODD; + } switch (this->data_bits_) { case 5: @@ -40,10 +41,11 @@ uint32_t ESP8266UartComponent::get_config() { break; } - if (this->stop_bits_ == 1) + if (this->stop_bits_ == 1) { config |= UART_NB_STOP_BIT_1; - else + } else { config |= UART_NB_STOP_BIT_2; + } if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) config |= BIT(22); @@ -234,12 +236,13 @@ void IRAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { } bool parity_bit = false; bool need_parity_bit = true; - if (this->parity_ == UART_CONFIG_PARITY_EVEN) + if (this->parity_ == UART_CONFIG_PARITY_EVEN) { parity_bit = false; - else if (this->parity_ == UART_CONFIG_PARITY_ODD) + } else if (this->parity_ == UART_CONFIG_PARITY_ODD) { parity_bit = true; - else + } else { need_parity_bit = false; + } { InterruptLock lock; diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 4d6a6af0fc..80255ccddf 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -16,10 +16,11 @@ static const char *const TAG = "uart.idf"; uart_config_t IDFUARTComponent::get_config_() { uart_parity_t parity = UART_PARITY_DISABLE; - if (this->parity_ == UART_CONFIG_PARITY_EVEN) + if (this->parity_ == UART_CONFIG_PARITY_EVEN) { parity = UART_PARITY_EVEN; - else if (this->parity_ == UART_CONFIG_PARITY_ODD) + } else if (this->parity_ == UART_CONFIG_PARITY_ODD) { parity = UART_PARITY_ODD; + } uart_word_length_t data_bits; switch (this->data_bits_) { @@ -141,9 +142,9 @@ bool IDFUARTComponent::peek_byte(uint8_t *data) { if (!this->check_read_timeout_()) return false; xSemaphoreTake(this->lock_, portMAX_DELAY); - if (this->has_peek_) + if (this->has_peek_) { *data = this->peek_byte_; - else { + } else { int len = uart_read_bytes(this->uart_num_, data, 1, 20 / portTICK_RATE_MS); if (len == 0) { *data = 0; diff --git a/esphome/components/vl53l0x/vl53l0x_sensor.cpp b/esphome/components/vl53l0x/vl53l0x_sensor.cpp index d68d69b79c..171484f6f2 100644 --- a/esphome/components/vl53l0x/vl53l0x_sensor.cpp +++ b/esphome/components/vl53l0x/vl53l0x_sensor.cpp @@ -124,10 +124,11 @@ void VL53L0XSensor::setup() { uint8_t &val = ref_spad_map[i / 8]; uint8_t mask = 1 << (i % 8); - if (i < first_spad_to_enable || spads_enabled == spad_count) + if (i < first_spad_to_enable || spads_enabled == spad_count) { val &= ~mask; - else if (val & mask) + } else if (val & mask) { spads_enabled += 1; + } } this->write_bytes(0xB0, ref_spad_map, 6); diff --git a/esphome/components/vl53l0x/vl53l0x_sensor.h b/esphome/components/vl53l0x/vl53l0x_sensor.h index a2e24e7550..85b5e3b31d 100644 --- a/esphome/components/vl53l0x/vl53l0x_sensor.h +++ b/esphome/components/vl53l0x/vl53l0x_sensor.h @@ -60,10 +60,11 @@ class VL53L0XSensor : public sensor::Sensor, public PollingComponent, public i2c if (enables.tcc) budget_us += (timeouts.msrc_dss_tcc_us + tcc_overhead); - if (enables.dss) + if (enables.dss) { budget_us += 2 * (timeouts.msrc_dss_tcc_us + dss_overhead); - else if (enables.msrc) + } else if (enables.msrc) { budget_us += (timeouts.msrc_dss_tcc_us + msrc_overhead); + } if (enables.pre_range) budget_us += (timeouts.pre_range_us + pre_range_overhead); @@ -191,12 +192,13 @@ class VL53L0XSensor : public sensor::Sensor, public PollingComponent, public i2c uint8_t get_vcsel_pulse_period_(VcselPeriodType type) { uint8_t vcsel; - if (type == VCSEL_PERIOD_PRE_RANGE) + if (type == VCSEL_PERIOD_PRE_RANGE) { vcsel = reg(0x50).get(); - else if (type == VCSEL_PERIOD_FINAL_RANGE) + } else if (type == VCSEL_PERIOD_FINAL_RANGE) { vcsel = reg(0x70).get(); - else + } else { return 255; + } return (vcsel + 1) << 1; } diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 322c375f0e..b2d2d92bb2 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -140,10 +140,11 @@ void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, Color color const uint32_t pos = (x + y * this->get_width_internal()) / 8u; const uint8_t subpos = x & 0x07; // flip logic - if (!color.is_on()) + if (!color.is_on()) { this->buffer_[pos] |= 0x80 >> subpos; - else + } else { this->buffer_[pos] &= ~(0x80 >> subpos); + } } uint32_t WaveshareEPaper::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal() / 8u; } void WaveshareEPaper::start_command_() { @@ -901,18 +902,20 @@ void HOT WaveshareEPaper5P8In::display() { uint8_t temp1 = this->buffer_[i]; for (uint8_t j = 0; j < 8; j++) { uint8_t temp2; - if (temp1 & 0x80) + if (temp1 & 0x80) { temp2 = 0x03; - else + } else { temp2 = 0x00; + } temp2 <<= 4; temp1 <<= 1; j++; - if (temp1 & 0x80) + if (temp1 & 0x80) { temp2 |= 0x03; - else + } else { temp2 |= 0x00; + } temp1 <<= 1; this->write_byte(temp2); } @@ -984,17 +987,19 @@ void HOT WaveshareEPaper7P5In::display() { uint8_t temp1 = this->buffer_[i]; for (uint8_t j = 0; j < 8; j++) { uint8_t temp2; - if (temp1 & 0x80) + if (temp1 & 0x80) { temp2 = 0x03; - else + } else { temp2 = 0x00; + } temp2 <<= 4; temp1 <<= 1; j++; - if (temp1 & 0x80) + if (temp1 & 0x80) { temp2 |= 0x03; - else + } else { temp2 |= 0x00; + } temp1 <<= 1; this->write_byte(temp2); } @@ -1187,10 +1192,11 @@ void HOT WaveshareEPaper2P13InDKE::display() { bool partial = this->at_update_ != 0; this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; - if (partial) + if (partial) { ESP_LOGI(TAG, "Performing partial e-paper update."); - else + } else { ESP_LOGI(TAG, "Performing full e-paper update."); + } // start and set up data format this->command(0x12); diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index a7dbcd4b85..83b6ca1e2f 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -91,64 +91,74 @@ void WebServer::setup() { client->send("", "ping", millis(), 30000); #ifdef USE_SENSOR - for (auto *obj : App.get_sensors()) + for (auto *obj : App.get_sensors()) { 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()) + for (auto *obj : App.get_switches()) { 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()) + for (auto *obj : App.get_binary_sensors()) { 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()) + for (auto *obj : App.get_fans()) { 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()) + for (auto *obj : App.get_lights()) { 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()) + for (auto *obj : App.get_text_sensors()) { 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()) + for (auto *obj : App.get_covers()) { 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()) + for (auto *obj : App.get_numbers()) { 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()) + for (auto *obj : App.get_selects()) { if (this->include_internal_ || !obj->is_internal()) client->send(this->select_json(obj, obj->state).c_str(), "state"); + } #endif }); #ifdef USE_LOGGER - if (logger::global_logger != nullptr) + if (logger::global_logger != nullptr) { logger::global_logger->add_on_log_callback( [this](int level, const char *tag, const char *message) { this->events_.send(message, "log", millis()); }); + } #endif this->base_->add_handler(&this->events_); this->base_->add_handler(this); @@ -187,15 +197,17 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { stream->addHeader("Access-Control-Allow-Origin", "*"); #ifdef USE_SENSOR - for (auto *obj : App.get_sensors()) + for (auto *obj : App.get_sensors()) { if (this->include_internal_ || !obj->is_internal()) write_row(stream, obj, "sensor", ""); + } #endif #ifdef USE_SWITCH - for (auto *obj : App.get_switches()) + for (auto *obj : App.get_switches()) { if (this->include_internal_ || !obj->is_internal()) write_row(stream, obj, "switch", ""); + } #endif #ifdef USE_BUTTON @@ -204,38 +216,43 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #endif #ifdef USE_BINARY_SENSOR - for (auto *obj : App.get_binary_sensors()) + for (auto *obj : App.get_binary_sensors()) { if (this->include_internal_ || !obj->is_internal()) write_row(stream, obj, "binary_sensor", ""); + } #endif #ifdef USE_FAN - for (auto *obj : App.get_fans()) + for (auto *obj : App.get_fans()) { if (this->include_internal_ || !obj->is_internal()) write_row(stream, obj, "fan", ""); + } #endif #ifdef USE_LIGHT - for (auto *obj : App.get_lights()) + for (auto *obj : App.get_lights()) { if (this->include_internal_ || !obj->is_internal()) write_row(stream, obj, "light", ""); + } #endif #ifdef USE_TEXT_SENSOR - for (auto *obj : App.get_text_sensors()) + for (auto *obj : App.get_text_sensors()) { if (this->include_internal_ || !obj->is_internal()) write_row(stream, obj, "text_sensor", ""); + } #endif #ifdef USE_COVER - for (auto *obj : App.get_covers()) + for (auto *obj : App.get_covers()) { if (this->include_internal_ || !obj->is_internal()) write_row(stream, obj, "cover", ""); + } #endif #ifdef USE_NUMBER - for (auto *obj : App.get_numbers()) - if (this->include_internal_ || !obj->is_internal()) + for (auto *obj : App.get_numbers()) { + if (this->include_internal_ || !obj->is_internal()) { write_row(stream, obj, "number", "", [](AsyncResponseStream &stream, EntityBase *obj) { number::Number *number = (number::Number *) obj; stream.print(R"(state); stream.print(R"("/>)"); }); + } + } #endif #ifdef USE_SELECT - for (auto *obj : App.get_selects()) - if (this->include_internal_ || !obj->is_internal()) + for (auto *obj : App.get_selects()) { + 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 " diff --git a/esphome/components/whirlpool/whirlpool.cpp b/esphome/components/whirlpool/whirlpool.cpp index 61ffffa192..f354ab070d 100644 --- a/esphome/components/whirlpool/whirlpool.cpp +++ b/esphome/components/whirlpool/whirlpool.cpp @@ -164,10 +164,10 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { return false; } for (int j = 0; j < 8; j++) { - if (data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_ONE_SPACE)) + if (data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_ONE_SPACE)) { remote_state[i] |= 1 << j; - else if (!data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_ZERO_SPACE)) { + } else if (!data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_ZERO_SPACE)) { ESP_LOGV(TAG, "Byte %d bit %d fail", i, j); return false; } diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 36944e3633..1348b54b37 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -78,9 +78,10 @@ void WiFiComponent::setup() { #endif } #ifdef USE_IMPROV - if (esp32_improv::global_improv_component != nullptr) + if (esp32_improv::global_improv_component != nullptr) { if (this->wifi_mode_(true, {})) esp32_improv::global_improv_component->start(); + } #endif this->wifi_apply_hostname_(); } @@ -142,10 +143,12 @@ void WiFiComponent::loop() { } #ifdef USE_IMPROV - if (esp32_improv::global_improv_component != nullptr) - if (!this->is_connected()) + if (esp32_improv::global_improv_component != nullptr) { + if (!this->is_connected()) { if (this->wifi_mode_(true, {})) esp32_improv::global_improv_component->start(); + } + } #endif diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index d032382dc8..615a6905bc 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -227,23 +227,26 @@ class WiFiComponent : public Component { network::IPAddress wifi_soft_ap_ip(); bool has_sta_priority(const bssid_t &bssid) { - for (auto &it : this->sta_priorities_) + for (auto &it : this->sta_priorities_) { if (it.bssid == bssid) return true; + } return false; } float get_sta_priority(const bssid_t bssid) { - for (auto &it : this->sta_priorities_) + for (auto &it : this->sta_priorities_) { if (it.bssid == bssid) return it.priority; + } return 0.0f; } void set_sta_priority(const bssid_t bssid, float priority) { - for (auto &it : this->sta_priorities_) + for (auto &it : this->sta_priorities_) { if (it.bssid == bssid) { it.priority = priority; return; } + } this->sta_priorities_.push_back(WiFiSTAPriority{ .bssid = bssid, .priority = priority, diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 5a81fd0a39..60a3bf171d 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -182,14 +182,15 @@ bool WiFiComponent::wifi_mode_(optional sta, optional ap) { bool set_ap = ap.has_value() ? *ap : current_ap; wifi_mode_t set_mode; - if (set_sta && set_ap) + if (set_sta && set_ap) { set_mode = WIFI_MODE_APSTA; - else if (set_sta && !set_ap) + } else if (set_sta && !set_ap) { set_mode = WIFI_MODE_STA; - else if (!set_sta && set_ap) + } else if (!set_sta && set_ap) { set_mode = WIFI_MODE_AP; - else + } else { set_mode = WIFI_MODE_NULL; + } if (current_mode == set_mode) return true; diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp index de77e6146b..6c3bd61cac 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp @@ -87,12 +87,13 @@ bool XiaomiMiscale::parse_message_v1_(const std::vector &message, Parse // 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) + if (data[0] == 0x22 || data[0] == 0xa2) { result.weight = weight * 0.01f / 2.0f; // unit 'kg' - else if (data[0] == 0x12 || data[0] == 0xb2) + } else if (data[0] == 0x12 || data[0] == 0xb2) { result.weight = weight * 0.01f * 0.6f; // unit 'jin' - else if (data[0] == 0x03 || data[0] == 0xb3) + } else if (data[0] == 0x03 || data[0] == 0xb3) { result.weight = weight * 0.01f * 0.453592f; // unit 'lbs' + } return true; } @@ -120,10 +121,11 @@ bool XiaomiMiscale::parse_message_v2_(const std::vector &message, Parse // 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) + if (data[0] == 0x02) { result.weight = weight * 0.01f / 2.0f; // unit 'kg' - else if (data[0] == 0x03) + } else if (data[0] == 0x03) { result.weight = weight * 0.01f * 0.453592f; // unit 'lbs' + } if (has_impedance) { // impedance, 2 bytes, 16-bit diff --git a/esphome/components/yashima/yashima.cpp b/esphome/components/yashima/yashima.cpp index 9a6e85bca1..493c689b42 100644 --- a/esphome/components/yashima/yashima.cpp +++ b/esphome/components/yashima/yashima.cpp @@ -179,12 +179,13 @@ void YashimaClimate::transmit_state_() { data->mark(YASHIMA_HEADER_MARK); data->space(YASHIMA_HEADER_SPACE); // Data (sent from the MSB to the LSB) - for (uint8_t i : remote_state) + for (uint8_t i : remote_state) { for (int8_t j = 7; j >= 0; j--) { data->mark(YASHIMA_BIT_MARK); bool bit = i & (1 << j); data->space(bit ? YASHIMA_ONE_SPACE : YASHIMA_ZERO_SPACE); } + } // Footer data->mark(YASHIMA_BIT_MARK); data->space(YASHIMA_GAP); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 5f29abe579..dc85bdb17d 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -206,19 +206,21 @@ void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float min_color_value = std::min(std::min(red, green), blue); float delta = max_color_value - min_color_value; - if (delta == 0) + if (delta == 0) { hue = 0; - else if (max_color_value == red) + } else if (max_color_value == red) { hue = int(fmod(((60 * ((green - blue) / delta)) + 360), 360)); - else if (max_color_value == green) + } else if (max_color_value == green) { hue = int(fmod(((60 * ((blue - red) / delta)) + 120), 360)); - else if (max_color_value == blue) + } else if (max_color_value == blue) { hue = int(fmod(((60 * ((red - green) / delta)) + 240), 360)); + } - if (max_color_value == 0) + if (max_color_value == 0) { saturation = 0; - else + } else { saturation = delta / max_color_value; + } value = max_color_value; } @@ -339,14 +341,15 @@ 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') + if (*str >= '0' && *str <= '9') { val = *str - '0'; - else if (*str >= 'A' && *str <= 'F') + } else if (*str >= 'A' && *str <= 'F') { val = 10 + (*str - 'A'); - else if (*str >= 'a' && *str <= 'f') + } else if (*str >= 'a' && *str <= 'f') { val = 10 + (*str - 'a'); - else + } else { return 0; + } data[i >> 1] = !(i & 1) ? val << 4 : data[i >> 1] | val; } return chars; diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 3fe07f94b5..7f0ed0b17c 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -167,9 +167,10 @@ void IRAM_ATTR HOT Scheduler::call() { { // Don't copy-by value yet auto &item = this->items_[0]; - if ((now - item->last_execution) < item->interval) + if ((now - item->last_execution) < item->interval) { // Not reached timeout yet, done for this call break; + } uint8_t major = item->next_execution_major(); if (this->millis_major_ - major > 1) break; @@ -190,10 +191,11 @@ void IRAM_ATTR HOT Scheduler::call() { // - timeouts/intervals get cancelled { WarnIfComponentBlockingGuard guard{item->component}; - if (item->type == SchedulerItem::RETRY) + if (item->type == SchedulerItem::RETRY) { retry_result = item->retry_callback(); - else + } else { item->void_callback(); + } } } @@ -257,17 +259,19 @@ void HOT Scheduler::pop_raw_() { void HOT Scheduler::push_(std::unique_ptr item) { this->to_add_.push_back(std::move(item)); } bool HOT Scheduler::cancel_item_(Component *component, const std::string &name, Scheduler::SchedulerItem::Type type) { bool ret = false; - for (auto &it : this->items_) + for (auto &it : this->items_) { if (it->component == component && it->name == name && it->type == type && !it->remove) { to_remove_++; it->remove = true; ret = true; } - for (auto &it : this->to_add_) + } + for (auto &it : this->to_add_) { if (it->component == component && it->name == name && it->type == type) { it->remove = true; ret = true; } + } return ret; } From 4e6bdb31ac0243aa2261a9889c7aa6474f6fec60 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 24 Jan 2022 20:57:26 +0100 Subject: [PATCH 041/238] Make CallbackManager invocable (#3089) --- esphome/core/helpers.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index c9a27a2fab..95bf10abc3 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -176,6 +176,9 @@ template class CallbackManager { cb(args...); } + /// Call all callbacks in this manager. + void operator()(Ts... args) { call(args...); } + protected: std::vector> callbacks_; }; From 6a2c58fcc0e94cd2657e449d3510c3032255b955 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 24 Jan 2022 21:30:48 +0100 Subject: [PATCH 042/238] Implement output button (#3109) --- esphome/components/output/button/__init__.py | 26 +++++++++++++++++++ .../output/button/output_button.cpp | 21 +++++++++++++++ .../components/output/button/output_button.h | 25 ++++++++++++++++++ tests/test3.yaml | 4 +++ 4 files changed, 76 insertions(+) create mode 100644 esphome/components/output/button/__init__.py create mode 100644 esphome/components/output/button/output_button.cpp create mode 100644 esphome/components/output/button/output_button.h diff --git a/esphome/components/output/button/__init__.py b/esphome/components/output/button/__init__.py new file mode 100644 index 0000000000..4b81cedc0c --- /dev/null +++ b/esphome/components/output/button/__init__.py @@ -0,0 +1,26 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button, output +from esphome.const import CONF_ID, CONF_OUTPUT, CONF_DURATION +from .. import output_ns + +OutputButton = output_ns.class_("OutputButton", button.Button, cg.Component) + +CONFIG_SCHEMA = button.BUTTON_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(OutputButton), + cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), + cv.Required(CONF_DURATION): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_duration(config[CONF_DURATION])) + + output_ = await cg.get_variable(config[CONF_OUTPUT]) + cg.add(var.set_output(output_)) + + await cg.register_component(var, config) + await button.register_button(var, config) diff --git a/esphome/components/output/button/output_button.cpp b/esphome/components/output/button/output_button.cpp new file mode 100644 index 0000000000..4dd7ec249b --- /dev/null +++ b/esphome/components/output/button/output_button.cpp @@ -0,0 +1,21 @@ +#include "output_button.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace output { + +static const char *const TAG = "output.button"; + +void OutputButton::dump_config() { + LOG_BUTTON("", "Output Button", this); + ESP_LOGCONFIG(TAG, " Duration: %.1fs", this->duration_ / 1e3f); +} +void OutputButton::press_action() { + this->output_->turn_on(); + + // Use a named timeout so that it's automatically cancelled if button is pressed again before it's reset + this->set_timeout("reset", this->duration_, [this]() { this->output_->turn_off(); }); +} + +} // namespace output +} // namespace esphome diff --git a/esphome/components/output/button/output_button.h b/esphome/components/output/button/output_button.h new file mode 100644 index 0000000000..8802c9754d --- /dev/null +++ b/esphome/components/output/button/output_button.h @@ -0,0 +1,25 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/button/button.h" +#include "esphome/components/output/binary_output.h" + +namespace esphome { +namespace output { + +class OutputButton : public button::Button, public Component { + public: + void dump_config() override; + + void set_output(BinaryOutput *output) { output_ = output; } + void set_duration(uint32_t duration) { duration_ = duration; } + + protected: + void press_action() override; + + output::BinaryOutput *output_; + uint32_t duration_; +}; + +} // namespace output +} // namespace esphome diff --git a/tests/test3.yaml b/tests/test3.yaml index b22ec72226..2b00a3612e 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1352,6 +1352,10 @@ daly_bms: uart_id: uart1 button: + - platform: output + id: output_button + output: out + duration: 100ms - platform: wake_on_lan target_mac_address: 12:34:56:78:90:ab name: wol_test_1 From ef5d9597887d75f6c218ef1fe750a58406c4eaaf Mon Sep 17 00:00:00 2001 From: Rebbe Pod <66928914+RebbePod@users.noreply.github.com> Date: Mon, 24 Jan 2022 15:54:46 -0500 Subject: [PATCH 043/238] Add increment_day function to ESPTime (#2955) --- esphome/components/time/real_time_clock.cpp | 17 +++++++++++++++++ esphome/components/time/real_time_clock.h | 2 ++ 2 files changed, 19 insertions(+) diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 6f6739d293..0469ba2c37 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -131,6 +131,23 @@ void ESPTime::increment_second() { this->year++; } } +void ESPTime::increment_day() { + this->timestamp += 86400; + + // increment day + increment_time_value(this->day_of_week, 1, 8); + + if (increment_time_value(this->day_of_month, 1, days_in_month(this->month, this->year) + 1)) { + // day of month roll-over, increment month + increment_time_value(this->month, 1, 13); + } + + uint16_t days_in_year = (this->year % 4 == 0) ? 366 : 365; + if (increment_time_value(this->day_of_year, 1, days_in_year + 1)) { + // day of year roll-over, increment year + this->year++; + } +} void ESPTime::recalc_timestamp_utc(bool use_day_of_year) { time_t res = 0; diff --git a/esphome/components/time/real_time_clock.h b/esphome/components/time/real_time_clock.h index 0c6fa6f3a0..c45deb0be5 100644 --- a/esphome/components/time/real_time_clock.h +++ b/esphome/components/time/real_time_clock.h @@ -90,6 +90,8 @@ struct ESPTime { /// Increment this clock instance by one second. void increment_second(); + /// Increment this clock instance by one day. + void increment_day(); bool operator<(ESPTime other); bool operator<=(ESPTime other); bool operator==(ESPTime other); From 6ff3942e8b551ebe52bf33d6695dfa3886ce2904 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 24 Jan 2022 23:41:14 +0100 Subject: [PATCH 044/238] [TCS34725] remove duplicated endian conversion (#3037) Co-authored-by: Oxan van Leeuwen --- esphome/components/tcs34725/tcs34725.cpp | 61 +++++++++++++++--------- esphome/components/tcs34725/tcs34725.h | 10 ++++ 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/esphome/components/tcs34725/tcs34725.cpp b/esphome/components/tcs34725/tcs34725.cpp index f7ffe2a97d..825f7da4cc 100644 --- a/esphome/components/tcs34725/tcs34725.cpp +++ b/esphome/components/tcs34725/tcs34725.cpp @@ -21,23 +21,23 @@ static const uint8_t TCS34725_REGISTER_BDATAL = TCS34725_COMMAND_BIT | 0x1A; void TCS34725Component::setup() { ESP_LOGCONFIG(TAG, "Setting up TCS34725..."); uint8_t id; - if (!this->read_byte(TCS34725_REGISTER_ID, &id)) { + if (this->read_register(TCS34725_REGISTER_ID, &id, 1) != i2c::ERROR_OK) { this->mark_failed(); return; } - - if (!this->write_byte(TCS34725_REGISTER_ATIME, this->integration_reg_) || - !this->write_byte(TCS34725_REGISTER_CONTROL, this->gain_reg_)) { + if (this->write_config_register_(TCS34725_REGISTER_ATIME, this->integration_reg_) != i2c::ERROR_OK || + this->write_config_register_(TCS34725_REGISTER_CONTROL, this->gain_reg_) != i2c::ERROR_OK) { this->mark_failed(); return; } - - if (!this->write_byte(TCS34725_REGISTER_ENABLE, 0x01)) { // Power on (internal oscillator on) + if (this->write_config_register_(TCS34725_REGISTER_ENABLE, 0x01) != + i2c::ERROR_OK) { // Power on (internal oscillator on) this->mark_failed(); return; } delay(3); - if (!this->write_byte(TCS34725_REGISTER_ENABLE, 0x03)) { // Power on (internal oscillator on) + RGBC ADC Enable + if (this->write_config_register_(TCS34725_REGISTER_ENABLE, 0x03) != + i2c::ERROR_OK) { // Power on (internal oscillator on) + RGBC ADC Enable this->mark_failed(); return; } @@ -134,9 +134,9 @@ void TCS34725Component::calculate_temperature_and_lux_(uint16_t r, uint16_t g, u /* 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) { + ESP_LOGW(TAG, "Saturation too high, discarding sample with saturation %.1f and clear %d", sat, c); return; } @@ -173,23 +173,40 @@ void TCS34725Component::update() { uint16_t raw_g; uint16_t raw_b; - if (!this->read_byte_16(TCS34725_REGISTER_CDATAL, &raw_c) || !this->read_byte_16(TCS34725_REGISTER_RDATAL, &raw_r) || - !this->read_byte_16(TCS34725_REGISTER_GDATAL, &raw_g) || !this->read_byte_16(TCS34725_REGISTER_BDATAL, &raw_b)) { - ESP_LOGW(TAG, "Reading data from TCS34725 failed!"); + if (this->read_data_register_(TCS34725_REGISTER_CDATAL, raw_c) != i2c::ERROR_OK) { this->status_set_warning(); return; } + if (this->read_data_register_(TCS34725_REGISTER_RDATAL, raw_r) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + if (this->read_data_register_(TCS34725_REGISTER_GDATAL, raw_g) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + if (this->read_data_register_(TCS34725_REGISTER_BDATAL, raw_b) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + ESP_LOGV(TAG, "Raw values clear=%x red=%x green=%x blue=%x", raw_c, raw_r, raw_g, raw_b); - // 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); + float channel_c; + float channel_r; + float channel_g; + float channel_b; + // avoid division by 0 and return black if clear is 0 + if (raw_c == 0) { + channel_c = channel_r = channel_g = channel_b = 0.0f; + } else { + float max_count = this->integration_time_ * 1024.0f / 2.4; + float sum = raw_c; + channel_r = raw_r / sum * 100.0f; + channel_g = raw_g / sum * 100.0f; + channel_b = raw_b / sum * 100.0f; + channel_c = raw_c / max_count * 100.0f; + } - const float channel_c = raw_c / 655.35f; - const float channel_r = raw_r / 655.35f; - const float channel_g = raw_g / 655.35f; - const float channel_b = raw_b / 655.35f; if (this->clear_sensor_ != nullptr) this->clear_sensor_->publish_state(channel_c); if (this->red_sensor_ != nullptr) @@ -209,8 +226,8 @@ void TCS34725Component::update() { if (this->color_temperature_sensor_ != nullptr) 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, this->illuminance_, this->color_temperature_); + ESP_LOGD(TAG, "Got Red=%.1f%%,Green=%.1f%%,Blue=%.1f%%,Clear=%.1f%% Illuminance=%.1flx Color Temperature=%.1fK", + channel_r, channel_g, channel_b, channel_c, this->illuminance_, this->color_temperature_); this->status_clear_warning(); } diff --git a/esphome/components/tcs34725/tcs34725.h b/esphome/components/tcs34725/tcs34725.h index 47ed2959c6..04565d948e 100644 --- a/esphome/components/tcs34725/tcs34725.h +++ b/esphome/components/tcs34725/tcs34725.h @@ -56,6 +56,16 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice { void dump_config() override; protected: + i2c::ErrorCode read_data_register_(uint8_t a_register, uint16_t &data) { + uint8_t buffer[2]; + auto retval = this->read_register(a_register, buffer, 2); + if (retval == i2c::ERROR_OK) + data = (uint16_t(buffer[1]) << 8) | (uint16_t(buffer[0]) & 0xFF); + return retval; + } + i2c::ErrorCode write_config_register_(uint8_t a_register, uint8_t data) { + return this->write_register(a_register, &data, 1); + } sensor::Sensor *clear_sensor_{nullptr}; sensor::Sensor *red_sensor_{nullptr}; sensor::Sensor *green_sensor_{nullptr}; From 28b65cb810fc0f8277cb67b20b4be782ea7138ca Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Tue, 25 Jan 2022 09:46:42 +1100 Subject: [PATCH 045/238] Perform merges when substituting dict keys (#3062) --- esphome/components/packages/__init__.py | 23 ++------------------ esphome/components/substitutions/__init__.py | 3 ++- esphome/config_helpers.py | 20 +++++++++++++++++ 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index 7483d65b9d..67220cae08 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -1,6 +1,7 @@ import re from pathlib import Path from esphome.core import EsphomeError +from esphome.config_helpers import merge_config from esphome import git, yaml_util from esphome.const import ( @@ -18,26 +19,6 @@ import esphome.config_validation as cv DOMAIN = CONF_PACKAGES -def _merge_package(full_old, full_new): - def merge(old, new): - # pylint: disable=no-else-return - if isinstance(new, dict): - if not isinstance(old, dict): - return new - res = old.copy() - for k, v in new.items(): - res[k] = merge(old[k], v) if k in old else v - return res - elif isinstance(new, list): - if not isinstance(old, list): - return new - return old + new - - return new - - return merge(full_old, full_new) - - def validate_git_package(config: dict): new_config = config for key, conf in config.items(): @@ -167,7 +148,7 @@ def do_packages_pass(config: dict): package_config = _process_base_package(package_config) if isinstance(package_config, dict): recursive_package = do_packages_pass(package_config) - config = _merge_package(recursive_package, config) + config = merge_config(recursive_package, config) del config[CONF_PACKAGES] return config diff --git a/esphome/components/substitutions/__init__.py b/esphome/components/substitutions/__init__.py index 0cef417c15..6188b14b35 100644 --- a/esphome/components/substitutions/__init__.py +++ b/esphome/components/substitutions/__init__.py @@ -5,6 +5,7 @@ import esphome.config_validation as cv from esphome import core from esphome.const import CONF_SUBSTITUTIONS from esphome.yaml_util import ESPHomeDataBase, make_data_base +from esphome.config_helpers import merge_config CODEOWNERS = ["@esphome/core"] _LOGGER = logging.getLogger(__name__) @@ -108,7 +109,7 @@ def _substitute_item(substitutions, item, path): if sub is not None: item[k] = sub for old, new in replace_keys: - item[new] = item[old] + item[new] = merge_config(item.get(old), item.get(new)) del item[old] elif isinstance(item, str): sub = _expand_substitutions(substitutions, item, path) diff --git a/esphome/config_helpers.py b/esphome/config_helpers.py index a88c5983b5..72362a3d73 100644 --- a/esphome/config_helpers.py +++ b/esphome/config_helpers.py @@ -23,3 +23,23 @@ def read_config_file(path): return data["content"] return read_file(path) + + +def merge_config(full_old, full_new): + def merge(old, new): + # pylint: disable=no-else-return + if isinstance(new, dict): + if not isinstance(old, dict): + return new + res = old.copy() + for k, v in new.items(): + res[k] = merge(old[k], v) if k in old else v + return res + elif isinstance(new, list): + if not isinstance(old, list): + return new + return old + new + + return new + + return merge(full_old, full_new) From 1de941e837ef81a8043260e2e6829cf2c029c128 Mon Sep 17 00:00:00 2001 From: Dav-id Date: Mon, 24 Jan 2022 23:53:47 +0100 Subject: [PATCH 046/238] Esp32cam full control (#3090) --- esphome/components/esp32_camera/__init__.py | 105 +++++++++++-- .../components/esp32_camera/esp32_camera.cpp | 133 +++++++++++----- .../components/esp32_camera/esp32_camera.h | 148 +++++++++++++----- 3 files changed, 293 insertions(+), 93 deletions(-) diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index d42d4f5de3..912e705766 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -23,6 +23,7 @@ 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") FRAME_SIZES = { "160X120": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_160X120, @@ -46,30 +47,76 @@ FRAME_SIZES = { "1600X1200": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200, "UXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200, } +ESP32GainControlMode = esp32_camera_ns.enum("ESP32GainControlMode") +ENUM_GAIN_CONTROL_MODE = { + "MANUAL": ESP32GainControlMode.ESP32_GC_MODE_MANU, + "AUTO": ESP32GainControlMode.ESP32_GC_MODE_AUTO, +} +ESP32AgcGainCeiling = esp32_camera_ns.enum("ESP32AgcGainCeiling") +ENUM_GAIN_CEILING = { + "2X": ESP32AgcGainCeiling.ESP32_GAINCEILING_2X, + "4X": ESP32AgcGainCeiling.ESP32_GAINCEILING_4X, + "8X": ESP32AgcGainCeiling.ESP32_GAINCEILING_8X, + "16X": ESP32AgcGainCeiling.ESP32_GAINCEILING_16X, + "32X": ESP32AgcGainCeiling.ESP32_GAINCEILING_32X, + "64X": ESP32AgcGainCeiling.ESP32_GAINCEILING_64X, + "128X": ESP32AgcGainCeiling.ESP32_GAINCEILING_128X, +} +ESP32WhiteBalanceMode = esp32_camera_ns.enum("ESP32WhiteBalanceMode") +ENUM_WB_MODE = { + "AUTO": ESP32WhiteBalanceMode.ESP32_WB_MODE_AUTO, + "SUNNY": ESP32WhiteBalanceMode.ESP32_WB_MODE_SUNNY, + "CLOUDY": ESP32WhiteBalanceMode.ESP32_WB_MODE_CLOUDY, + "OFFICE": ESP32WhiteBalanceMode.ESP32_WB_MODE_OFFICE, + "HOME": ESP32WhiteBalanceMode.ESP32_WB_MODE_HOME, +} +ESP32SpecialEffect = esp32_camera_ns.enum("ESP32SpecialEffect") +ENUM_SPECIAL_EFFECT = { + "NONE": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_NONE, + "NEGATIVE": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_NEGATIVE, + "GRAYSCALE": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_GRAYSCALE, + "RED_TINT": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_RED_TINT, + "GREEN_TINT": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_GREEN_TINT, + "BLUE_TINT": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_BLUE_TINT, + "SEPIA": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_SEPIA, +} +# pin assignment CONF_VSYNC_PIN = "vsync_pin" CONF_HREF_PIN = "href_pin" CONF_PIXEL_CLOCK_PIN = "pixel_clock_pin" CONF_EXTERNAL_CLOCK = "external_clock" CONF_I2C_PINS = "i2c_pins" CONF_POWER_DOWN_PIN = "power_down_pin" - -CONF_MAX_FRAMERATE = "max_framerate" -CONF_IDLE_FRAMERATE = "idle_framerate" +# image CONF_JPEG_QUALITY = "jpeg_quality" CONF_VERTICAL_FLIP = "vertical_flip" CONF_HORIZONTAL_MIRROR = "horizontal_mirror" +CONF_SATURATION = "saturation" +CONF_SPECIAL_EFFECT = "special_effect" +# exposure +CONF_AEC_MODE = "aec_mode" CONF_AEC2 = "aec2" CONF_AE_LEVEL = "ae_level" CONF_AEC_VALUE = "aec_value" -CONF_SATURATION = "saturation" +# gains +CONF_AGC_MODE = "agc_mode" +CONF_AGC_VALUE = "agc_value" +CONF_AGC_GAIN_CEILING = "agc_gain_ceiling" +# white balance +CONF_WB_MODE = "wb_mode" +# test pattern CONF_TEST_PATTERN = "test_pattern" +# framerates +CONF_MAX_FRAMERATE = "max_framerate" +CONF_IDLE_FRAMERATE = "idle_framerate" camera_range_param = cv.int_range(min=-2, max=2) CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(ESP32Camera), + # pin assignment cv.Required(CONF_DATA_PINS): cv.All( [pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8) ), @@ -92,12 +139,7 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( ), cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, cv.Optional(CONF_POWER_DOWN_PIN): pins.internal_gpio_output_pin_number, - cv.Optional(CONF_MAX_FRAMERATE, default="10 fps"): cv.All( - cv.framerate, cv.Range(min=0, min_included=False, max=60) - ), - cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All( - cv.framerate, cv.Range(min=0, max=1) - ), + # image cv.Optional(CONF_RESOLUTION, default="640X480"): cv.enum( FRAME_SIZES, upper=True ), @@ -107,29 +149,66 @@ 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_SPECIAL_EFFECT, default="NONE"): cv.enum( + ENUM_SPECIAL_EFFECT, upper=True + ), + # exposure + cv.Optional(CONF_AGC_MODE, default="AUTO"): cv.enum( + ENUM_GAIN_CONTROL_MODE, upper=True + ), 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), + # gains + cv.Optional(CONF_AEC_MODE, default="AUTO"): cv.enum( + ENUM_GAIN_CONTROL_MODE, upper=True + ), + cv.Optional(CONF_AGC_VALUE, default=0): cv.int_range(min=0, max=30), + cv.Optional(CONF_AGC_GAIN_CEILING, default="2X"): cv.enum( + ENUM_GAIN_CEILING, upper=True + ), + # white balance + cv.Optional(CONF_WB_MODE, default="AUTO"): cv.enum(ENUM_WB_MODE, upper=True), + # test pattern cv.Optional(CONF_TEST_PATTERN, default=False): cv.boolean, + # framerates + cv.Optional(CONF_MAX_FRAMERATE, default="10 fps"): cv.All( + cv.framerate, cv.Range(min=0, min_included=False, max=60) + ), + cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All( + cv.framerate, cv.Range(min=0, max=1) + ), } ).extend(cv.COMPONENT_SCHEMA) SETTERS = { + # pin assignment CONF_DATA_PINS: "set_data_pins", CONF_VSYNC_PIN: "set_vsync_pin", CONF_HREF_PIN: "set_href_pin", CONF_PIXEL_CLOCK_PIN: "set_pixel_clock_pin", CONF_RESET_PIN: "set_reset_pin", CONF_POWER_DOWN_PIN: "set_power_down_pin", + # image 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", + CONF_SPECIAL_EFFECT: "set_special_effect", + # exposure + CONF_AEC_MODE: "set_aec_mode", + CONF_AEC2: "set_aec2", + CONF_AE_LEVEL: "set_ae_level", + CONF_AEC_VALUE: "set_aec_value", + # gains + CONF_AGC_MODE: "set_agc_mode", + CONF_AGC_VALUE: "set_agc_value", + CONF_AGC_GAIN_CEILING: "set_agc_gain_ceiling", + # white balance + CONF_WB_MODE: "set_wb_mode", + # test pattern CONF_TEST_PATTERN: "set_test_pattern", } diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 7d11f98d09..851926b083 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -11,10 +11,14 @@ namespace esp32_camera { static const char *const TAG = "esp32_camera"; +/* ---------------- public API (derivated) ---------------- */ void ESP32Camera::setup() { global_esp32_camera = this; + /* initialize time to now */ this->last_update_ = millis(); + + /* initialize camera */ esp_err_t err = esp_camera_init(&this->config_); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_camera_init failed: %s", esp_err_to_name(err)); @@ -23,16 +27,10 @@ void ESP32Camera::setup() { return; } - 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_); - s->set_colorbar(s, this->test_pattern_); + /* initialize camera parameters */ + this->update_camera_parameters(); + + /* initialize RTOS */ this->framebuffer_get_queue_ = xQueueCreate(1, sizeof(camera_fb_t *)); this->framebuffer_return_queue_ = xQueueCreate(1, sizeof(camera_fb_t *)); xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task, @@ -44,6 +42,7 @@ void ESP32Camera::setup() { 1 // core ); } + void ESP32Camera::dump_config() { auto conf = this->config_; ESP_LOGCONFIG(TAG, "ESP32 Camera:"); @@ -106,17 +105,17 @@ void ESP32Camera::dump_config() { ESP_LOGCONFIG(TAG, " Saturation: %d", st.saturation); ESP_LOGCONFIG(TAG, " Vertical Flip: %s", ONOFF(st.vflip)); ESP_LOGCONFIG(TAG, " Horizontal Mirror: %s", ONOFF(st.hmirror)); - // ESP_LOGCONFIG(TAG, " Special Effect: %u", st.special_effect); - // ESP_LOGCONFIG(TAG, " White Balance Mode: %u", st.wb_mode); + ESP_LOGCONFIG(TAG, " Special Effect: %u", st.special_effect); + ESP_LOGCONFIG(TAG, " White Balance Mode: %u", st.wb_mode); // 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: %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, " AGC: %u", st.agc); - // ESP_LOGCONFIG(TAG, " AGC Gain: %u", st.agc_gain); - // ESP_LOGCONFIG(TAG, " Gain Ceiling: %u", st.gainceiling); + ESP_LOGCONFIG(TAG, " AGC: %u", st.agc); + ESP_LOGCONFIG(TAG, " AGC Gain: %u", st.agc_gain); + ESP_LOGCONFIG(TAG, " Gain Ceiling: %u", st.gainceiling); // ESP_LOGCONFIG(TAG, " BPC: %u", st.bpc); // ESP_LOGCONFIG(TAG, " WPC: %u", st.wpc); // ESP_LOGCONFIG(TAG, " RAW_GMA: %u", st.raw_gma); @@ -124,6 +123,7 @@ void ESP32Camera::dump_config() { // ESP_LOGCONFIG(TAG, " DCW: %u", st.dcw); ESP_LOGCONFIG(TAG, " Test Pattern: %s", YESNO(st.colorbar)); } + void ESP32Camera::loop() { // check if we can return the image if (this->can_return_image_()) { @@ -170,15 +170,10 @@ void ESP32Camera::loop() { this->last_update_ = now; this->single_requesters_ = 0; } -void ESP32Camera::framebuffer_task(void *pv) { - while (true) { - camera_fb_t *framebuffer = esp_camera_fb_get(); - xQueueSend(global_esp32_camera->framebuffer_get_queue_, &framebuffer, portMAX_DELAY); - // return is no-op for config with 1 fb - xQueueReceive(global_esp32_camera->framebuffer_return_queue_, &framebuffer, portMAX_DELAY); - esp_camera_fb_return(framebuffer); - } -} + +float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; } + +/* ---------------- constructors ---------------- */ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { this->config_.pin_pwdn = -1; this->config_.pin_reset = -1; @@ -193,6 +188,9 @@ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { global_esp32_camera = this; } ESP32Camera::ESP32Camera() : ESP32Camera("") {} + +/* ---------------- setters ---------------- */ +/* set pin assignment */ void ESP32Camera::set_data_pins(std::array pins) { this->config_.pin_d0 = pins[0]; this->config_.pin_d1 = pins[1]; @@ -214,6 +212,10 @@ void ESP32Camera::set_i2c_pins(uint8_t sda, uint8_t scl) { this->config_.pin_sscb_sda = sda; this->config_.pin_sscb_scl = scl; } +void ESP32Camera::set_reset_pin(uint8_t pin) { this->config_.pin_reset = pin; } +void ESP32Camera::set_power_down_pin(uint8_t pin) { this->config_.pin_pwdn = pin; } + +/* set image parameters */ void ESP32Camera::set_frame_size(ESP32CameraFrameSize size) { switch (size) { case ESP32_CAMERA_SIZE_160X120: @@ -249,36 +251,81 @@ void ESP32Camera::set_frame_size(ESP32CameraFrameSize size) { } } void ESP32Camera::set_jpeg_quality(uint8_t quality) { this->config_.jpeg_quality = quality; } -void ESP32Camera::set_reset_pin(uint8_t pin) { this->config_.pin_reset = pin; } -void ESP32Camera::set_power_down_pin(uint8_t pin) { this->config_.pin_pwdn = pin; } -void ESP32Camera::add_image_callback(std::function)> &&f) { - this->new_image_callback_.add(std::move(f)); -} void ESP32Camera::set_vertical_flip(bool vertical_flip) { this->vertical_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; } -float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; } -uint32_t ESP32Camera::hash_base() { return 3010542557UL; } -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_special_effect(ESP32SpecialEffect effect) { this->special_effect_ = effect; } +/* set exposure parameters */ +void ESP32Camera::set_aec_mode(ESP32GainControlMode mode) { this->aec_mode_ = mode; } +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; } +/* set gains parameters */ +void ESP32Camera::set_agc_mode(ESP32GainControlMode mode) { this->agc_mode_ = mode; } +void ESP32Camera::set_agc_value(uint8_t agc_value) { this->agc_value_ = agc_value; } +void ESP32Camera::set_agc_gain_ceiling(ESP32AgcGainCeiling gain_ceiling) { this->agc_gain_ceiling_ = gain_ceiling; } +/* set white balance */ +void ESP32Camera::set_wb_mode(ESP32WhiteBalanceMode mode) { this->wb_mode_ = mode; } +/* set test mode */ +void ESP32Camera::set_test_pattern(bool test_pattern) { this->test_pattern_ = test_pattern; } +/* set fps */ void ESP32Camera::set_max_update_interval(uint32_t max_update_interval) { this->max_update_interval_ = max_update_interval; } void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) { this->idle_update_interval_ = idle_update_interval; } -void ESP32Camera::set_test_pattern(bool test_pattern) { this->test_pattern_ = test_pattern; } + +/* ---------------- public API (specific) ---------------- */ +void ESP32Camera::add_image_callback(std::function)> &&f) { + this->new_image_callback_.add(std::move(f)); +} +void ESP32Camera::start_stream(CameraRequester requester) { this->stream_requesters_ |= (1U << requester); } +void ESP32Camera::stop_stream(CameraRequester requester) { this->stream_requesters_ &= ~(1U << requester); } +void ESP32Camera::request_image(CameraRequester requester) { this->single_requesters_ |= (1U << requester); } +void ESP32Camera::update_camera_parameters() { + sensor_t *s = esp_camera_sensor_get(); + /* update image */ + s->set_vflip(s, this->vertical_flip_); + s->set_hmirror(s, this->horizontal_mirror_); + s->set_contrast(s, this->contrast_); + s->set_brightness(s, this->brightness_); + s->set_saturation(s, this->saturation_); + s->set_special_effect(s, (int) this->special_effect_); // 0 to 6 + /* update exposure */ + s->set_exposure_ctrl(s, (bool) this->aec_mode_); + 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 + /* update gains */ + s->set_gain_ctrl(s, (bool) this->agc_mode_); + s->set_agc_gain(s, (int) this->agc_value_); // 0 to 30 + s->set_gainceiling(s, (gainceiling_t) this->agc_gain_ceiling_); + /* update white balance mode */ + s->set_wb_mode(s, (int) this->wb_mode_); // 0 to 4 + /* update test patern */ + s->set_colorbar(s, this->test_pattern_); +} + +/* ---------------- Internal methods ---------------- */ +uint32_t ESP32Camera::hash_base() { return 3010542557UL; } +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::framebuffer_task(void *pv) { + while (true) { + camera_fb_t *framebuffer = esp_camera_fb_get(); + xQueueSend(global_esp32_camera->framebuffer_get_queue_, &framebuffer, portMAX_DELAY); + // return is no-op for config with 1 fb + xQueueReceive(global_esp32_camera->framebuffer_return_queue_, &framebuffer, portMAX_DELAY); + esp_camera_fb_return(framebuffer); + } +} ESP32Camera *global_esp32_camera; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +/* ---------------- CameraImageReader class ---------------- */ void CameraImageReader::set_image(std::shared_ptr image) { this->image_ = std::move(image); this->offset_ = 0; @@ -293,13 +340,15 @@ void CameraImageReader::return_image() { this->image_.reset(); } void CameraImageReader::consume_data(size_t consumed) { this->offset_ += consumed; } uint8_t *CameraImageReader::peek_data_buffer() { return this->image_->get_data_buffer() + this->offset_; } +/* ---------------- CameraImage class ---------------- */ +CameraImage::CameraImage(camera_fb_t *buffer, uint8_t requesters) : buffer_(buffer), requesters_(requesters) {} + 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; } 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 b2670078f3..743b5bde5f 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -14,34 +14,9 @@ namespace esp32_camera { class ESP32Camera; +/* ---------------- enum classes ---------------- */ enum CameraRequester { IDLE, API_REQUESTER, WEB_REQUESTER }; -class CameraImage { - public: - 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 { - public: - void set_image(std::shared_ptr image); - size_t available() const; - uint8_t *peek_data_buffer(); - void consume_data(size_t consumed); - void return_image(); - - protected: - std::shared_ptr image_; - size_t offset_{0}; -}; - enum ESP32CameraFrameSize { ESP32_CAMERA_SIZE_160X120, // QQVGA ESP32_CAMERA_SIZE_176X144, // QCIF @@ -55,57 +30,155 @@ enum ESP32CameraFrameSize { ESP32_CAMERA_SIZE_1600X1200, // UXGA }; +enum ESP32AgcGainCeiling { + ESP32_GAINCEILING_2X = GAINCEILING_2X, + ESP32_GAINCEILING_4X = GAINCEILING_4X, + ESP32_GAINCEILING_8X = GAINCEILING_8X, + ESP32_GAINCEILING_16X = GAINCEILING_16X, + ESP32_GAINCEILING_32X = GAINCEILING_32X, + ESP32_GAINCEILING_64X = GAINCEILING_64X, + ESP32_GAINCEILING_128X = GAINCEILING_128X, +}; + +enum ESP32GainControlMode { + ESP32_GC_MODE_MANU = false, + ESP32_GC_MODE_AUTO = true, +}; + +enum ESP32WhiteBalanceMode { + ESP32_WB_MODE_AUTO = 0U, + ESP32_WB_MODE_SUNNY = 1U, + ESP32_WB_MODE_CLOUDY = 2U, + ESP32_WB_MODE_OFFICE = 3U, + ESP32_WB_MODE_HOME = 4U, +}; + +enum ESP32SpecialEffect { + ESP32_SPECIAL_EFFECT_NONE = 0U, + ESP32_SPECIAL_EFFECT_NEGATIVE = 1U, + ESP32_SPECIAL_EFFECT_GRAYSCALE = 2U, + ESP32_SPECIAL_EFFECT_RED_TINT = 3U, + ESP32_SPECIAL_EFFECT_GREEN_TINT = 4U, + ESP32_SPECIAL_EFFECT_BLUE_TINT = 5U, + ESP32_SPECIAL_EFFECT_SEPIA = 6U, +}; + +/* ---------------- CameraImage class ---------------- */ +class CameraImage { + public: + 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_; +}; + +/* ---------------- CameraImageReader class ---------------- */ +class CameraImageReader { + public: + void set_image(std::shared_ptr image); + size_t available() const; + uint8_t *peek_data_buffer(); + void consume_data(size_t consumed); + void return_image(); + + protected: + std::shared_ptr image_; + size_t offset_{0}; +}; + +/* ---------------- ESP32Camera class ---------------- */ class ESP32Camera : public Component, public EntityBase { public: ESP32Camera(const std::string &name); ESP32Camera(); + + /* setters */ + /* -- pin assignment */ void set_data_pins(std::array pins); void set_vsync_pin(uint8_t pin); void set_href_pin(uint8_t pin); void set_pixel_clock_pin(uint8_t pin); void set_external_clock(uint8_t pin, uint32_t frequency); void set_i2c_pins(uint8_t sda, uint8_t scl); - void set_frame_size(ESP32CameraFrameSize size); - void set_jpeg_quality(uint8_t quality); void set_reset_pin(uint8_t pin); void set_power_down_pin(uint8_t pin); + /* -- image */ + void set_frame_size(ESP32CameraFrameSize size); + void set_jpeg_quality(uint8_t quality); 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); + void set_special_effect(ESP32SpecialEffect effect); + /* -- exposure */ + void set_aec_mode(ESP32GainControlMode mode); + void set_aec2(bool aec2); + void set_ae_level(int ae_level); + void set_aec_value(uint32_t aec_value); + /* -- gains */ + void set_agc_mode(ESP32GainControlMode mode); + void set_agc_value(uint8_t agc_value); + void set_agc_gain_ceiling(ESP32AgcGainCeiling gain_ceiling); + /* -- white balance */ + void set_wb_mode(ESP32WhiteBalanceMode mode); + /* -- test */ + void set_test_pattern(bool test_pattern); + /* -- framerates */ void set_max_update_interval(uint32_t max_update_interval); void set_idle_update_interval(uint32_t idle_update_interval); - void set_test_pattern(bool test_pattern); + + /* public API (derivated) */ void setup() override; void loop() override; void dump_config() override; - void add_image_callback(std::function)> &&f); float get_setup_priority() const override; + /* public API (specific) */ + void add_image_callback(std::function)> &&f); void start_stream(CameraRequester requester); void stop_stream(CameraRequester requester); void request_image(CameraRequester requester); + void update_camera_parameters(); protected: + /* internal methods */ uint32_t hash_base() override; bool has_requested_image_() const; bool can_return_image_() const; static void framebuffer_task(void *pv); + /* attributes */ + /* camera configuration */ camera_config_t config_{}; + /* -- image */ 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}; + ESP32SpecialEffect special_effect_{ESP32_SPECIAL_EFFECT_NONE}; + /* -- exposure */ + ESP32GainControlMode aec_mode_{ESP32_GC_MODE_AUTO}; + bool aec2_{false}; + int ae_level_{0}; + uint32_t aec_value_{300}; + /* -- gains */ + ESP32GainControlMode agc_mode_{ESP32_GC_MODE_AUTO}; + uint8_t agc_value_{0}; + ESP32AgcGainCeiling agc_gain_ceiling_{ESP32_GAINCEILING_2X}; + /* -- white balance */ + ESP32WhiteBalanceMode wb_mode_{ESP32_WB_MODE_AUTO}; + /* -- Test */ bool test_pattern_{false}; + /* -- framerates */ + uint32_t max_update_interval_{1000}; + uint32_t idle_update_interval_{15000}; esp_err_t init_error_{ESP_OK}; std::shared_ptr current_image_; @@ -114,8 +187,7 @@ class ESP32Camera : public Component, public EntityBase { 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}; }; From ef256a64b815b639f5f902e2f2dc5311943b7fa0 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Tue, 25 Jan 2022 19:24:59 +1100 Subject: [PATCH 047/238] Fix config merging with null (#3113) --- esphome/config_helpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/config_helpers.py b/esphome/config_helpers.py index 72362a3d73..39b57e441b 100644 --- a/esphome/config_helpers.py +++ b/esphome/config_helpers.py @@ -39,6 +39,8 @@ def merge_config(full_old, full_new): if not isinstance(old, list): return new return old + new + elif new is None: + return old return new From 7a0827e3d0d91923eb4fcc875357754e364ae74d Mon Sep 17 00:00:00 2001 From: guillempages Date: Tue, 25 Jan 2022 09:53:22 +0100 Subject: [PATCH 048/238] Configurable HTTP redirect following (#3100) Co-authored-by: Oxan van Leeuwen --- esphome/components/http_request/__init__.py | 7 +++++ .../components/http_request/http_request.cpp | 27 +++++++++++-------- .../components/http_request/http_request.h | 4 +++ 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index e044e5fece..c8c0ca5369 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -31,6 +31,8 @@ CONF_BODY = "body" CONF_JSON = "json" CONF_VERIFY_SSL = "verify_ssl" CONF_ON_RESPONSE = "on_response" +CONF_FOLLOW_REDIRECTS = "follow_redirects" +CONF_REDIRECT_LIMIT = "redirect_limit" def validate_url(value): @@ -71,6 +73,8 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(HttpRequestComponent), cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string, + cv.Optional(CONF_FOLLOW_REDIRECTS, True): cv.boolean, + cv.Optional(CONF_REDIRECT_LIMIT, 3): cv.int_, cv.Optional( CONF_TIMEOUT, default="5s" ): cv.positive_time_period_milliseconds, @@ -90,6 +94,9 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_timeout(config[CONF_TIMEOUT])) cg.add(var.set_useragent(config[CONF_USERAGENT])) + cg.add(var.set_follow_redirects(config[CONF_FOLLOW_REDIRECTS])) + cg.add(var.set_redirect_limit(config[CONF_REDIRECT_LIMIT])) + if CORE.is_esp8266 and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]: cg.add_define("USE_HTTP_REQUEST_ESP8266_HTTPS") diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index 0fd9c6a4ae..64f3f97de9 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -14,6 +14,8 @@ void HttpRequestComponent::dump_config() { ESP_LOGCONFIG(TAG, "HTTP Request:"); ESP_LOGCONFIG(TAG, " Timeout: %ums", this->timeout_); ESP_LOGCONFIG(TAG, " User-Agent: %s", this->useragent_); + ESP_LOGCONFIG(TAG, " Follow Redirects: %d", this->follow_redirects_); + ESP_LOGCONFIG(TAG, " Redirect limit: %d", this->redirect_limit_); } void HttpRequestComponent::set_url(std::string url) { @@ -38,18 +40,21 @@ void HttpRequestComponent::send(const std::vector bool begin_status = false; const String url = this->url_.c_str(); -#ifdef USE_ESP32 +#if defined(USE_ESP32) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0)) +#if defined(USE_ESP32) || USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) + if (this->follow_redirects_) { + this->client_.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); + } else { + this->client_.setFollowRedirects(HTTPC_DISABLE_FOLLOW_REDIRECTS); + } +#else + this->client_.setFollowRedirects(this->follow_redirects_); +#endif + this->client_.setRedirectLimit(this->redirect_limit_); +#endif +#if defined(USE_ESP32) begin_status = this->client_.begin(url); -#endif -#ifdef USE_ESP8266 -#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) - this->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); -#elif USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) - this->client_.setFollowRedirects(true); -#endif -#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) - this->client_.setRedirectLimit(3); -#endif +#elif defined(USE_ESP8266) begin_status = this->client_.begin(*this->get_wifi_client_(), url); #endif diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index a38bdf9c95..4590163f2c 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -40,6 +40,8 @@ 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_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; } + void set_redirect_limit(uint16_t limit) { this->redirect_limit_ = limit; } 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); @@ -53,6 +55,8 @@ class HttpRequestComponent : public Component { const char *method_; const char *useragent_{nullptr}; bool secure_; + bool follow_redirects_; + uint16_t redirect_limit_; uint16_t timeout_{5000}; std::string body_; std::list
headers_; From d92f297bc0a1815196a547471907447d6797e78f Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Tue, 25 Jan 2022 09:55:33 +0100 Subject: [PATCH 049/238] Add IPv6 support for ESP-IDF framework (#2953) Co-authored-by: Oxan van Leeuwen --- esphome/components/api/api_server.cpp | 17 +++++--- esphome/components/network/__init__.py | 20 +++++++++ esphome/components/ota/ota_component.cpp | 15 ++++--- esphome/components/socket/socket.cpp | 43 +++++++++++++++++++ esphome/components/socket/socket.h | 7 +++ .../wifi/wifi_component_esp_idf.cpp | 18 +++++--- esphome/const.py | 1 + 7 files changed, 102 insertions(+), 19 deletions(-) create mode 100644 esphome/components/socket/socket.cpp diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 3614c7d593..93c9209716 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -24,7 +24,7 @@ static const char *const TAG = "api"; void APIServer::setup() { ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server..."); this->setup_controller(); - socket_ = socket::socket(AF_INET, SOCK_STREAM, 0); + socket_ = socket::socket_ip(SOCK_STREAM, 0); if (socket_ == nullptr) { ESP_LOGW(TAG, "Could not create socket."); this->mark_failed(); @@ -43,13 +43,16 @@ void APIServer::setup() { return; } - struct sockaddr_in server; - memset(&server, 0, sizeof(server)); - server.sin_family = AF_INET; - server.sin_addr.s_addr = ESPHOME_INADDR_ANY; - server.sin_port = htons(this->port_); + struct sockaddr_storage server; - err = socket_->bind((struct sockaddr *) &server, sizeof(server)); + socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), htons(this->port_)); + if (sl == 0) { + ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno); + this->mark_failed(); + return; + } + + err = socket_->bind((struct sockaddr *) &server, sl); if (err != 0) { ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno); this->mark_failed(); diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index 6d39023a80..40d420c48c 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -1,7 +1,27 @@ import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components.esp32 import add_idf_sdkconfig_option + +from esphome.const import ( + CONF_ENABLE_IPV6, +) CODEOWNERS = ["@esphome/core"] AUTO_LOAD = ["mdns"] network_ns = cg.esphome_ns.namespace("network") IPAddress = network_ns.class_("IPAddress") + +CONFIG_SCHEMA = cv.Schema( + { + cv.SplitDefault(CONF_ENABLE_IPV6, esp32_idf=False): cv.All( + cv.only_with_esp_idf, cv.boolean + ), + } +) + + +async def to_code(config): + if CONF_ENABLE_IPV6 in config and config[CONF_ENABLE_IPV6]: + add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", True) + add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", True) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index c05f2aa546..37da3bdc44 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -36,7 +36,7 @@ std::unique_ptr make_ota_backend() { } void OTAComponent::setup() { - server_ = socket::socket(AF_INET, SOCK_STREAM, 0); + server_ = socket::socket_ip(SOCK_STREAM, 0); if (server_ == nullptr) { ESP_LOGW(TAG, "Could not create socket."); this->mark_failed(); @@ -55,11 +55,14 @@ void OTAComponent::setup() { return; } - struct sockaddr_in server; - memset(&server, 0, sizeof(server)); - server.sin_family = AF_INET; - server.sin_addr.s_addr = ESPHOME_INADDR_ANY; - server.sin_port = htons(this->port_); + struct sockaddr_storage server; + + socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), htons(this->port_)); + if (sl == 0) { + ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno); + this->mark_failed(); + return; + } err = server_->bind((struct sockaddr *) &server, sizeof(server)); if (err != 0) { diff --git a/esphome/components/socket/socket.cpp b/esphome/components/socket/socket.cpp new file mode 100644 index 0000000000..22a4c11df8 --- /dev/null +++ b/esphome/components/socket/socket.cpp @@ -0,0 +1,43 @@ +#include "socket.h" +#include "esphome/core/log.h" +#include +#include + +namespace esphome { +namespace socket { + +std::unique_ptr socket_ip(int type, int protocol) { +#if LWIP_IPV6 + return socket(AF_INET6, type, protocol); +#else + return socket(AF_INET, type, protocol); +#endif +} + +socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port) { +#if LWIP_IPV6 + if (addrlen < sizeof(sockaddr_in6)) { + errno = EINVAL; + return 0; + } + auto *server = reinterpret_cast(addr); + memset(server, 0, sizeof(sockaddr_in6)); + server->sin6_family = AF_INET6; + server->sin6_port = port; + server->sin6_addr = in6addr_any; + return sizeof(sockaddr_in6); +#else + if (addrlen < sizeof(sockaddr_in)) { + errno = EINVAL; + return 0; + } + auto *server = reinterpret_cast(addr); + memset(server, 0, sizeof(sockaddr_in)); + server->sin_family = AF_INET; + server->sin_addr.s_addr = ESPHOME_INADDR_ANY; + server->sin_port = port; + return sizeof(sockaddr_in); +#endif +} +} // namespace socket +} // namespace esphome diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index 9920610bf5..ecf117deeb 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -38,7 +38,14 @@ class Socket { virtual int loop() { return 0; }; }; +/// Create a socket of the given domain, type and protocol. std::unique_ptr socket(int domain, int type, int protocol); +/// Create a socket in the newest available IP domain (IPv6 or IPv4) of the given type and protocol. +std::unique_ptr socket_ip(int type, int protocol); + +/// Set a sockaddr to the any address for the IP version used by socket_ip(). +socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port); + } // namespace socket } // namespace esphome diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 60a3bf171d..b838e42b0d 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -56,6 +56,7 @@ struct IDFWiFiEvent { wifi_event_ap_probe_req_rx_t ap_probe_req_rx; wifi_event_bss_rssi_low_t bss_rssi_low; ip_event_got_ip_t ip_got_ip; + ip_event_got_ip6_t ip_got_ip6; ip_event_ap_staipassigned_t ip_ap_staipassigned; } data; }; @@ -79,6 +80,8 @@ 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_GOT_IP6) { + memcpy(&event.data.ip_got_ip6, event_data, sizeof(ip_event_got_ip6_t)); } 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) { @@ -497,12 +500,8 @@ const char *get_auth_mode_str(uint8_t mode) { } } -std::string format_ip4_addr(const esp_ip4_addr_t &ip) { - char buf[20]; - snprintf(buf, sizeof(buf), "%u.%u.%u.%u", uint8_t(ip.addr >> 0), uint8_t(ip.addr >> 8), uint8_t(ip.addr >> 16), - uint8_t(ip.addr >> 24)); - return buf; -} +std::string format_ip4_addr(const esp_ip4_addr_t &ip) { return str_snprintf(IPSTR, 15, IP2STR(&ip)); } +std::string format_ip6_addr(const esp_ip6_addr_t &ip) { return str_snprintf(IPV6STR, 39, IPV62STR(ip)); } const char *get_disconnect_reason_str(uint8_t reason) { switch (reason) { case WIFI_REASON_AUTH_EXPIRE: @@ -635,10 +634,17 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_GOT_IP) { const auto &it = data->data.ip_got_ip; +#ifdef LWIP_IPV6_AUTOCONFIG + tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA); +#endif ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(it.ip_info.ip).c_str(), format_ip4_addr(it.ip_info.gw).c_str()); s_sta_got_ip = true; + } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_GOT_IP6) { + const auto &it = data->data.ip_got_ip6; + ESP_LOGV(TAG, "Event: Got IPv6 address=%s", format_ip6_addr(it.ip6_info.ip).c_str()); + } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_LOST_IP) { ESP_LOGV(TAG, "Event: Lost IP"); s_sta_got_ip = false; diff --git a/esphome/const.py b/esphome/const.py index 0c8e53380e..a008c5ab7f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -188,6 +188,7 @@ CONF_ECO2 = "eco2" CONF_EFFECT = "effect" CONF_EFFECTS = "effects" CONF_ELSE = "else" +CONF_ENABLE_IPV6 = "enable_ipv6" CONF_ENABLE_PIN = "enable_pin" CONF_ENABLE_TIME = "enable_time" CONF_ENERGY = "energy" From 297824e2d75e568a8196ccb9e62d1f2e36e02f53 Mon Sep 17 00:00:00 2001 From: Zebble Date: Tue, 25 Jan 2022 11:18:36 -0500 Subject: [PATCH 050/238] Add support for additional colors on GROW R503 (#3087) --- esphome/components/fingerprint_grow/__init__.py | 4 ++++ esphome/components/fingerprint_grow/fingerprint_grow.h | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/esphome/components/fingerprint_grow/__init__.py b/esphome/components/fingerprint_grow/__init__.py index 757a633e09..ecbbc3d477 100644 --- a/esphome/components/fingerprint_grow/__init__.py +++ b/esphome/components/fingerprint_grow/__init__.py @@ -80,6 +80,10 @@ AURA_LED_COLORS = { "RED": AuraLEDColor.RED, "BLUE": AuraLEDColor.BLUE, "PURPLE": AuraLEDColor.PURPLE, + "GREEN": AuraLEDColor.GREEN, + "YELLOW": AuraLEDColor.YELLOW, + "CYAN": AuraLEDColor.CYAN, + "WHITE": AuraLEDColor.WHITE, } validate_aura_led_colors = cv.enum(AURA_LED_COLORS, upper=True) diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.h b/esphome/components/fingerprint_grow/fingerprint_grow.h index e7d734777a..7ec253ff3a 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.h +++ b/esphome/components/fingerprint_grow/fingerprint_grow.h @@ -76,6 +76,10 @@ enum GrowAuraLEDColor { RED = 0x01, BLUE = 0x02, PURPLE = 0x03, + GREEN = 0x04, + YELLOW = 0x05, + CYAN = 0x06, + WHITE = 0x07, }; class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevice { From 3a62455948339bbe80486dde0908e836aa346e85 Mon Sep 17 00:00:00 2001 From: micronen Date: Tue, 25 Jan 2022 20:18:41 +0200 Subject: [PATCH 051/238] Add Heap Sensors - free/max block/fragmentation (#1578) Co-authored-by: Oxan van Leeuwen Co-authored-by: Otto winter --- esphome/components/debug/__init__.py | 55 +++++++- esphome/components/debug/debug_component.cpp | 130 ++++++++++++++++--- esphome/components/debug/debug_component.h | 26 +++- esphome/const.py | 6 + 4 files changed, 194 insertions(+), 23 deletions(-) diff --git a/esphome/components/debug/__init__.py b/esphome/components/debug/__init__.py index 32c4339530..98ad9e2b10 100644 --- a/esphome/components/debug/__init__.py +++ b/esphome/components/debug/__init__.py @@ -1,19 +1,68 @@ import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID +from esphome.components import sensor, text_sensor +from esphome.const import ( + CONF_ID, + CONF_DEVICE, + CONF_FREE, + CONF_FRAGMENTATION, + CONF_BLOCK, + CONF_LOOP_TIME, + UNIT_MILLISECOND, + UNIT_PERCENT, + UNIT_BYTES, + ICON_COUNTER, + ICON_TIMER, +) CODEOWNERS = ["@OttoWinter"] DEPENDENCIES = ["logger"] debug_ns = cg.esphome_ns.namespace("debug") -DebugComponent = debug_ns.class_("DebugComponent", cg.Component) +DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent) + + CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(DebugComponent), + cv.Optional(CONF_DEVICE): text_sensor.TEXT_SENSOR_SCHEMA.extend( + {cv.GenerateID(): cv.declare_id(text_sensor.TextSensor)} + ), + cv.Optional(CONF_FREE): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0), + cv.Optional(CONF_BLOCK): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0), + cv.Optional(CONF_FRAGMENTATION): cv.All( + cv.only_on_esp8266, + cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)), + sensor.sensor_schema(UNIT_PERCENT, ICON_COUNTER, 1), + ), + cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema( + UNIT_MILLISECOND, ICON_TIMER, 1 + ), } -).extend(cv.COMPONENT_SCHEMA) +).extend(cv.polling_component_schema("60s")) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + + if CONF_DEVICE in config: + sens = cg.new_Pvariable(config[CONF_DEVICE][CONF_ID]) + await text_sensor.register_text_sensor(sens, config[CONF_DEVICE]) + cg.add(var.set_device_info_sensor(sens)) + + if CONF_FREE in config: + sens = await sensor.new_sensor(config[CONF_FREE]) + cg.add(var.set_free_sensor(sens)) + + if CONF_BLOCK in config: + sens = await sensor.new_sensor(config[CONF_BLOCK]) + cg.add(var.set_block_sensor(sens)) + + if CONF_FRAGMENTATION in config: + sens = await sensor.new_sensor(config[CONF_FRAGMENTATION]) + cg.add(var.set_fragmentation_sensor(sens)) + + if CONF_LOOP_TIME in config: + sens = await sensor.new_sensor(config[CONF_LOOP_TIME]) + cg.add(var.set_loop_time_sensor(sens)) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index f3d0bded13..41bf5f50c7 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -1,21 +1,23 @@ #include "debug_component.h" + +#include #include "esphome/core/log.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" -#include "esphome/core/defines.h" #include "esphome/core/version.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 + #include #include -#endif -#ifdef USE_ESP32 #if ESP_IDF_VERSION_MAJOR >= 4 #include #else #include #endif -#endif + +#endif // USE_ESP32 #ifdef USE_ARDUINO #include @@ -26,19 +28,36 @@ namespace debug { static const char *const TAG = "debug"; +static uint32_t get_free_heap() { +#if defined(USE_ESP8266) + return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) +#elif defined(USE_ESP32) + return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); +#endif +} + void DebugComponent::dump_config() { + std::string device_info; + device_info.reserve(256); + #ifndef ESPHOME_LOG_HAS_DEBUG ESP_LOGE(TAG, "Debug Component requires debug log level!"); this->status_set_error(); return; #endif - ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); -#ifdef USE_ARDUINO - this->free_heap_ = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) -#elif defined(USE_ESP_IDF) - this->free_heap_ = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); + ESP_LOGCONFIG(TAG, "Debug component:"); + LOG_TEXT_SENSOR(" ", "Device info", this->device_info_); + LOG_SENSOR(" ", "Free space on heap", this->free_sensor_); + LOG_SENSOR(" ", "Largest free heap block", this->block_sensor_); +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) + LOG_SENSOR(" ", "Heap fragmentation", this->fragmentation_sensor_); #endif + + ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); + device_info += ESPHOME_VERSION; + + this->free_heap_ = get_free_heap(); ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_); #ifdef USE_ARDUINO @@ -67,9 +86,12 @@ void DebugComponent::dump_config() { default: flash_mode = "UNKNOWN"; } - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", ESP.getFlashChipSize() / 1024, - ESP.getFlashChipSpeed() / 1000000, flash_mode); + ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", + ESP.getFlashChipSize() / 1024, // NOLINT + ESP.getFlashChipSpeed() / 1000000, flash_mode); // NOLINT + device_info += "|Flash: " + to_string(ESP.getFlashChipSize() / 1024) + // NOLINT + "kB Speed:" + to_string(ESP.getFlashChipSpeed() / 1000000) + "MHz Mode:"; // NOLINT + device_info += flash_mode; #endif // USE_ARDUINO #ifdef USE_ESP32 @@ -104,10 +126,21 @@ void DebugComponent::dump_config() { 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); + device_info += "|Chip: "; + device_info += model; + device_info += " Features:"; + device_info += features; + device_info += " Cores:" + to_string(info.cores); + device_info += " Revision:" + to_string(info.revision); ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version()); + device_info += "|ESP-IDF: "; + device_info += esp_get_idf_version(); - ESP_LOGD(TAG, "EFuse MAC: %s", get_mac_address_pretty().c_str()); + std::string mac = get_mac_address_pretty(); + ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str()); + device_info += "|EFuse MAC: "; + device_info += mac; const char *reset_reason; switch (rtc_get_reset_reason(0)) { @@ -160,6 +193,8 @@ void DebugComponent::dump_config() { reset_reason = "Unknown Reset Reason"; } ESP_LOGD(TAG, "Reset Reason: %s", reset_reason); + device_info += "|Reset: "; + device_info += reset_reason; const char *wakeup_reason; switch (rtc_get_wakeup_cause()) { @@ -203,6 +238,8 @@ void DebugComponent::dump_config() { wakeup_reason = "Unknown"; } ESP_LOGD(TAG, "Wakeup Reason: %s", wakeup_reason); + device_info += "|Wakeup: "; + device_info += wakeup_reason; #endif #if defined(USE_ESP8266) && !defined(CLANG_TIDY) @@ -214,20 +251,75 @@ void DebugComponent::dump_config() { ESP_LOGD(TAG, "Flash Chip ID=0x%08X", ESP.getFlashChipId()); ESP_LOGD(TAG, "Reset Reason: %s", ESP.getResetReason().c_str()); ESP_LOGD(TAG, "Reset Info: %s", ESP.getResetInfo().c_str()); + + device_info += "|Chip: 0x" + format_hex(ESP.getChipId()); + device_info += "|SDK: "; + device_info += ESP.getSdkVersion(); + device_info += "|Core: "; + device_info += ESP.getCoreVersion().c_str(); + device_info += "|Boot: "; + device_info += to_string(ESP.getBootVersion()); + device_info += "|Mode: " + to_string(ESP.getBootMode()); + device_info += "|CPU: " + to_string(ESP.getCpuFreqMHz()); + device_info += "|Flash: 0x" + format_hex(ESP.getFlashChipId()); + device_info += "|Reset: "; + device_info += ESP.getResetReason().c_str(); + device_info += "|"; + device_info += ESP.getResetInfo().c_str(); #endif + + if (this->device_info_ != nullptr) { + if (device_info.length() > 255) + device_info.resize(255); + this->device_info_->publish_state(device_info); + } } + void DebugComponent::loop() { -#ifdef USE_ARDUINO - uint32_t new_free_heap = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) -#elif defined(USE_ESP_IDF) - uint32_t new_free_heap = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); -#endif + // log when free heap space has halved + uint32_t new_free_heap = get_free_heap(); if (new_free_heap < this->free_heap_ / 2) { this->free_heap_ = new_free_heap; ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_); this->status_momentary_warning("heap", 1000); } + + // calculate loop time - from last call to this one + if (this->loop_time_sensor_ != nullptr) { + uint32_t now = millis(); + uint32_t loop_time = now - this->last_loop_timetag_; + this->max_loop_time_ = std::max(this->max_loop_time_, loop_time); + this->last_loop_timetag_ = now; + } } + +void DebugComponent::update() { + if (this->free_sensor_ != nullptr) { + this->free_sensor_->publish_state(get_free_heap()); + } + + if (this->block_sensor_ != nullptr) { +#if defined(USE_ESP8266) + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + this->block_sensor_->publish_state(ESP.getMaxFreeBlockSize()); +#elif defined(USE_ESP32) + this->block_sensor_->publish_state(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL)); +#endif + } + +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) + if (this->fragmentation_sensor_ != nullptr) { + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + this->fragmentation_sensor_->publish_state(ESP.getHeapFragmentation()); + } +#endif + + if (this->loop_time_sensor_ != nullptr) { + this->loop_time_sensor_->publish_state(this->max_loop_time_); + this->max_loop_time_ = 0; + } +} + float DebugComponent::get_setup_priority() const { return setup_priority::LATE; } } // namespace debug diff --git a/esphome/components/debug/debug_component.h b/esphome/components/debug/debug_component.h index e3d3d8f810..a362c52617 100644 --- a/esphome/components/debug/debug_component.h +++ b/esphome/components/debug/debug_component.h @@ -1,18 +1,42 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/macros.h" +#include "esphome/core/helpers.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/components/sensor/sensor.h" namespace esphome { namespace debug { -class DebugComponent : public Component { +class DebugComponent : public PollingComponent { public: void loop() override; + void update() override; float get_setup_priority() const override; void dump_config() override; + void set_device_info_sensor(text_sensor::TextSensor *device_info) { device_info_ = device_info; } + void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; } + void set_block_sensor(sensor::Sensor *block_sensor) { block_sensor_ = block_sensor; } +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) + void set_fragmentation_sensor(sensor::Sensor *fragmentation_sensor) { fragmentation_sensor_ = fragmentation_sensor; } +#endif + void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; } + protected: uint32_t free_heap_{}; + + uint32_t last_loop_timetag_{0}; + uint32_t max_loop_time_{0}; + + text_sensor::TextSensor *device_info_{nullptr}; + sensor::Sensor *free_sensor_{nullptr}; + sensor::Sensor *block_sensor_{nullptr}; +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) + sensor::Sensor *fragmentation_sensor_{nullptr}; +#endif + sensor::Sensor *loop_time_sensor_{nullptr}; }; } // namespace debug diff --git a/esphome/const.py b/esphome/const.py index a008c5ab7f..136d88173c 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -62,6 +62,7 @@ CONF_BINARY_SENSORS = "binary_sensors" CONF_BINDKEY = "bindkey" CONF_BIRTH_MESSAGE = "birth_message" CONF_BIT_DEPTH = "bit_depth" +CONF_BLOCK = "block" CONF_BLUE = "blue" CONF_BOARD = "board" CONF_BOARD_FLASH_MODE = "board_flash_mode" @@ -239,7 +240,9 @@ CONF_FORCE_UPDATE = "force_update" CONF_FORMALDEHYDE = "formaldehyde" CONF_FORMAT = "format" CONF_FORWARD_ACTIVE_ENERGY = "forward_active_energy" +CONF_FRAGMENTATION = "fragmentation" CONF_FRAMEWORK = "framework" +CONF_FREE = "free" CONF_FREQUENCY = "frequency" CONF_FROM = "from" CONF_FULL_SPECTRUM = "full_spectrum" @@ -335,6 +338,7 @@ CONF_LOG_TOPIC = "log_topic" CONF_LOGGER = "logger" CONF_LOGS = "logs" CONF_LONGITUDE = "longitude" +CONF_LOOP_TIME = "loop_time" CONF_LOW = "low" CONF_LOW_VOLTAGE_REFERENCE = "low_voltage_reference" CONF_MAC_ADDRESS = "mac_address" @@ -806,6 +810,7 @@ ICON_WIFI = "mdi:wifi" UNIT_AMPERE = "A" UNIT_BECQUEREL_PER_CUBIC_METER = "Bq/m³" +UNIT_BYTES = "B" UNIT_CELSIUS = "°C" UNIT_COUNT_DECILITRE = "/dL" UNIT_COUNTS_PER_CUBIC_METER = "#/m³" @@ -834,6 +839,7 @@ UNIT_MICROMETER = "µm" UNIT_MICROSIEMENS_PER_CENTIMETER = "µS/cm" UNIT_MICROTESLA = "µT" UNIT_MILLIGRAMS_PER_CUBIC_METER = "mg/m³" +UNIT_MILLISECOND = "ms" UNIT_MINUTE = "min" UNIT_OHM = "Ω" UNIT_PARTS_PER_BILLION = "ppb" From ef832becf1b9490499fbd9132464b0e53650a841 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 26 Jan 2022 16:26:46 +1300 Subject: [PATCH 052/238] Create base touchscreen component and refactor ektf2232 (#3083) Co-authored-by: Oxan van Leeuwen --- CODEOWNERS | 1 + esphome/components/display/display_buffer.h | 8 +- esphome/components/ektf2232/__init__.py | 80 ------------------- esphome/components/ektf2232/ektf2232.cpp | 5 +- esphome/components/ektf2232/ektf2232.h | 37 +-------- esphome/components/ektf2232/touchscreen.py | 48 +++++++++++ esphome/components/touchscreen/__init__.py | 39 +++++++++ .../binary_sensor/__init__.py | 14 ++-- .../touchscreen_binary_sensor.cpp} | 10 +-- .../touchscreen_binary_sensor.h} | 8 +- .../components/touchscreen/touchscreen.cpp | 18 +++++ esphome/components/touchscreen/touchscreen.h | 58 ++++++++++++++ esphome/const.py | 1 + script/ci-custom.py | 11 ++- tests/test4.yaml | 36 +++++++-- tests/test5.yaml | 9 --- 16 files changed, 224 insertions(+), 159 deletions(-) create mode 100644 esphome/components/ektf2232/touchscreen.py create mode 100644 esphome/components/touchscreen/__init__.py rename esphome/components/{ektf2232 => touchscreen}/binary_sensor/__init__.py (74%) rename esphome/components/{ektf2232/binary_sensor/ektf2232_binary_sensor.cpp => touchscreen/binary_sensor/touchscreen_binary_sensor.cpp} (52%) rename esphome/components/{ektf2232/binary_sensor/ektf2232_binary_sensor.h => touchscreen/binary_sensor/touchscreen_binary_sensor.h} (72%) create mode 100644 esphome/components/touchscreen/touchscreen.cpp create mode 100644 esphome/components/touchscreen/touchscreen.h diff --git a/CODEOWNERS b/CODEOWNERS index 56899be01a..efc00db94d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -188,6 +188,7 @@ esphome/components/tmp102/* @timsavage esphome/components/tmp117/* @Azimath esphome/components/tof10120/* @wstrzalka esphome/components/toshiba/* @kbx81 +esphome/components/touchscreen/* @jesserockz esphome/components/tsl2591/* @wjcarpenter esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/climate/* @jesserockz diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index b275b43b0e..096aaace4a 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -341,15 +341,15 @@ class DisplayBuffer { // Internal method to set display auto clearing. void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; } + virtual int get_height_internal() = 0; + virtual int get_width_internal() = 0; + DisplayRotation get_rotation() const { return this->rotation_; } + protected: void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg); virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0; - virtual int get_height_internal() = 0; - - virtual int get_width_internal() = 0; - void init_internal_(uint32_t buffer_length); void do_update_(); diff --git a/esphome/components/ektf2232/__init__.py b/esphome/components/ektf2232/__init__.py index 0427bda4fb..e69de29bb2 100644 --- a/esphome/components/ektf2232/__init__.py +++ b/esphome/components/ektf2232/__init__.py @@ -1,80 +0,0 @@ -import esphome.codegen as cg -import esphome.config_validation as cv - -from esphome import pins, automation -from esphome.components import i2c -from esphome.const import CONF_HEIGHT, CONF_ID, CONF_ROTATION, CONF_WIDTH - -CODEOWNERS = ["@jesserockz"] -DEPENDENCIES = ["i2c"] - -ektf2232_ns = cg.esphome_ns.namespace("ektf2232") -EKTF2232Touchscreen = ektf2232_ns.class_( - "EKTF2232Touchscreen", cg.Component, i2c.I2CDevice -) -TouchPoint = ektf2232_ns.struct("TouchPoint") -TouchListener = ektf2232_ns.class_("TouchListener") - -EKTF2232Rotation = ektf2232_ns.enum("EKTF2232Rotation") - -CONF_EKTF2232_ID = "ektf2232_id" -CONF_INTERRUPT_PIN = "interrupt_pin" -CONF_RTS_PIN = "rts_pin" -CONF_ON_TOUCH = "on_touch" - -ROTATIONS = { - 0: EKTF2232Rotation.ROTATE_0_DEGREES, - 90: EKTF2232Rotation.ROTATE_90_DEGREES, - 180: EKTF2232Rotation.ROTATE_180_DEGREES, - 270: EKTF2232Rotation.ROTATE_270_DEGREES, -} - - -def validate_rotation(value): - value = cv.string(value) - if value.endswith("°"): - value = value[:-1] - return cv.enum(ROTATIONS, int=True)(value) - - -CONFIG_SCHEMA = ( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(EKTF2232Touchscreen), - cv.Required(CONF_INTERRUPT_PIN): cv.All( - pins.internal_gpio_input_pin_schema - ), - cv.Required(CONF_RTS_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_HEIGHT, default=758): cv.int_, - cv.Optional(CONF_WIDTH, default=1024): cv.int_, - cv.Optional(CONF_ROTATION, default=0): validate_rotation, - cv.Optional(CONF_ON_TOUCH): automation.validate_automation(single=True), - } - ) - .extend(i2c.i2c_device_schema(0x15)) - .extend(cv.COMPONENT_SCHEMA) -) - - -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) - - interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) - cg.add(var.set_interrupt_pin(interrupt_pin)) - rts_pin = await cg.gpio_pin_expression(config[CONF_RTS_PIN]) - cg.add(var.set_rts_pin(rts_pin)) - - cg.add( - var.set_display_details( - config[CONF_WIDTH], - config[CONF_HEIGHT], - config[CONF_ROTATION], - ) - ) - - if CONF_ON_TOUCH in config: - await automation.build_automation( - var.get_touch_trigger(), [(TouchPoint, "touch")], config[CONF_ON_TOUCH] - ) diff --git a/esphome/components/ektf2232/ektf2232.cpp b/esphome/components/ektf2232/ektf2232.cpp index f96928a8de..8df25fce24 100644 --- a/esphome/components/ektf2232/ektf2232.cpp +++ b/esphome/components/ektf2232/ektf2232.cpp @@ -111,10 +111,7 @@ void EKTF2232Touchscreen::loop() { break; } - ESP_LOGV(TAG, "Touch %d: (x=%d, y=%d)", i, tp.x, tp.y); - this->touch_trigger_->trigger(tp); - for (auto *listener : this->touch_listeners_) - listener->touch(tp); + this->defer([this, tp]() { this->send_touch_(tp); }); } } diff --git a/esphome/components/ektf2232/ektf2232.h b/esphome/components/ektf2232/ektf2232.h index 0d6fb7a699..e880b77f99 100644 --- a/esphome/components/ektf2232/ektf2232.h +++ b/esphome/components/ektf2232/ektf2232.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/components/i2c/i2c.h" +#include "esphome/components/touchscreen/touchscreen.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" @@ -15,25 +16,9 @@ struct EKTF2232TouchscreenStore { static void gpio_intr(EKTF2232TouchscreenStore *store); }; -struct TouchPoint { - uint16_t x; - uint16_t y; -}; +using namespace touchscreen; -class TouchListener { - public: - virtual void touch(TouchPoint tp) = 0; - virtual void release(); -}; - -enum EKTF2232Rotation : uint8_t { - ROTATE_0_DEGREES = 0, - ROTATE_90_DEGREES, - ROTATE_180_DEGREES, - ROTATE_270_DEGREES, -}; - -class EKTF2232Touchscreen : public Component, public i2c::I2CDevice { +class EKTF2232Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice { public: void setup() override; void loop() override; @@ -42,19 +27,9 @@ class EKTF2232Touchscreen : public Component, public i2c::I2CDevice { void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } void set_rts_pin(GPIOPin *pin) { this->rts_pin_ = pin; } - void set_display_details(uint16_t width, uint16_t height, EKTF2232Rotation rotation) { - this->display_width_ = width; - this->display_height_ = height; - this->rotation_ = rotation; - } - void set_power_state(bool enable); bool get_power_state(); - Trigger *get_touch_trigger() const { return this->touch_trigger_; } - - void register_listener(TouchListener *listener) { this->touch_listeners_.push_back(listener); } - protected: void hard_reset_(); bool soft_reset_(); @@ -64,12 +39,6 @@ class EKTF2232Touchscreen : public Component, public i2c::I2CDevice { EKTF2232TouchscreenStore store_; uint16_t x_resolution_; uint16_t y_resolution_; - - uint16_t display_width_; - uint16_t display_height_; - EKTF2232Rotation rotation_; - Trigger *touch_trigger_ = new Trigger(); - std::vector touch_listeners_; }; } // namespace ektf2232 diff --git a/esphome/components/ektf2232/touchscreen.py b/esphome/components/ektf2232/touchscreen.py new file mode 100644 index 0000000000..b3513b2670 --- /dev/null +++ b/esphome/components/ektf2232/touchscreen.py @@ -0,0 +1,48 @@ +import esphome.codegen as cg +import esphome.config_validation as cv + +from esphome import pins +from esphome.components import i2c, touchscreen +from esphome.const import CONF_ID + +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["i2c"] + +ektf2232_ns = cg.esphome_ns.namespace("ektf2232") +EKTF2232Touchscreen = ektf2232_ns.class_( + "EKTF2232Touchscreen", + touchscreen.Touchscreen, + cg.Component, + i2c.I2CDevice, +) + +CONF_EKTF2232_ID = "ektf2232_id" +CONF_INTERRUPT_PIN = "interrupt_pin" +CONF_RTS_PIN = "rts_pin" + + +CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(EKTF2232Touchscreen), + cv.Required(CONF_INTERRUPT_PIN): cv.All( + pins.internal_gpio_input_pin_schema + ), + cv.Required(CONF_RTS_PIN): pins.gpio_output_pin_schema, + } + ) + .extend(i2c.i2c_device_schema(0x15)) + .extend(cv.COMPONENT_SCHEMA) +) + + +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 touchscreen.register_touchscreen(var, config) + + interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) + cg.add(var.set_interrupt_pin(interrupt_pin)) + rts_pin = await cg.gpio_pin_expression(config[CONF_RTS_PIN]) + cg.add(var.set_rts_pin(rts_pin)) diff --git a/esphome/components/touchscreen/__init__.py b/esphome/components/touchscreen/__init__.py new file mode 100644 index 0000000000..8246b95187 --- /dev/null +++ b/esphome/components/touchscreen/__init__.py @@ -0,0 +1,39 @@ +import esphome.config_validation as cv +import esphome.codegen as cg + +from esphome.components import display +from esphome import automation +from esphome.const import CONF_ON_TOUCH + +CODEOWNERS = ["@jesserockz"] +IS_PLATFORM_COMPONENT = True + +touchscreen_ns = cg.esphome_ns.namespace("touchscreen") + +Touchscreen = touchscreen_ns.class_("Touchscreen") +TouchRotation = touchscreen_ns.enum("TouchRotation") +TouchPoint = touchscreen_ns.struct("TouchPoint") +TouchListener = touchscreen_ns.class_("TouchListener") + +CONF_DISPLAY = "display" +CONF_TOUCHSCREEN_ID = "touchscreen_id" + + +TOUCHSCREEN_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_DISPLAY): cv.use_id(display.DisplayBuffer), + cv.Optional(CONF_ON_TOUCH): automation.validate_automation(single=True), + } +) + + +async def register_touchscreen(var, config): + disp = await cg.get_variable(config[CONF_DISPLAY]) + cg.add(var.set_display(disp)) + + if CONF_ON_TOUCH in config: + await automation.build_automation( + var.get_touch_trigger(), + [(TouchPoint, "touch")], + config[CONF_ON_TOUCH], + ) diff --git a/esphome/components/ektf2232/binary_sensor/__init__.py b/esphome/components/touchscreen/binary_sensor/__init__.py similarity index 74% rename from esphome/components/ektf2232/binary_sensor/__init__.py rename to esphome/components/touchscreen/binary_sensor/__init__.py index 349c45b31c..cbd03c0a32 100644 --- a/esphome/components/ektf2232/binary_sensor/__init__.py +++ b/esphome/components/touchscreen/binary_sensor/__init__.py @@ -4,12 +4,12 @@ import esphome.config_validation as cv from esphome.components import binary_sensor from esphome.const import CONF_ID -from .. import ektf2232_ns, CONF_EKTF2232_ID, EKTF2232Touchscreen, TouchListener +from .. import touchscreen_ns, CONF_TOUCHSCREEN_ID, Touchscreen, TouchListener -DEPENDENCIES = ["ektf2232"] +DEPENDENCIES = ["touchscreen"] -EKTF2232Button = ektf2232_ns.class_( - "EKTF2232Button", binary_sensor.BinarySensor, TouchListener +TouchscreenBinarySensor = touchscreen_ns.class_( + "TouchscreenBinarySensor", binary_sensor.BinarySensor, TouchListener ) CONF_X_MIN = "x_min" @@ -32,8 +32,8 @@ def validate_coords(config): CONFIG_SCHEMA = cv.All( binary_sensor.BINARY_SENSOR_SCHEMA.extend( { - cv.GenerateID(): cv.declare_id(EKTF2232Button), - cv.GenerateID(CONF_EKTF2232_ID): cv.use_id(EKTF2232Touchscreen), + cv.GenerateID(): cv.declare_id(TouchscreenBinarySensor), + cv.GenerateID(CONF_TOUCHSCREEN_ID): cv.use_id(Touchscreen), cv.Required(CONF_X_MIN): cv.int_range(min=0, max=2000), cv.Required(CONF_X_MAX): cv.int_range(min=0, max=2000), cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=2000), @@ -47,7 +47,7 @@ CONFIG_SCHEMA = cv.All( 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_EKTF2232_ID]) + hub = await cg.get_variable(config[CONF_TOUCHSCREEN_ID]) cg.add( var.set_area( config[CONF_X_MIN], diff --git a/esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.cpp b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp similarity index 52% rename from esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.cpp rename to esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp index a6fdf8b76c..ba12aeeae0 100644 --- a/esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.cpp +++ b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp @@ -1,9 +1,9 @@ -#include "ektf2232_binary_sensor.h" +#include "touchscreen_binary_sensor.h" namespace esphome { -namespace ektf2232 { +namespace touchscreen { -void EKTF2232Button::touch(TouchPoint tp) { +void TouchscreenBinarySensor::touch(TouchPoint tp) { bool touched = (tp.x >= this->x_min_ && tp.x <= this->x_max_ && tp.y >= this->y_min_ && tp.y <= this->y_max_); if (touched) { @@ -13,7 +13,7 @@ void EKTF2232Button::touch(TouchPoint tp) { } } -void EKTF2232Button::release() { this->publish_state(false); } +void TouchscreenBinarySensor::release() { this->publish_state(false); } -} // namespace ektf2232 +} // namespace touchscreen } // namespace esphome diff --git a/esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.h b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h similarity index 72% rename from esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.h rename to esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h index 170dfcdebb..8fb7749766 100644 --- a/esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.h +++ b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h @@ -1,12 +1,12 @@ #pragma once -#include "../ektf2232.h" +#include "esphome/components/touchscreen/touchscreen.h" #include "esphome/components/binary_sensor/binary_sensor.h" namespace esphome { -namespace ektf2232 { +namespace touchscreen { -class EKTF2232Button : public binary_sensor::BinarySensor, public TouchListener { +class TouchscreenBinarySensor : public binary_sensor::BinarySensor, public TouchListener { public: /// Set the touch screen area where the button will detect the touch. void set_area(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) { @@ -23,5 +23,5 @@ class EKTF2232Button : public binary_sensor::BinarySensor, public TouchListener int16_t x_min_, x_max_, y_min_, y_max_; }; -} // namespace ektf2232 +} // namespace touchscreen } // namespace esphome diff --git a/esphome/components/touchscreen/touchscreen.cpp b/esphome/components/touchscreen/touchscreen.cpp new file mode 100644 index 0000000000..9b337fc02c --- /dev/null +++ b/esphome/components/touchscreen/touchscreen.cpp @@ -0,0 +1,18 @@ +#include "touchscreen.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace touchscreen { + +static const char *const TAG = "touchscreen"; + +void Touchscreen::send_touch_(TouchPoint tp) { + ESP_LOGV(TAG, "Touch (x=%d, y=%d)", tp.x, tp.y); + this->touch_trigger_.trigger(tp); + for (auto *listener : this->touch_listeners_) + listener->touch(tp); +} + +} // namespace touchscreen +} // namespace esphome diff --git a/esphome/components/touchscreen/touchscreen.h b/esphome/components/touchscreen/touchscreen.h new file mode 100644 index 0000000000..2c0ec9e268 --- /dev/null +++ b/esphome/components/touchscreen/touchscreen.h @@ -0,0 +1,58 @@ +#pragma once + +#include "esphome/components/display/display_buffer.h" +#include "esphome/core/automation.h" +#include "esphome/core/hal.h" + +#include + +namespace esphome { +namespace touchscreen { + +struct TouchPoint { + uint16_t x; + uint16_t y; + uint8_t id; + uint8_t state; +}; + +class TouchListener { + public: + virtual void touch(TouchPoint tp) = 0; + virtual void release() {} +}; + +enum TouchRotation { + ROTATE_0_DEGREES = 0, + ROTATE_90_DEGREES = 90, + ROTATE_180_DEGREES = 180, + ROTATE_270_DEGREES = 270, +}; + +class Touchscreen { + public: + void set_display(display::DisplayBuffer *display) { + this->display_ = display; + this->display_width_ = display->get_width_internal(); + this->display_height_ = display->get_height_internal(); + this->rotation_ = static_cast(display->get_rotation()); + } + + Trigger *get_touch_trigger() { return &this->touch_trigger_; } + + void register_listener(TouchListener *listener) { this->touch_listeners_.push_back(listener); } + + protected: + /// Call this function to send touch points to the `on_touch` listener and the binary_sensors. + void send_touch_(TouchPoint tp); + + uint16_t display_width_; + uint16_t display_height_; + display::DisplayBuffer *display_; + TouchRotation rotation_; + Trigger touch_trigger_; + std::vector touch_listeners_; +}; + +} // namespace touchscreen +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 136d88173c..2d2f5f1da0 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -439,6 +439,7 @@ CONF_ON_TAG = "on_tag" CONF_ON_TAG_REMOVED = "on_tag_removed" CONF_ON_TIME = "on_time" CONF_ON_TIME_SYNC = "on_time_sync" +CONF_ON_TOUCH = "on_touch" CONF_ON_TURN_OFF = "on_turn_off" CONF_ON_TURN_ON = "on_turn_on" CONF_ON_VALUE = "on_value" diff --git a/script/ci-custom.py b/script/ci-custom.py index 2beba3483d..956716e5fa 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.split('\n')): + for i, line in enumerate(a_str.split("\n")): column = 0 while True: column = line.find(sub, column) @@ -592,6 +592,8 @@ def lint_inclusive_language(fname, match): include=["*.h", "*.tcc"], exclude=[ "esphome/components/binary_sensor/binary_sensor.h", + "esphome/components/button/button.h", + "esphome/components/climate/climate.h", "esphome/components/cover/cover.h", "esphome/components/display/display_buffer.h", "esphome/components/fan/fan.h", @@ -606,8 +608,6 @@ def lint_inclusive_language(fname, match): "esphome/components/stepper/stepper.h", "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", @@ -666,7 +666,10 @@ run_checks(LINT_POST_CHECKS, "POST") for f, errs in sorted(errors.items()): 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) + 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: diff --git a/tests/test4.yaml b/tests/test4.yaml index eec1c2eb5e..de641d92ff 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -313,7 +313,7 @@ binary_sensor: then: - lambda: 'ESP_LOGI("ar1:", "%d", x);' - platform: xpt2046 - xpt2046_id: touchscreen + xpt2046_id: xpt_touchscreen id: touch_key0 x_min: 80 x_max: 160 @@ -327,6 +327,16 @@ binary_sensor: sx1509: sx1509_hub number: 3 + - platform: touchscreen + id: touch_key1 + x_min: 0 + x_max: 100 + y_min: 0 + y_max: 100 + on_press: + - logger.log: "Touched" + + climate: - platform: tuya id: tuya_climate @@ -509,7 +519,7 @@ external_components: - source: ../esphome/components components: ["sntp"] xpt2046: - id: touchscreen + id: xpt_touchscreen cs_pin: 17 irq_pin: 16 update_interval: 50ms @@ -526,12 +536,12 @@ xpt2046: - lambda: |- ESP_LOGI("main", "args x=%d, y=%d, touched=%s", x, y, (touched ? "touch" : "release")); ESP_LOGI("main", "member x=%d, y=%d, touched=%d, x_raw=%d, y_raw=%d, z_raw=%d", - id(touchscreen).x, - id(touchscreen).y, - (int) id(touchscreen).touched, - id(touchscreen).x_raw, - id(touchscreen).y_raw, - id(touchscreen).z_raw + id(xpt_touchscreen).x, + id(xpt_touchscreen).y, + (int) id(xpt_touchscreen).touched, + id(xpt_touchscreen).x_raw, + id(xpt_touchscreen).y_raw, + id(xpt_touchscreen).z_raw ); button: @@ -541,3 +551,13 @@ button: name: Safe Mode Button - platform: shutdown name: Shutdown Button + +touchscreen: + - platform: ektf2232 + interrupt_pin: GPIO36 + rts_pin: GPIO5 + display: inkplate_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: ["touch.x", "touch.y"] diff --git a/tests/test5.yaml b/tests/test5.yaml index 55786050b3..9bfd395538 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -231,12 +231,3 @@ switch: register_type: coil address: 2 bitmask: 1 - -ektf2232: - interrupt_pin: GPIO36 - rts_pin: GPIO5 - rotation: 90 - on_touch: - - logger.log: - format: Touch at (%d, %d) - args: ["touch.x", "touch.y"] From a718ac7ee0f7345e8bd94b1eeaa8c55770d1abfb Mon Sep 17 00:00:00 2001 From: Wouter van der Wal <33957974+wjtje@users.noreply.github.com> Date: Wed, 26 Jan 2022 10:20:45 +0100 Subject: [PATCH 053/238] Add qr code support for displays (#2952) Co-authored-by: Oxan van Leeuwen --- CODEOWNERS | 1 + esphome/components/display/display_buffer.cpp | 6 ++ esphome/components/display/display_buffer.h | 15 +++++ esphome/components/qr_code/__init__.py | 41 ++++++++++++++ esphome/components/qr_code/qr_code.cpp | 55 +++++++++++++++++++ esphome/components/qr_code/qr_code.h | 34 ++++++++++++ esphome/core/defines.h | 1 + platformio.ini | 9 +-- tests/test1.yaml | 5 ++ tests/test3.yaml | 4 ++ 10 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 esphome/components/qr_code/__init__.py create mode 100644 esphome/components/qr_code/qr_code.cpp create mode 100644 esphome/components/qr_code/qr_code.h diff --git a/CODEOWNERS b/CODEOWNERS index efc00db94d..6a46732dc1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -137,6 +137,7 @@ esphome/components/preferences/* @esphome/core esphome/components/psram/* @esphome/core esphome/components/pulse_meter/* @stevebaxter esphome/components/pvvx_mithermometer/* @pasiz +esphome/components/qr_code/* @wjtje esphome/components/rc522/* @glmnet esphome/components/rc522_i2c/* @glmnet esphome/components/rc522_spi/* @glmnet diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index ac878b01e1..4ad353a254 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -252,6 +252,12 @@ void DisplayBuffer::legend(int x, int y, graph::Graph *graph, Color color_on) { } #endif // USE_GRAPH +#ifdef USE_QR_CODE +void DisplayBuffer::qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on, int scale) { + qr_code->draw(this, x, y, color_on, scale); +} +#endif // USE_QR_CODE + void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1, int *width, int *height) { int x_offset, baseline; diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index 096aaace4a..8ee1cd8779 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -14,6 +14,10 @@ #include "esphome/components/graph/graph.h" #endif +#ifdef USE_QR_CODE +#include "esphome/components/qr_code/qr_code.h" +#endif + namespace esphome { namespace display { @@ -307,6 +311,17 @@ class DisplayBuffer { void legend(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON); #endif // USE_GRAPH +#ifdef USE_QR_CODE + /** Draw the `qr_code` with the top-left corner at [x,y] to the screen. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param qr_code The qr_code to draw + * @param color_on The color to replace in binary images for the on bits. + */ + void qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on = COLOR_ON, int scale = 1); +#endif + /** Get the text bounds of the given string. * * @param x The x coordinate to place the string at, can be 0 if only interested in dimensions. diff --git a/esphome/components/qr_code/__init__.py b/esphome/components/qr_code/__init__.py new file mode 100644 index 0000000000..855db86335 --- /dev/null +++ b/esphome/components/qr_code/__init__.py @@ -0,0 +1,41 @@ +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_ID, CONF_VALUE + +CONF_SCALE = "scale" +CONF_ECC = "ecc" + +CODEOWNERS = ["@wjtje"] + +DEPENDENCIES = ["display"] +MULTI_CONF = True + +qr_code_ns = cg.esphome_ns.namespace("qr_code") +QRCode = qr_code_ns.class_("QrCode", cg.Component) + +qrcodegen_Ecc = cg.esphome_ns.enum("qrcodegen_Ecc") +ECC = { + "LOW": qrcodegen_Ecc.qrcodegen_Ecc_LOW, + "MEDIUM": qrcodegen_Ecc.qrcodegen_Ecc_MEDIUM, + "QUARTILE": qrcodegen_Ecc.qrcodegen_Ecc_QUARTILE, + "HIGH": qrcodegen_Ecc.qrcodegen_Ecc_HIGH, +} + +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(QRCode), + cv.Required(CONF_VALUE): cv.string, + cv.Optional(CONF_ECC, default="LOW"): cv.enum(ECC, upper=True), + } +) + + +async def to_code(config): + cg.add_library("wjtje/qr-code-generator-library", "^1.7.0") + + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_value(config[CONF_VALUE])) + cg.add(var.set_ecc(ECC[config[CONF_ECC]])) + await cg.register_component(var, config) + + cg.add_define("USE_QR_CODE") diff --git a/esphome/components/qr_code/qr_code.cpp b/esphome/components/qr_code/qr_code.cpp new file mode 100644 index 0000000000..a2efbdb804 --- /dev/null +++ b/esphome/components/qr_code/qr_code.cpp @@ -0,0 +1,55 @@ +#include "qr_code.h" +#include "esphome/components/display/display_buffer.h" +#include "esphome/core/color.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace qr_code { + +static const char *const TAG = "qr_code"; + +void QrCode::dump_config() { + ESP_LOGCONFIG(TAG, "QR code:"); + ESP_LOGCONFIG(TAG, " Value: '%s'", this->value_.c_str()); +} + +void QrCode::set_value(const std::string &value) { + this->value_ = value; + this->needs_update_ = true; +} + +void QrCode::set_ecc(qrcodegen_Ecc ecc) { + this->ecc_ = ecc; + this->needs_update_ = true; +} + +void QrCode::generate_qr_code() { + ESP_LOGV(TAG, "Generating QR code..."); + uint8_t tempbuffer[qrcodegen_BUFFER_LEN_MAX]; + + if (!qrcodegen_encodeText(this->value_.c_str(), tempbuffer, this->qr_, this->ecc_, qrcodegen_VERSION_MIN, + qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true)) { + ESP_LOGE(TAG, "Failed to generate QR code"); + } +} + +void QrCode::draw(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color, int scale) { + ESP_LOGV(TAG, "Drawing QR code at (%d, %d)", x_offset, y_offset); + + if (this->needs_update_) { + this->generate_qr_code(); + this->needs_update_ = false; + } + + uint8_t qrcode_width = qrcodegen_getSize(this->qr_); + + for (int y = 0; y < qrcode_width * scale; y++) { + for (int x = 0; x < qrcode_width * scale; x++) { + if (qrcodegen_getModule(this->qr_, x / scale, y / scale)) { + buff->draw_pixel_at(x_offset + x, y_offset + y, color); + } + } + } +} +} // namespace qr_code +} // namespace esphome diff --git a/esphome/components/qr_code/qr_code.h b/esphome/components/qr_code/qr_code.h new file mode 100644 index 0000000000..58f3a70321 --- /dev/null +++ b/esphome/components/qr_code/qr_code.h @@ -0,0 +1,34 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/core/color.h" + +#include + +#include "qrcodegen.h" + +namespace esphome { +// forward declare DisplayBuffer +namespace display { +class DisplayBuffer; +} // namespace display + +namespace qr_code { +class QrCode : public Component { + public: + void draw(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color, int scale); + + void dump_config() override; + + void set_value(const std::string &value); + void set_ecc(qrcodegen_Ecc ecc); + + void generate_qr_code(); + + protected: + std::string value_; + qrcodegen_Ecc ecc_; + bool needs_update_ = true; + uint8_t qr_[qrcodegen_BUFFER_LEN_MAX]; +}; +} // namespace qr_code +} // namespace esphome diff --git a/esphome/core/defines.h b/esphome/core/defines.h index ded98054b6..acdc5df815 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -32,6 +32,7 @@ #define USE_OTA_PASSWORD #define USE_OTA_STATE_CALLBACK #define USE_POWER_SUPPLY +#define USE_QR_CODE #define USE_SELECT #define USE_SENSOR #define USE_STATUS_LED diff --git a/platformio.ini b/platformio.ini index e2081a3ff6..e7bf848f1b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -33,10 +33,11 @@ build_flags = ; This are common settings for all environments. [common] lib_deps = - esphome/noise-c@0.1.4 ; api - makuna/NeoPixelBus@2.6.9 ; neopixelbus - esphome/Improv@1.1.0 ; improv_serial / esp32_improv - bblanchon/ArduinoJson@6.18.5 ; json + esphome/noise-c@0.1.4 ; api + makuna/NeoPixelBus@2.6.9 ; neopixelbus + esphome/Improv@1.1.0 ; improv_serial / esp32_improv + bblanchon/ArduinoJson@6.18.5 ; json + wjtje/qr-code-generator-library@1.7.0 ; qr_code build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = diff --git a/tests/test1.yaml b/tests/test1.yaml index 0ded069638..40cd0d4827 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2154,6 +2154,7 @@ display: pages: - id: page1 lambda: |- + it.qr_code(0, 0, id(homepage_qr)); it.rectangle(0, 0, it.get_width(), it.get_height()); - id: page2 lambda: |- @@ -2577,3 +2578,7 @@ select: - one - two optimistic: true + +qr_code: + - id: homepage_qr + value: https://esphome.io/index.html diff --git a/tests/test3.yaml b/tests/test3.yaml index 2b00a3612e..32a3b1be3d 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1351,6 +1351,10 @@ daly_bms: update_interval: 20s uart_id: uart1 +qr_code: + - id: homepage_qr + value: https://esphome.io/index.html + button: - platform: output id: output_button From 511c8de6f39e0afcda27682da89aa94d49339665 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 26 Jan 2022 10:41:57 +0100 Subject: [PATCH 054/238] ESP8266 Set recommended framework to 3.0.2 (#2606) --- 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 9b1f091432..015caf92e6 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -61,7 +61,7 @@ def _format_framework_arduino_version(ver: cv.Version) -> str: # The default/recommended arduino framework version # - https://github.com/esp8266/Arduino/releases # - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif8266 -RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 7, 4) +RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 0, 2) # 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 From c5974b8833c89b5b064567da32cb1dff5178156b Mon Sep 17 00:00:00 2001 From: Nicholas Peters Date: Wed, 26 Jan 2022 04:48:51 -0500 Subject: [PATCH 055/238] TSL2591 automatic gain control (#3071) --- esphome/components/tsl2591/sensor.py | 25 ++++----- esphome/components/tsl2591/tsl2591.cpp | 72 +++++++++++++++++++++++--- esphome/components/tsl2591/tsl2591.h | 23 +++++++- 3 files changed, 101 insertions(+), 19 deletions(-) diff --git a/esphome/components/tsl2591/sensor.py b/esphome/components/tsl2591/sensor.py index 095a8c886c..1ec37b5f93 100644 --- a/esphome/components/tsl2591/sensor.py +++ b/esphome/components/tsl2591/sensor.py @@ -56,18 +56,19 @@ INTEGRATION_TIMES = { 600: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_600MS, } -TSL2591Gain = tsl2591_ns.enum("TSL2591Gain") +TSL2591ComponentGain = tsl2591_ns.enum("TSL2591ComponentGain") GAINS = { - "1X": TSL2591Gain.TSL2591_GAIN_LOW, - "LOW": TSL2591Gain.TSL2591_GAIN_LOW, - "25X": TSL2591Gain.TSL2591_GAIN_MED, - "MED": TSL2591Gain.TSL2591_GAIN_MED, - "MEDIUM": TSL2591Gain.TSL2591_GAIN_MED, - "400X": TSL2591Gain.TSL2591_GAIN_HIGH, - "HIGH": TSL2591Gain.TSL2591_GAIN_HIGH, - "9500X": TSL2591Gain.TSL2591_GAIN_MAX, - "MAX": TSL2591Gain.TSL2591_GAIN_MAX, - "MAXIMUM": TSL2591Gain.TSL2591_GAIN_MAX, + "1X": TSL2591ComponentGain.TSL2591_CGAIN_LOW, + "LOW": TSL2591ComponentGain.TSL2591_CGAIN_LOW, + "25X": TSL2591ComponentGain.TSL2591_CGAIN_MED, + "MED": TSL2591ComponentGain.TSL2591_CGAIN_MED, + "MEDIUM": TSL2591ComponentGain.TSL2591_CGAIN_MED, + "400X": TSL2591ComponentGain.TSL2591_CGAIN_HIGH, + "HIGH": TSL2591ComponentGain.TSL2591_CGAIN_HIGH, + "9500X": TSL2591ComponentGain.TSL2591_CGAIN_MAX, + "MAX": TSL2591ComponentGain.TSL2591_CGAIN_MAX, + "MAXIMUM": TSL2591ComponentGain.TSL2591_CGAIN_MAX, + "AUTO": TSL2591ComponentGain.TSL2591_CGAIN_AUTO, } @@ -117,7 +118,7 @@ CONFIG_SCHEMA = ( CONF_INTEGRATION_TIME, default="100ms" ): validate_integration_time, cv.Optional(CONF_NAME, default="TLS2591"): cv.string, - cv.Optional(CONF_GAIN, default="MEDIUM"): cv.enum(GAINS, upper=True), + cv.Optional(CONF_GAIN, default="AUTO"): cv.enum(GAINS, upper=True), cv.Optional(CONF_POWER_SAVE_MODE, default=True): cv.boolean, cv.Optional(CONF_DEVICE_FACTOR, default=53.0): cv.float_with_unit( "device_factor", "", True diff --git a/esphome/components/tsl2591/tsl2591.cpp b/esphome/components/tsl2591/tsl2591.cpp index 7755437de2..53cd65bd88 100644 --- a/esphome/components/tsl2591/tsl2591.cpp +++ b/esphome/components/tsl2591/tsl2591.cpp @@ -43,6 +43,8 @@ void TSL2591Component::disable_if_power_saving_() { } void TSL2591Component::setup() { + if (this->component_gain_ == TSL2591_CGAIN_AUTO) + this->gain_ = TSL2591_GAIN_MED; uint8_t address = this->address_; ESP_LOGI(TAG, "Setting up TSL2591 sensor at I2C address 0x%02X", address); uint8_t id; @@ -73,26 +75,30 @@ void TSL2591Component::dump_config() { } ESP_LOGCONFIG(TAG, " Name: %s", this->name_); - TSL2591Gain raw_gain = this->gain_; + TSL2591ComponentGain raw_gain = this->component_gain_; int gain = 0; std::string gain_word = "unknown"; switch (raw_gain) { - case TSL2591_GAIN_LOW: + case TSL2591_CGAIN_LOW: gain = 1; gain_word = "low"; break; - case TSL2591_GAIN_MED: + case TSL2591_CGAIN_MED: gain = 25; gain_word = "medium"; break; - case TSL2591_GAIN_HIGH: + case TSL2591_CGAIN_HIGH: gain = 400; gain_word = "high"; break; - case TSL2591_GAIN_MAX: + case TSL2591_CGAIN_MAX: gain = 9500; gain_word = "maximum"; break; + case TSL2591_CGAIN_AUTO: + gain = -1; + gain_word = "auto"; + break; } ESP_LOGCONFIG(TAG, " Gain: %dx (%s)", gain, gain_word.c_str()); TSL2591IntegrationTime raw_timing = this->integration_time_; @@ -129,6 +135,9 @@ void TSL2591Component::process_update_() { if (this->calculated_lux_sensor_ != nullptr) { this->calculated_lux_sensor_->publish_state(lux); } + if (this->component_gain_ == TSL2591_CGAIN_AUTO) { + this->automatic_gain_update(full); + } this->status_clear_warning(); } @@ -183,7 +192,7 @@ void TSL2591Component::set_integration_time(TSL2591IntegrationTime integration_t this->integration_time_ = integration_time; } -void TSL2591Component::set_gain(TSL2591Gain gain) { this->gain_ = gain; } +void TSL2591Component::set_gain(TSL2591ComponentGain gain) { this->component_gain_ = gain; } void TSL2591Component::set_device_and_glass_attenuation_factors(float device_factor, float glass_attenuation_factor) { this->device_factor_ = device_factor; @@ -366,5 +375,56 @@ float TSL2591Component::get_calculated_lux(uint16_t full_spectrum, uint16_t infr return std::max(lux, 0.0F); } +/** Calculates and updates the sensor gain setting, trying to keep the full spectrum counts near + * the middle of the range + * + * It's hard to tell how far down to turn the gain when it's at the top of the scale, so decrease + * the gain by up to 2 steps if it's near the top to be sure we get a good reading next time. + * Increase gain by max 2 steps per reading. + * + * If integration time is 100 MS, divide the upper thresholds by 2 to account for ADC saturation + * + * @param full_spectrum The ADC reading for TSL2591 channel 0. + * + * 1/3 FS = 21,845 + */ +void TSL2591Component::automatic_gain_update(uint16_t full_spectrum) { + TSL2591Gain new_gain = this->gain_; + uint fs_divider = (this->integration_time_ == TSL2591_INTEGRATION_TIME_100MS) ? 2 : 1; + + switch (this->gain_) { + case TSL2591_GAIN_LOW: + if (full_spectrum < 54) // 1/3 FS / GAIN_HIGH + new_gain = TSL2591_GAIN_HIGH; + else if (full_spectrum < 875) // 1/3 FS / GAIN_MED + new_gain = TSL2591_GAIN_MED; + break; + case TSL2591_GAIN_MED: + if (full_spectrum < 57) // 1/3 FS / (GAIN_MAX/GAIN_MED) + new_gain = TSL2591_GAIN_MAX; + else if (full_spectrum < 1365) // 1/3 FS / (GAIN_HIGH/GAIN_MED) + new_gain = TSL2591_GAIN_HIGH; + else if (full_spectrum > 62000 / fs_divider) // 2/3 FS / (GAIN_LOW/GAIN_MED) clipped to 95% FS + new_gain = TSL2591_GAIN_LOW; + break; + case TSL2591_GAIN_HIGH: + if (full_spectrum < 920) // 1/3 FS / (GAIN_MAX/GAIN_HIGH) + new_gain = TSL2591_GAIN_MAX; + else if (full_spectrum > 62000 / fs_divider) // 2/3 FS / (GAIN_MED/GAIN_HIGH) clipped to 95% FS + new_gain = TSL2591_GAIN_LOW; + break; + case TSL2591_GAIN_MAX: + if (full_spectrum > 62000 / fs_divider) // 2/3 FS / (GAIN_MED/GAIN_HIGH) clipped to 95% FS + new_gain = TSL2591_GAIN_MED; + break; + } + + if (this->gain_ != new_gain) { + this->gain_ = new_gain; + this->set_integration_time_and_gain(this->integration_time_, this->gain_); + } + ESP_LOGD(TAG, "Gain setting: %d", this->gain_); +} + } // namespace tsl2591 } // namespace esphome diff --git a/esphome/components/tsl2591/tsl2591.h b/esphome/components/tsl2591/tsl2591.h index 19352a15c5..d82dbc395f 100644 --- a/esphome/components/tsl2591/tsl2591.h +++ b/esphome/components/tsl2591/tsl2591.h @@ -21,6 +21,19 @@ enum TSL2591IntegrationTime { TSL2591_INTEGRATION_TIME_600MS = 0b101, }; +/** Enum listing all gain settings for the TSL2591 component. + * + * Enum constants are used by the component to allow auto gain, not directly to registers + * Higher values are better for low light situations, but can increase noise. + */ +enum TSL2591ComponentGain { + TSL2591_CGAIN_LOW, // 1x + TSL2591_CGAIN_MED, // 25x + TSL2591_CGAIN_HIGH, // 400x + TSL2591_CGAIN_MAX, // 9500x + TSL2591_CGAIN_AUTO +}; + /** Enum listing all gain settings for the TSL2591. * * Specific values of the enum constants are register values taken from the TSL2591 datasheet. @@ -200,6 +213,13 @@ class TSL2591Component : public PollingComponent, public i2c::I2CDevice { */ void disable(); + /** Updates the gain setting based on the most recent full spectrum reading + * + * This gets called on update and tries to keep the ADC readings in the middle of the range + */ + + void automatic_gain_update(uint16_t full_spectrum); + // ========== INTERNAL METHODS ========== // (In most use cases you won't need these. They're for ESPHome integration use.) /** Used by ESPHome framework. */ @@ -213,7 +233,7 @@ class TSL2591Component : public PollingComponent, public i2c::I2CDevice { /** Used by ESPHome framework. Does NOT actually set the value on the device. */ void set_integration_time(TSL2591IntegrationTime integration_time); /** Used by ESPHome framework. Does NOT actually set the value on the device. */ - void set_gain(TSL2591Gain gain); + void set_gain(TSL2591ComponentGain gain); /** Used by ESPHome framework. */ void setup() override; /** Used by ESPHome framework. */ @@ -230,6 +250,7 @@ class TSL2591Component : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *visible_sensor_; sensor::Sensor *calculated_lux_sensor_; TSL2591IntegrationTime integration_time_; + TSL2591ComponentGain component_gain_; TSL2591Gain gain_; bool power_save_mode_enabled_; float device_factor_; From 7590d5eacb746fb0f8edf01e8e0acda79598a451 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Wed, 26 Jan 2022 13:33:59 +0100 Subject: [PATCH 056/238] set adc width to 13 bits for esp32-s2 (#3117) --- esphome/components/adc/adc_sensor.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index c3e5221bf7..ad9cf29b6f 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -15,6 +15,11 @@ namespace esphome { namespace adc { static const char *const TAG = "adc"; +// 13 bits for S3 / 12 bit for all other esp32 variants +// create a const to avoid the repated cast to enum +#ifdef USE_ESP32 +static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast(ADC_WIDTH_MAX - 1); +#endif void ADCSensor::setup() { ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); @@ -23,14 +28,14 @@ void ADCSensor::setup() { #endif #ifdef USE_ESP32 - adc1_config_width(ADC_WIDTH_BIT_12); + adc1_config_width(ADC_WIDTH_MAX_SOC_BITS); 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, + auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS, 1100, // default vref &cal_characteristics_[i]); switch (cal_value) { From ad1f4429c929c552013ae3bdb2648d4160c8aaa9 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 26 Jan 2022 13:50:43 +0100 Subject: [PATCH 057/238] Fix lint for TSL2591 (#3118) --- esphome/components/tsl2591/tsl2591.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/esphome/components/tsl2591/tsl2591.cpp b/esphome/components/tsl2591/tsl2591.cpp index 53cd65bd88..8a540c5f13 100644 --- a/esphome/components/tsl2591/tsl2591.cpp +++ b/esphome/components/tsl2591/tsl2591.cpp @@ -394,24 +394,27 @@ void TSL2591Component::automatic_gain_update(uint16_t full_spectrum) { switch (this->gain_) { case TSL2591_GAIN_LOW: - if (full_spectrum < 54) // 1/3 FS / GAIN_HIGH + if (full_spectrum < 54) { // 1/3 FS / GAIN_HIGH new_gain = TSL2591_GAIN_HIGH; - else if (full_spectrum < 875) // 1/3 FS / GAIN_MED + } else if (full_spectrum < 875) { // 1/3 FS / GAIN_MED new_gain = TSL2591_GAIN_MED; + } break; case TSL2591_GAIN_MED: - if (full_spectrum < 57) // 1/3 FS / (GAIN_MAX/GAIN_MED) + if (full_spectrum < 57) { // 1/3 FS / (GAIN_MAX/GAIN_MED) new_gain = TSL2591_GAIN_MAX; - else if (full_spectrum < 1365) // 1/3 FS / (GAIN_HIGH/GAIN_MED) + } else if (full_spectrum < 1365) { // 1/3 FS / (GAIN_HIGH/GAIN_MED) new_gain = TSL2591_GAIN_HIGH; - else if (full_spectrum > 62000 / fs_divider) // 2/3 FS / (GAIN_LOW/GAIN_MED) clipped to 95% FS + } else if (full_spectrum > 62000 / fs_divider) { // 2/3 FS / (GAIN_LOW/GAIN_MED) clipped to 95% FS new_gain = TSL2591_GAIN_LOW; + } break; case TSL2591_GAIN_HIGH: - if (full_spectrum < 920) // 1/3 FS / (GAIN_MAX/GAIN_HIGH) + if (full_spectrum < 920) { // 1/3 FS / (GAIN_MAX/GAIN_HIGH) new_gain = TSL2591_GAIN_MAX; - else if (full_spectrum > 62000 / fs_divider) // 2/3 FS / (GAIN_MED/GAIN_HIGH) clipped to 95% FS + } else if (full_spectrum > 62000 / fs_divider) { // 2/3 FS / (GAIN_MED/GAIN_HIGH) clipped to 95% FS new_gain = TSL2591_GAIN_LOW; + } break; case TSL2591_GAIN_MAX: if (full_spectrum > 62000 / fs_divider) // 2/3 FS / (GAIN_MED/GAIN_HIGH) clipped to 95% FS From 0f3d4d9a4703a4aedd7908ce89272599e9f3727b Mon Sep 17 00:00:00 2001 From: drug123 Date: Wed, 26 Jan 2022 23:54:29 +0000 Subject: [PATCH 058/238] Add Xiaomi MHOC303 sensor e-ink clock (#3115) --- CODEOWNERS | 1 + esphome/components/xiaomi_ble/xiaomi_ble.cpp | 3 + esphome/components/xiaomi_ble/xiaomi_ble.h | 1 + esphome/components/xiaomi_mhoc303/__init__.py | 1 + esphome/components/xiaomi_mhoc303/sensor.py | 73 +++++++++++++++++++ .../xiaomi_mhoc303/xiaomi_mhoc303.cpp | 59 +++++++++++++++ .../xiaomi_mhoc303/xiaomi_mhoc303.h | 35 +++++++++ tests/test2.yaml | 8 ++ 8 files changed, 181 insertions(+) create mode 100644 esphome/components/xiaomi_mhoc303/__init__.py create mode 100644 esphome/components/xiaomi_mhoc303/sensor.py create mode 100644 esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp create mode 100644 esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.h diff --git a/CODEOWNERS b/CODEOWNERS index 6a46732dc1..f075cc8649 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -204,5 +204,6 @@ esphome/components/wake_on_lan/* @willwill2will54 esphome/components/web_server_base/* @OttoWinter esphome/components/whirlpool/* @glmnet esphome/components/xiaomi_lywsd03mmc/* @ahpohl +esphome/components/xiaomi_mhoc303/* @drug123 esphome/components/xiaomi_mhoc401/* @vevsvevs esphome/components/xpt2046/* @numo68 diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 583b68a77b..bdd745b859 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -198,6 +198,9 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service result.name = "MJYD02YLA"; if (raw.size() == 19) result.raw_offset -= 6; + } else if ((raw[2] == 0xd3) && (raw[3] == 0x06)) { // rectangular body, e-ink display with alarm + result.type = XiaomiParseResult::TYPE_MHOC303; + result.name = "MHOC303"; } else if ((raw[2] == 0x87) && (raw[3] == 0x03)) { // square body, e-ink display result.type = XiaomiParseResult::TYPE_MHOC401; result.name = "MHOC401"; diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.h b/esphome/components/xiaomi_ble/xiaomi_ble.h index 54ab9a144f..ee65d7c82f 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.h +++ b/esphome/components/xiaomi_ble/xiaomi_ble.h @@ -23,6 +23,7 @@ struct XiaomiParseResult { TYPE_MUE4094RT, TYPE_WX08ZM, TYPE_MJYD02YLA, + TYPE_MHOC303, TYPE_MHOC401, TYPE_CGPR1 } type; diff --git a/esphome/components/xiaomi_mhoc303/__init__.py b/esphome/components/xiaomi_mhoc303/__init__.py new file mode 100644 index 0000000000..e7005b3957 --- /dev/null +++ b/esphome/components/xiaomi_mhoc303/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@drug123"] diff --git a/esphome/components/xiaomi_mhoc303/sensor.py b/esphome/components/xiaomi_mhoc303/sensor.py new file mode 100644 index 0000000000..18f5ad7764 --- /dev/null +++ b/esphome/components/xiaomi_mhoc303/sensor.py @@ -0,0 +1,73 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, esp32_ble_tracker +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_HUMIDITY, + CONF_MAC_ADDRESS, + CONF_TEMPERATURE, + DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PERCENT, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_BATTERY, + CONF_ID, +) + +DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["xiaomi_ble"] + +xiaomi_mhoc303_ns = cg.esphome_ns.namespace("xiaomi_mhoc303") +XiaomiMHOC303 = xiaomi_mhoc303_ns.class_( + "XiaomiMHOC303", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiMHOC303), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + 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, + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } + ) + .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_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature(sens)) + if CONF_HUMIDITY in config: + sens = await sensor.new_sensor(config[CONF_HUMIDITY]) + cg.add(var.set_humidity(sens)) + if CONF_BATTERY_LEVEL in config: + sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) + cg.add(var.set_battery_level(sens)) diff --git a/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp b/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp new file mode 100644 index 0000000000..e613faec7e --- /dev/null +++ b/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp @@ -0,0 +1,59 @@ +#include "xiaomi_mhoc303.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace xiaomi_mhoc303 { + +static const char *const TAG = "xiaomi_mhoc303"; + +void XiaomiMHOC303::dump_config() { + ESP_LOGCONFIG(TAG, "Xiaomi MHOC303"); + LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "Humidity", this->humidity_); + LOG_SENSOR(" ", "Battery Level", this->battery_level_); +} + +bool XiaomiMHOC303::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 = xiaomi_ble::parse_xiaomi_header(service_data); + if (!res.has_value()) { + continue; + } + if (res->is_duplicate) { + continue; + } + if (res->has_encryption) { + ESP_LOGVV(TAG, "parse_device(): payload decryption is currently not supported on this device."); + continue; + } + if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { + continue; + } + if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + continue; + } + if (res->temperature.has_value() && this->temperature_ != nullptr) + this->temperature_->publish_state(*res->temperature); + if (res->humidity.has_value() && this->humidity_ != nullptr) + this->humidity_->publish_state(*res->humidity); + if (res->battery_level.has_value() && this->battery_level_ != nullptr) + this->battery_level_->publish_state(*res->battery_level); + success = true; + } + + return success; +} + +} // namespace xiaomi_mhoc303 +} // namespace esphome + +#endif diff --git a/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.h b/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.h new file mode 100644 index 0000000000..d0304f7894 --- /dev/null +++ b/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.h @@ -0,0 +1,35 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/xiaomi_ble/xiaomi_ble.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace xiaomi_mhoc303 { + +class XiaomiMHOC303 : 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_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } + void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } + void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } + + protected: + uint64_t address_; + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *humidity_{nullptr}; + sensor::Sensor *battery_level_{nullptr}; +}; + +} // namespace xiaomi_mhoc303 +} // namespace esphome + +#endif diff --git a/tests/test2.yaml b/tests/test2.yaml index 7920bf3fe3..6d606a3143 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -227,6 +227,14 @@ sensor: name: 'JQJCY01YM Formaldehyde' battery_level: name: 'JQJCY01YM Battery Level' + - platform: xiaomi_mhoc303 + mac_address: 'E7:50:59:32:A0:1C' + temperature: + name: 'MHO-C303 Temperature' + humidity: + name: 'MHO-C303 Humidity' + battery_level: + name: 'MHO-C303 Battery Level' - platform: atc_mithermometer mac_address: 'A4:C1:38:4E:16:78' temperature: From 976f5d91edcb97d7452326ace4867631871c8a87 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 27 Jan 2022 08:35:42 +0100 Subject: [PATCH 059/238] Logically group and document helper functions (#3112) --- esphome/core/automation.h | 5 + esphome/core/helpers.cpp | 456 +++++++++++++++++++------------------- esphome/core/helpers.h | 455 ++++++++++++++++++++----------------- 3 files changed, 482 insertions(+), 434 deletions(-) diff --git a/esphome/core/automation.h b/esphome/core/automation.h index f43fb98f20..92bc32247b 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -8,6 +8,11 @@ namespace esphome { +// 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 +template struct gens<0, S...> { using type = seq; }; // NOLINT + #define TEMPLATABLE_VALUE_(type, name) \ protected: \ TemplatableValue name##_{}; \ diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index dc85bdb17d..c3383489b1 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -1,5 +1,8 @@ #include "esphome/core/helpers.h" + #include "esphome/core/defines.h" +#include "esphome/core/hal.h" + #include #include #include @@ -18,95 +21,31 @@ #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" - namespace esphome { -static const char *const TAG = "helpers"; +// STL backports -void get_mac_address_raw(uint8_t *mac) { -#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, - // without doing the CRC check. - esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, 48); -#else - esp_efuse_mac_get_default(mac); -#endif -#elif defined(USE_ESP8266) - wifi_get_macaddr(STATION_IF, mac); -#endif -} - -std::string get_mac_address() { - uint8_t mac[6]; - get_mac_address_raw(mac); - 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_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); -} - -#ifdef USE_ESP32 -void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); } +#if _GLIBCXX_RELEASE < 7 +std::string to_string(int value) { return str_snprintf("%d", 32, value); } // NOLINT +std::string to_string(long value) { return str_snprintf("%ld", 32, value); } // NOLINT +std::string to_string(long long value) { return str_snprintf("%lld", 32, value); } // NOLINT +std::string to_string(unsigned value) { return str_snprintf("%u", 32, value); } // NOLINT +std::string to_string(unsigned long value) { return str_snprintf("%lu", 32, value); } // NOLINT +std::string to_string(unsigned long long value) { return str_snprintf("%llu", 32, value); } // NOLINT +std::string to_string(float value) { return str_snprintf("%f", 32, value); } +std::string to_string(double value) { return str_snprintf("%f", 32, value); } +std::string to_string(long double value) { return str_snprintf("%Lf", 32, value); } #endif -std::string generate_hostname(const std::string &base) { return base + std::string("-") + get_mac_address(); } - -float gamma_correct(float value, float gamma) { - if (value <= 0.0f) - return 0.0f; - if (gamma <= 0.0f) - return value; - - return powf(value, gamma); -} -float gamma_uncorrect(float value, float gamma) { - if (value <= 0.0f) - return 0.0f; - if (gamma <= 0.0f) - return value; - - return powf(value, 1 / gamma); -} - -std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { - if (accuracy_decimals < 0) { - auto multiplier = powf(10.0f, accuracy_decimals); - value = roundf(value * multiplier) / multiplier; - accuracy_decimals = 0; - } - char tmp[32]; // should be enough, but we should maybe improve this at some point. - snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value); - return std::string(tmp); -} - -ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { - if (on == nullptr && strcasecmp(str, "on") == 0) - return PARSE_ON; - if (on != nullptr && strcasecmp(str, on) == 0) - return PARSE_ON; - if (off == nullptr && strcasecmp(str, "off") == 0) - return PARSE_OFF; - if (off != nullptr && strcasecmp(str, off) == 0) - return PARSE_OFF; - if (strcasecmp(str, "toggle") == 0) - return PARSE_TOGGLE; - - return PARSE_NONE; -} +// Mathematics +float lerp(float completion, float start, float end) { return start + (end - start) * completion; } uint8_t crc8(uint8_t *data, uint8_t len) { uint8_t crc = 0; @@ -122,21 +61,6 @@ uint8_t crc8(uint8_t *data, uint8_t len) { } return crc; } - -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 - ; -} - uint32_t fnv1_hash(const std::string &str) { uint32_t hash = 2166136261UL; for (char c : str) { @@ -145,139 +69,6 @@ uint32_t fnv1_hash(const std::string &str) { } return hash; } -bool str_equals_case_insensitive(const std::string &a, const std::string &b) { - return strcasecmp(a.c_str(), b.c_str()) == 0; -} - -static int high_freq_num_requests = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - -void HighFrequencyLoopRequester::start() { - if (this->started_) - return; - high_freq_num_requests++; - this->started_ = true; -} -void HighFrequencyLoopRequester::stop() { - if (!this->started_) - return; - high_freq_num_requests--; - this->started_ = false; -} -bool HighFrequencyLoopRequester::is_high_frequency() { return high_freq_num_requests > 0; } - -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; } -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; - - 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; -} - -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_); } -#endif -#ifdef USE_ESP32 -IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } -IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } -#endif - -// --------------------------------------------------------------------------------------------------------------------- - -// Mathematics uint32_t random_uint32() { #ifdef USE_ESP32 @@ -302,6 +93,13 @@ bool random_bytes(uint8_t *data, size_t len) { // Strings +bool str_equals_case_insensitive(const std::string &a, const std::string &b) { + return strcasecmp(a.c_str(), b.c_str()) == 0; +} +bool str_startswith(const std::string &str, const std::string &start) { return str.rfind(start, 0) == 0; } +bool str_endswith(const std::string &str, const std::string &end) { + return str.rfind(end) == (str.size() - end.size()); +} std::string str_truncate(const std::string &str, size_t length) { return str.length() > length ? str.substr(0, length) : str; } @@ -334,6 +132,35 @@ std::string str_sanitize(const std::string &str) { }); return out; } +std::string str_snprintf(const char *fmt, size_t len, ...) { + std::string str; + va_list args; + + str.resize(len); + va_start(args, len); + size_t out_length = vsnprintf(&str[0], len + 1, fmt, args); + va_end(args); + + if (out_length < len) + str.resize(out_length); + + return str; +} +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; +} // Parsing & formatting @@ -385,4 +212,181 @@ std::string format_hex_pretty(const uint8_t *data, size_t length) { } std::string format_hex_pretty(const std::vector &data) { return format_hex_pretty(data.data(), data.size()); } +ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { + if (on == nullptr && strcasecmp(str, "on") == 0) + return PARSE_ON; + if (on != nullptr && strcasecmp(str, on) == 0) + return PARSE_ON; + if (off == nullptr && strcasecmp(str, "off") == 0) + return PARSE_OFF; + if (off != nullptr && strcasecmp(str, off) == 0) + return PARSE_OFF; + if (strcasecmp(str, "toggle") == 0) + return PARSE_TOGGLE; + + return PARSE_NONE; +} + +std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { + if (accuracy_decimals < 0) { + auto multiplier = powf(10.0f, accuracy_decimals); + value = roundf(value * multiplier) / multiplier; + accuracy_decimals = 0; + } + char tmp[32]; // should be enough, but we should maybe improve this at some point. + snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value); + return std::string(tmp); +} + +// Colors + +float gamma_correct(float value, float gamma) { + if (value <= 0.0f) + return 0.0f; + if (gamma <= 0.0f) + return value; + + return powf(value, gamma); +} +float gamma_uncorrect(float value, float gamma) { + if (value <= 0.0f) + return 0.0f; + if (gamma <= 0.0f) + return value; + + return powf(value, 1 / gamma); +} + +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; +} + +// System APIs + +#if defined(USE_ESP8266) +IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } +IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } +#elif defined(USE_ESP32) +IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } +IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } +#endif + +uint8_t HighFrequencyLoopRequester::num_requests = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +void HighFrequencyLoopRequester::start() { + if (this->started_) + return; + num_requests++; + this->started_ = true; +} +void HighFrequencyLoopRequester::stop() { + if (!this->started_) + return; + num_requests--; + this->started_ = false; +} +bool HighFrequencyLoopRequester::is_high_frequency() { return num_requests > 0; } + +void get_mac_address_raw(uint8_t *mac) { +#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, + // without doing the CRC check. + esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, 48); +#else + esp_efuse_mac_get_default(mac); +#endif +#elif defined(USE_ESP8266) + wifi_get_macaddr(STATION_IF, mac); +#endif +} +std::string get_mac_address() { + uint8_t mac[6]; + get_mac_address_raw(mac); + 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_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +} +#ifdef USE_ESP32 +void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); } +#endif + +void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trigger WDT or affect WiFi/BT stability + uint32_t 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 + ; +} + } // namespace esphome diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 95bf10abc3..e0763d2c71 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -2,19 +2,18 @@ #include #include - -#include #include -#include #include +#include #include +#include + +#include "esphome/core/optional.h" #ifdef USE_ESP32 #include #endif -#include "esphome/core/optional.h" - #define HOT __attribute__((hot)) #define ESPDEPRECATED(msg, when) __attribute__((deprecated(msg))) #define ALWAYS_INLINE __attribute__((always_inline)) @@ -30,212 +29,26 @@ namespace esphome { -/// 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 device MAC address as a string, in lowercase hex notation. -std::string get_mac_address(); - -/// Get the device MAC address as a string, in colon-separated uppercase 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 - -/// 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); -bool str_endswith(const std::string &full, const std::string &ending); - -/// 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 { - public: - void start(); - void stop(); - - static bool is_high_frequency(); - - protected: - bool started_{false}; -}; - -/** Linearly interpolate between end start and end by completion. - * - * @tparam T The input/output typename. - * @param start The start value. - * @param end The end value. - * @param completion The completion. 0 is start value, 1 is end value. - * @return The linearly interpolated value. - */ -float lerp(float completion, float start, float end); - -// Not all platforms we support target C++14 yet, so we can't unconditionally use std::make_unique. Provide our own -// implementation if needed, and otherwise pull std::make_unique into scope so that we have a uniform API. -#if __cplusplus >= 201402L -using std::make_unique; -#else -template std::unique_ptr make_unique(Args &&...args) { - return std::unique_ptr(new T(std::forward(args)...)); -} -#endif - -/// 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. -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 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); - -/// 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. - * - * This behaves like std::lock_guard. As long as the value is visible in the current stack, all interrupts - * (including flash reads) will be disabled. - * - * Please note all functions called when the interrupt lock must be marked IRAM_ATTR (loading code into - * instruction cache is done via interrupts; disabling interrupts prevents data not already in cache from being - * pulled from flash). - * - * Example: - * - * ```cpp - * // interrupts are enabled - * { - * InterruptLock lock; - * // do something - * // interrupts are disabled - * } - * // interrupts are enabled - * ``` - */ -class InterruptLock { - public: - InterruptLock(); - ~InterruptLock(); - - protected: -#ifdef USE_ESP8266 - uint32_t xt_state_; -#endif -}; - -/// Calculate a crc8 of data with the provided data length. -uint8_t crc8(uint8_t *data, uint8_t len); - -enum ParseOnOffState { - PARSE_NONE = 0, - PARSE_ON, - PARSE_OFF, - PARSE_TOGGLE, -}; - -ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const char *off = nullptr); - -// 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 -template struct gens<0, S...> { using type = seq; }; // NOLINT - -template using enable_if_t = typename std::enable_if::type; - -template::value, int> = 0> T id(T value) { return value; } -template::value, int> = 0> T &id(T *value) { return *value; } - -template class CallbackManager; - -/** Simple helper class to allow having multiple subscribers to a signal. - * - * @tparam Ts The arguments for the callback, wrapped in void(). - */ -template class CallbackManager { - public: - /// Add a callback to the internal callback list. - void add(std::function &&callback) { this->callbacks_.push_back(std::move(callback)); } - - /// Call all callbacks in this manager. - void call(Ts... args) { - for (auto &cb : this->callbacks_) - cb(args...); - } - - /// Call all callbacks in this manager. - void operator()(Ts... args) { call(args...); } - - protected: - std::vector> callbacks_; -}; - -void delay_microseconds_safe(uint32_t us); - -template class Deduplicator { - public: - bool next(T value) { - if (this->has_value_) { - if (this->last_value_ == value) - return false; - } - this->has_value_ = true; - this->last_value_ = value; - return true; - } - bool has_value() const { return this->has_value_; } - - protected: - bool has_value_{false}; - T last_value_{}; -}; - -template class Parented { - public: - Parented() {} - Parented(T *parent) : parent_(parent) {} - - T *get_parent() const { return parent_; } - void set_parent(T *parent) { parent_ = parent; } - - protected: - T *parent_{nullptr}; -}; - -uint32_t fnv1_hash(const std::string &str); - -// --------------------------------------------------------------------------------------------------------------------- - /// @name STL backports ///@{ +// Backports for various STL features we like to use. Pull in the STL implementation wherever available, to avoid +// ambiguity and to provide a uniform API. + // 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); } +std::string to_string(int value); // NOLINT +std::string to_string(long value); // NOLINT +std::string to_string(long long value); // NOLINT +std::string to_string(unsigned value); // NOLINT +std::string to_string(unsigned long value); // NOLINT +std::string to_string(unsigned long long value); // NOLINT +std::string to_string(float value); +std::string to_string(double value); +std::string to_string(long double value); #endif // std::is_trivially_copyable from C++11, implemented in libstdc++/g++ 5.1 (but minor releases can't be detected) @@ -248,6 +61,22 @@ using std::is_trivially_copyable; template struct is_trivially_copyable : public std::integral_constant {}; #endif +// std::make_unique() from C++14 +#if __cpp_lib_make_unique >= 201304 +using std::make_unique; +#else +template std::unique_ptr make_unique(Args &&...args) { + return std::unique_ptr(new T(std::forward(args)...)); +} +#endif + +// std::enable_if_t from C++14 +#if __cplusplus >= 201402L +using std::enable_if_t; +#else +template using enable_if_t = typename std::enable_if::type; +#endif + // std::clamp from C++17 #if __cpp_lib_clamp >= 201603 using std::clamp; @@ -309,6 +138,20 @@ template<> constexpr14 int64_t byteswap(int64_t n) { return __builtin_bswap64(n) /// @name Mathematics ///@{ +/// Linearly interpolate between \p start and \p end by \p completion (between 0 and 1). +float lerp(float completion, float start, float end); + +/// Remap \p value from the range (\p min, \p max) to (\p min_out, \p max_out). +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; +} + +/// Calculate a CRC-8 checksum of \p data with size \p len. +uint8_t crc8(uint8_t *data, uint8_t len); + +/// Calculate a FNV-1 hash of \p str. +uint32_t fnv1_hash(const std::string &str); + /// Return a random 32-bit unsigned integer. uint32_t random_uint32(); /// Return a random float between 0 and 1. @@ -397,6 +240,14 @@ template constexpr14 T convert_little_endian(T val) { /// @name Strings ///@{ +/// Compare strings for equality in case-insensitive manner. +bool str_equals_case_insensitive(const std::string &a, const std::string &b); + +/// Check whether a string starts with a value. +bool str_startswith(const std::string &str, const std::string &start); +/// Check whether a string ends with a value. +bool str_endswith(const std::string &str, const std::string &end); + /// 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; } @@ -419,6 +270,12 @@ 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); +/// snprintf-like function returning std::string of maximum length \p len (excluding null terminator). +std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t len, ...); + +/// sprintf-like function returning std::string. +std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...); + ///@} /// @name Parsing & formatting @@ -537,15 +394,181 @@ template::value, int> = 0> std::stri return format_hex_pretty(reinterpret_cast(&val), sizeof(T)); } +/// Return values for parse_on_off(). +enum ParseOnOffState { + PARSE_NONE = 0, + PARSE_ON, + PARSE_OFF, + PARSE_TOGGLE, +}; +/// Parse a string that contains either on, off or toggle. +ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const char *off = nullptr); + +/// Create a string from a value and an accuracy in decimals. +std::string value_accuracy_to_string(float value, int8_t accuracy_decimals); + ///@} -/// @name Number manipulation +/// @name Colors ///@{ -/// Remap a number from one range to another. -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; -} +/// Applies gamma correction of \p gamma to \p value. +float gamma_correct(float value, float gamma); +/// Reverts gamma correction of \p gamma to \p value. +float gamma_uncorrect(float value, float gamma); + +/// Convert \p red, \p green and \p blue (all 0-1) values to \p hue (0-360), \p saturation (0-1) and \p value (0-1). +void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value); +/// Convert \p hue (0-360), \p saturation (0-1) and \p value (0-1) to \p red, \p green and \p blue (all 0-1). +void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue); + +///@} + +/// @name Units +///@{ + +/// Convert degrees Celsius to degrees Fahrenheit. +constexpr float celsius_to_fahrenheit(float value) { return value * 1.8f + 32.0f; } +/// Convert degrees Fahrenheit to degrees Celsius. +constexpr float fahrenheit_to_celsius(float value) { return (value - 32.0f) / 1.8f; } + +///@} + +/// @name Utilities +/// @{ + +template class CallbackManager; + +/** Helper class to allow having multiple subscribers to a callback. + * + * @tparam Ts The arguments for the callbacks, wrapped in void(). + */ +template class CallbackManager { + public: + /// Add a callback to the list. + void add(std::function &&callback) { this->callbacks_.push_back(std::move(callback)); } + + /// Call all callbacks in this manager. + void call(Ts... args) { + for (auto &cb : this->callbacks_) + cb(args...); + } + + /// Call all callbacks in this manager. + void operator()(Ts... args) { call(args...); } + + protected: + std::vector> callbacks_; +}; + +/// Helper class to deduplicate items in a series of values. +template class Deduplicator { + public: + /// Feeds the next item in the series to the deduplicator and returns whether this is a duplicate. + bool next(T value) { + if (this->has_value_) { + if (this->last_value_ == value) + return false; + } + this->has_value_ = true; + this->last_value_ = value; + return true; + } + /// Returns whether this deduplicator has processed any items so far. + bool has_value() const { return this->has_value_; } + + protected: + bool has_value_{false}; + T last_value_{}; +}; + +/// Helper class to easily give an object a parent of type \p T. +template class Parented { + public: + Parented() {} + Parented(T *parent) : parent_(parent) {} + + /// Get the parent of this object. + T *get_parent() const { return parent_; } + /// Set the parent of this object. + void set_parent(T *parent) { parent_ = parent; } + + protected: + T *parent_{nullptr}; +}; + +/// @} + +/// @name System APIs +///@{ + +/** Helper class to disable interrupts. + * + * This behaves like std::lock_guard: as long as the object is alive, all interrupts are disabled. + * + * Please note all functions called when the interrupt lock must be marked IRAM_ATTR (loading code into + * instruction cache is done via interrupts; disabling interrupts prevents data not already in cache from being + * pulled from flash). + * + * Example usage: + * + * \code{.cpp} + * // interrupts are enabled + * { + * InterruptLock lock; + * // do something + * // interrupts are disabled + * } + * // interrupts are enabled + * \endcode + */ +class InterruptLock { + public: + InterruptLock(); + ~InterruptLock(); + + protected: +#ifdef USE_ESP8266 + uint32_t xt_state_; +#endif +}; + +/** Helper class to request `loop()` to be called as fast as possible. + * + * Usually the ESPHome main loop runs at 60 Hz, sleeping in between invocations of `loop()` if necessary. When a higher + * execution frequency is necessary, you can use this class to make the loop run continuously without waiting. + */ +class HighFrequencyLoopRequester { + public: + /// Start running the loop continuously. + void start(); + /// Stop running the loop continuously. + void stop(); + + /// Check whether the loop is running continuously. + static bool is_high_frequency(); + + protected: + bool started_{false}; + static uint8_t num_requests; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +}; + +/// 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 device MAC address as a string, in lowercase hex notation. +std::string get_mac_address(); + +/// Get the device MAC address as a string, in colon-separated uppercase 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 + +/// Delay for the given amount of microseconds, possibly yielding to other processes during the wait. +void delay_microseconds_safe(uint32_t us); ///@} @@ -594,6 +617,22 @@ template class ExternalRAMAllocator { /// @} +/// @name Internal functions +///@{ + +/** Helper function to make `id(var)` known from lambdas work in custom components. + * + * This function is not called from lambdas, the code generator replaces calls to it with the appropriate variable. + */ +template::value, int> = 0> T id(T value) { return value; } +/** Helper function to make `id(var)` known from lambdas work in custom components. + * + * This function is not called from lambdas, the code generator replaces calls to it with the appropriate variable. + */ +template::value, int> = 0> T &id(T *value) { return *value; } + +///@} + /// @name Deprecated functions ///@{ From 0c3568fad5f0cdc8f6f2c46ad05a2bbaf55a7f36 Mon Sep 17 00:00:00 2001 From: Matt Hamilton Date: Fri, 28 Jan 2022 06:37:47 -0500 Subject: [PATCH 060/238] Add support for Waveshare 7.5in-bv2 (#3121) --- .../components/waveshare_epaper/display.py | 4 ++ .../waveshare_epaper/waveshare_epaper.cpp | 68 +++++++++++++++++++ .../waveshare_epaper/waveshare_epaper.h | 24 +++++++ 3 files changed, 96 insertions(+) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 1d1644dc25..44120ebbc5 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -44,6 +44,9 @@ WaveshareEPaper7P5In = waveshare_epaper_ns.class_( WaveshareEPaper7P5InBC = waveshare_epaper_ns.class_( "WaveshareEPaper7P5InBC", WaveshareEPaper ) +WaveshareEPaper7P5InBV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InBV2", WaveshareEPaper +) WaveshareEPaper7P5InV2 = waveshare_epaper_ns.class_( "WaveshareEPaper7P5InV2", WaveshareEPaper ) @@ -70,6 +73,7 @@ MODELS = { "4.20in-bv2": ("b", WaveshareEPaper4P2InBV2), "5.83in": ("b", WaveshareEPaper5P8In), "7.50in": ("b", WaveshareEPaper7P5In), + "7.50in-bv2": ("b", WaveshareEPaper7P5InBV2), "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 b2d2d92bb2..71e3b22e7d 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -937,6 +937,74 @@ void WaveshareEPaper5P8In::dump_config() { LOG_PIN(" Busy Pin: ", this->busy_pin_); LOG_UPDATE_INTERVAL(this); } +void WaveshareEPaper7P5InBV2::initialize() { + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x07); + this->data(0x07); // VGH=20V,VGL=-20V + this->data(0x3f); // VDH=15V + this->data(0x3f); // VDL=-15V + // COMMAND POWER ON + this->command(0x04); + delay(100); // NOLINT + this->wait_until_idle_(); + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0x0F); // KW3f, KWR-2F, BWROTP 0f, BWOTP 1f + this->command(0x61); // tres + this->data(0x03); // 800px + this->data(0x20); + this->data(0x01); // 400px + this->data(0xE0); + this->command(0x15); + this->data(0x00); + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x11); + this->data(0x07); + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + // COMMAND RESOLUTION SETTING + this->command(0x65); + this->data(0x00); + this->data(0x00); // 800*480 + this->data(0x00); + this->data(0x00); +} +void HOT WaveshareEPaper7P5InBV2::display() { + // COMMAND DATA START TRANSMISSION 1 (B/W data) + this->command(0x10); + delay(2); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + delay(2); + + // COMMAND DATA START TRANSMISSION 2 (RED data) + this->command(0x13); + delay(2); + this->start_data_(); + for (size_t i = 0; i < this->get_buffer_length_(); i++) + this->write_byte(0x00); + this->end_data_(); + delay(2); + + // COMMAND DISPLAY REFRESH + this->command(0x12); + delay(100); // NOLINT + this->wait_until_idle_(); +} +int WaveshareEPaper7P5InBV2::get_width_internal() { return 800; } +int WaveshareEPaper7P5InBV2::get_height_internal() { return 480; } +void WaveshareEPaper7P5InBV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5in-bv2"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} void WaveshareEPaper7P5In::initialize() { // COMMAND POWER SETTING this->command(0x01); diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 4de2ac7d97..96bd2fc782 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -121,6 +121,7 @@ enum WaveshareEPaperTypeBModel { WAVESHARE_EPAPER_4_2_IN_B_V2, WAVESHARE_EPAPER_7_5_IN, WAVESHARE_EPAPER_7_5_INV2, + WAVESHARE_EPAPER_7_5_IN_B_V2, }; class WaveshareEPaper2P7In : public WaveshareEPaper { @@ -280,6 +281,29 @@ class WaveshareEPaper7P5In : public WaveshareEPaper { int get_height_internal() override; }; +class WaveshareEPaper7P5InBV2 : 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); // deep sleep + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + class WaveshareEPaper7P5InBC : public WaveshareEPaper { public: void initialize() override; From df0de2fc2d62e5a69f9a637ff2a8db69ebaf001d Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 29 Jan 2022 13:04:15 +0100 Subject: [PATCH 061/238] Bump docker dependencies (#3131) --- docker/Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 330901a776..0eebbe827e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -8,9 +8,9 @@ ARG BASEIMGTYPE=docker 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 +FROM debian:bullseye-20220125-slim AS base-docker-amd64 +FROM debian:bullseye-20220125-slim AS base-docker-arm64 +FROM debian:bullseye-20220125-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 @@ -23,7 +23,7 @@ RUN \ python3=3.9.2-3 \ python3-pip=20.3.4-4 \ python3-setuptools=52.0.0-4 \ - python3-pil=8.1.2+dfsg-0.3 \ + python3-pil=8.1.2+dfsg-0.3+deb11u1 \ python3-cryptography=3.3.2-1 \ iputils-ping=3:20210202-1 \ git=1:2.30.2-1 \ From e3fd68c849cacb217469d1fa574c228a2fec7279 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Jan 2022 13:27:41 +0100 Subject: [PATCH 062/238] Bump pytest-mock from 3.6.1 to 3.7.0 (#3128) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 1203858c96..499e950481 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -6,7 +6,7 @@ pre-commit # Unit tests pytest==6.2.5 pytest-cov==3.0.0 -pytest-mock==3.6.1 +pytest-mock==3.7.0 pytest-asyncio==0.17.2 asyncmock==0.4.2 hypothesis==5.49.0 From 4a5970b4af6ff9386c766291d6b26bcc6b7d4717 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 31 Jan 2022 10:58:27 +1300 Subject: [PATCH 063/238] Fix backwards string case helpers (#3126) --- esphome/components/dallas/dallas_component.cpp | 2 +- esphome/core/helpers.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index fb38f57f8c..1eed2ebf78 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -235,7 +235,7 @@ float DallasTemperatureSensor::get_temp_c() { return temp / 128.0f; } -std::string DallasTemperatureSensor::unique_id() { return "dallas-" + str_upper_case(format_hex(this->address_)); } +std::string DallasTemperatureSensor::unique_id() { return "dallas-" + str_lower_case(format_hex(this->address_)); } } // namespace dallas } // namespace esphome diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index c3383489b1..05c86f86bb 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -116,8 +116,8 @@ template std::string str_ctype_transform(const std::string &str) 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_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()); From bf91443f38d273f5fae4868e608a898dd33746cf Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 31 Jan 2022 11:08:20 +1300 Subject: [PATCH 064/238] Improv_serial scan and send wifi networks list (#3116) --- esphome/components/esp32_improv/__init__.py | 2 +- esphome/components/improv_serial/__init__.py | 2 +- .../improv_serial/improv_serial_component.cpp | 23 +++++++++++++++++++ .../improv_serial/improv_serial_component.h | 2 +- platformio.ini | 2 +- 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 26e586e4ff..c95d4075bc 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -56,7 +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.1.0") + cg.add_library("esphome/Improv", "1.2.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/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py index 4499bef1fd..4de4fe66a7 100644 --- a/esphome/components/improv_serial/__init__.py +++ b/esphome/components/improv_serial/__init__.py @@ -30,4 +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.1.0") + cg.add_library("esphome/Improv", "1.2.0") diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index b55855abf9..0dab71060c 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -24,6 +24,8 @@ void ImprovSerialComponent::setup() { if (wifi::global_wifi_component->has_sta()) { this->state_ = improv::STATE_PROVISIONED; + } else { + wifi::global_wifi_component->start_scanning(); } } @@ -152,6 +154,27 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command this->send_response_(info); return true; } + case improv::GET_WIFI_NETWORKS: { + std::vector networks; + auto results = wifi::global_wifi_component->get_scan_result(); + for (auto &scan : results) { + if (scan.get_is_hidden()) + continue; + const std::string &ssid = scan.get_ssid(); + if (std::find(networks.begin(), networks.end(), ssid) != networks.end()) + continue; + // Send each ssid separately to avoid overflowing the buffer + std::vector data = improv::build_rpc_response( + improv::GET_WIFI_NETWORKS, {ssid, str_sprintf("%d", scan.get_rssi()), YESNO(scan.get_with_auth())}, false); + this->send_response_(data); + networks.push_back(ssid); + } + // Send empty response to signify the end of the list. + std::vector data = + improv::build_rpc_response(improv::GET_WIFI_NETWORKS, std::vector{}, false); + this->send_response_(data); + return true; + } default: { ESP_LOGW(TAG, "Unknown Improv payload"); this->set_error_(improv::ERROR_UNKNOWN_RPC); diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h index 304afdaf75..c6b980ab99 100644 --- a/esphome/components/improv_serial/improv_serial_component.h +++ b/esphome/components/improv_serial/improv_serial_component.h @@ -32,7 +32,7 @@ class ImprovSerialComponent : public Component { void loop() override; void dump_config() override; - float get_setup_priority() const override { return setup_priority::HARDWARE; } + float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } protected: bool parse_improv_serial_byte_(uint8_t byte); diff --git a/platformio.ini b/platformio.ini index e7bf848f1b..927d58cb89 100644 --- a/platformio.ini +++ b/platformio.ini @@ -35,7 +35,7 @@ build_flags = lib_deps = esphome/noise-c@0.1.4 ; api makuna/NeoPixelBus@2.6.9 ; neopixelbus - esphome/Improv@1.1.0 ; improv_serial / esp32_improv + esphome/Improv@1.2.0 ; improv_serial / esp32_improv bblanchon/ArduinoJson@6.18.5 ; json wjtje/qr-code-generator-library@1.7.0 ; qr_code build_flags = From 0384efcfc29fda9e9cfb9ea60d086606c1197d2d Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 31 Jan 2022 02:27:10 +0100 Subject: [PATCH 065/238] Disable platformio ldf for build (#3130) --- platformio.ini | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/platformio.ini b/platformio.ini index 927d58cb89..bac2486907 100644 --- a/platformio.ini +++ b/platformio.ini @@ -44,12 +44,15 @@ src_filter = +<./> +<../tests/dummy_main.cpp> +<../.temp/all-include.cpp> +lib_ldf_mode = off ; This are common settings for all Arduino-framework based environments. [common:arduino] extends = common lib_deps = ${common.lib_deps} + SPI ; spi (Arduino built-in) + Wire ; i2c (Arduino built-int) ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt esphome/ESPAsyncWebServer-esphome@2.1.0 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base @@ -85,6 +88,9 @@ lib_deps = ESP8266WiFi ; wifi (Arduino built-in) Update ; ota (Arduino built-in) ottowinter/ESPAsyncTCP-esphome@1.2.3 ; async_tcp + ESP8266HTTPClient ; http_request (Arduino built-in) + ESP8266mDNS ; mdns (Arduino built-in) + DNSServer ; captive_portal (Arduino built-in) build_flags = ${common:arduino.build_flags} -DUSE_ESP8266 @@ -101,8 +107,17 @@ platform_packages = framework = arduino board = nodemcu-32s lib_deps = + ; order matters with lib-deps; some of the libs in common:arduino.lib_deps + ; don't declare built-in libraries as dependencies, so they have to be declared first + FS ; web_server_base (Arduino built-in) + WiFi ; wifi,web_server_base,ethernet (Arduino built-in) + Update ; ota,web_server_base (Arduino built-in) ${common:arduino.lib_deps} esphome/AsyncTCP-esphome@1.2.2 ; async_tcp + WiFiClientSecure ; http_request,nextion (Arduino built-in) + HTTPClient ; http_request,nextion (Arduino built-in) + ESPmDNS ; mdns (Arduino built-in) + DNSServer ; captive_portal (Arduino built-in) build_flags = ${common:arduino.build_flags} -DUSE_ESP32 From 4de642ff28260944e6e7d6399bff1fad7b4d6205 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 31 Jan 2022 07:59:56 +0100 Subject: [PATCH 066/238] Bump esp-idf framework version from 4.3.0 to 4.3.2 (#3120) --- esphome/components/esp32/__init__.py | 20 ++++++++++++++------ esphome/components/md5/md5.cpp | 13 +++++++++++++ esphome/components/md5/md5.h | 4 ++-- platformio.ini | 6 +++--- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index cec72e1b77..1229675ad8 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -18,6 +18,7 @@ from esphome.const import ( KEY_FRAMEWORK_VERSION, KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, + __version__, ) from esphome.core import CORE, HexInt import esphome.config_validation as cv @@ -106,7 +107,6 @@ def _format_framework_espidf_version(ver: cv.Version) -> str: # The new version needs to be thoroughly validated before changing the # recommended version as otherwise a bunch of devices could be bricked # * For all constants below, update platformio.ini (in this repo) -# and platformio.ini/platformio-lint.ini in the esphome-docker-base repository # The default/recommended arduino framework version # - https://github.com/espressif/arduino-esp32/releases @@ -115,16 +115,16 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(1, 0, 6) # The platformio/espressif32 version to use for arduino frameworks # - https://github.com/platformio/platform-espressif32/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 -ARDUINO_PLATFORM_VERSION = cv.Version(3, 3, 2) +ARDUINO_PLATFORM_VERSION = cv.Version(3, 5, 0) # The default/recommended esp-idf framework version # - https://github.com/espressif/esp-idf/releases # - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf -RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 3, 0) +RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 3, 2) # The platformio/espressif32 version to use for esp-idf frameworks # - https://github.com/platformio/platform-espressif32/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 -ESP_IDF_PLATFORM_VERSION = cv.Version(3, 3, 2) +ESP_IDF_PLATFORM_VERSION = cv.Version(3, 5, 0) def _arduino_check_versions(value): @@ -165,8 +165,8 @@ def _arduino_check_versions(value): def _esp_idf_check_versions(value): value = value.copy() lookups = { - "dev": (cv.Version(4, 3, 1), "https://github.com/espressif/esp-idf.git"), - "latest": (cv.Version(4, 3, 0), None), + "dev": (cv.Version(5, 0, 0), "https://github.com/espressif/esp-idf.git"), + "latest": (cv.Version(4, 3, 2), None), "recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None), } @@ -431,6 +431,14 @@ def copy_files(): CORE.relative_build_path("partitions.csv"), IDF_PARTITIONS_CSV, ) + # IDF build scripts look for version string to put in the build. + # However, if the build path does not have an initialized git repo, + # and no version.txt file exists, the CMake script fails for some setups. + # Fix by manually pasting a version.txt file, containing the ESPHome version + write_file_if_changed( + CORE.relative_build_path("version.txt"), + __version__, + ) dir = os.path.dirname(__file__) post_build_file = os.path.join(dir, "post_build.py.script") diff --git a/esphome/components/md5/md5.cpp b/esphome/components/md5/md5.cpp index 0528a87d0e..8d4bac1fd2 100644 --- a/esphome/components/md5/md5.cpp +++ b/esphome/components/md5/md5.cpp @@ -6,6 +6,7 @@ namespace esphome { namespace md5 { +#ifdef USE_ARDUINO void MD5Digest::init() { memset(this->digest_, 0, 16); MD5Init(&this->ctx_); @@ -14,6 +15,18 @@ void MD5Digest::init() { void MD5Digest::add(const uint8_t *data, size_t len) { MD5Update(&this->ctx_, data, len); } void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); } +#endif // USE_ARDUINO + +#ifdef USE_ESP_IDF +void MD5Digest::init() { + memset(this->digest_, 0, 16); + esp_rom_md5_init(&this->ctx_); +} + +void MD5Digest::add(const uint8_t *data, size_t len) { esp_rom_md5_update(&this->ctx_, data, len); } + +void MD5Digest::calculate() { esp_rom_md5_final(this->digest_, &this->ctx_); } +#endif // USE_ESP_IDF void MD5Digest::get_bytes(uint8_t *output) { memcpy(output, this->digest_, 16); } diff --git a/esphome/components/md5/md5.h b/esphome/components/md5/md5.h index 1c15c9e57d..a9628c9242 100644 --- a/esphome/components/md5/md5.h +++ b/esphome/components/md5/md5.h @@ -3,8 +3,8 @@ #include "esphome/core/defines.h" #ifdef USE_ESP_IDF -#include "esp32/rom/md5_hash.h" -#define MD5_CTX_TYPE MD5Context +#include "esp_rom_md5.h" +#define MD5_CTX_TYPE md5_context_t #endif #if defined(USE_ARDUINO) && defined(USE_ESP32) diff --git a/platformio.ini b/platformio.ini index bac2486907..d63f28d05e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -100,7 +100,7 @@ extra_scripts = post:esphome/components/esp8266/post_build.py.script ; This are common settings for the ESP32 (all variants) using Arduino. [common:esp32-arduino] extends = common:arduino -platform = platformio/espressif32 @ 3.3.2 +platform = platformio/espressif32 @ 3.5.0 platform_packages = platformio/framework-arduinoespressif32 @ ~3.10006.0 @@ -127,9 +127,9 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script ; This are common settings for the ESP32 (all variants) using IDF. [common:esp32-idf] extends = common:idf -platform = platformio/espressif32 @ 3.3.2 +platform = platformio/espressif32 @ 3.5.0 platform_packages = - platformio/framework-espidf @ ~3.40300.0 + platformio/framework-espidf @ ~3.40302.0 framework = espidf lib_deps = From f9e7291050510bb5f2719168e14ff951748e6e88 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 1 Feb 2022 10:22:43 +0100 Subject: [PATCH 067/238] Bump pre-commit flake8 from 3.8.4 to 4.0.1 (#3149) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a821c21fa7..16564dc89c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - --quiet files: ^((esphome|script|tests)/.+)?[^/]+\.py$ - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 + rev: 4.0.1 hooks: - id: flake8 additional_dependencies: From 2b39988707a655c7745899f75d3946775f0de6a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Feb 2022 10:26:37 +0100 Subject: [PATCH 068/238] Bump black from 21.12b0 to 22.1.0 (#3147) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Otto winter --- .pre-commit-config.yaml | 2 +- esphome/components/cs5460a/sensor.py | 4 ++-- esphome/components/esp8266/boards.py | 2 +- esphome/components/ledc/output.py | 6 +++--- esphome/mqtt.py | 2 +- requirements_test.txt | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 16564dc89c..e38717fe5b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/ambv/black - rev: 20.8b1 + rev: 22.1.0 hooks: - id: black args: diff --git a/esphome/components/cs5460a/sensor.py b/esphome/components/cs5460a/sensor.py index 82df881bfc..d6e3c2ba48 100644 --- a/esphome/components/cs5460a/sensor.py +++ b/esphome/components/cs5460a/sensor.py @@ -49,8 +49,8 @@ def validate_config(config): if current_gain == 0.0 or voltage_gain == 0.0: raise cv.Invalid("The gains can't be zero") - max_energy = (0.25 * 0.25 / 3600 / (2 ** -4)) / (voltage_gain * current_gain) - min_energy = (0.25 * 0.25 / 3600 / (2 ** 18)) / (voltage_gain * current_gain) + max_energy = (0.25 * 0.25 / 3600 / (2**-4)) / (voltage_gain * current_gain) + min_energy = (0.25 * 0.25 / 3600 / (2**18)) / (voltage_gain * current_gain) mech_min_energy = (0.25 * 0.25 / 3600 / 7.8) / (voltage_gain * current_gain) if pulse_energy < min_energy or pulse_energy > max_energy: raise cv.Invalid( diff --git a/esphome/components/esp8266/boards.py b/esphome/components/esp8266/boards.py index 410e934615..8b0a23a00f 100644 --- a/esphome/components/esp8266/boards.py +++ b/esphome/components/esp8266/boards.py @@ -1,4 +1,4 @@ -FLASH_SIZE_1_MB = 2 ** 20 +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 diff --git a/esphome/components/ledc/output.py b/esphome/components/ledc/output.py index 895dcc998b..f6dc89cd9b 100644 --- a/esphome/components/ledc/output.py +++ b/esphome/components/ledc/output.py @@ -13,12 +13,12 @@ DEPENDENCIES = ["esp32"] def calc_max_frequency(bit_depth): - return 80e6 / (2 ** bit_depth) + return 80e6 / (2**bit_depth) def calc_min_frequency(bit_depth): - max_div_num = ((2 ** 20) - 1) / 256.0 - return 80e6 / (max_div_num * (2 ** bit_depth)) + max_div_num = ((2**20) - 1) / 256.0 + return 80e6 / (max_div_num * (2**bit_depth)) def validate_frequency(value): diff --git a/esphome/mqtt.py b/esphome/mqtt.py index 07602e8ced..0ddd976072 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -47,7 +47,7 @@ def initialize(config, subscriptions, on_message, username, password, client_id) except OSError: pass - wait_time = min(2 ** tries, 300) + wait_time = min(2**tries, 300) _LOGGER.warning( "Disconnected from MQTT (%s). Trying to reconnect in %s s", result_code, diff --git a/requirements_test.txt b/requirements_test.txt index 499e950481..45d8c02099 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==2.12.2 flake8==4.0.1 -black==21.12b0 +black==22.1.0 pre-commit # Unit tests From 62b366a5ec4edc7b7dec0b8ad4089b5bafcf1f90 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 1 Feb 2022 13:05:59 +0100 Subject: [PATCH 069/238] Fix ESP32C3 toolchain requires stdarg import in helpers (#3151) --- esphome/core/helpers.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 05c86f86bb..6d399c4064 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #if defined(USE_ESP8266) #include From 21803607e79ecb799171c59081c53979c1bfc84a Mon Sep 17 00:00:00 2001 From: Keilin Bickar Date: Thu, 3 Feb 2022 13:24:31 -0500 Subject: [PATCH 070/238] Add new Lock core component (#2958) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/api/api.proto | 59 ++++ esphome/components/api/api_connection.cpp | 43 +++ esphome/components/api/api_connection.h | 5 + esphome/components/api/api_pb2.cpp | 262 +++++++++++++++++- esphome/components/api/api_pb2.h | 65 +++++ esphome/components/api/api_pb2_service.cpp | 42 +++ esphome/components/api/api_pb2_service.h | 15 + esphome/components/api/api_server.cpp | 9 + esphome/components/api/api_server.h | 3 + esphome/components/api/list_entities.cpp | 3 + esphome/components/api/list_entities.h | 3 + esphome/components/api/subscribe_state.cpp | 3 + esphome/components/api/subscribe_state.h | 3 + esphome/components/api/util.cpp | 15 + esphome/components/api/util.h | 6 + esphome/components/cover/__init__.py | 2 +- esphome/components/lock/__init__.py | 102 +++++++ esphome/components/lock/automation.h | 87 ++++++ esphome/components/lock/lock.cpp | 109 ++++++++ esphome/components/lock/lock.h | 178 ++++++++++++ esphome/components/mqtt/__init__.py | 1 + esphome/components/mqtt/mqtt_lock.cpp | 55 ++++ esphome/components/mqtt/mqtt_lock.h | 41 +++ esphome/components/output/lock/__init__.py | 23 ++ .../components/output/lock/output_lock.cpp | 22 ++ esphome/components/output/lock/output_lock.h | 24 ++ .../prometheus/prometheus_handler.cpp | 30 ++ .../prometheus/prometheus_handler.h | 7 + esphome/components/template/lock/__init__.py | 103 +++++++ .../template/lock/template_lock.cpp | 59 ++++ .../components/template/lock/template_lock.h | 38 +++ esphome/components/web_server/web_server.cpp | 70 +++++ esphome/components/web_server/web_server.h | 10 + esphome/const.py | 5 + esphome/core/application.h | 19 ++ esphome/core/controller.cpp | 6 + esphome/core/controller.h | 6 + esphome/core/defines.h | 1 + script/ci-custom.py | 1 + tests/test1.yaml | 25 ++ 41 files changed, 1558 insertions(+), 3 deletions(-) create mode 100644 esphome/components/lock/__init__.py create mode 100644 esphome/components/lock/automation.h create mode 100644 esphome/components/lock/lock.cpp create mode 100644 esphome/components/lock/lock.h create mode 100644 esphome/components/mqtt/mqtt_lock.cpp create mode 100644 esphome/components/mqtt/mqtt_lock.h create mode 100644 esphome/components/output/lock/__init__.py create mode 100644 esphome/components/output/lock/output_lock.cpp create mode 100644 esphome/components/output/lock/output_lock.h create mode 100644 esphome/components/template/lock/__init__.py create mode 100644 esphome/components/template/lock/template_lock.cpp create mode 100644 esphome/components/template/lock/template_lock.h diff --git a/CODEOWNERS b/CODEOWNERS index f075cc8649..5fa3090aaf 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -89,6 +89,7 @@ esphome/components/json/* @OttoWinter esphome/components/kalman_combinator/* @Cat-Ion esphome/components/ledc/* @OttoWinter esphome/components/light/* @esphome/core +esphome/components/lock/* @esphome/core esphome/components/logger/* @esphome/core esphome/components/ltr390/* @sjtrny esphome/components/max7219digit/* @rspaargaren diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 9d43e22497..3ab426979e 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -41,6 +41,7 @@ service APIConnection { rpc number_command (NumberCommandRequest) returns (void) {} rpc select_command (SelectCommandRequest) returns (void) {} rpc button_command (ButtonCommandRequest) returns (void) {} + rpc lock_command (LockCommandRequest) returns (void) {} } @@ -956,6 +957,63 @@ message SelectCommandRequest { string state = 2; } + +// ==================== LOCK ==================== +enum LockState { + LOCK_STATE_NONE = 0; + LOCK_STATE_LOCKED = 1; + LOCK_STATE_UNLOCKED = 2; + LOCK_STATE_JAMMED = 3; + LOCK_STATE_LOCKING = 4; + LOCK_STATE_UNLOCKING = 5; +} +enum LockCommand { + LOCK_UNLOCK = 0; + LOCK_LOCK = 1; + LOCK_OPEN = 2; +} +message ListEntitiesLockResponse { + option (id) = 58; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_LOCK"; + + 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; + bool assumed_state = 8; + + bool supports_open = 9; + bool requires_code = 10; + + # Not yet implemented: + string code_format = 11; +} +message LockStateResponse { + option (id) = 59; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_LOCK"; + option (no_delay) = true; + fixed32 key = 1; + LockState state = 2; +} +message LockCommandRequest { + option (id) = 60; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_LOCK"; + option (no_delay) = true; + fixed32 key = 1; + LockCommand command = 2; + + # Not yet implemented: + bool has_code = 3; + string code = 4; +} + // ==================== BUTTON ==================== message ListEntitiesButtonResponse { option (id) = 61; @@ -980,3 +1038,4 @@ message ButtonCommandRequest { fixed32 key = 1; } + diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 8a106dc39c..21388b547e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -700,6 +700,49 @@ void APIConnection::button_command(const ButtonCommandRequest &msg) { } #endif +#ifdef USE_LOCK +bool APIConnection::send_lock_state(lock::Lock *a_lock, lock::LockState state) { + if (!this->state_subscription_) + return false; + + LockStateResponse resp{}; + resp.key = a_lock->get_object_id_hash(); + resp.state = static_cast(state); + return this->send_lock_state_response(resp); +} +bool APIConnection::send_lock_info(lock::Lock *a_lock) { + ListEntitiesLockResponse msg; + msg.key = a_lock->get_object_id_hash(); + msg.object_id = a_lock->get_object_id(); + msg.name = a_lock->get_name(); + msg.unique_id = get_default_unique_id("lock", a_lock); + msg.icon = a_lock->get_icon(); + msg.assumed_state = a_lock->traits.get_assumed_state(); + msg.disabled_by_default = a_lock->is_disabled_by_default(); + msg.entity_category = static_cast(a_lock->get_entity_category()); + msg.supports_open = a_lock->traits.get_supports_open(); + msg.requires_code = a_lock->traits.get_requires_code(); + return this->send_list_entities_lock_response(msg); +} +void APIConnection::lock_command(const LockCommandRequest &msg) { + lock::Lock *a_lock = App.get_lock_by_key(msg.key); + if (a_lock == nullptr) + return; + + switch (msg.command) { + case enums::LOCK_UNLOCK: + a_lock->unlock(); + break; + case enums::LOCK_LOCK: + a_lock->lock(); + break; + case enums::LOCK_OPEN: + a_lock->open(); + break; + } +} +#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 c1f520c83b..10f0becc54 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -77,6 +77,11 @@ class APIConnection : public APIServerConnection { #ifdef USE_BUTTON bool send_button_info(button::Button *button); void button_command(const ButtonCommandRequest &msg) override; +#endif +#ifdef USE_LOCK + bool send_lock_state(lock::Lock *a_lock, lock::LockState state); + bool send_lock_info(lock::Lock *a_lock); + void lock_command(const LockCommandRequest &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 4ecd727f29..e7e0476afc 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -278,6 +278,36 @@ template<> const char *proto_enum_to_string(enums::NumberMode return "UNKNOWN"; } } +template<> const char *proto_enum_to_string(enums::LockState value) { + switch (value) { + case enums::LOCK_STATE_NONE: + return "LOCK_STATE_NONE"; + case enums::LOCK_STATE_LOCKED: + return "LOCK_STATE_LOCKED"; + case enums::LOCK_STATE_UNLOCKED: + return "LOCK_STATE_UNLOCKED"; + case enums::LOCK_STATE_JAMMED: + return "LOCK_STATE_JAMMED"; + case enums::LOCK_STATE_LOCKING: + return "LOCK_STATE_LOCKING"; + case enums::LOCK_STATE_UNLOCKING: + return "LOCK_STATE_UNLOCKING"; + default: + return "UNKNOWN"; + } +} +template<> const char *proto_enum_to_string(enums::LockCommand value) { + switch (value) { + case enums::LOCK_UNLOCK: + return "LOCK_UNLOCK"; + case enums::LOCK_LOCK: + return "LOCK_LOCK"; + case enums::LOCK_OPEN: + return "LOCK_OPEN"; + default: + return "UNKNOWN"; + } +} bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -4186,6 +4216,234 @@ void SelectCommandRequest::dump_to(std::string &out) const { out.append("}"); } #endif +bool ListEntitiesLockResponse::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; + } + case 8: { + this->assumed_state = value.as_bool(); + return true; + } + case 9: { + this->supports_open = value.as_bool(); + return true; + } + case 10: { + this->requires_code = value.as_bool(); + return true; + } + default: + return false; + } +} +bool ListEntitiesLockResponse::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; + } + case 11: { + this->code_format = value.as_string(); + return true; + } + default: + return false; + } +} +bool ListEntitiesLockResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 2: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void ListEntitiesLockResponse::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); + buffer.encode_bool(8, this->assumed_state); + buffer.encode_bool(9, this->supports_open); + buffer.encode_bool(10, this->requires_code); + buffer.encode_string(11, this->code_format); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void ListEntitiesLockResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("ListEntitiesLockResponse {\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(" assumed_state: "); + out.append(YESNO(this->assumed_state)); + out.append("\n"); + + out.append(" supports_open: "); + out.append(YESNO(this->supports_open)); + out.append("\n"); + + out.append(" requires_code: "); + out.append(YESNO(this->requires_code)); + out.append("\n"); + + out.append(" code_format: "); + out.append("'").append(this->code_format).append("'"); + out.append("\n"); + out.append("}"); +} +#endif +bool LockStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: { + this->state = value.as_enum(); + return true; + } + default: + return false; + } +} +bool LockStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void LockStateResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_fixed32(1, this->key); + buffer.encode_enum(2, this->state); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void LockStateResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("LockStateResponse {\n"); + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" state: "); + out.append(proto_enum_to_string(this->state)); + out.append("\n"); + out.append("}"); +} +#endif +bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: { + this->command = value.as_enum(); + return true; + } + case 3: { + this->has_code = value.as_bool(); + return true; + } + default: + return false; + } +} +bool LockCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 4: { + this->code = value.as_string(); + return true; + } + default: + return false; + } +} +bool LockCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void LockCommandRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_fixed32(1, this->key); + buffer.encode_enum(2, this->command); + buffer.encode_bool(3, this->has_code); + buffer.encode_string(4, this->code); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void LockCommandRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("LockCommandRequest {\n"); + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" command: "); + out.append(proto_enum_to_string(this->command)); + out.append("\n"); + + out.append(" has_code: "); + out.append(YESNO(this->has_code)); + out.append("\n"); + + out.append(" code: "); + out.append("'").append(this->code).append("'"); + out.append("\n"); + out.append("}"); +} +#endif bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 6: { @@ -4248,7 +4506,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesButtonResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesButtonResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -4298,7 +4556,7 @@ bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { 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]; + __attribute__((unused)) char buffer[64]; out.append("ButtonCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 48ecb5f682..4c9a0e9c0f 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -128,6 +128,19 @@ enum NumberMode : uint32_t { NUMBER_MODE_BOX = 1, NUMBER_MODE_SLIDER = 2, }; +enum LockState : uint32_t { + LOCK_STATE_NONE = 0, + LOCK_STATE_LOCKED = 1, + LOCK_STATE_UNLOCKED = 2, + LOCK_STATE_JAMMED = 3, + LOCK_STATE_LOCKING = 4, + LOCK_STATE_UNLOCKING = 5, +}; +enum LockCommand : uint32_t { + LOCK_UNLOCK = 0, + LOCK_LOCK = 1, + LOCK_OPEN = 2, +}; } // namespace enums @@ -1049,6 +1062,58 @@ 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 ListEntitiesLockResponse : 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{}; + bool assumed_state{false}; + bool supports_open{false}; + bool requires_code{false}; + std::string code_format{}; + 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 LockStateResponse : public ProtoMessage { + public: + uint32_t key{0}; + enums::LockState state{}; + 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_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class LockCommandRequest : public ProtoMessage { + public: + uint32_t key{0}; + enums::LockCommand command{}; + bool has_code{false}; + std::string code{}; + 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 ListEntitiesButtonResponse : public ProtoMessage { public: std::string object_id{}; diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 567fbf02c9..d981a3bf4e 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -282,6 +282,24 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon #endif #ifdef USE_SELECT #endif +#ifdef USE_LOCK +bool APIServerConnectionBase::send_list_entities_lock_response(const ListEntitiesLockResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_list_entities_lock_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 58); +} +#endif +#ifdef USE_LOCK +bool APIServerConnectionBase::send_lock_state_response(const LockStateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_lock_state_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 59); +} +#endif +#ifdef USE_LOCK +#endif #ifdef USE_BUTTON bool APIServerConnectionBase::send_list_entities_button_response(const ListEntitiesButtonResponse &msg) { #ifdef HAS_PROTO_MESSAGE_DUMP @@ -523,6 +541,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 60: { +#ifdef USE_LOCK + LockCommandRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_lock_command_request: %s", msg.dump().c_str()); +#endif + this->on_lock_command_request(msg); #endif break; } @@ -771,6 +800,19 @@ void APIServerConnection::on_button_command_request(const ButtonCommandRequest & this->button_command(msg); } #endif +#ifdef USE_LOCK +void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->lock_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 50b08d3ec4..5aaf831c91 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -130,6 +130,15 @@ class APIServerConnectionBase : public ProtoService { #ifdef USE_SELECT virtual void on_select_command_request(const SelectCommandRequest &value){}; #endif +#ifdef USE_LOCK + bool send_list_entities_lock_response(const ListEntitiesLockResponse &msg); +#endif +#ifdef USE_LOCK + bool send_lock_state_response(const LockStateResponse &msg); +#endif +#ifdef USE_LOCK + virtual void on_lock_command_request(const LockCommandRequest &value){}; +#endif #ifdef USE_BUTTON bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg); #endif @@ -180,6 +189,9 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_BUTTON virtual void button_command(const ButtonCommandRequest &msg) = 0; +#endif +#ifdef USE_LOCK + virtual void lock_command(const LockCommandRequest &msg) = 0; #endif protected: void on_hello_request(const HelloRequest &msg) override; @@ -221,6 +233,9 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_BUTTON void on_button_command_request(const ButtonCommandRequest &msg) override; #endif +#ifdef USE_LOCK + void on_lock_command_request(const LockCommandRequest &msg) override; +#endif }; } // namespace api diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 93c9209716..4521cc5bfc 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -263,6 +263,15 @@ void APIServer::on_select_update(select::Select *obj, const std::string &state) } #endif +#ifdef USE_LOCK +void APIServer::on_lock_update(lock::Lock *obj) { + if (obj->is_internal()) + return; + for (auto &c : this->clients_) + c->send_lock_state(obj, obj->state); +} +#endif + float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; } void APIServer::set_port(uint16_t port) { this->port_ = port; } APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 23b01df375..3214da5b3d 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -66,6 +66,9 @@ class APIServer : public Component, public Controller { #endif #ifdef USE_SELECT void on_select_update(select::Select *obj, const std::string &state) override; +#endif +#ifdef USE_LOCK + void on_lock_update(lock::Lock *obj) override; #endif void send_homeassistant_service_call(const HomeassistantServiceResponse &call); void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index 35a590a828..fb0dfa3d05 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -35,6 +35,9 @@ bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) return this->client_->send_text_sensor_info(text_sensor); } #endif +#ifdef USE_LOCK +bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_info(a_lock); } +#endif bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); } ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client) diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index 81b814676a..bfceb39ebf 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -48,6 +48,9 @@ class ListEntitiesIterator : public ComponentIterator { #endif #ifdef USE_SELECT bool on_select(select::Select *select) override; +#endif +#ifdef USE_LOCK + bool on_lock(lock::Lock *a_lock) override; #endif bool on_end() override; diff --git a/esphome/components/api/subscribe_state.cpp b/esphome/components/api/subscribe_state.cpp index 07b3913ff7..10416ecc5c 100644 --- a/esphome/components/api/subscribe_state.cpp +++ b/esphome/components/api/subscribe_state.cpp @@ -47,6 +47,9 @@ bool InitialStateIterator::on_select(select::Select *select) { return this->client_->send_select_state(select, select->state); } #endif +#ifdef USE_LOCK +bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); } +#endif InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client) : ComponentIterator(server), client_(client) {} diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index 4b83b5e793..caea013f84 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -45,6 +45,9 @@ class InitialStateIterator : public ComponentIterator { #endif #ifdef USE_SELECT bool on_select(select::Select *select) override; +#endif +#ifdef USE_LOCK + bool on_lock(lock::Lock *a_lock) override; #endif protected: APIConnection *client_; diff --git a/esphome/components/api/util.cpp b/esphome/components/api/util.cpp index f5fd752101..fd55f89f9b 100644 --- a/esphome/components/api/util.cpp +++ b/esphome/components/api/util.cpp @@ -212,6 +212,21 @@ void ComponentIterator::advance() { } } break; +#endif +#ifdef USE_LOCK + case IteratorState::LOCK: + if (this->at_ >= App.get_locks().size()) { + advance_platform = true; + } else { + auto *a_lock = App.get_locks()[this->at_]; + if (a_lock->is_internal()) { + success = true; + break; + } else { + success = this->on_lock(a_lock); + } + } + break; #endif case IteratorState::MAX: if (this->on_end()) { diff --git a/esphome/components/api/util.h b/esphome/components/api/util.h index f329867a4e..9204b0829e 100644 --- a/esphome/components/api/util.h +++ b/esphome/components/api/util.h @@ -56,6 +56,9 @@ class ComponentIterator { #endif #ifdef USE_SELECT virtual bool on_select(select::Select *select) = 0; +#endif +#ifdef USE_LOCK + virtual bool on_lock(lock::Lock *a_lock) = 0; #endif virtual bool on_end(); @@ -99,6 +102,9 @@ class ComponentIterator { #endif #ifdef USE_SELECT SELECT, +#endif +#ifdef USE_LOCK + LOCK, #endif MAX, } state_{IteratorState::NONE}; diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 0fd27f3f27..d2421f07d9 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -7,6 +7,7 @@ from esphome.const import ( CONF_ID, CONF_DEVICE_CLASS, CONF_STATE, + CONF_ON_OPEN, CONF_POSITION, CONF_POSITION_COMMAND_TOPIC, CONF_POSITION_STATE_TOPIC, @@ -74,7 +75,6 @@ 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( diff --git a/esphome/components/lock/__init__.py b/esphome/components/lock/__init__.py new file mode 100644 index 0000000000..f659c48a6e --- /dev/null +++ b/esphome/components/lock/__init__.py @@ -0,0 +1,102 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.automation import Condition, maybe_simple_id +from esphome.components import mqtt +from esphome.const import ( + CONF_ID, + CONF_ON_LOCK, + CONF_ON_UNLOCK, + 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 + +lock_ns = cg.esphome_ns.namespace("lock") +Lock = lock_ns.class_("Lock", cg.EntityBase) +LockPtr = Lock.operator("ptr") +LockCall = lock_ns.class_("LockCall") + +UnlockAction = lock_ns.class_("UnlockAction", automation.Action) +LockAction = lock_ns.class_("LockAction", automation.Action) +OpenAction = lock_ns.class_("OpenAction", automation.Action) +LockPublishAction = lock_ns.class_("LockPublishAction", automation.Action) + +LockCondition = lock_ns.class_("LockCondition", Condition) +LockLockTrigger = lock_ns.class_("LockLockTrigger", automation.Trigger.template()) +LockUnlockTrigger = lock_ns.class_("LockUnlockTrigger", automation.Trigger.template()) + +LOCK_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTLockComponent), + cv.Optional(CONF_ON_LOCK): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockLockTrigger), + } + ), + cv.Optional(CONF_ON_UNLOCK): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockUnlockTrigger), + } + ), + } +) + + +async def setup_lock_core_(var, config): + await setup_entity(var, config) + + for conf in config.get(CONF_ON_LOCK, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_UNLOCK, []): + 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_lock(var, config): + if not CORE.has_id(config[CONF_ID]): + var = cg.Pvariable(config[CONF_ID], var) + cg.add(cg.App.register_lock(var)) + await setup_lock_core_(var, config) + + +LOCK_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(Lock), + } +) + + +@automation.register_action("lock.unlock", UnlockAction, LOCK_ACTION_SCHEMA) +@automation.register_action("lock.lock", LockAction, LOCK_ACTION_SCHEMA) +@automation.register_action("lock.open", OpenAction, LOCK_ACTION_SCHEMA) +async def lock_action_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("lock.is_locked", LockCondition, LOCK_ACTION_SCHEMA) +async def lock_is_on_to_code(config, condition_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(condition_id, template_arg, paren, True) + + +@automation.register_condition("lock.is_unlocked", LockCondition, LOCK_ACTION_SCHEMA) +async def lock_is_off_to_code(config, condition_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(condition_id, template_arg, paren, False) + + +@coroutine_with_priority(100.0) +async def to_code(config): + cg.add_global(lock_ns.using) + cg.add_define("USE_LOCK") diff --git a/esphome/components/lock/automation.h b/esphome/components/lock/automation.h new file mode 100644 index 0000000000..74cfbe2ef6 --- /dev/null +++ b/esphome/components/lock/automation.h @@ -0,0 +1,87 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/lock/lock.h" + +namespace esphome { +namespace lock { + +template class LockAction : public Action { + public: + explicit LockAction(Lock *a_lock) : lock_(a_lock) {} + + void play(Ts... x) override { this->lock_->lock(); } + + protected: + Lock *lock_; +}; + +template class UnlockAction : public Action { + public: + explicit UnlockAction(Lock *a_lock) : lock_(a_lock) {} + + void play(Ts... x) override { this->lock_->unlock(); } + + protected: + Lock *lock_; +}; + +template class OpenAction : public Action { + public: + explicit OpenAction(Lock *a_lock) : lock_(a_lock) {} + + void play(Ts... x) override { this->lock_->open(); } + + protected: + Lock *lock_; +}; + +template class LockCondition : public Condition { + public: + LockCondition(Lock *parent, bool state) : parent_(parent), state_(state) {} + bool check(Ts... x) override { + auto check_state = this->state_ ? LockState::LOCK_STATE_LOCKED : LockState::LOCK_STATE_UNLOCKED; + return this->parent_->state == check_state; + } + + protected: + Lock *parent_; + bool state_; +}; + +class LockLockTrigger : public Trigger<> { + public: + LockLockTrigger(Lock *a_lock) { + a_lock->add_on_state_callback([this, a_lock]() { + if (a_lock->state == LockState::LOCK_STATE_LOCKED) { + this->trigger(); + } + }); + } +}; + +class LockUnlockTrigger : public Trigger<> { + public: + LockUnlockTrigger(Lock *a_lock) { + a_lock->add_on_state_callback([this, a_lock]() { + if (a_lock->state == LockState::LOCK_STATE_UNLOCKED) { + this->trigger(); + } + }); + } +}; + +template class LockPublishAction : public Action { + public: + LockPublishAction(Lock *a_lock) : lock_(a_lock) {} + TEMPLATABLE_VALUE(LockState, state) + + void play(Ts... x) override { this->lock_->publish_state(this->state_.value(x...)); } + + protected: + Lock *lock_; +}; + +} // namespace lock +} // namespace esphome diff --git a/esphome/components/lock/lock.cpp b/esphome/components/lock/lock.cpp new file mode 100644 index 0000000000..e32ab6d0a6 --- /dev/null +++ b/esphome/components/lock/lock.cpp @@ -0,0 +1,109 @@ +#include "lock.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace lock { + +static const char *const TAG = "lock"; + +const char *lock_state_to_string(LockState state) { + switch (state) { + case LOCK_STATE_LOCKED: + return "LOCKED"; + case LOCK_STATE_UNLOCKED: + return "UNLOCKED"; + case LOCK_STATE_JAMMED: + return "JAMMED"; + case LOCK_STATE_LOCKING: + return "LOCKING"; + case LOCK_STATE_UNLOCKING: + return "UNLOCKING"; + case LOCK_STATE_NONE: + default: + return "UNKNOWN"; + } +} + +Lock::Lock(const std::string &name) : EntityBase(name), state(LOCK_STATE_NONE) {} +Lock::Lock() : Lock("") {} +LockCall Lock::make_call() { return LockCall(this); } + +void Lock::lock() { + auto call = this->make_call(); + call.set_state(LOCK_STATE_LOCKED); + this->control(call); +} +void Lock::unlock() { + auto call = this->make_call(); + call.set_state(LOCK_STATE_UNLOCKED); + this->control(call); +} +void Lock::open() { + if (traits.get_supports_open()) { + ESP_LOGD(TAG, "'%s' Opening.", this->get_name().c_str()); + this->open_latch(); + } else { + ESP_LOGW(TAG, "'%s' Does not support Open.", this->get_name().c_str()); + } +} +void Lock::publish_state(LockState state) { + if (!this->publish_dedup_.next(state)) + return; + + this->state = state; + this->rtc_.save(&this->state); + ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), lock_state_to_string(state)); + this->state_callback_.call(); +} + +void Lock::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } +uint32_t Lock::hash_base() { return 856245656UL; } + +void LockCall::perform() { + ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); + this->validate_(); + if (this->state_.has_value()) { + const char *state_s = lock_state_to_string(*this->state_); + ESP_LOGD(TAG, " State: %s", state_s); + } + this->parent_->control(*this); +} +void LockCall::validate_() { + if (this->state_.has_value()) { + auto state = *this->state_; + if (!this->parent_->traits.supports_state(state)) { + ESP_LOGW(TAG, " State %s is not supported by this device!", lock_state_to_string(*this->state_)); + this->state_.reset(); + } + } +} +LockCall &LockCall::set_state(LockState state) { + this->state_ = state; + return *this; +} +LockCall &LockCall::set_state(optional state) { + this->state_ = state; + return *this; +} +LockCall &LockCall::set_state(const std::string &state) { + if (str_equals_case_insensitive(state, "LOCKED")) { + this->set_state(LOCK_STATE_LOCKED); + } else if (str_equals_case_insensitive(state, "UNLOCKED")) { + this->set_state(LOCK_STATE_UNLOCKED); + } else if (str_equals_case_insensitive(state, "JAMMED")) { + this->set_state(LOCK_STATE_JAMMED); + } else if (str_equals_case_insensitive(state, "LOCKING")) { + this->set_state(LOCK_STATE_LOCKING); + } else if (str_equals_case_insensitive(state, "UNLOCKING")) { + this->set_state(LOCK_STATE_UNLOCKING); + } else if (str_equals_case_insensitive(state, "NONE")) { + this->set_state(LOCK_STATE_NONE); + } else { + ESP_LOGW(TAG, "'%s' - Unrecognized state %s", this->parent_->get_name().c_str(), state.c_str()); + } + return *this; +} +const optional &LockCall::get_state() const { return this->state_; } + +} // namespace lock +} // namespace esphome diff --git a/esphome/components/lock/lock.h b/esphome/components/lock/lock.h new file mode 100644 index 0000000000..f11035c03e --- /dev/null +++ b/esphome/components/lock/lock.h @@ -0,0 +1,178 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/entity_base.h" +#include "esphome/core/preferences.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace lock { + +class Lock; + +#define LOG_LOCK(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()); \ + } \ + if ((obj)->traits.get_assumed_state()) { \ + ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \ + } \ + } +/// Enum for all states a lock can be in. +enum LockState : uint8_t { + LOCK_STATE_NONE = 0, + LOCK_STATE_LOCKED = 1, + LOCK_STATE_UNLOCKED = 2, + LOCK_STATE_JAMMED = 3, + LOCK_STATE_LOCKING = 4, + LOCK_STATE_UNLOCKING = 5 +}; +const char *lock_state_to_string(LockState state); + +class LockTraits { + public: + LockTraits() = default; + + bool get_supports_open() const { return this->supports_open_; } + void set_supports_open(bool supports_open) { this->supports_open_ = supports_open; } + bool get_requires_code() const { return this->requires_code_; } + void set_requires_code(bool requires_code) { this->requires_code_ = requires_code; } + bool get_assumed_state() const { return this->assumed_state_; } + void set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } + + bool supports_state(LockState state) const { return supported_states_.count(state); } + std::set get_supported_states() const { return supported_states_; } + void set_supported_states(std::set states) { supported_states_ = std::move(states); } + void add_supported_state(LockState state) { supported_states_.insert(state); } + + protected: + bool supports_open_{false}; + bool requires_code_{false}; + bool assumed_state_{false}; + std::set supported_states_ = {LOCK_STATE_NONE, LOCK_STATE_LOCKED, LOCK_STATE_UNLOCKED}; +}; + +/** This class is used to encode all control actions on a lock device. + * + * It is supposed to be used by all code that wishes to control a lock device (mqtt, api, lambda etc). + * Create an instance of this class by calling `id(lock_device).make_call();`. Then set all attributes + * with the `set_x` methods. Finally, to apply the changes call `.perform();`. + * + * The integration that implements the lock device receives this instance with the `control` method. + * It should check all the properties it implements and apply them as needed. It should do so by + * getting all properties it controls with the getter methods in this class. If the optional value is + * set (check with `.has_value()`) that means the user wants to control this property. Get the value + * of the optional with the star operator (`*call.get_state()`) and apply it. + */ +class LockCall { + public: + LockCall(Lock *parent) : parent_(parent) {} + + /// Set the state of the lock device. + LockCall &set_state(LockState state); + /// Set the state of the lock device. + LockCall &set_state(optional state); + /// Set the state of the lock device based on a string. + LockCall &set_state(const std::string &state); + + void perform(); + + const optional &get_state() const; + + protected: + void validate_(); + + Lock *const parent_; + optional state_; +}; + +/** Base class for all locks. + * + * A lock is basically a switch with a combination of a binary sensor (for reporting lock values) + * and a write_state method that writes a state to the hardware. Locks can also have an "open" + * method to unlatch. + * + * For integrations: Integrations must implement the method control(). + * Control will be called with the arguments supplied by the user and should be used + * to control all values of the lock. + */ +class Lock : public EntityBase { + public: + explicit Lock(); + explicit Lock(const std::string &name); + + /** Make a lock device control call, this is used to control the lock device, see the LockCall description + * for more info. + * @return A new LockCall instance targeting this lock device. + */ + LockCall make_call(); + + /** Publish a state to the front-end from the back-end. + * + * Then the internal value member is set and finally the callbacks are called. + * + * @param state The new state. + */ + void publish_state(LockState state); + + /// The current reported state of the lock. + LockState state{LOCK_STATE_NONE}; + + LockTraits traits; + + /** Turn this lock on. This is called by the front-end. + * + * For implementing locks, please override control. + */ + void lock(); + /** Turn this lock off. This is called by the front-end. + * + * For implementing locks, please override control. + */ + void unlock(); + /** Open (unlatch) this lock. This is called by the front-end. + * + * For implementing locks, please override control. + */ + void open(); + + /** Set callback for state changes. + * + * @param callback The void(bool) callback. + */ + void add_on_state_callback(std::function &&callback); + + protected: + friend LockCall; + + /** Perform the open latch action with hardware. This method is optional to implement + * when creating a new lock. + * + * In the implementation of this method, it is recommended you also call + * publish_state with "unlock" to acknowledge that the state was written to the hardware. + */ + virtual void open_latch() { unlock(); }; + + /** Control the lock device, this is a virtual method that each lock integration must implement. + * + * See more info in LockCall. The integration should check all of its values in this method and + * set them accordingly. At the end of the call, the integration must call `publish_state()` to + * notify the frontend of a changed state. + * + * @param call The LockCall instance encoding all attribute changes. + */ + virtual void control(const LockCall &call) = 0; + + uint32_t hash_base() override; + + CallbackManager state_callback_{}; + Deduplicator publish_dedup_; + ESPPreferenceObject rtc_; +}; + +} // namespace lock +} // namespace esphome diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index a7e14bd4a6..88e5d43509 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -97,6 +97,7 @@ MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent) MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent) MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent) MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent) +MQTTLockComponent = mqtt_ns.class_("MQTTLockComponent", MQTTComponent) MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator") MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = { diff --git a/esphome/components/mqtt/mqtt_lock.cpp b/esphome/components/mqtt/mqtt_lock.cpp new file mode 100644 index 0000000000..197d0c32d4 --- /dev/null +++ b/esphome/components/mqtt/mqtt_lock.cpp @@ -0,0 +1,55 @@ +#include "mqtt_lock.h" +#include "esphome/core/log.h" + +#include "mqtt_const.h" + +#ifdef USE_MQTT +#ifdef USE_LOCK + +namespace esphome { +namespace mqtt { + +static const char *const TAG = "mqtt.lock"; + +using namespace esphome::lock; + +MQTTLockComponent::MQTTLockComponent(lock::Lock *a_lock) : lock_(a_lock) {} + +void MQTTLockComponent::setup() { + this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { + if (strcasecmp(payload.c_str(), "LOCK") == 0) { + this->lock_->lock(); + } else if (strcasecmp(payload.c_str(), "UNLOCK") == 0) { + this->lock_->unlock(); + } else if (strcasecmp(payload.c_str(), "OPEN") == 0) { + this->lock_->open(); + } else { + ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str()); + this->status_momentary_warning("state", 5000); + } + }); + this->lock_->add_on_state_callback([this]() { this->defer("send", [this]() { this->publish_state(); }); }); +} +void MQTTLockComponent::dump_config() { + ESP_LOGCONFIG(TAG, "MQTT Lock '%s': ", this->lock_->get_name().c_str()); + LOG_MQTT_COMPONENT(true, true); +} + +std::string MQTTLockComponent::component_type() const { return "lock"; } +const EntityBase *MQTTLockComponent::get_entity() const { return this->lock_; } +void MQTTLockComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + if (this->lock_->traits.get_assumed_state()) + root[MQTT_OPTIMISTIC] = true; +} +bool MQTTLockComponent::send_initial_state() { return this->publish_state(); } + +bool MQTTLockComponent::publish_state() { + std::string payload = lock_state_to_string(this->lock_->state); + return this->publish(this->get_state_topic_(), payload); +} + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_lock.h b/esphome/components/mqtt/mqtt_lock.h new file mode 100644 index 0000000000..789f74c795 --- /dev/null +++ b/esphome/components/mqtt/mqtt_lock.h @@ -0,0 +1,41 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_MQTT +#ifdef USE_LOCK + +#include "esphome/components/lock/lock.h" +#include "mqtt_component.h" + +namespace esphome { +namespace mqtt { + +class MQTTLockComponent : public mqtt::MQTTComponent { + public: + explicit MQTTLockComponent(lock::Lock *a_lock); + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + void setup() override; + void dump_config() override; + + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; + + bool send_initial_state() override; + + bool publish_state(); + + protected: + /// "lock" component type. + std::string component_type() const override; + const EntityBase *get_entity() const override; + + lock::Lock *lock_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/output/lock/__init__.py b/esphome/components/output/lock/__init__.py new file mode 100644 index 0000000000..3be2cb09aa --- /dev/null +++ b/esphome/components/output/lock/__init__.py @@ -0,0 +1,23 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output, lock +from esphome.const import CONF_ID, CONF_OUTPUT +from .. import output_ns + +OutputLock = output_ns.class_("OutputLock", lock.Lock, cg.Component) + +CONFIG_SCHEMA = lock.LOCK_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(OutputLock), + cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await lock.register_lock(var, config) + + output_ = await cg.get_variable(config[CONF_OUTPUT]) + cg.add(var.set_output(output_)) diff --git a/esphome/components/output/lock/output_lock.cpp b/esphome/components/output/lock/output_lock.cpp new file mode 100644 index 0000000000..2545f62481 --- /dev/null +++ b/esphome/components/output/lock/output_lock.cpp @@ -0,0 +1,22 @@ +#include "output_lock.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace output { + +static const char *const TAG = "output.lock"; + +void OutputLock::dump_config() { LOG_LOCK("", "Output Lock", this); } + +void OutputLock::control(const lock::LockCall &call) { + auto state = *call.get_state(); + if (state == lock::LOCK_STATE_LOCKED) { + this->output_->turn_on(); + } else if (state == lock::LOCK_STATE_UNLOCKED) { + this->output_->turn_off(); + } + this->publish_state(state); +} + +} // namespace output +} // namespace esphome diff --git a/esphome/components/output/lock/output_lock.h b/esphome/components/output/lock/output_lock.h new file mode 100644 index 0000000000..c183c3a3ea --- /dev/null +++ b/esphome/components/output/lock/output_lock.h @@ -0,0 +1,24 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/lock/lock.h" +#include "esphome/components/output/binary_output.h" + +namespace esphome { +namespace output { + +class OutputLock : public lock::Lock, public Component { + public: + void set_output(BinaryOutput *output) { output_ = output; } + + float get_setup_priority() const override { return setup_priority::HARDWARE - 1.0f; } + void dump_config() override; + + protected: + void control(const lock::LockCall &call) override; + + output::BinaryOutput *output_; +}; + +} // namespace output +} // namespace esphome diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index e65729b184..e4dd6b9043 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -45,6 +45,12 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) { this->switch_row_(stream, obj); #endif +#ifdef USE_LOCK + this->lock_type_(stream); + for (auto *obj : App.get_locks()) + this->lock_row_(stream, obj); +#endif + req->send(stream); } @@ -310,6 +316,30 @@ void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch } #endif +#ifdef USE_LOCK +void PrometheusHandler::lock_type_(AsyncResponseStream *stream) { + stream->print(F("#TYPE esphome_lock_value GAUGE\n")); + stream->print(F("#TYPE esphome_lock_failed GAUGE\n")); +} +void PrometheusHandler::lock_row_(AsyncResponseStream *stream, lock::Lock *obj) { + if (obj->is_internal()) + return; + stream->print(F("esphome_lock_failed{id=\"")); + stream->print(obj->get_object_id().c_str()); + stream->print(F("\",name=\"")); + stream->print(obj->get_name().c_str()); + stream->print(F("\"} 0\n")); + // Data itself + stream->print(F("esphome_lock_value{id=\"")); + stream->print(obj->get_object_id().c_str()); + stream->print(F("\",name=\"")); + stream->print(obj->get_name().c_str()); + stream->print(F("\"} ")); + stream->print(obj->state); + stream->print('\n'); +} +#endif + } // namespace prometheus } // namespace esphome diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index 82e3fe28e0..5c8d51c60f 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -76,6 +76,13 @@ class PrometheusHandler : public AsyncWebHandler, public Component { void switch_row_(AsyncResponseStream *stream, switch_::Switch *obj); #endif +#ifdef USE_LOCK + /// Return the type for prometheus + void lock_type_(AsyncResponseStream *stream); + /// Return the lock Values state as prometheus data point + void lock_row_(AsyncResponseStream *stream, lock::Lock *obj); +#endif + web_server_base::WebServerBase *base_; }; diff --git a/esphome/components/template/lock/__init__.py b/esphome/components/template/lock/__init__.py new file mode 100644 index 0000000000..24709ff4f2 --- /dev/null +++ b/esphome/components/template/lock/__init__.py @@ -0,0 +1,103 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.components import lock +from esphome.const import ( + CONF_ASSUMED_STATE, + CONF_ID, + CONF_LAMBDA, + CONF_LOCK_ACTION, + CONF_OPEN_ACTION, + CONF_OPTIMISTIC, + CONF_STATE, + CONF_UNLOCK_ACTION, +) +from .. import template_ns + +TemplateLock = template_ns.class_("TemplateLock", lock.Lock, cg.Component) + +LockState = lock.lock_ns.enum("LockState") + +LOCK_STATES = { + "LOCKED": LockState.LOCK_STATE_LOCKED, + "UNLOCKED": LockState.LOCK_STATE_UNLOCKED, + "JAMMED": LockState.LOCK_STATE_JAMMED, + "LOCKING": LockState.LOCK_STATE_LOCKING, + "UNLOCKING": LockState.LOCK_STATE_UNLOCKING, +} + +validate_lock_state = cv.enum(LOCK_STATES, upper=True) + + +def validate(config): + if not config[CONF_OPTIMISTIC] and ( + CONF_LOCK_ACTION not in config or CONF_UNLOCK_ACTION not in config + ): + raise cv.Invalid( + "Either optimistic mode must be enabled, or lock_action and unlock_action must be set, " + "to handle the lock being changed." + ) + return config + + +CONFIG_SCHEMA = cv.All( + lock.LOCK_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TemplateLock), + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean, + cv.Optional(CONF_UNLOCK_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_LOCK_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True), + } + ).extend(cv.COMPONENT_SCHEMA), + validate, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await lock.register_lock(var, config) + + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], [], return_type=cg.optional.template(LockState) + ) + cg.add(var.set_state_lambda(template_)) + if CONF_UNLOCK_ACTION in config: + await automation.build_automation( + var.get_unlock_trigger(), [], config[CONF_UNLOCK_ACTION] + ) + if CONF_LOCK_ACTION in config: + await automation.build_automation( + var.get_lock_trigger(), [], config[CONF_LOCK_ACTION] + ) + if CONF_OPEN_ACTION in config: + await automation.build_automation( + var.get_open_trigger(), [], config[CONF_OPEN_ACTION] + ) + cg.add(var.traits.set_supports_open(CONF_OPEN_ACTION in config)) + cg.add(var.traits.set_assumed_state(config[CONF_ASSUMED_STATE])) + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + + +@automation.register_action( + "lock.template.publish", + lock.LockPublishAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(lock.Lock), + cv.Required(CONF_STATE): cv.templatable(validate_lock_state), + } + ), +) +async def lock_template_publish_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_STATE], args, LockState) + cg.add(var.set_state(template_)) + return var diff --git a/esphome/components/template/lock/template_lock.cpp b/esphome/components/template/lock/template_lock.cpp new file mode 100644 index 0000000000..87ba1046eb --- /dev/null +++ b/esphome/components/template/lock/template_lock.cpp @@ -0,0 +1,59 @@ +#include "template_lock.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace template_ { + +using namespace esphome::lock; + +static const char *const TAG = "template.lock"; + +TemplateLock::TemplateLock() + : lock_trigger_(new Trigger<>()), unlock_trigger_(new Trigger<>()), open_trigger_(new Trigger<>()) {} + +void TemplateLock::loop() { + if (!this->f_.has_value()) + return; + auto val = (*this->f_)(); + if (!val.has_value()) + return; + + this->publish_state(*val); +} +void TemplateLock::control(const lock::LockCall &call) { + if (this->prev_trigger_ != nullptr) { + this->prev_trigger_->stop_action(); + } + + auto state = *call.get_state(); + if (state == LOCK_STATE_LOCKED) { + this->prev_trigger_ = this->lock_trigger_; + this->lock_trigger_->trigger(); + } else if (state == LOCK_STATE_UNLOCKED) { + this->prev_trigger_ = this->unlock_trigger_; + this->unlock_trigger_->trigger(); + } + + if (this->optimistic_) + this->publish_state(state); +} +void TemplateLock::open_latch() { + if (this->prev_trigger_ != nullptr) { + this->prev_trigger_->stop_action(); + } + this->prev_trigger_ = this->open_trigger_; + this->open_trigger_->trigger(); +} +void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } +void TemplateLock::set_state_lambda(std::function()> &&f) { this->f_ = f; } +float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; } +Trigger<> *TemplateLock::get_lock_trigger() const { return this->lock_trigger_; } +Trigger<> *TemplateLock::get_unlock_trigger() const { return this->unlock_trigger_; } +Trigger<> *TemplateLock::get_open_trigger() const { return this->open_trigger_; } +void TemplateLock::dump_config() { + LOG_LOCK("", "Template Lock", this); + ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); +} + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/template/lock/template_lock.h b/esphome/components/template/lock/template_lock.h new file mode 100644 index 0000000000..4f798eca81 --- /dev/null +++ b/esphome/components/template/lock/template_lock.h @@ -0,0 +1,38 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/lock/lock.h" + +namespace esphome { +namespace template_ { + +class TemplateLock : public lock::Lock, public Component { + public: + TemplateLock(); + + void dump_config() override; + + void set_state_lambda(std::function()> &&f); + Trigger<> *get_lock_trigger() const; + Trigger<> *get_unlock_trigger() const; + Trigger<> *get_open_trigger() const; + void set_optimistic(bool optimistic); + void loop() override; + + float get_setup_priority() const override; + + protected: + void control(const lock::LockCall &call) override; + void open_latch() override; + + optional()>> f_; + bool optimistic_{false}; + Trigger<> *lock_trigger_; + Trigger<> *unlock_trigger_; + Trigger<> *open_trigger_; + Trigger<> *prev_trigger_{nullptr}; +}; + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 83b6ca1e2f..44d044750e 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -152,6 +152,13 @@ void WebServer::setup() { client->send(this->select_json(obj, obj->state).c_str(), "state"); } #endif + +#ifdef USE_LOCK + for (auto *obj : App.get_locks()) { + if (this->include_internal_ || !obj->is_internal()) + client->send(this->lock_json(obj, obj->state).c_str(), "state"); + } +#endif }); #ifdef USE_LOGGER @@ -287,6 +294,20 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { } #endif +#ifdef USE_LOCK + for (auto *obj : App.get_locks()) { + if (this->include_internal_ || !obj->is_internal()) { + write_row(stream, obj, "lock", "", [](AsyncResponseStream &stream, EntityBase *obj) { + lock::Lock *lock = (lock::Lock *) obj; + stream.print(""); + if (lock->traits.get_supports_open()) { + stream.print(""); + } + }); + } + } +#endif + stream->print(F("

See ESPHome Web API for " "REST API documentation.

")); if (this->allow_ota_) { @@ -763,6 +784,43 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value } #endif +#ifdef USE_LOCK +void WebServer::on_lock_update(lock::Lock *obj) { + this->events_.send(this->lock_json(obj, obj->state).c_str(), "state"); +} +std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value) { + return json::build_json([obj, value](JsonObject root) { + root["id"] = "lock-" + obj->get_object_id(); + root["state"] = lock::lock_state_to_string(value); + root["value"] = value; + }); +} +void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) { + for (lock::Lock *obj : App.get_locks()) { + if (obj->get_object_id() != match.id) + continue; + + if (request->method() == HTTP_GET) { + std::string data = this->lock_json(obj, obj->state); + request->send(200, "text/json", data.c_str()); + } else if (match.method == "lock") { + this->defer([obj]() { obj->lock(); }); + request->send(200); + } else if (match.method == "unlock") { + this->defer([obj]() { obj->unlock(); }); + request->send(200); + } else if (match.method == "open") { + this->defer([obj]() { obj->open(); }); + request->send(200); + } else { + request->send(404); + } + return; + } + request->send(404); +} +#endif + bool WebServer::canHandle(AsyncWebServerRequest *request) { if (request->url() == "/") return true; @@ -830,6 +888,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return true; #endif +#ifdef USE_LOCK + if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "lock") + return true; +#endif + return false; } void WebServer::handleRequest(AsyncWebServerRequest *request) { @@ -922,6 +985,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { return; } #endif + +#ifdef USE_LOCK + if (match.domain == "lock") { + this->handle_lock_request(request, match); + return; + } +#endif } bool WebServer::isRequestHandlerTrivial() { return false; } diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index afd4f1d4b5..3dd5c93f59 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -185,6 +185,16 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { std::string select_json(select::Select *obj, const std::string &value); #endif +#ifdef USE_LOCK + void on_lock_update(lock::Lock *obj) override; + + /// Handle a lock request under '/lock//'. + void handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match); + + /// Dump the lock state with its value as a JSON string. + std::string lock_json(lock::Lock *obj, lock::LockState value); +#endif + /// Override the web handler's canHandle method. bool canHandle(AsyncWebServerRequest *request) override; /// Override the web handler's handleRequest method. diff --git a/esphome/const.py b/esphome/const.py index 2d2f5f1da0..a2dd20269a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -334,6 +334,7 @@ CONF_LINE_THICKNESS = "line_thickness" CONF_LINE_TYPE = "line_type" CONF_LOADED_INTEGRATIONS = "loaded_integrations" CONF_LOCAL = "local" +CONF_LOCK_ACTION = "lock_action" CONF_LOG_TOPIC = "log_topic" CONF_LOGGER = "logger" CONF_LOGS = "logs" @@ -426,9 +427,11 @@ CONF_ON_ENROLLMENT_SCAN = "on_enrollment_scan" CONF_ON_FINGER_SCAN_MATCHED = "on_finger_scan_matched" CONF_ON_FINGER_SCAN_UNMATCHED = "on_finger_scan_unmatched" CONF_ON_JSON_MESSAGE = "on_json_message" +CONF_ON_LOCK = "on_lock" CONF_ON_LOOP = "on_loop" CONF_ON_MESSAGE = "on_message" CONF_ON_MULTI_CLICK = "on_multi_click" +CONF_ON_OPEN = "on_open" CONF_ON_PRESS = "on_press" CONF_ON_RAW_VALUE = "on_raw_value" CONF_ON_RELEASE = "on_release" @@ -442,6 +445,7 @@ CONF_ON_TIME_SYNC = "on_time_sync" CONF_ON_TOUCH = "on_touch" CONF_ON_TURN_OFF = "on_turn_off" CONF_ON_TURN_ON = "on_turn_on" +CONF_ON_UNLOCK = "on_unlock" CONF_ON_VALUE = "on_value" CONF_ON_VALUE_RANGE = "on_value_range" CONF_ONE = "one" @@ -709,6 +713,7 @@ CONF_UART_ID = "uart_id" CONF_UID = "uid" CONF_UNIQUE = "unique" CONF_UNIT_OF_MEASUREMENT = "unit_of_measurement" +CONF_UNLOCK_ACTION = "unlock_action" CONF_UPDATE_INTERVAL = "update_interval" CONF_UPDATE_ON_BOOT = "update_on_boot" CONF_URL = "url" diff --git a/esphome/core/application.h b/esphome/core/application.h index 2598a2f4a4..cec6e7baa9 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -42,6 +42,9 @@ #ifdef USE_SELECT #include "esphome/components/select/select.h" #endif +#ifdef USE_LOCK +#include "esphome/components/lock/lock.h" +#endif namespace esphome { @@ -104,6 +107,10 @@ class Application { void register_select(select::Select *select) { this->selects_.push_back(select); } #endif +#ifdef USE_LOCK + void register_lock(lock::Lock *a_lock) { this->locks_.push_back(a_lock); } +#endif + /// Register the component in this Application instance. template C *register_component(C *c) { static_assert(std::is_base_of::value, "Only Component subclasses can be registered"); @@ -257,6 +264,15 @@ class Application { return nullptr; } #endif +#ifdef USE_LOCK + const std::vector &get_locks() { return this->locks_; } + lock::Lock *get_lock_by_key(uint32_t key, bool include_internal = false) { + for (auto *obj : this->locks_) + if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) + return obj; + return nullptr; + } +#endif Scheduler scheduler; @@ -305,6 +321,9 @@ class Application { #ifdef USE_SELECT std::vector selects_{}; #endif +#ifdef USE_LOCK + std::vector locks_{}; +#endif std::string name_; std::string compilation_time_; diff --git a/esphome/core/controller.cpp b/esphome/core/controller.cpp index 6d3a76a292..dfcef5e4c1 100644 --- a/esphome/core/controller.cpp +++ b/esphome/core/controller.cpp @@ -65,6 +65,12 @@ void Controller::setup_controller(bool include_internal) { obj->add_on_state_callback([this, obj](const std::string &state) { this->on_select_update(obj, state); }); } #endif +#ifdef USE_LOCK + for (auto *obj : App.get_locks()) { + if (include_internal || !obj->is_internal()) + obj->add_on_state_callback([this, obj]() { this->on_lock_update(obj); }); + } +#endif } } // namespace esphome diff --git a/esphome/core/controller.h b/esphome/core/controller.h index 49750d1cc4..0be854828b 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -34,6 +34,9 @@ #ifdef USE_SELECT #include "esphome/components/select/select.h" #endif +#ifdef USE_LOCK +#include "esphome/components/lock/lock.h" +#endif namespace esphome { @@ -70,6 +73,9 @@ class Controller { #ifdef USE_SELECT virtual void on_select_update(select::Select *obj, const std::string &state){}; #endif +#ifdef USE_LOCK + virtual void on_lock_update(lock::Lock *obj){}; +#endif }; } // namespace esphome diff --git a/esphome/core/defines.h b/esphome/core/defines.h index acdc5df815..574a8dcafe 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -26,6 +26,7 @@ #define USE_GRAPH #define USE_HOMEASSISTANT_TIME #define USE_LIGHT +#define USE_LOCK #define USE_LOGGER #define USE_MDNS #define USE_NUMBER diff --git a/script/ci-custom.py b/script/ci-custom.py index 956716e5fa..7bbaaf1c79 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -598,6 +598,7 @@ def lint_inclusive_language(fname, match): "esphome/components/display/display_buffer.h", "esphome/components/fan/fan.h", "esphome/components/i2c/i2c.h", + "esphome/components/lock/lock.h", "esphome/components/mqtt/mqtt_component.h", "esphome/components/number/number.h", "esphome/components/output/binary_output.h", diff --git a/tests/test1.yaml b/tests/test1.yaml index 40cd0d4827..a0c9d03f14 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2582,3 +2582,28 @@ select: qr_code: - id: homepage_qr value: https://esphome.io/index.html + +lock: + - platform: template + id: test_lock1 + name: "Template Switch" + lambda: |- + if (id(binary_sensor1).state) { + return LOCK_STATE_LOCKED; + }else{ + return LOCK_STATE_UNLOCKED; + } + optimistic: true + assumed_state: no + on_unlock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_UNLOCKED;" + on_lock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_LOCKED;" + - platform: output + name: "Generic Output Lock" + id: test_lock2 + output: pca_6 From e7864a28a112d64a98fecd738ad1658994f2dacb Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 3 Feb 2022 21:04:48 +0100 Subject: [PATCH 071/238] Add device class support to Switch (#3012) Co-authored-by: Oxan van Leeuwen --- 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/switch/__init__.py | 15 ++++++++++++++- esphome/components/switch/switch.cpp | 7 +++++++ esphome/components/switch/switch.h | 9 +++++++++ esphome/const.py | 3 +++ tests/test1.yaml | 4 ++++ 9 files changed, 49 insertions(+), 1 deletion(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 3ab426979e..bd39893825 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -529,6 +529,7 @@ message ListEntitiesSwitchResponse { bool assumed_state = 6; bool disabled_by_default = 7; EntityCategory entity_category = 8; + string device_class = 9; } message SwitchStateResponse { option (id) = 26; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 21388b547e..d9ce6cd79e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -462,6 +462,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) { 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()); + msg.device_class = a_switch->get_device_class(); return this->send_list_entities_switch_response(msg); } void APIConnection::switch_command(const SwitchCommandRequest &msg) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index e7e0476afc..5a78587473 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -2177,6 +2177,10 @@ bool ListEntitiesSwitchResponse::decode_length(uint32_t field_id, ProtoLengthDel this->icon = value.as_string(); return true; } + case 9: { + this->device_class = value.as_string(); + return true; + } default: return false; } @@ -2200,6 +2204,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->assumed_state); buffer.encode_bool(7, this->disabled_by_default); buffer.encode_enum(8, this->entity_category); + buffer.encode_string(9, this->device_class); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSwitchResponse::dump_to(std::string &out) const { @@ -2237,6 +2242,10 @@ void ListEntitiesSwitchResponse::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 4c9a0e9c0f..28c0a7ce88 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -580,6 +580,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage { bool assumed_state{false}; 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/switch/__init__.py b/esphome/components/switch/__init__.py index 08cbccbe35..71a16439cd 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -4,18 +4,27 @@ from esphome import automation from esphome.automation import Condition, maybe_simple_id from esphome.components import mqtt from esphome.const import ( + CONF_DEVICE_CLASS, CONF_ID, CONF_INVERTED, + CONF_MQTT_ID, CONF_ON_TURN_OFF, CONF_ON_TURN_ON, CONF_TRIGGER_ID, - CONF_MQTT_ID, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_OUTLET, + DEVICE_CLASS_SWITCH, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True +DEVICE_CLASSES = [ + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_OUTLET, + DEVICE_CLASS_SWITCH, +] switch_ns = cg.esphome_ns.namespace("switch_") Switch = switch_ns.class_("Switch", cg.EntityBase) @@ -51,6 +60,7 @@ SWITCH_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOffTrigger), } ), + cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True), } ) @@ -71,6 +81,9 @@ async def setup_switch_core_(var, config): mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) + if CONF_DEVICE_CLASS in config: + cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) + async def register_switch(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index b9b99b4147..ca36e6feb9 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -46,5 +46,12 @@ void Switch::set_inverted(bool inverted) { this->inverted_ = inverted; } uint32_t Switch::hash_base() { return 3129890955UL; } bool Switch::is_inverted() const { return this->inverted_; } +std::string Switch::get_device_class() { + if (this->device_class_.has_value()) + return *this->device_class_; + return ""; +} +void Switch::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } + } // namespace switch_ } // namespace esphome diff --git a/esphome/components/switch/switch.h b/esphome/components/switch/switch.h index 071393003a..dda24e85fa 100644 --- a/esphome/components/switch/switch.h +++ b/esphome/components/switch/switch.h @@ -20,6 +20,9 @@ namespace switch_ { if ((obj)->is_inverted()) { \ ESP_LOGCONFIG(TAG, "%s Inverted: YES", prefix); \ } \ + if (!(obj)->get_device_class().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ + } \ } /** Base class for all switches. @@ -88,6 +91,11 @@ class Switch : public EntityBase { bool is_inverted() const; + /// Get the device class for this switch. + std::string get_device_class(); + /// Set the Home Assistant device class for this switch. + void set_device_class(const std::string &device_class); + protected: /** Write the given state to hardware. You should implement this * abstract method if you want to create your own switch. @@ -105,6 +113,7 @@ class Switch : public EntityBase { bool inverted_{false}; Deduplicator publish_dedup_; ESPPreferenceObject rtc_; + optional device_class_; }; } // namespace switch_ diff --git a/esphome/const.py b/esphome/const.py index a2dd20269a..61b152654a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -920,6 +920,9 @@ DEVICE_CLASS_VOLTAGE = "voltage" DEVICE_CLASS_UPDATE = "update" # device classes of button component DEVICE_CLASS_RESTART = "restart" +# device classes of switch component +DEVICE_CLASS_OUTLET = "outlet" +DEVICE_CLASS_SWITCH = "switch" # state classes diff --git a/tests/test1.yaml b/tests/test1.yaml index a0c9d03f14..e97b3aed73 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2037,6 +2037,10 @@ switch: - platform: template id: ble1_status optimistic: true + - platform: template + id: outlet_switch + optimistic: true + device_class: outlet fan: - platform: binary From 42984fa72a9541bc3e36d537893360248965f80e Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Thu, 3 Feb 2022 18:50:42 -0800 Subject: [PATCH 072/238] Handle Tuya multi-datapoint messages (#3159) Co-authored-by: Samuel Sieb --- esphome/components/tuya/tuya.cpp | 189 ++++++++++++++++--------------- esphome/components/tuya/tuya.h | 2 +- 2 files changed, 98 insertions(+), 93 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index f2dceed33f..1fbca7796d 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -199,7 +199,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); }); this->initialized_callback_.call(); } - this->handle_datapoint_(buffer, len); + this->handle_datapoints_(buffer, len); break; case TuyaCommandType::DATAPOINT_QUERY: break; @@ -224,105 +224,110 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff } } -void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) { - if (len < 2) - return; +void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) { + while (len >= 4) { + TuyaDatapoint datapoint{}; + datapoint.id = buffer[0]; + datapoint.type = (TuyaDatapointType) buffer[1]; + datapoint.value_uint = 0; - TuyaDatapoint datapoint{}; - datapoint.id = buffer[0]; - datapoint.type = (TuyaDatapointType) buffer[1]; - datapoint.value_uint = 0; - - // Drop update if datapoint is in ignore_mcu_datapoint_update list - for (uint8_t i : this->ignore_mcu_update_on_datapoints_) { - if (datapoint.id == i) { - ESP_LOGV(TAG, "Datapoint %u found in ignore_mcu_update_on_datapoints list, dropping MCU update", datapoint.id); + 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 truncated and cannot be parsed (%zu > %zu)", datapoint.id, data_size, data_len); return; } - } - 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 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; + datapoint.len = data_size; - 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, format_hex_pretty(datapoint.value_raw).c_str()); - break; - case TuyaDatapointType::BOOLEAN: - if (data_len != 1) { - ESP_LOGW(TAG, "Datapoint %u has bad boolean len %zu", datapoint.id, data_len); - return; - } - datapoint.value_bool = data[0]; - ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, ONOFF(datapoint.value_bool)); - break; - case TuyaDatapointType::INTEGER: - if (data_len != 4) { - ESP_LOGW(TAG, "Datapoint %u has bad integer len %zu", datapoint.id, data_len); - return; - } - datapoint.value_uint = encode_uint32(data[0], data[1], data[2], data[3]); - ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_int); - break; - case TuyaDatapointType::STRING: - datapoint.value_string = std::string(reinterpret_cast(data), data_len); - ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, datapoint.value_string.c_str()); - break; - case TuyaDatapointType::ENUM: - if (data_len != 1) { - ESP_LOGW(TAG, "Datapoint %u has bad enum len %zu", datapoint.id, data_len); - return; - } - datapoint.value_enum = data[0]; - ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_enum); - break; - case TuyaDatapointType::BITMASK: - switch (data_len) { - case 1: - datapoint.value_bitmask = encode_uint32(0, 0, 0, data[0]); - break; - case 2: - datapoint.value_bitmask = encode_uint32(0, 0, data[0], data[1]); - break; - case 4: - datapoint.value_bitmask = encode_uint32(data[0], data[1], data[2], data[3]); - break; - default: - ESP_LOGW(TAG, "Datapoint %u has bad bitmask len %zu", datapoint.id, data_len); + switch (datapoint.type) { + case TuyaDatapointType::RAW: + datapoint.value_raw = std::vector(data, data + data_size); + ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str()); + break; + case TuyaDatapointType::BOOLEAN: + if (data_size != 1) { + ESP_LOGW(TAG, "Datapoint %u has bad boolean len %zu", datapoint.id, data_size); return; - } - ESP_LOGD(TAG, "Datapoint %u update to %#08X", datapoint.id, datapoint.value_bitmask); - break; - default: - ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast(datapoint.type)); - return; - } - - // Update internal datapoints - bool found = false; - for (auto &other : this->datapoints_) { - if (other.id == datapoint.id) { - other = datapoint; - found = true; + } + datapoint.value_bool = data[0]; + ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, ONOFF(datapoint.value_bool)); + break; + case TuyaDatapointType::INTEGER: + if (data_size != 4) { + ESP_LOGW(TAG, "Datapoint %u has bad integer len %zu", datapoint.id, data_size); + return; + } + datapoint.value_uint = encode_uint32(data[0], data[1], data[2], data[3]); + ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_int); + break; + case TuyaDatapointType::STRING: + datapoint.value_string = std::string(reinterpret_cast(data), data_size); + ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, datapoint.value_string.c_str()); + break; + case TuyaDatapointType::ENUM: + if (data_size != 1) { + ESP_LOGW(TAG, "Datapoint %u has bad enum len %zu", datapoint.id, data_size); + return; + } + datapoint.value_enum = data[0]; + ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_enum); + break; + case TuyaDatapointType::BITMASK: + switch (data_size) { + case 1: + datapoint.value_bitmask = encode_uint32(0, 0, 0, data[0]); + break; + case 2: + datapoint.value_bitmask = encode_uint32(0, 0, data[0], data[1]); + break; + case 4: + datapoint.value_bitmask = encode_uint32(data[0], data[1], data[2], data[3]); + break; + default: + ESP_LOGW(TAG, "Datapoint %u has bad bitmask len %zu", datapoint.id, data_size); + return; + } + ESP_LOGD(TAG, "Datapoint %u update to %#08X", datapoint.id, datapoint.value_bitmask); + break; + default: + ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast(datapoint.type)); + return; } - } - if (!found) { - this->datapoints_.push_back(datapoint); - } - // Run through listeners - for (auto &listener : this->listeners_) { - if (listener.datapoint_id == datapoint.id) - listener.on_datapoint(datapoint); + len -= data_size + 4; + buffer = data + data_size; + + // drop update if datapoint is in ignore_mcu_datapoint_update list + bool skip = false; + for (auto i : this->ignore_mcu_update_on_datapoints_) { + if (datapoint.id == i) { + ESP_LOGV(TAG, "Datapoint %u found in ignore_mcu_update_on_datapoints list, dropping MCU update", datapoint.id); + skip = true; + break; + } + } + if (skip) + continue; + + // Update internal datapoints + bool found = false; + for (auto &other : this->datapoints_) { + if (other.id == datapoint.id) { + other = datapoint; + found = true; + } + } + if (!found) { + this->datapoints_.push_back(datapoint); + } + + // Run through listeners + for (auto &listener : this->listeners_) { + if (listener.datapoint_id == datapoint.id) + listener.on_datapoint(datapoint); + } } } diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index c46d61119e..3828c49b48 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -101,7 +101,7 @@ class Tuya : public Component, public uart::UARTDevice { protected: void handle_char_(uint8_t c); - void handle_datapoint_(const uint8_t *buffer, size_t len); + void handle_datapoints_(const uint8_t *buffer, size_t len); optional get_datapoint_(uint8_t datapoint_id); bool validate_message_(); From ab47e201c71efb94e38de1097d6cb3ebb177dbfc Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 4 Feb 2022 19:15:00 +1300 Subject: [PATCH 073/238] Bump improv library to 1.2.1 (#3160) --- esphome/components/esp32_improv/__init__.py | 2 +- esphome/components/improv_serial/__init__.py | 2 +- platformio.ini | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index c95d4075bc..9f8438f785 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -56,7 +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.2.0") + cg.add_library("esphome/Improv", "1.2.1") 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/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py index 4de4fe66a7..21073a8ab3 100644 --- a/esphome/components/improv_serial/__init__.py +++ b/esphome/components/improv_serial/__init__.py @@ -30,4 +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.2.0") + cg.add_library("esphome/Improv", "1.2.1") diff --git a/platformio.ini b/platformio.ini index d63f28d05e..70cfb11bf2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -35,7 +35,7 @@ build_flags = lib_deps = esphome/noise-c@0.1.4 ; api makuna/NeoPixelBus@2.6.9 ; neopixelbus - esphome/Improv@1.2.0 ; improv_serial / esp32_improv + esphome/Improv@1.2.1 ; improv_serial / esp32_improv bblanchon/ArduinoJson@6.18.5 ; json wjtje/qr-code-generator-library@1.7.0 ; qr_code build_flags = From 253161d3d0a3d69a8d83c6e39b5dbc6d95085ad0 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 7 Feb 2022 21:26:16 +0100 Subject: [PATCH 074/238] Fix copy_file_if_changed src permissions copied too (#3161) --- esphome/helpers.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/esphome/helpers.py b/esphome/helpers.py index 1193d61eaa..289abe5459 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -1,4 +1,5 @@ import codecs +from contextlib import suppress import logging import os @@ -233,8 +234,20 @@ def copy_file_if_changed(src: os.PathLike, dst: os.PathLike) -> None: return mkdir_p(os.path.dirname(dst)) try: - shutil.copy(src, dst) + shutil.copyfile(src, dst) except OSError as err: + if isinstance(err, PermissionError): + # Older esphome versions copied over the src file permissions too. + # So when the dst file had 444 permissions, the dst file would have those + # too and subsequent writes would fail + + # -> delete file (it would be overwritten anyway), and try again + # if that fails, use normal error handler + with suppress(OSError): + os.unlink(dst) + shutil.copyfile(src, dst) + return + from esphome.core import EsphomeError raise EsphomeError(f"Error copying file {src} to {dst}: {err}") from err From 1e5004f495a1bb10d9b70b9ec96f2250bbe59f3e Mon Sep 17 00:00:00 2001 From: mknjc Date: Tue, 8 Feb 2022 00:45:27 +0100 Subject: [PATCH 075/238] [debug] Refactor debug sensors to use the normal sensor model. (#3162) --- esphome/components/debug/__init__.py | 50 +++++--------------- esphome/components/debug/debug_component.cpp | 12 ++++- esphome/components/debug/debug_component.h | 20 ++++++-- esphome/components/debug/sensor.py | 49 +++++++++++++++++++ esphome/components/debug/text_sensor.py | 29 ++++++++++++ 5 files changed, 119 insertions(+), 41 deletions(-) create mode 100644 esphome/components/debug/sensor.py create mode 100644 esphome/components/debug/text_sensor.py diff --git a/esphome/components/debug/__init__.py b/esphome/components/debug/__init__.py index 98ad9e2b10..ff9b9c5314 100644 --- a/esphome/components/debug/__init__.py +++ b/esphome/components/debug/__init__.py @@ -1,6 +1,5 @@ import esphome.config_validation as cv import esphome.codegen as cg -from esphome.components import sensor, text_sensor from esphome.const import ( CONF_ID, CONF_DEVICE, @@ -8,16 +7,12 @@ from esphome.const import ( CONF_FRAGMENTATION, CONF_BLOCK, CONF_LOOP_TIME, - UNIT_MILLISECOND, - UNIT_PERCENT, - UNIT_BYTES, - ICON_COUNTER, - ICON_TIMER, ) CODEOWNERS = ["@OttoWinter"] DEPENDENCIES = ["logger"] +CONF_DEBUG_ID = "debug_id" debug_ns = cg.esphome_ns.namespace("debug") DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent) @@ -25,18 +20,20 @@ DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent) CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(DebugComponent), - cv.Optional(CONF_DEVICE): text_sensor.TEXT_SENSOR_SCHEMA.extend( - {cv.GenerateID(): cv.declare_id(text_sensor.TextSensor)} + cv.Optional(CONF_DEVICE): cv.invalid( + "The 'device' option has been moved to the 'debug' text_sensor component" ), - cv.Optional(CONF_FREE): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0), - cv.Optional(CONF_BLOCK): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0), - cv.Optional(CONF_FRAGMENTATION): cv.All( - cv.only_on_esp8266, - cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)), - sensor.sensor_schema(UNIT_PERCENT, ICON_COUNTER, 1), + cv.Optional(CONF_FREE): cv.invalid( + "The 'free' option has been moved to the 'debug' sensor component" ), - cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema( - UNIT_MILLISECOND, ICON_TIMER, 1 + cv.Optional(CONF_BLOCK): cv.invalid( + "The 'block' option has been moved to the 'debug' sensor component" + ), + cv.Optional(CONF_FRAGMENTATION): cv.invalid( + "The 'fragmentation' option has been moved to the 'debug' sensor component" + ), + cv.Optional(CONF_LOOP_TIME): cv.invalid( + "The 'loop_time' option has been moved to the 'debug' sensor component" ), } ).extend(cv.polling_component_schema("60s")) @@ -45,24 +42,3 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - - if CONF_DEVICE in config: - sens = cg.new_Pvariable(config[CONF_DEVICE][CONF_ID]) - await text_sensor.register_text_sensor(sens, config[CONF_DEVICE]) - cg.add(var.set_device_info_sensor(sens)) - - if CONF_FREE in config: - sens = await sensor.new_sensor(config[CONF_FREE]) - cg.add(var.set_free_sensor(sens)) - - if CONF_BLOCK in config: - sens = await sensor.new_sensor(config[CONF_BLOCK]) - cg.add(var.set_block_sensor(sens)) - - if CONF_FRAGMENTATION in config: - sens = await sensor.new_sensor(config[CONF_FRAGMENTATION]) - cg.add(var.set_fragmentation_sensor(sens)) - - if CONF_LOOP_TIME in config: - sens = await sensor.new_sensor(config[CONF_LOOP_TIME]) - cg.add(var.set_loop_time_sensor(sens)) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index 41bf5f50c7..a2697084bd 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -47,12 +47,16 @@ void DebugComponent::dump_config() { #endif ESP_LOGCONFIG(TAG, "Debug component:"); +#ifdef USE_TEXT_SENSOR LOG_TEXT_SENSOR(" ", "Device info", this->device_info_); +#endif // USE_TEXT_SENSOR +#ifdef USE_SENSOR LOG_SENSOR(" ", "Free space on heap", this->free_sensor_); LOG_SENSOR(" ", "Largest free heap block", this->block_sensor_); #if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) LOG_SENSOR(" ", "Heap fragmentation", this->fragmentation_sensor_); -#endif +#endif // defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) +#endif // USE_SENSOR ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); device_info += ESPHOME_VERSION; @@ -268,11 +272,13 @@ void DebugComponent::dump_config() { device_info += ESP.getResetInfo().c_str(); #endif +#ifdef USE_TEXT_SENSOR if (this->device_info_ != nullptr) { if (device_info.length() > 255) device_info.resize(255); this->device_info_->publish_state(device_info); } +#endif // USE_TEXT_SENSOR } void DebugComponent::loop() { @@ -284,6 +290,7 @@ void DebugComponent::loop() { this->status_momentary_warning("heap", 1000); } +#ifdef USE_SENSOR // calculate loop time - from last call to this one if (this->loop_time_sensor_ != nullptr) { uint32_t now = millis(); @@ -291,9 +298,11 @@ void DebugComponent::loop() { this->max_loop_time_ = std::max(this->max_loop_time_, loop_time); this->last_loop_timetag_ = now; } +#endif // USE_SENSOR } void DebugComponent::update() { +#ifdef USE_SENSOR if (this->free_sensor_ != nullptr) { this->free_sensor_->publish_state(get_free_heap()); } @@ -318,6 +327,7 @@ void DebugComponent::update() { this->loop_time_sensor_->publish_state(this->max_loop_time_); this->max_loop_time_ = 0; } +#endif // USE_SENSOR } float DebugComponent::get_setup_priority() const { return setup_priority::LATE; } diff --git a/esphome/components/debug/debug_component.h b/esphome/components/debug/debug_component.h index a362c52617..f966b4fafc 100644 --- a/esphome/components/debug/debug_component.h +++ b/esphome/components/debug/debug_component.h @@ -1,10 +1,16 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/macros.h" #include "esphome/core/helpers.h" -#include "esphome/components/text_sensor/text_sensor.h" + +#ifdef USE_SENSOR #include "esphome/components/sensor/sensor.h" +#endif +#ifdef USE_TEXT_SENSOR +#include "esphome/components/text_sensor/text_sensor.h" +#endif namespace esphome { namespace debug { @@ -16,27 +22,35 @@ class DebugComponent : public PollingComponent { float get_setup_priority() const override; void dump_config() override; +#ifdef USE_TEXT_SENSOR void set_device_info_sensor(text_sensor::TextSensor *device_info) { device_info_ = device_info; } +#endif // USE_TEXT_SENSOR +#ifdef USE_SENSOR void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; } void set_block_sensor(sensor::Sensor *block_sensor) { block_sensor_ = block_sensor; } #if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) void set_fragmentation_sensor(sensor::Sensor *fragmentation_sensor) { fragmentation_sensor_ = fragmentation_sensor; } #endif void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; } - +#endif // USE_SENSOR protected: uint32_t free_heap_{}; +#ifdef USE_SENSOR uint32_t last_loop_timetag_{0}; uint32_t max_loop_time_{0}; - text_sensor::TextSensor *device_info_{nullptr}; sensor::Sensor *free_sensor_{nullptr}; sensor::Sensor *block_sensor_{nullptr}; #if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) sensor::Sensor *fragmentation_sensor_{nullptr}; #endif sensor::Sensor *loop_time_sensor_{nullptr}; +#endif // USE_SENSOR + +#ifdef USE_TEXT_SENSOR + text_sensor::TextSensor *device_info_{nullptr}; +#endif // USE_TEXT_SENSOR }; } // namespace debug diff --git a/esphome/components/debug/sensor.py b/esphome/components/debug/sensor.py new file mode 100644 index 0000000000..deea6fd5ed --- /dev/null +++ b/esphome/components/debug/sensor.py @@ -0,0 +1,49 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_FREE, + CONF_FRAGMENTATION, + CONF_BLOCK, + CONF_LOOP_TIME, + UNIT_MILLISECOND, + UNIT_PERCENT, + UNIT_BYTES, + ICON_COUNTER, + ICON_TIMER, +) +from . import CONF_DEBUG_ID, DebugComponent + +DEPENDENCIES = ["debug"] + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent), + cv.Optional(CONF_FREE): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0), + cv.Optional(CONF_BLOCK): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0), + cv.Optional(CONF_FRAGMENTATION): cv.All( + cv.only_on_esp8266, + cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)), + sensor.sensor_schema(UNIT_PERCENT, ICON_COUNTER, 1), + ), + cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema(UNIT_MILLISECOND, ICON_TIMER, 0), +} + + +async def to_code(config): + debug_component = await cg.get_variable(config[CONF_DEBUG_ID]) + + if CONF_FREE in config: + sens = await sensor.new_sensor(config[CONF_FREE]) + cg.add(debug_component.set_free_sensor(sens)) + + if CONF_BLOCK in config: + sens = await sensor.new_sensor(config[CONF_BLOCK]) + cg.add(debug_component.set_block_sensor(sens)) + + if CONF_FRAGMENTATION in config: + sens = await sensor.new_sensor(config[CONF_FRAGMENTATION]) + cg.add(debug_component.set_fragmentation_sensor(sens)) + + if CONF_LOOP_TIME in config: + sens = await sensor.new_sensor(config[CONF_LOOP_TIME]) + cg.add(debug_component.set_loop_time_sensor(sens)) diff --git a/esphome/components/debug/text_sensor.py b/esphome/components/debug/text_sensor.py new file mode 100644 index 0000000000..98abc67245 --- /dev/null +++ b/esphome/components/debug/text_sensor.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_DEVICE, +) +from . import CONF_DEBUG_ID, DebugComponent + +DEPENDENCIES = ["debug"] + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent), + cv.Optional(CONF_DEVICE): text_sensor.TEXT_SENSOR_SCHEMA.extend( + {cv.GenerateID(): cv.declare_id(text_sensor.TextSensor)} + ), + } +) + + +async def to_code(config): + debug_component = await cg.get_variable(config[CONF_DEBUG_ID]) + + if CONF_DEVICE in config: + sens = cg.new_Pvariable(config[CONF_DEVICE][CONF_ID]) + await text_sensor.register_text_sensor(sens, config[CONF_DEVICE]) + cg.add(debug_component.set_device_info_sensor(sens)) From ad43d6a5bcb53441edd18cd3f1907dcc47e97452 Mon Sep 17 00:00:00 2001 From: Jeff Eberl Date: Mon, 7 Feb 2022 20:32:37 -0700 Subject: [PATCH 076/238] Added RadonEye RD200 Component (#3119) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 2 + esphome/components/radon_eye_ble/__init__.py | 23 +++ .../radon_eye_ble/radon_eye_listener.cpp | 25 +++ .../radon_eye_ble/radon_eye_listener.h | 19 ++ .../components/radon_eye_rd200/__init__.py | 1 + .../radon_eye_rd200/radon_eye_rd200.cpp | 179 ++++++++++++++++++ .../radon_eye_rd200/radon_eye_rd200.h | 59 ++++++ esphome/components/radon_eye_rd200/sensor.py | 55 ++++++ tests/test2.yaml | 11 ++ 9 files changed, 374 insertions(+) create mode 100644 esphome/components/radon_eye_ble/__init__.py create mode 100644 esphome/components/radon_eye_ble/radon_eye_listener.cpp create mode 100644 esphome/components/radon_eye_ble/radon_eye_listener.h create mode 100644 esphome/components/radon_eye_rd200/__init__.py create mode 100644 esphome/components/radon_eye_rd200/radon_eye_rd200.cpp create mode 100644 esphome/components/radon_eye_rd200/radon_eye_rd200.h create mode 100644 esphome/components/radon_eye_rd200/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 5fa3090aaf..a353906da2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -139,6 +139,8 @@ esphome/components/psram/* @esphome/core esphome/components/pulse_meter/* @stevebaxter esphome/components/pvvx_mithermometer/* @pasiz esphome/components/qr_code/* @wjtje +esphome/components/radon_eye_ble/* @jeffeb3 +esphome/components/radon_eye_rd200/* @jeffeb3 esphome/components/rc522/* @glmnet esphome/components/rc522_i2c/* @glmnet esphome/components/rc522_spi/* @glmnet diff --git a/esphome/components/radon_eye_ble/__init__.py b/esphome/components/radon_eye_ble/__init__.py new file mode 100644 index 0000000000..ffe434d19b --- /dev/null +++ b/esphome/components/radon_eye_ble/__init__.py @@ -0,0 +1,23 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import esp32_ble_tracker +from esphome.const import CONF_ID + +DEPENDENCIES = ["esp32_ble_tracker"] +CODEOWNERS = ["@jeffeb3"] + +radon_eye_ble_ns = cg.esphome_ns.namespace("radon_eye_ble") +RadonEyeListener = radon_eye_ble_ns.class_( + "RadonEyeListener", esp32_ble_tracker.ESPBTDeviceListener +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(RadonEyeListener), + } +).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield esp32_ble_tracker.register_ble_device(var, config) diff --git a/esphome/components/radon_eye_ble/radon_eye_listener.cpp b/esphome/components/radon_eye_ble/radon_eye_listener.cpp new file mode 100644 index 0000000000..b10986c9cb --- /dev/null +++ b/esphome/components/radon_eye_ble/radon_eye_listener.cpp @@ -0,0 +1,25 @@ +#include "radon_eye_listener.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace radon_eye_ble { + +static const char *const TAG = "radon_eye_ble"; + +bool RadonEyeListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + if (not device.get_name().empty()) { + if (device.get_name().rfind("FR:R20:SN", 0) == 0) { + // This is an RD200, I think + ESP_LOGD(TAG, "Found Radon Eye RD200 device Name: %s (MAC: %s)", device.get_name().c_str(), + device.address_str().c_str()); + } + } + return false; +} + +} // namespace radon_eye_ble +} // namespace esphome + +#endif diff --git a/esphome/components/radon_eye_ble/radon_eye_listener.h b/esphome/components/radon_eye_ble/radon_eye_listener.h new file mode 100644 index 0000000000..26d0233c56 --- /dev/null +++ b/esphome/components/radon_eye_ble/radon_eye_listener.h @@ -0,0 +1,19 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/core/component.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +namespace esphome { +namespace radon_eye_ble { + +class RadonEyeListener : public esp32_ble_tracker::ESPBTDeviceListener { + public: + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; +}; + +} // namespace radon_eye_ble +} // namespace esphome + +#endif diff --git a/esphome/components/radon_eye_rd200/__init__.py b/esphome/components/radon_eye_rd200/__init__.py new file mode 100644 index 0000000000..0740f6967b --- /dev/null +++ b/esphome/components/radon_eye_rd200/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@jeffeb3"] diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp new file mode 100644 index 0000000000..6bb17f0508 --- /dev/null +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp @@ -0,0 +1,179 @@ +#include "radon_eye_rd200.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace radon_eye_rd200 { + +static const char *const TAG = "radon_eye_rd200"; + +void RadonEyeRD200::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->read_handle_ = 0; + auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_read_characteristic_uuid_); + if (chr == nullptr) { + ESP_LOGW(TAG, "No sensor read characteristic found at service %s char %s", service_uuid_.to_string().c_str(), + sensors_read_characteristic_uuid_.to_string().c_str()); + break; + } + this->read_handle_ = chr->handle; + + // Write a 0x50 to the write characteristic. + auto *write_chr = this->parent()->get_characteristic(service_uuid_, sensors_write_characteristic_uuid_); + if (write_chr == nullptr) { + ESP_LOGW(TAG, "No sensor write characteristic found at service %s char %s", service_uuid_.to_string().c_str(), + sensors_read_characteristic_uuid_.to_string().c_str()); + break; + } + this->write_handle_ = write_chr->handle; + + this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; + + write_query_message_(); + + 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->read_handle_) { + read_sensors_(param->read.value, param->read.value_len); + } + break; + } + + default: + break; + } +} + +void RadonEyeRD200::read_sensors_(uint8_t *value, uint16_t value_len) { + if (value_len < 20) { + ESP_LOGD(TAG, "Invalid read"); + return; + } + + // Example data + // [13:08:47][D][radon_eye_rd200:107]: result bytes: 5010 85EBB940 00000000 00000000 2200 2500 0000 + ESP_LOGV(TAG, "result bytes: %02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X %02X%02X %02X%02X", + value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9], + value[10], value[11], value[12], value[13], value[14], value[15], value[16], value[17], value[18], + value[19]); + + if (value[0] != 0x50) { + // This isn't a sensor reading. + return; + } + + // Convert from pCi/L to Bq/m³ + constexpr float convert_to_bwpm3 = 37.0; + + RadonValue radon_value; + radon_value.chars[0] = value[2]; + radon_value.chars[1] = value[3]; + radon_value.chars[2] = value[4]; + radon_value.chars[3] = value[5]; + float radon_now = radon_value.number * convert_to_bwpm3; + if (is_valid_radon_value_(radon_now)) { + radon_sensor_->publish_state(radon_now); + } + + radon_value.chars[0] = value[6]; + radon_value.chars[1] = value[7]; + radon_value.chars[2] = value[8]; + radon_value.chars[3] = value[9]; + float radon_day = radon_value.number * convert_to_bwpm3; + + radon_value.chars[0] = value[10]; + radon_value.chars[1] = value[11]; + radon_value.chars[2] = value[12]; + radon_value.chars[3] = value[13]; + float radon_month = radon_value.number * convert_to_bwpm3; + + if (is_valid_radon_value_(radon_month)) { + ESP_LOGV(TAG, "Radon Long Term based on month"); + radon_long_term_sensor_->publish_state(radon_month); + } else if (is_valid_radon_value_(radon_day)) { + ESP_LOGV(TAG, "Radon Long Term based on day"); + radon_long_term_sensor_->publish_state(radon_day); + } + + ESP_LOGV(TAG, " Measurements (Bq/m³) now: %0.03f, day: %0.03f, month: %0.03f", radon_now, radon_day, radon_month); + + ESP_LOGV(TAG, " Measurements (pCi/L) now: %0.03f, day: %0.03f, month: %0.03f", radon_now / convert_to_bwpm3, + radon_day / convert_to_bwpm3, radon_month / convert_to_bwpm3); + + // This instance must not stay connected + // so other clients can connect to it (e.g. the + // mobile app). + parent()->set_enabled(false); +} + +bool RadonEyeRD200::is_valid_radon_value_(float radon) { return radon > 0.0 and radon < 37000; } + +void RadonEyeRD200::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 RadonEyeRD200::write_query_message_() { + ESP_LOGV(TAG, "writing 0x50 to write service"); + int request = 0x50; + auto status = esp_ble_gattc_write_char_descr(this->parent()->gattc_if, this->parent()->conn_id, this->write_handle_, + sizeof(request), (uint8_t *) &request, ESP_GATT_WRITE_TYPE_NO_RSP, + ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "Error sending write request for sensor, status=%d", status); + } +} + +void RadonEyeRD200::request_read_values_() { + auto status = esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->read_handle_, + ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); + } +} + +void RadonEyeRD200::dump_config() { + LOG_SENSOR(" ", "Radon", this->radon_sensor_); + LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_); +} + +RadonEyeRD200::RadonEyeRD200() + : PollingComponent(10000), + service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)), + sensors_write_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(WRITE_CHARACTERISTIC_UUID)), + sensors_read_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(READ_CHARACTERISTIC_UUID)) {} + +} // namespace radon_eye_rd200 +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.h b/esphome/components/radon_eye_rd200/radon_eye_rd200.h new file mode 100644 index 0000000000..7b29be7bd8 --- /dev/null +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.h @@ -0,0 +1,59 @@ +#pragma once + +#ifdef USE_ESP32 + +#include +#include +#include +#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 radon_eye_rd200 { + +static const char *const SERVICE_UUID = "00001523-1212-efde-1523-785feabcd123"; +static const char *const WRITE_CHARACTERISTIC_UUID = "00001524-1212-efde-1523-785feabcd123"; +static const char *const READ_CHARACTERISTIC_UUID = "00001525-1212-efde-1523-785feabcd123"; + +class RadonEyeRD200 : public PollingComponent, public ble_client::BLEClientNode { + public: + RadonEyeRD200(); + + void dump_config() override; + void update() override; + + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + + void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; } + void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; } + + protected: + bool is_valid_radon_value_(float radon); + + void read_sensors_(uint8_t *value, uint16_t value_len); + void write_query_message_(); + void request_read_values_(); + + sensor::Sensor *radon_sensor_{nullptr}; + sensor::Sensor *radon_long_term_sensor_{nullptr}; + + uint16_t read_handle_; + uint16_t write_handle_; + esp32_ble_tracker::ESPBTUUID service_uuid_; + esp32_ble_tracker::ESPBTUUID sensors_write_characteristic_uuid_; + esp32_ble_tracker::ESPBTUUID sensors_read_characteristic_uuid_; + + union RadonValue { + char chars[4]; + float number; + }; +}; + +} // namespace radon_eye_rd200 +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/radon_eye_rd200/sensor.py b/esphome/components/radon_eye_rd200/sensor.py new file mode 100644 index 0000000000..a9667869b8 --- /dev/null +++ b/esphome/components/radon_eye_rd200/sensor.py @@ -0,0 +1,55 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, ble_client + +from esphome.const import ( + STATE_CLASS_MEASUREMENT, + UNIT_BECQUEREL_PER_CUBIC_METER, + CONF_ID, + CONF_RADON, + CONF_RADON_LONG_TERM, + ICON_RADIOACTIVE, +) + +DEPENDENCIES = ["ble_client"] + +radon_eye_rd200_ns = cg.esphome_ns.namespace("radon_eye_rd200") +RadonEyeRD200 = radon_eye_rd200_ns.class_( + "RadonEyeRD200", cg.PollingComponent, ble_client.BLEClientNode +) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(RadonEyeRD200), + cv.Optional(CONF_RADON): sensor.sensor_schema( + unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, + icon=ICON_RADIOACTIVE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema( + unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, + icon=ICON_RADIOACTIVE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("5min")) + .extend(ble_client.BLE_CLIENT_SCHEMA), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + await ble_client.register_ble_node(var, config) + + if CONF_RADON in config: + sens = await sensor.new_sensor(config[CONF_RADON]) + cg.add(var.set_radon(sens)) + if CONF_RADON_LONG_TERM in config: + sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM]) + cg.add(var.set_radon_long_term(sens)) diff --git a/tests/test2.yaml b/tests/test2.yaml index 6d606a3143..2a122b971f 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -324,6 +324,13 @@ sensor: bus_voltage: name: "INA260 Voltage" update_interval: 60s + - platform: radon_eye_rd200 + ble_client_id: radon_eye_ble_id + update_interval: 10min + radon: + name: "RD200 Radon" + radon_long_term: + name: "RD200 Radon Long Term" time: - platform: homeassistant @@ -423,10 +430,14 @@ ble_client: id: airthings01 - mac_address: 01:02:03:04:05:06 id: airthingsmini01 + - mac_address: 01:02:03:04:05:06 + id: radon_eye_ble_id airthings_ble: +radon_eye_ble: + ruuvi_ble: xiaomi_ble: From 69856286e86dbb292fd1d8f4111ed9a1149ae0ba Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 8 Feb 2022 17:23:45 +1300 Subject: [PATCH 077/238] Text sensor schema generator similar to sensor (#3172) --- esphome/components/ble_scanner/text_sensor.py | 10 +-- esphome/components/bme680_bsec/text_sensor.py | 11 +-- esphome/components/ccs811/sensor.py | 11 +-- .../components/custom/text_sensor/__init__.py | 6 +- esphome/components/daly_bms/text_sensor.py | 12 +-- esphome/components/debug/text_sensor.py | 13 +-- esphome/components/demo/__init__.py | 13 +-- esphome/components/dsmr/text_sensor.py | 85 ++++--------------- .../homeassistant/text_sensor/__init__.py | 8 +- .../modbus_controller/text_sensor/__init__.py | 3 +- .../mqtt_subscribe/text_sensor/__init__.py | 26 +++--- .../nextion/text_sensor/__init__.py | 8 +- esphome/components/pipsolar/__init__.py | 2 +- .../pipsolar/text_sensor/__init__.py | 18 +--- .../text_sensor/pipsolar_textsensor.cpp | 13 --- .../text_sensor/pipsolar_textsensor.h | 20 ----- .../components/sun/text_sensor/__init__.py | 10 +-- esphome/components/teleinfo/__init__.py | 13 ++- .../components/teleinfo/sensor/__init__.py | 22 ++--- .../teleinfo/text_sensor/__init__.py | 13 +-- .../template/text_sensor/__init__.py | 19 +++-- esphome/components/text_sensor/__init__.py | 36 +++++++- .../components/tuya/text_sensor/__init__.py | 23 ++--- esphome/components/version/text_sensor.py | 29 +++---- esphome/components/wifi_info/text_sensor.py | 50 +++-------- 25 files changed, 176 insertions(+), 298 deletions(-) delete mode 100644 esphome/components/pipsolar/text_sensor/pipsolar_textsensor.cpp delete mode 100644 esphome/components/pipsolar/text_sensor/pipsolar_textsensor.h diff --git a/esphome/components/ble_scanner/text_sensor.py b/esphome/components/ble_scanner/text_sensor.py index 0e140aa701..31dccdf119 100644 --- a/esphome/components/ble_scanner/text_sensor.py +++ b/esphome/components/ble_scanner/text_sensor.py @@ -1,7 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor, esp32_ble_tracker -from esphome.const import CONF_ID DEPENDENCIES = ["esp32_ble_tracker"] @@ -14,18 +13,13 @@ BLEScanner = ble_scanner_ns.class_( ) CONFIG_SCHEMA = cv.All( - text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(BLEScanner), - } - ) + text_sensor.text_sensor_schema(klass=BLEScanner) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) .extend(cv.COMPONENT_SCHEMA) ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await text_sensor.new_text_sensor(config) await cg.register_component(var, config) await esp32_ble_tracker.register_ble_device(var, config) - await text_sensor.register_text_sensor(var, config) diff --git a/esphome/components/bme680_bsec/text_sensor.py b/esphome/components/bme680_bsec/text_sensor.py index 96020544e7..2d93c90818 100644 --- a/esphome/components/bme680_bsec/text_sensor.py +++ b/esphome/components/bme680_bsec/text_sensor.py @@ -1,7 +1,6 @@ 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 from . import BME680BSECComponent, CONF_BME680_BSEC_ID DEPENDENCIES = ["bme680_bsec"] @@ -14,11 +13,8 @@ TYPES = [CONF_IAQ_ACCURACY] CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent), - cv.Optional(CONF_IAQ_ACCURACY): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - cv.Optional(CONF_ICON, default=ICON_ACCURACY): cv.icon, - } + cv.Optional(CONF_IAQ_ACCURACY): text_sensor.text_sensor_schema( + icon=ICON_ACCURACY ), } ) @@ -27,8 +23,7 @@ CONFIG_SCHEMA = cv.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) + sens = await text_sensor.new_text_sensor(conf) cg.add(getattr(hub, f"set_{key}_text_sensor")(sens)) diff --git a/esphome/components/ccs811/sensor.py b/esphome/components/ccs811/sensor.py index bb8200273d..cb5c1108ba 100644 --- a/esphome/components/ccs811/sensor.py +++ b/esphome/components/ccs811/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor, text_sensor from esphome.const import ( - CONF_ICON, CONF_ID, ICON_RADIATOR, ICON_RESTART, @@ -47,11 +46,8 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, state_class=STATE_CLASS_MEASUREMENT, ), - cv.Optional(CONF_VERSION): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - cv.Optional(CONF_ICON, default=ICON_RESTART): cv.icon, - } + cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema( + icon=ICON_RESTART ), cv.Optional(CONF_BASELINE): cv.hex_uint16_t, cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor), @@ -74,8 +70,7 @@ async def to_code(config): cg.add(var.set_tvoc(sens)) if CONF_VERSION in config: - sens = cg.new_Pvariable(config[CONF_VERSION][CONF_ID]) - await text_sensor.register_text_sensor(sens, config[CONF_VERSION]) + sens = await text_sensor.new_text_sensor(config[CONF_VERSION]) cg.add(var.set_version(sens)) if CONF_BASELINE in config: diff --git a/esphome/components/custom/text_sensor/__init__.py b/esphome/components/custom/text_sensor/__init__.py index 5b6d416436..70728af604 100644 --- a/esphome/components/custom/text_sensor/__init__.py +++ b/esphome/components/custom/text_sensor/__init__.py @@ -11,11 +11,7 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(): cv.declare_id(CustomTextSensorConstructor), cv.Required(CONF_LAMBDA): cv.returning_lambda, cv.Required(CONF_TEXT_SENSORS): cv.ensure_list( - text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ) + text_sensor.text_sensor_schema() ), } ) diff --git a/esphome/components/daly_bms/text_sensor.py b/esphome/components/daly_bms/text_sensor.py index de49a0b4b9..9f23e5f373 100644 --- a/esphome/components/daly_bms/text_sensor.py +++ b/esphome/components/daly_bms/text_sensor.py @@ -1,7 +1,7 @@ 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 esphome.const import CONF_STATUS from . import DalyBmsComponent, CONF_BMS_DALY_ID ICON_CAR_BATTERY = "mdi:car-battery" @@ -14,11 +14,8 @@ 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, - } + cv.Optional(CONF_STATUS): text_sensor.text_sensor_schema( + icon=ICON_CAR_BATTERY ), } ).extend(cv.COMPONENT_SCHEMA) @@ -28,8 +25,7 @@ CONFIG_SCHEMA = cv.All( 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) + sens = await text_sensor.new_text_sensor(conf) cg.add(getattr(hub, f"set_{key}_text_sensor")(sens)) diff --git a/esphome/components/debug/text_sensor.py b/esphome/components/debug/text_sensor.py index 98abc67245..f8d1016fbf 100644 --- a/esphome/components/debug/text_sensor.py +++ b/esphome/components/debug/text_sensor.py @@ -1,10 +1,8 @@ from esphome.components import text_sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import ( - CONF_ID, - CONF_DEVICE, -) +from esphome.const import CONF_DEVICE + from . import CONF_DEBUG_ID, DebugComponent DEPENDENCIES = ["debug"] @@ -13,9 +11,7 @@ DEPENDENCIES = ["debug"] CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent), - cv.Optional(CONF_DEVICE): text_sensor.TEXT_SENSOR_SCHEMA.extend( - {cv.GenerateID(): cv.declare_id(text_sensor.TextSensor)} - ), + cv.Optional(CONF_DEVICE): text_sensor.text_sensor_schema(), } ) @@ -24,6 +20,5 @@ async def to_code(config): debug_component = await cg.get_variable(config[CONF_DEBUG_ID]) if CONF_DEVICE in config: - sens = cg.new_Pvariable(config[CONF_DEVICE][CONF_ID]) - await text_sensor.register_text_sensor(sens, config[CONF_DEVICE]) + sens = await text_sensor.new_text_sensor(config[CONF_DEVICE]) cg.add(debug_component.set_device_info_sensor(sens)) diff --git a/esphome/components/demo/__init__.py b/esphome/components/demo/__init__.py index 6b4a55aac9..f20a96ebd4 100644 --- a/esphome/components/demo/__init__.py +++ b/esphome/components/demo/__init__.py @@ -37,12 +37,10 @@ from esphome.const import ( DEVICE_CLASS_TEMPERATURE, ICON_BLUETOOTH, ICON_BLUR, - ICON_EMPTY, ICON_THERMOMETER, STATE_CLASS_MEASUREMENT, STATE_CLASS_TOTAL_INCREASING, UNIT_CELSIUS, - UNIT_EMPTY, UNIT_PERCENT, UNIT_WATT_HOURS, ) @@ -339,7 +337,7 @@ CONFIG_SCHEMA = cv.Schema( }, ], ): [ - sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 0) + sensor.sensor_schema(accuracy_decimals=0) .extend(cv.polling_component_schema("60s")) .extend( { @@ -378,12 +376,8 @@ CONFIG_SCHEMA = cv.Schema( }, ], ): [ - text_sensor.TEXT_SENSOR_SCHEMA.extend( + text_sensor.text_sensor_schema(klass=DemoTextSensor).extend( cv.polling_component_schema("60s") - ).extend( - { - cv.GenerateID(): cv.declare_id(DemoTextSensor), - } ) ], } @@ -443,6 +437,5 @@ async def to_code(config): await switch.register_switch(var, conf) for conf in config[CONF_TEXT_SENSORS]: - var = cg.new_Pvariable(conf[CONF_ID]) + var = await text_sensor.new_text_sensor(conf) await cg.register_component(var, conf) - await text_sensor.register_text_sensor(var, conf) diff --git a/esphome/components/dsmr/text_sensor.py b/esphome/components/dsmr/text_sensor.py index 339eea711f..202cc07020 100644 --- a/esphome/components/dsmr/text_sensor.py +++ b/esphome/components/dsmr/text_sensor.py @@ -1,9 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor -from esphome.const import ( - CONF_ID, -) + from . import Dsmr, CONF_DSMR_ID AUTO_LOAD = ["dsmr"] @@ -11,71 +9,19 @@ AUTO_LOAD = ["dsmr"] CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_DSMR_ID): cv.use_id(Dsmr), - cv.Optional("identification"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("p1_version"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("p1_version_be"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("timestamp"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("electricity_tariff"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("electricity_failure_log"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("message_short"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("message_long"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("gas_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("thermal_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("water_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("sub_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("gas_delivered_text"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), + cv.Optional("identification"): text_sensor.text_sensor_schema(), + cv.Optional("p1_version"): text_sensor.text_sensor_schema(), + cv.Optional("p1_version_be"): text_sensor.text_sensor_schema(), + cv.Optional("timestamp"): text_sensor.text_sensor_schema(), + cv.Optional("electricity_tariff"): text_sensor.text_sensor_schema(), + cv.Optional("electricity_failure_log"): text_sensor.text_sensor_schema(), + cv.Optional("message_short"): text_sensor.text_sensor_schema(), + cv.Optional("message_long"): text_sensor.text_sensor_schema(), + cv.Optional("gas_equipment_id"): text_sensor.text_sensor_schema(), + cv.Optional("thermal_equipment_id"): text_sensor.text_sensor_schema(), + cv.Optional("water_equipment_id"): text_sensor.text_sensor_schema(), + cv.Optional("sub_equipment_id"): text_sensor.text_sensor_schema(), + cv.Optional("gas_delivered_text"): text_sensor.text_sensor_schema(), } ).extend(cv.COMPONENT_SCHEMA) @@ -89,8 +35,7 @@ async def to_code(config): continue id = conf.get("id") if id and id.type == text_sensor.TextSensor: - var = cg.new_Pvariable(conf[CONF_ID]) - await text_sensor.register_text_sensor(var, conf) + var = await text_sensor.new_text_sensor(conf) cg.add(getattr(hub, f"set_{key}")(var)) text_sensors.append(f"F({key})") diff --git a/esphome/components/homeassistant/text_sensor/__init__.py b/esphome/components/homeassistant/text_sensor/__init__.py index b63d45b9ce..be59bab676 100644 --- a/esphome/components/homeassistant/text_sensor/__init__.py +++ b/esphome/components/homeassistant/text_sensor/__init__.py @@ -1,7 +1,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor -from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_ID +from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID + from .. import homeassistant_ns DEPENDENCIES = ["api"] @@ -10,7 +11,7 @@ HomeassistantTextSensor = homeassistant_ns.class_( "HomeassistantTextSensor", text_sensor.TextSensor, cg.Component ) -CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = text_sensor.text_sensor_schema().extend( { cv.GenerateID(): cv.declare_id(HomeassistantTextSensor), cv.Required(CONF_ENTITY_ID): cv.entity_id, @@ -20,9 +21,8 @@ CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await text_sensor.new_text_sensor(config) await cg.register_component(var, config) - await text_sensor.register_text_sensor(var, config) cg.add(var.set_entity_id(config[CONF_ENTITY_ID])) if CONF_ATTRIBUTE in config: diff --git a/esphome/components/modbus_controller/text_sensor/__init__.py b/esphome/components/modbus_controller/text_sensor/__init__.py index 5cc85af5bc..763336e104 100644 --- a/esphome/components/modbus_controller/text_sensor/__init__.py +++ b/esphome/components/modbus_controller/text_sensor/__init__.py @@ -40,7 +40,8 @@ RAW_ENCODING = { } CONFIG_SCHEMA = cv.All( - text_sensor.TEXT_SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA) + text_sensor.text_sensor_schema() + .extend(cv.COMPONENT_SCHEMA) .extend(ModbusItemBaseSchema) .extend( { diff --git a/esphome/components/mqtt_subscribe/text_sensor/__init__.py b/esphome/components/mqtt_subscribe/text_sensor/__init__.py index 477e4dec45..5b5c0ae17f 100644 --- a/esphome/components/mqtt_subscribe/text_sensor/__init__.py +++ b/esphome/components/mqtt_subscribe/text_sensor/__init__.py @@ -1,7 +1,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor, mqtt -from esphome.const import CONF_ID, CONF_QOS, CONF_TOPIC +from esphome.const import CONF_QOS, CONF_TOPIC + from .. import mqtt_subscribe_ns DEPENDENCIES = ["mqtt"] @@ -11,20 +12,23 @@ MQTTSubscribeTextSensor = mqtt_subscribe_ns.class_( "MQTTSubscribeTextSensor", text_sensor.TextSensor, cg.Component ) -CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(MQTTSubscribeTextSensor), - cv.GenerateID(CONF_MQTT_PARENT_ID): cv.use_id(mqtt.MQTTClientComponent), - cv.Required(CONF_TOPIC): cv.subscribe_topic, - cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + text_sensor.text_sensor_schema() + .extend( + { + cv.GenerateID(): cv.declare_id(MQTTSubscribeTextSensor), + cv.GenerateID(CONF_MQTT_PARENT_ID): cv.use_id(mqtt.MQTTClientComponent), + cv.Required(CONF_TOPIC): cv.subscribe_topic, + cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await text_sensor.new_text_sensor(config) await cg.register_component(var, config) - await text_sensor.register_text_sensor(var, config) parent = await cg.get_variable(config[CONF_MQTT_PARENT_ID]) cg.add(var.set_parent(parent)) diff --git a/esphome/components/nextion/text_sensor/__init__.py b/esphome/components/nextion/text_sensor/__init__.py index 9c170dd807..9b8518d8c4 100644 --- a/esphome/components/nextion/text_sensor/__init__.py +++ b/esphome/components/nextion/text_sensor/__init__.py @@ -17,11 +17,7 @@ NextionTextSensor = nextion_ns.class_( ) CONFIG_SCHEMA = ( - text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(NextionTextSensor), - } - ) + text_sensor.text_sensor_schema(klass=NextionTextSensor) .extend(CONFIG_TEXT_COMPONENT_SCHEMA) .extend(cv.polling_component_schema("never")) ) @@ -30,8 +26,8 @@ CONFIG_SCHEMA = ( async def to_code(config): hub = await cg.get_variable(config[CONF_NEXTION_ID]) var = cg.new_Pvariable(config[CONF_ID], hub) - await cg.register_component(var, config) await text_sensor.register_text_sensor(var, config) + await cg.register_component(var, config) cg.add(hub.register_textsensor_component(var)) diff --git a/esphome/components/pipsolar/__init__.py b/esphome/components/pipsolar/__init__.py index 20e4672125..875de05713 100644 --- a/esphome/components/pipsolar/__init__.py +++ b/esphome/components/pipsolar/__init__.py @@ -13,7 +13,7 @@ CONF_PIPSOLAR_ID = "pipsolar_id" pipsolar_ns = cg.esphome_ns.namespace("pipsolar") PipsolarComponent = pipsolar_ns.class_("Pipsolar", cg.Component) -PIPSOLAR_COMPONENT_SCHEMA = cv.COMPONENT_SCHEMA.extend( +PIPSOLAR_COMPONENT_SCHEMA = cv.Schema( { cv.Required(CONF_PIPSOLAR_ID): cv.use_id(PipsolarComponent), } diff --git a/esphome/components/pipsolar/text_sensor/__init__.py b/esphome/components/pipsolar/text_sensor/__init__.py index fe6c4979f3..856352f8cb 100644 --- a/esphome/components/pipsolar/text_sensor/__init__.py +++ b/esphome/components/pipsolar/text_sensor/__init__.py @@ -1,8 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor -from esphome.const import CONF_ID -from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA, pipsolar_ns +from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA DEPENDENCIES = ["uart"] @@ -15,10 +14,6 @@ CONF_LAST_QPIWS = "last_qpiws" CONF_LAST_QT = "last_qt" CONF_LAST_QMN = "last_qmn" -PipsolarTextSensor = pipsolar_ns.class_( - "PipsolarTextSensor", text_sensor.TextSensor, cg.Component -) - TYPES = [ CONF_DEVICE_MODE, CONF_LAST_QPIGS, @@ -31,12 +26,7 @@ TYPES = [ ] CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend( - { - cv.Optional(type): text_sensor.TEXT_SENSOR_SCHEMA.extend( - {cv.GenerateID(): cv.declare_id(PipsolarTextSensor)} - ) - for type in TYPES - } + {cv.Optional(type): text_sensor.text_sensor_schema() for type in TYPES} ) @@ -46,7 +36,5 @@ async def to_code(config): for type in TYPES: if type in config: conf = config[type] - var = cg.new_Pvariable(conf[CONF_ID]) - await text_sensor.register_text_sensor(var, conf) - await cg.register_component(var, conf) + var = await text_sensor.new_text_sensor(conf) cg.add(getattr(paren, f"set_{type}")(var)) diff --git a/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.cpp b/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.cpp deleted file mode 100644 index ee1fe2d1d8..0000000000 --- a/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "pipsolar_textsensor.h" -#include "esphome/core/log.h" -#include "esphome/core/application.h" - -namespace esphome { -namespace pipsolar { - -static const char *const TAG = "pipsolar.text_sensor"; - -void PipsolarTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Pipsolar TextSensor", this); } - -} // namespace pipsolar -} // namespace esphome diff --git a/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.h b/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.h deleted file mode 100644 index 871f6d8dee..0000000000 --- a/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "../pipsolar.h" -#include "esphome/components/text_sensor/text_sensor.h" -#include "esphome/core/component.h" - -namespace esphome { -namespace pipsolar { -class Pipsolar; -class PipsolarTextSensor : public Component, public text_sensor::TextSensor { - public: - void set_parent(Pipsolar *parent) { this->parent_ = parent; }; - void dump_config() override; - - protected: - Pipsolar *parent_; -}; - -} // namespace pipsolar -} // namespace esphome diff --git a/esphome/components/sun/text_sensor/__init__.py b/esphome/components/sun/text_sensor/__init__.py index ac1ce223d1..80737bb9f2 100644 --- a/esphome/components/sun/text_sensor/__init__.py +++ b/esphome/components/sun/text_sensor/__init__.py @@ -6,7 +6,6 @@ from esphome.const import ( ICON_WEATHER_SUNSET_DOWN, ICON_WEATHER_SUNSET_UP, CONF_TYPE, - CONF_ID, CONF_FORMAT, ) from .. import sun_ns, CONF_SUN_ID, Sun, CONF_ELEVATION, elevation, DEFAULT_ELEVATION @@ -33,7 +32,8 @@ def validate_optional_icon(config): CONFIG_SCHEMA = cv.All( - text_sensor.TEXT_SENSOR_SCHEMA.extend( + text_sensor.text_sensor_schema() + .extend( { cv.GenerateID(): cv.declare_id(SunTextSensor), cv.GenerateID(CONF_SUN_ID): cv.use_id(Sun), @@ -41,15 +41,15 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_ELEVATION, default=DEFAULT_ELEVATION): elevation, cv.Optional(CONF_FORMAT, default="%X"): cv.string_strict, } - ).extend(cv.polling_component_schema("60s")), + ) + .extend(cv.polling_component_schema("60s")), validate_optional_icon, ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await text_sensor.new_text_sensor(config) await cg.register_component(var, config) - await text_sensor.register_text_sensor(var, config) paren = await cg.get_variable(config[CONF_SUN_ID]) cg.add(var.set_parent(paren)) diff --git a/esphome/components/teleinfo/__init__.py b/esphome/components/teleinfo/__init__.py index 9a5712e10f..33b748a031 100644 --- a/esphome/components/teleinfo/__init__.py +++ b/esphome/components/teleinfo/__init__.py @@ -7,9 +7,20 @@ CODEOWNERS = ["@0hax"] teleinfo_ns = cg.esphome_ns.namespace("teleinfo") TeleInfo = teleinfo_ns.class_("TeleInfo", cg.PollingComponent, uart.UARTDevice) -CONF_TELEINFO_ID = "teleinfo_id" +CONF_TELEINFO_ID = "teleinfo_id" +CONF_TAG_NAME = "tag_name" CONF_HISTORICAL_MODE = "historical_mode" + + +TELEINFO_LISTENER_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_TELEINFO_ID): cv.use_id(TeleInfo), + cv.Required(CONF_TAG_NAME): cv.string, + } +) + + CONFIG_SCHEMA = ( cv.Schema( { diff --git a/esphome/components/teleinfo/sensor/__init__.py b/esphome/components/teleinfo/sensor/__init__.py index e7cc2fcb1b..aa875be157 100644 --- a/esphome/components/teleinfo/sensor/__init__.py +++ b/esphome/components/teleinfo/sensor/__init__.py @@ -3,20 +3,22 @@ import esphome.config_validation as cv from esphome.components import sensor from esphome.const import CONF_ID, ICON_FLASH, UNIT_WATT_HOURS -from .. import teleinfo_ns, TeleInfo, CONF_TELEINFO_ID +from .. import ( + CONF_TAG_NAME, + TELEINFO_LISTENER_SCHEMA, + teleinfo_ns, + CONF_TELEINFO_ID, +) -CONF_TAG_NAME = "tag_name" TeleInfoSensor = teleinfo_ns.class_("TeleInfoSensor", sensor.Sensor, cg.Component) -CONFIG_SCHEMA = sensor.sensor_schema( - unit_of_measurement=UNIT_WATT_HOURS, icon=ICON_FLASH, accuracy_decimals=0 -).extend( - { - cv.GenerateID(): cv.declare_id(TeleInfoSensor), - cv.GenerateID(CONF_TELEINFO_ID): cv.use_id(TeleInfo), - cv.Required(CONF_TAG_NAME): cv.string, - } +CONFIG_SCHEMA = ( + sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, icon=ICON_FLASH, accuracy_decimals=0 + ) + .extend({cv.GenerateID(): cv.declare_id(TeleInfoSensor)}) + .extend(TELEINFO_LISTENER_SCHEMA) ) diff --git a/esphome/components/teleinfo/text_sensor/__init__.py b/esphome/components/teleinfo/text_sensor/__init__.py index 3bd73ff272..848b08d742 100644 --- a/esphome/components/teleinfo/text_sensor/__init__.py +++ b/esphome/components/teleinfo/text_sensor/__init__.py @@ -1,22 +1,15 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import text_sensor from esphome.const import CONF_ID -from .. import teleinfo_ns, TeleInfo, CONF_TELEINFO_ID - -CONF_TAG_NAME = "tag_name" +from .. import CONF_TAG_NAME, TELEINFO_LISTENER_SCHEMA, teleinfo_ns, CONF_TELEINFO_ID TeleInfoTextSensor = teleinfo_ns.class_( "TeleInfoTextSensor", text_sensor.TextSensor, cg.Component ) -CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(TeleInfoTextSensor), - cv.GenerateID(CONF_TELEINFO_ID): cv.use_id(TeleInfo), - cv.Required(CONF_TAG_NAME): cv.string, - } +CONFIG_SCHEMA = text_sensor.text_sensor_schema(klass=TeleInfoTextSensor).extend( + TELEINFO_LISTENER_SCHEMA ) diff --git a/esphome/components/template/text_sensor/__init__.py b/esphome/components/template/text_sensor/__init__.py index 2e098a77c2..9bd603bbca 100644 --- a/esphome/components/template/text_sensor/__init__.py +++ b/esphome/components/template/text_sensor/__init__.py @@ -10,18 +10,21 @@ TemplateTextSensor = template_ns.class_( "TemplateTextSensor", text_sensor.TextSensor, cg.PollingComponent ) -CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(TemplateTextSensor), - cv.Optional(CONF_LAMBDA): cv.returning_lambda, - } -).extend(cv.polling_component_schema("60s")) +CONFIG_SCHEMA = ( + text_sensor.text_sensor_schema() + .extend( + { + cv.GenerateID(): cv.declare_id(TemplateTextSensor), + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + } + ) + .extend(cv.polling_component_schema("60s")) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await text_sensor.new_text_sensor(config) await cg.register_component(var, config) - await text_sensor.register_text_sensor(var, config) if CONF_LAMBDA in config: template_ = await cg.process_lambda( diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index e0fc6af19c..de72579402 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -3,7 +3,9 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import mqtt from esphome.const import ( + CONF_ENTITY_CATEGORY, CONF_FILTERS, + CONF_ICON, CONF_ID, CONF_ON_VALUE, CONF_ON_RAW_VALUE, @@ -14,6 +16,7 @@ from esphome.const import ( CONF_TO, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_generator import MockObjClass from esphome.cpp_helpers import setup_entity from esphome.util import Registry @@ -110,12 +113,10 @@ async def map_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), + cv.GenerateID(): cv.declare_id(TextSensor), cv.Optional(CONF_FILTERS): validate_filters, cv.Optional(CONF_ON_VALUE): automation.validate_automation( { @@ -132,6 +133,29 @@ TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).exte } ) +_UNDEF = object() + + +def text_sensor_schema( + klass: MockObjClass = _UNDEF, + icon: str = _UNDEF, + entity_category: str = _UNDEF, +) -> cv.Schema: + schema = TEXT_SENSOR_SCHEMA + if klass is not _UNDEF: + schema = schema.extend({cv.GenerateID(): cv.declare_id(klass)}) + 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 build_filters(config): return await cg.build_registry_list(FILTER_REGISTRY, config) @@ -164,6 +188,12 @@ async def register_text_sensor(var, config): await setup_text_sensor_core_(var, config) +async def new_text_sensor(config): + var = cg.new_Pvariable(config[CONF_ID]) + await register_text_sensor(var, config) + return var + + @coroutine_with_priority(100.0) async def to_code(config): cg.add_define("USE_TEXT_SENSOR") diff --git a/esphome/components/tuya/text_sensor/__init__.py b/esphome/components/tuya/text_sensor/__init__.py index 1989ca10e3..bc60369377 100644 --- a/esphome/components/tuya/text_sensor/__init__.py +++ b/esphome/components/tuya/text_sensor/__init__.py @@ -1,7 +1,7 @@ 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 esphome.const import CONF_SENSOR_DATAPOINT from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] @@ -9,19 +9,22 @@ 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) +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]) + var = await text_sensor.new_text_sensor(config) 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)) diff --git a/esphome/components/version/text_sensor.py b/esphome/components/version/text_sensor.py index 4835caf35b..c8774bb322 100644 --- a/esphome/components/version/text_sensor.py +++ b/esphome/components/version/text_sensor.py @@ -2,9 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor from esphome.const import ( - CONF_ENTITY_CATEGORY, - CONF_ID, - CONF_ICON, ENTITY_CATEGORY_DIAGNOSTIC, ICON_NEW_BOX, CONF_HIDE_TIMESTAMP, @@ -15,20 +12,22 @@ VersionTextSensor = version_ns.class_( "VersionTextSensor", text_sensor.TextSensor, cg.Component ) -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) +CONFIG_SCHEMA = ( + text_sensor.text_sensor_schema( + icon=ICON_NEW_BOX, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ) + .extend( + { + cv.GenerateID(): cv.declare_id(VersionTextSensor), + cv.Optional(CONF_HIDE_TIMESTAMP, default=False): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await text_sensor.register_text_sensor(var, config) + var = await text_sensor.new_text_sensor(config) await cg.register_component(var, config) cg.add(var.set_hide_timestamp(config[CONF_HIDE_TIMESTAMP])) diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index 706a8967be..d7d652ac7b 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -3,8 +3,6 @@ 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, @@ -31,45 +29,20 @@ MacAddressWifiInfo = wifi_info_ns.class_( 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_IP_ADDRESS): text_sensor.text_sensor_schema( + klass=IPAddressWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ), - 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, - } + cv.Optional(CONF_SCAN_RESULTS): text_sensor.text_sensor_schema( + klass=ScanResultsWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ).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_SSID): text_sensor.text_sensor_schema( + klass=SSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ), - 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_BSSID): text_sensor.text_sensor_schema( + klass=BSSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ), - 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, - } + cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema( + klass=MacAddressWifiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ), } ) @@ -78,9 +51,8 @@ CONFIG_SCHEMA = cv.Schema( async def setup_conf(config, key): if key in config: conf = config[key] - var = cg.new_Pvariable(conf[CONF_ID]) + var = await text_sensor.new_text_sensor(conf) await cg.register_component(var, conf) - await text_sensor.register_text_sensor(var, conf) async def to_code(config): From 7ca9245735ee04b22fe5fb00ea81d52449b0f7c8 Mon Sep 17 00:00:00 2001 From: Jonas Bergler Date: Tue, 8 Feb 2022 20:27:39 +1300 Subject: [PATCH 078/238] wifi_info, reduce polling interval (#3165) Co-authored-by: Jonas Bergler Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/wifi_info/text_sensor.py | 14 ++++++++------ .../components/wifi_info/wifi_info_text_sensor.h | 12 ++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index d7d652ac7b..58250c3759 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -14,14 +14,16 @@ DEPENDENCIES = ["wifi"] wifi_info_ns = cg.esphome_ns.namespace("wifi_info") IPAddressWiFiInfo = wifi_info_ns.class_( - "IPAddressWiFiInfo", text_sensor.TextSensor, cg.Component + "IPAddressWiFiInfo", text_sensor.TextSensor, cg.PollingComponent ) ScanResultsWiFiInfo = wifi_info_ns.class_( "ScanResultsWiFiInfo", text_sensor.TextSensor, cg.PollingComponent ) -SSIDWiFiInfo = wifi_info_ns.class_("SSIDWiFiInfo", text_sensor.TextSensor, cg.Component) +SSIDWiFiInfo = wifi_info_ns.class_( + "SSIDWiFiInfo", text_sensor.TextSensor, cg.PollingComponent +) BSSIDWiFiInfo = wifi_info_ns.class_( - "BSSIDWiFiInfo", text_sensor.TextSensor, cg.Component + "BSSIDWiFiInfo", text_sensor.TextSensor, cg.PollingComponent ) MacAddressWifiInfo = wifi_info_ns.class_( "MacAddressWifiInfo", text_sensor.TextSensor, cg.Component @@ -31,16 +33,16 @@ CONFIG_SCHEMA = cv.Schema( { cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema( klass=IPAddressWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC - ), + ).extend(cv.polling_component_schema("1s")), cv.Optional(CONF_SCAN_RESULTS): text_sensor.text_sensor_schema( klass=ScanResultsWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ).extend(cv.polling_component_schema("60s")), cv.Optional(CONF_SSID): text_sensor.text_sensor_schema( klass=SSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC - ), + ).extend(cv.polling_component_schema("1s")), cv.Optional(CONF_BSSID): text_sensor.text_sensor_schema( klass=BSSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC - ), + ).extend(cv.polling_component_schema("1s")), cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema( klass=MacAddressWifiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ), diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index 5b54451ed0..e5b0fa3223 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -7,9 +7,9 @@ namespace esphome { namespace wifi_info { -class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor { +class IPAddressWiFiInfo : public PollingComponent, public text_sensor::TextSensor { public: - void loop() override { + void update() override { auto ip = wifi::global_wifi_component->wifi_sta_ip(); if (ip != this->last_ip_) { this->last_ip_ = ip; @@ -53,9 +53,9 @@ class ScanResultsWiFiInfo : public PollingComponent, public text_sensor::TextSen std::string last_scan_results_; }; -class SSIDWiFiInfo : public Component, public text_sensor::TextSensor { +class SSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor { public: - void loop() override { + void update() override { std::string ssid = wifi::global_wifi_component->wifi_ssid(); if (this->last_ssid_ != ssid) { this->last_ssid_ = ssid; @@ -70,9 +70,9 @@ class SSIDWiFiInfo : public Component, public text_sensor::TextSensor { std::string last_ssid_; }; -class BSSIDWiFiInfo : public Component, public text_sensor::TextSensor { +class BSSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor { public: - void loop() override { + void update() override { wifi::bssid_t bssid = wifi::global_wifi_component->wifi_bssid(); if (memcmp(bssid.data(), last_bssid_.data(), 6) != 0) { std::copy(bssid.begin(), bssid.end(), last_bssid_.begin()); From 397ef72b16c595c74285820697387b206967cbf5 Mon Sep 17 00:00:00 2001 From: functionpointer Date: Tue, 8 Feb 2022 08:42:11 +0100 Subject: [PATCH 079/238] MLX90393 three-axis magnetometer (#2770) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/mlx90393/__init__.py | 1 + esphome/components/mlx90393/sensor.py | 135 ++++++++++++++++++ .../components/mlx90393/sensor_mlx90393.cpp | 91 ++++++++++++ esphome/components/mlx90393/sensor_mlx90393.h | 59 ++++++++ platformio.ini | 1 + tests/test3.yaml | 14 ++ 7 files changed, 302 insertions(+) create mode 100644 esphome/components/mlx90393/__init__.py create mode 100644 esphome/components/mlx90393/sensor.py create mode 100644 esphome/components/mlx90393/sensor_mlx90393.cpp create mode 100644 esphome/components/mlx90393/sensor_mlx90393.h diff --git a/CODEOWNERS b/CODEOWNERS index a353906da2..d786dc165e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -109,6 +109,7 @@ esphome/components/mdns/* @esphome/core esphome/components/midea/* @dudanov esphome/components/midea_ir/* @dudanov esphome/components/mitsubishi/* @RubyBailey +esphome/components/mlx90393/* @functionpointer esphome/components/modbus_controller/* @martgras esphome/components/modbus_controller/binary_sensor/* @martgras esphome/components/modbus_controller/number/* @martgras diff --git a/esphome/components/mlx90393/__init__.py b/esphome/components/mlx90393/__init__.py new file mode 100644 index 0000000000..fc92f02120 --- /dev/null +++ b/esphome/components/mlx90393/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@functionpointer"] diff --git a/esphome/components/mlx90393/sensor.py b/esphome/components/mlx90393/sensor.py new file mode 100644 index 0000000000..92ba30bea3 --- /dev/null +++ b/esphome/components/mlx90393/sensor.py @@ -0,0 +1,135 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + UNIT_MICROTESLA, + UNIT_CELSIUS, + STATE_CLASS_MEASUREMENT, + ICON_MAGNET, + ICON_THERMOMETER, + CONF_GAIN, + CONF_RESOLUTION, + CONF_OVERSAMPLING, + CONF_FILTER, + CONF_TEMPERATURE, +) +from esphome import pins + +CODEOWNERS = ["@functionpointer"] +DEPENDENCIES = ["i2c"] + +mlx90393_ns = cg.esphome_ns.namespace("mlx90393") + +MLX90393Component = mlx90393_ns.class_( + "MLX90393Cls", cg.PollingComponent, i2c.I2CDevice +) + +GAIN = { + "1X": 7, + "1_33X": 6, + "1_67X": 5, + "2X": 4, + "2_5X": 3, + "3X": 2, + "4X": 1, + "5X": 0, +} + +RESOLUTION = { + "16BIT": 0, + "17BIT": 1, + "18BIT": 2, + "19BIT": 3, +} + +CONF_X_AXIS = "x_axis" +CONF_Y_AXIS = "y_axis" +CONF_Z_AXIS = "z_axis" +CONF_DRDY_PIN = "drdy_pin" + + +def mlx90393_axis_schema(default_resolution: str): + return sensor.sensor_schema( + unit_of_measurement=UNIT_MICROTESLA, + accuracy_decimals=0, + icon=ICON_MAGNET, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + cv.Schema( + { + cv.Optional(CONF_RESOLUTION, default=default_resolution): cv.enum( + RESOLUTION, upper=True, space="_" + ) + } + ) + ) + + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MLX90393Component), + cv.Optional(CONF_GAIN, default="2_5X"): cv.enum( + GAIN, upper=True, space="_" + ), + cv.Optional(CONF_DRDY_PIN): pins.gpio_input_pin_schema, + cv.Optional(CONF_OVERSAMPLING, default=2): cv.int_range(min=0, max=3), + cv.Optional(CONF_FILTER, default=6): cv.int_range(min=0, max=7), + cv.Optional(CONF_X_AXIS): mlx90393_axis_schema("19BIT"), + cv.Optional(CONF_Y_AXIS): mlx90393_axis_schema("19BIT"), + cv.Optional(CONF_Z_AXIS): mlx90393_axis_schema("16BIT"), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + icon=ICON_THERMOMETER, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + cv.Schema( + { + cv.Optional(CONF_OVERSAMPLING, default=0): cv.int_range( + min=0, max=3 + ), + } + ) + ), + }, + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x0C)) +) + + +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_DRDY_PIN in config: + pin = await cg.gpio_pin_expression(config[CONF_DRDY_PIN]) + cg.add(var.set_drdy_pin(pin)) + cg.add(var.set_gain(GAIN[config[CONF_GAIN]])) + cg.add(var.set_oversampling(config[CONF_OVERSAMPLING])) + cg.add(var.set_filter(config[CONF_FILTER])) + + if CONF_X_AXIS in config: + sens = await sensor.new_sensor(config[CONF_X_AXIS]) + cg.add(var.set_x_sensor(sens)) + cg.add(var.set_resolution(0, RESOLUTION[config[CONF_X_AXIS][CONF_RESOLUTION]])) + if CONF_Y_AXIS in config: + sens = await sensor.new_sensor(config[CONF_Y_AXIS]) + cg.add(var.set_y_sensor(sens)) + cg.add(var.set_resolution(1, RESOLUTION[config[CONF_Y_AXIS][CONF_RESOLUTION]])) + if CONF_Z_AXIS in config: + sens = await sensor.new_sensor(config[CONF_Z_AXIS]) + cg.add(var.set_z_sensor(sens)) + cg.add(var.set_resolution(2, RESOLUTION[config[CONF_Z_AXIS][CONF_RESOLUTION]])) + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_t_sensor(sens)) + cg.add(var.set_t_oversampling(config[CONF_TEMPERATURE][CONF_OVERSAMPLING])) + if CONF_DRDY_PIN in config: + pin = await cg.gpio_pin_expression(config[CONF_DRDY_PIN]) + cg.add(var.set_drdy_gpio(pin)) + + cg.add_library("functionpointer/arduino-MLX90393", "1.0.0") diff --git a/esphome/components/mlx90393/sensor_mlx90393.cpp b/esphome/components/mlx90393/sensor_mlx90393.cpp new file mode 100644 index 0000000000..d4431a7334 --- /dev/null +++ b/esphome/components/mlx90393/sensor_mlx90393.cpp @@ -0,0 +1,91 @@ +#include "sensor_mlx90393.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mlx90393 { + +static const char *const TAG = "mlx90393"; + +bool MLX90393Cls::transceive(const uint8_t *request, size_t request_size, uint8_t *response, size_t response_size) { + i2c::ErrorCode e = this->write(request, request_size); + if (e != i2c::ErrorCode::ERROR_OK) { + return false; + } + e = this->read(response, response_size); + return e == i2c::ErrorCode::ERROR_OK; +} + +bool MLX90393Cls::has_drdy_pin() { return this->drdy_pin_ != nullptr; } + +bool MLX90393Cls::read_drdy_pin() { + if (this->drdy_pin_ == nullptr) { + return false; + } else { + return this->drdy_pin_->digital_read(); + } +} +void MLX90393Cls::sleep_millis(uint32_t millis) { delay(millis); } +void MLX90393Cls::sleep_micros(uint32_t micros) { delayMicroseconds(micros); } + +void MLX90393Cls::setup() { + ESP_LOGCONFIG(TAG, "Setting up MLX90393..."); + // note the two arguments A0 and A1 which are used to construct an i2c address + // we can hard-code these because we never actually use the constructed address + // see the transceive function above, which uses the address from I2CComponent + this->mlx_.begin_with_hal(this, 0, 0); + + this->mlx_.setGainSel(this->gain_); + + this->mlx_.setResolution(this->resolutions_[0], this->resolutions_[1], this->resolutions_[2]); + + this->mlx_.setOverSampling(this->oversampling_); + + this->mlx_.setDigitalFiltering(this->filter_); + + this->mlx_.setTemperatureOverSampling(this->temperature_oversampling_); +} + +void MLX90393Cls::dump_config() { + ESP_LOGCONFIG(TAG, "MLX90393:"); + LOG_I2C_DEVICE(this); + + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with MLX90393 failed!"); + return; + } + LOG_UPDATE_INTERVAL(this); + + LOG_SENSOR(" ", "X Axis", this->x_sensor_); + LOG_SENSOR(" ", "Y Axis", this->y_sensor_); + LOG_SENSOR(" ", "Z Axis", this->z_sensor_); + LOG_SENSOR(" ", "Temperature", this->t_sensor_); +} + +float MLX90393Cls::get_setup_priority() const { return setup_priority::DATA; } + +void MLX90393Cls::update() { + MLX90393::txyz data; + + if (this->mlx_.readData(data) == MLX90393::STATUS_OK) { + ESP_LOGD(TAG, "received %f %f %f", data.x, data.y, data.z); + if (this->x_sensor_ != nullptr) { + this->x_sensor_->publish_state(data.x); + } + if (this->y_sensor_ != nullptr) { + this->y_sensor_->publish_state(data.y); + } + if (this->z_sensor_ != nullptr) { + this->z_sensor_->publish_state(data.z); + } + if (this->t_sensor_ != nullptr) { + this->t_sensor_->publish_state(data.t); + } + this->status_clear_warning(); + } else { + ESP_LOGE(TAG, "failed to read data"); + this->status_set_warning(); + } +} + +} // namespace mlx90393 +} // namespace esphome diff --git a/esphome/components/mlx90393/sensor_mlx90393.h b/esphome/components/mlx90393/sensor_mlx90393.h new file mode 100644 index 0000000000..fc33ad1aa8 --- /dev/null +++ b/esphome/components/mlx90393/sensor_mlx90393.h @@ -0,0 +1,59 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/core/hal.h" +#include +#include + +namespace esphome { +namespace mlx90393 { + +class MLX90393Cls : public PollingComponent, public i2c::I2CDevice, public MLX90393Hal { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + + void set_drdy_gpio(GPIOPin *pin) { drdy_pin_ = pin; } + + void set_x_sensor(sensor::Sensor *x_sensor) { x_sensor_ = x_sensor; } + void set_y_sensor(sensor::Sensor *y_sensor) { y_sensor_ = y_sensor; } + void set_z_sensor(sensor::Sensor *z_sensor) { z_sensor_ = z_sensor; } + void set_t_sensor(sensor::Sensor *t_sensor) { t_sensor_ = t_sensor; } + + void set_oversampling(uint8_t osr) { oversampling_ = osr; } + void set_t_oversampling(uint8_t osr2) { temperature_oversampling_ = osr2; } + void set_resolution(uint8_t xyz, uint8_t res) { resolutions_[xyz] = res; } + void set_filter(uint8_t filter) { filter_ = filter; } + void set_gain(uint8_t gain_sel) { gain_ = gain_sel; } + + // overrides for MLX library + + // disable lint because it keeps suggesting const uint8_t *response. + // this->read() writes data into response, so it can't be const + bool transceive(const uint8_t *request, size_t request_size, uint8_t *response, + size_t response_size) override; // NOLINT + bool has_drdy_pin() override; + bool read_drdy_pin() override; + void sleep_millis(uint32_t millis) override; + void sleep_micros(uint32_t micros) override; + + protected: + MLX90393 mlx_; + sensor::Sensor *x_sensor_{nullptr}; + sensor::Sensor *y_sensor_{nullptr}; + sensor::Sensor *z_sensor_{nullptr}; + sensor::Sensor *t_sensor_{nullptr}; + uint8_t gain_; + uint8_t oversampling_; + uint8_t temperature_oversampling_ = 0; + uint8_t filter_; + uint8_t resolutions_[3] = {0}; + GPIOPin *drdy_pin_ = nullptr; +}; + +} // namespace mlx90393 +} // namespace esphome diff --git a/platformio.ini b/platformio.ini index 70cfb11bf2..ba232033a1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -38,6 +38,7 @@ lib_deps = esphome/Improv@1.2.1 ; improv_serial / esp32_improv bblanchon/ArduinoJson@6.18.5 ; json wjtje/qr-code-generator-library@1.7.0 ; qr_code + functionpointer/arduino-MLX90393@1.0.0 ; mlx90393 build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = diff --git a/tests/test3.yaml b/tests/test3.yaml index 32a3b1be3d..853d7bd389 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -692,6 +692,20 @@ sensor: name: 'testwave' component_id: 2 wave_channel_id: 1 + - platform: mlx90393 + oversampling: 1 + filter: 0 + gain: "3X" + x_axis: + name: "mlxxaxis" + y_axis: + name: "mlxyaxis" + z_axis: + name: "mlxzaxis" + resolution: 17BIT + temperature: + name: "mlxtemp" + oversampling: 2 time: - platform: homeassistant From 434ca47ea086affd044b97780250e9689b0c3820 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 8 Feb 2022 09:21:52 +0100 Subject: [PATCH 080/238] Enable mDNS during OTA safe mode (#3146) --- esphome/components/mdns/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index b95469d9da..b5be153d5a 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -1,7 +1,7 @@ from esphome.const import CONF_ID import esphome.codegen as cg import esphome.config_validation as cv -from esphome.core import CORE +from esphome.core import CORE, coroutine_with_priority CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] @@ -29,6 +29,7 @@ CONFIG_SCHEMA = cv.All( ) +@coroutine_with_priority(55.0) async def to_code(config): if CORE.using_arduino: if CORE.is_esp32: From 1c0697b5d45be313a4dc2fba43309d4834d6747d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 8 Feb 2022 21:28:12 +1300 Subject: [PATCH 081/238] Dont warn on nonnull comparisons (#3123) --- esphome/components/esp8266/__init__.py | 1 + platformio.ini | 1 + 2 files changed, 2 insertions(+) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 015caf92e6..3c83400c1d 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -166,6 +166,7 @@ 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_build_flag("-Wno-nonnull-compare") cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) cg.add_platformio_option( "platform_packages", diff --git a/platformio.ini b/platformio.ini index ba232033a1..8775b28156 100644 --- a/platformio.ini +++ b/platformio.ini @@ -94,6 +94,7 @@ lib_deps = DNSServer ; captive_portal (Arduino built-in) build_flags = ${common:arduino.build_flags} + -Wno-nonnull-compare -DUSE_ESP8266 -DUSE_ESP8266_FRAMEWORK_ARDUINO extra_scripts = post:esphome/components/esp8266/post_build.py.script From 116ddbdd017347abdf6ada854e8658ecd39dad7c Mon Sep 17 00:00:00 2001 From: Ashton Kemerling Date: Tue, 8 Feb 2022 01:30:31 -0700 Subject: [PATCH 082/238] Add require response option for BLE binary output (#3091) --- esphome/components/ble_client/ble_client.cpp | 8 ++++++-- esphome/components/ble_client/ble_client.h | 1 + esphome/components/ble_client/output/__init__.py | 8 +++++--- .../components/ble_client/output/ble_binary_output.cpp | 6 +++++- esphome/components/ble_client/output/ble_binary_output.h | 2 ++ 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 89981e8174..5b2701d77a 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -395,15 +395,19 @@ 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) { +void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) { 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); + write_type, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status); } } +void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) { + write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP); +} + } // namespace ble_client } // namespace esphome diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index c173f7a995..e0a1bf61b9 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -60,6 +60,7 @@ class BLECharacteristic { BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); BLEDescriptor *get_descriptor(uint16_t uuid); void write_value(uint8_t *new_val, int16_t new_val_size); + void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type); BLEService *service; }; diff --git a/esphome/components/ble_client/output/__init__.py b/esphome/components/ble_client/output/__init__.py index fe5835ca82..e28421d1a7 100644 --- a/esphome/components/ble_client/output/__init__.py +++ b/esphome/components/ble_client/output/__init__.py @@ -1,13 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import output, ble_client, esp32_ble_tracker +from esphome.components import ble_client, esp32_ble_tracker, output from esphome.const import CONF_ID, CONF_SERVICE_UUID -from .. import ble_client_ns +from .. import ble_client_ns DEPENDENCIES = ["ble_client"] CONF_CHARACTERISTIC_UUID = "characteristic_uuid" +CONF_REQUIRE_RESPONSE = "require_response" BLEBinaryOutput = ble_client_ns.class_( "BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component @@ -19,6 +20,7 @@ CONFIG_SCHEMA = cv.All( 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, + cv.Optional(CONF_REQUIRE_RESPONSE, default=False): cv.boolean, } ) .extend(cv.COMPONENT_SCHEMA) @@ -61,7 +63,7 @@ def to_code(config): config[CONF_CHARACTERISTIC_UUID] ) cg.add(var.set_char_uuid128(uuid128)) - + cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE])) 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 index 7bf5ea4ead..6709803936 100644 --- a/esphome/components/ble_client/output/ble_binary_output.cpp +++ b/esphome/components/ble_client/output/ble_binary_output.cpp @@ -63,7 +63,11 @@ void BLEBinaryOutput::write_state(bool state) { 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)); + if (this->require_response_) { + chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_RSP); + } else { + chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_NO_RSP); + } } } // namespace ble_client diff --git a/esphome/components/ble_client/output/ble_binary_output.h b/esphome/components/ble_client/output/ble_binary_output.h index e1d62a267b..83eabcf5f2 100644 --- a/esphome/components/ble_client/output/ble_binary_output.h +++ b/esphome/components/ble_client/output/ble_binary_output.h @@ -25,9 +25,11 @@ class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, publi 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; + void set_require_response(bool response) { this->require_response_ = response; } protected: void write_state(bool state) override; + bool require_response_; espbt::ESPBTUUID service_uuid_; espbt::ESPBTUUID char_uuid_; espbt::ClientState client_state_; From 94f944dc9c37074a46970f6efe9da916698bf88d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 8 Feb 2022 21:50:25 +1300 Subject: [PATCH 083/238] Add Lilygo t5 4.7 Touchscreen (#3084) --- CODEOWNERS | 1 + esphome/codegen.py | 1 + esphome/components/lilygo_t5_47/__init__.py | 3 + .../lilygo_t5_47/touchscreen/__init__.py | 45 ++++++ .../touchscreen/lilygo_t5_47_touchscreen.cpp | 141 ++++++++++++++++++ .../touchscreen/lilygo_t5_47_touchscreen.h | 35 +++++ .../touchscreen/binary_sensor/__init__.py | 13 +- .../binary_sensor/touchscreen_binary_sensor.h | 11 +- esphome/cpp_types.py | 1 + tests/test4.yaml | 10 ++ 10 files changed, 255 insertions(+), 6 deletions(-) create mode 100644 esphome/components/lilygo_t5_47/__init__.py create mode 100644 esphome/components/lilygo_t5_47/touchscreen/__init__.py create mode 100644 esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp create mode 100644 esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.h diff --git a/CODEOWNERS b/CODEOWNERS index d786dc165e..931e699566 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -89,6 +89,7 @@ esphome/components/json/* @OttoWinter esphome/components/kalman_combinator/* @Cat-Ion esphome/components/ledc/* @OttoWinter esphome/components/light/* @esphome/core +esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lock/* @esphome/core esphome/components/logger/* @esphome/core esphome/components/ltr390/* @sjtrny diff --git a/esphome/codegen.py b/esphome/codegen.py index 3ea3df8706..52191e05e2 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -81,4 +81,5 @@ from esphome.cpp_types import ( # noqa InternalGPIOPin, gpio_Flags, EntityCategory, + Parented, ) diff --git a/esphome/components/lilygo_t5_47/__init__.py b/esphome/components/lilygo_t5_47/__init__.py new file mode 100644 index 0000000000..5499d096a9 --- /dev/null +++ b/esphome/components/lilygo_t5_47/__init__.py @@ -0,0 +1,3 @@ +import esphome.codegen as cg + +lilygo_t5_47_ns = cg.esphome_ns.namespace("lilygo_t5_47") diff --git a/esphome/components/lilygo_t5_47/touchscreen/__init__.py b/esphome/components/lilygo_t5_47/touchscreen/__init__.py new file mode 100644 index 0000000000..9ec6c925ee --- /dev/null +++ b/esphome/components/lilygo_t5_47/touchscreen/__init__.py @@ -0,0 +1,45 @@ +import esphome.codegen as cg +import esphome.config_validation as cv + +from esphome import pins +from esphome.components import i2c, touchscreen +from esphome.const import CONF_ID + +from .. import lilygo_t5_47_ns + +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["i2c"] + +LilygoT547Touchscreen = lilygo_t5_47_ns.class_( + "LilygoT547Touchscreen", + touchscreen.Touchscreen, + cg.Component, + i2c.I2CDevice, +) + +CONF_LILYGO_T5_47_TOUCHSCREEN_ID = "lilygo_t5_47_touchscreen_id" +CONF_INTERRUPT_PIN = "interrupt_pin" + + +CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(LilygoT547Touchscreen), + cv.Required(CONF_INTERRUPT_PIN): cv.All( + pins.internal_gpio_input_pin_schema + ), + } + ) + .extend(i2c.i2c_device_schema(0x5A)) + .extend(cv.COMPONENT_SCHEMA) +) + + +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 touchscreen.register_touchscreen(var, config) + + interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) + cg.add(var.set_interrupt_pin(interrupt_pin)) diff --git a/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp new file mode 100644 index 0000000000..b5cf63980b --- /dev/null +++ b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp @@ -0,0 +1,141 @@ +#include "lilygo_t5_47_touchscreen.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace lilygo_t5_47 { + +static const char *const TAG = "lilygo_t5_47.touchscreen"; + +static const uint8_t POWER_REGISTER = 0xD6; +static const uint8_t TOUCH_REGISTER = 0xD0; + +static const uint8_t WAKEUP_CMD[1] = {0x06}; +static const uint8_t READ_FLAGS[1] = {0x00}; +static const uint8_t CLEAR_FLAGS[2] = {0x00, 0xAB}; +static const uint8_t READ_TOUCH[1] = {0x07}; + +#define ERROR_CHECK(err) \ + if ((err) != i2c::ERROR_OK) { \ + ESP_LOGE(TAG, "Failed to communicate!"); \ + this->status_set_warning(); \ + return; \ + } + +void Store::gpio_intr(Store *store) { store->touch = true; } + +void LilygoT547Touchscreen::setup() { + ESP_LOGCONFIG(TAG, "Setting up Lilygo T5 4.7 Touchscreen..."); + this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + this->interrupt_pin_->setup(); + + this->store_.pin = this->interrupt_pin_->to_isr(); + this->interrupt_pin_->attach_interrupt(Store::gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE); + + if (this->write(nullptr, 0) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Failed to communicate!"); + this->interrupt_pin_->detach_interrupt(); + this->mark_failed(); + return; + } + + this->write_register(POWER_REGISTER, WAKEUP_CMD, 1); +} + +void LilygoT547Touchscreen::loop() { + if (!this->store_.touch) { + for (auto *listener : this->touch_listeners_) + listener->release(); + return; + } + this->store_.touch = false; + + uint8_t point = 0; + uint8_t buffer[40] = {0}; + uint32_t sum_l = 0, sum_h = 0; + + i2c::ErrorCode err; + err = this->write_register(TOUCH_REGISTER, READ_FLAGS, 1); + ERROR_CHECK(err); + + err = this->read(buffer, 7); + ERROR_CHECK(err); + + if (buffer[0] == 0xAB) { + this->write_register(TOUCH_REGISTER, CLEAR_FLAGS, 2); + return; + } + + point = buffer[5] & 0xF; + + if (point == 0) { + for (auto *listener : this->touch_listeners_) + listener->release(); + return; + } else if (point == 1) { + err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1); + ERROR_CHECK(err); + err = this->read(&buffer[5], 2); + ERROR_CHECK(err); + + sum_l = buffer[5] << 8 | buffer[6]; + } else if (point > 1) { + err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1); + ERROR_CHECK(err); + err = this->read(&buffer[5], 5 * (point - 1) + 3); + ERROR_CHECK(err); + + sum_l = buffer[5 * point + 1] << 8 | buffer[5 * point + 2]; + } + + this->write_register(TOUCH_REGISTER, CLEAR_FLAGS, 2); + + for (int i = 0; i < 5 * point; i++) + sum_h += buffer[i]; + + if (sum_l != sum_h) + point = 0; + + if (point) { + uint8_t offset; + for (int i = 0; i < point; i++) { + if (i == 0) { + offset = 0; + } else { + offset = 4; + } + + TouchPoint tp; + + tp.id = (buffer[i * 5 + offset] >> 4) & 0x0F; + tp.state = buffer[i * 5 + offset] & 0x0F; + if (tp.state == 0x06) + tp.state = 0x07; + + tp.y = (uint16_t)((buffer[i * 5 + 1 + offset] << 4) | ((buffer[i * 5 + 3 + offset] >> 4) & 0x0F)); + tp.x = (uint16_t)((buffer[i * 5 + 2 + offset] << 4) | (buffer[i * 5 + 3 + offset] & 0x0F)); + + this->defer([this, tp]() { this->send_touch_(tp); }); + } + } else { + TouchPoint tp; + tp.id = (buffer[0] >> 4) & 0x0F; + tp.state = 0x06; + tp.y = (uint16_t)((buffer[0 * 5 + 1] << 4) | ((buffer[0 * 5 + 3] >> 4) & 0x0F)); + tp.x = (uint16_t)((buffer[0 * 5 + 2] << 4) | (buffer[0 * 5 + 3] & 0x0F)); + + this->defer([this, tp]() { this->send_touch_(tp); }); + } + + this->status_clear_warning(); +} + +void LilygoT547Touchscreen::dump_config() { + ESP_LOGCONFIG(TAG, "Lilygo T5 47 Touchscreen:"); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); +} + +} // namespace lilygo_t5_47 +} // namespace esphome diff --git a/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.h b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.h new file mode 100644 index 0000000000..3d00e0b117 --- /dev/null +++ b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.h @@ -0,0 +1,35 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/touchscreen/touchscreen.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace lilygo_t5_47 { + +struct Store { + volatile bool touch; + ISRInternalGPIOPin pin; + + static void gpio_intr(Store *store); +}; + +using namespace touchscreen; + +class LilygoT547Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice { + public: + void setup() override; + void loop() override; + void dump_config() override; + + void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } + + protected: + InternalGPIOPin *interrupt_pin_; + Store store_; +}; + +} // namespace lilygo_t5_47 +} // namespace esphome diff --git a/esphome/components/touchscreen/binary_sensor/__init__.py b/esphome/components/touchscreen/binary_sensor/__init__.py index cbd03c0a32..9dba821d4d 100644 --- a/esphome/components/touchscreen/binary_sensor/__init__.py +++ b/esphome/components/touchscreen/binary_sensor/__init__.py @@ -9,7 +9,11 @@ from .. import touchscreen_ns, CONF_TOUCHSCREEN_ID, Touchscreen, TouchListener DEPENDENCIES = ["touchscreen"] TouchscreenBinarySensor = touchscreen_ns.class_( - "TouchscreenBinarySensor", binary_sensor.BinarySensor, TouchListener + "TouchscreenBinarySensor", + binary_sensor.BinarySensor, + cg.Component, + TouchListener, + cg.Parented.template(Touchscreen), ) CONF_X_MIN = "x_min" @@ -39,7 +43,7 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=2000), cv.Required(CONF_Y_MAX): cv.int_range(min=0, max=2000), } - ), + ).extend(cv.COMPONENT_SCHEMA), validate_coords, ) @@ -47,7 +51,9 @@ CONFIG_SCHEMA = cv.All( 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_TOUCHSCREEN_ID]) + await cg.register_component(var, config) + await cg.register_parented(var, config[CONF_TOUCHSCREEN_ID]) + cg.add( var.set_area( config[CONF_X_MIN], @@ -56,4 +62,3 @@ async def to_code(config): config[CONF_Y_MAX], ) ) - cg.add(hub.register_listener(var)) diff --git a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h index 8fb7749766..7b8cac5c4c 100644 --- a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h +++ b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h @@ -1,13 +1,20 @@ #pragma once -#include "esphome/components/touchscreen/touchscreen.h" #include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/touchscreen/touchscreen.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" namespace esphome { namespace touchscreen { -class TouchscreenBinarySensor : public binary_sensor::BinarySensor, public TouchListener { +class TouchscreenBinarySensor : public binary_sensor::BinarySensor, + public Component, + public TouchListener, + public Parented { public: + void setup() override { this->parent_->register_listener(this); } + /// Set the touch screen area where the button will detect the touch. void set_area(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) { this->x_min_ = x_min; diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 806a2d832c..110641d6c9 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -34,3 +34,4 @@ 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") +Parented = esphome_ns.class_("Parented") diff --git a/tests/test4.yaml b/tests/test4.yaml index de641d92ff..998db8ed2d 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -328,6 +328,7 @@ binary_sensor: number: 3 - platform: touchscreen + touchscreen_id: lilygo_touchscreen id: touch_key1 x_min: 0 x_max: 100 @@ -561,3 +562,12 @@ touchscreen: - logger.log: format: Touch at (%d, %d) args: ["touch.x", "touch.y"] + + - platform: lilygo_t5_47 + id: lilygo_touchscreen + interrupt_pin: GPIO36 + display: inkplate_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: ["touch.x", "touch.y"] From 58fa63ad88b2c441d5d765f6865f0a10dd08ebed Mon Sep 17 00:00:00 2001 From: stegm Date: Tue, 8 Feb 2022 10:27:22 +0100 Subject: [PATCH 084/238] Add Select for modbus (#3032) Co-authored-by: Oxan van Leeuwen --- CODEOWNERS | 1 + esphome/codegen.py | 1 + .../modbus_controller/modbus_controller.cpp | 104 +++++-------- .../modbus_controller/modbus_controller.h | 54 +++++-- .../modbus_controller/select/__init__.py | 142 ++++++++++++++++++ .../select/modbus_select.cpp | 86 +++++++++++ .../modbus_controller/select/modbus_select.h | 51 +++++++ esphome/cpp_generator.py | 4 +- esphome/cpp_types.py | 1 + 9 files changed, 361 insertions(+), 83 deletions(-) create mode 100644 esphome/components/modbus_controller/select/__init__.py create mode 100644 esphome/components/modbus_controller/select/modbus_select.cpp create mode 100644 esphome/components/modbus_controller/select/modbus_select.h diff --git a/CODEOWNERS b/CODEOWNERS index 931e699566..3326b7d90c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -115,6 +115,7 @@ 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/select/* @martgras @stegm esphome/components/modbus_controller/sensor/* @martgras esphome/components/modbus_controller/switch/* @martgras esphome/components/modbus_controller/text_sensor/* @martgras diff --git a/esphome/codegen.py b/esphome/codegen.py index 52191e05e2..b862a8ce86 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -63,6 +63,7 @@ from esphome.cpp_types import ( # noqa uint32, uint64, int32, + int64, const_char_ptr, NAN, esphome_ns, diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 8b6482b1dc..64046b9578 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -110,7 +110,8 @@ void ModbusController::queue_command(const ModbusCommandItem &command) { 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->function_code == command.function_code) { - ESP_LOGW(TAG, "Duplicate modbus command found"); + ESP_LOGW(TAG, "Duplicate modbus command found: type=0x%x address=%u count=%u", + static_cast(command.register_type), command.register_address, command.register_count); // update the payload of the queued command // replaces a previous command item->payload = command.payload; @@ -360,8 +361,9 @@ ModbusCommandItem ModbusCommandItem::create_write_multiple_command(ModbusControl 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); + auto decoded_value = decode_value(v); + cmd.payload.push_back(decoded_value[0]); + cmd.payload.push_back(decoded_value[1]); } return cmd; } @@ -416,7 +418,7 @@ ModbusCommandItem ModbusCommandItem::create_write_multiple_coils(ModbusControlle } ModbusCommandItem ModbusCommandItem::create_write_single_command(ModbusController *modbusdevice, uint16_t start_address, - int16_t value) { + uint16_t value) { ModbusCommandItem cmd; cmd.modbusdevice = modbusdevice; cmd.register_type = ModbusRegisterType::HOLDING; @@ -427,8 +429,10 @@ ModbusCommandItem ModbusCommandItem::create_write_single_command(ModbusControlle 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); + + auto decoded_value = decode_value(value); + cmd.payload.push_back(decoded_value[0]); + cmd.payload.push_back(decoded_value[1]); return cmd; } @@ -463,84 +467,70 @@ bool ModbusCommandItem::send() { 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; - +void number_to_payload(std::vector &data, int64_t value, SensorValueType value_type) { 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); + data.push_back(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); + case SensorValueType::FP32: + case SensorValueType::FP32_R: + data.push_back((value & 0xFFFF0000) >> 16); + data.push_back(value & 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); + data.push_back(value & 0xFFFF); + data.push_back((value & 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); + case SensorValueType::U_QWORD: + case SensorValueType::S_QWORD: + data.push_back((value & 0xFFFF000000000000) >> 48); + data.push_back((value & 0xFFFF00000000) >> 32); + data.push_back((value & 0xFFFF0000) >> 16); + data.push_back(value & 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); + case SensorValueType::U_QWORD_R: + case SensorValueType::S_QWORD_R: + data.push_back(value & 0xFFFF); + data.push_back((value & 0xFFFF0000) >> 16); + data.push_back((value & 0xFFFF00000000) >> 32); + data.push_back((value & 0xFFFF000000000000) >> 48); break; default: - ESP_LOGE(TAG, "Invalid data type for modbus float to payload conversation"); + ESP_LOGE(TAG, "Invalid data type for modbus number to payload conversation: %d", + static_cast(value_type)); 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 payload_to_number(const std::vector &data, SensorValueType sensor_value_type, uint8_t offset, + uint32_t bitmask) { 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: + case SensorValueType::FP32: 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: + case SensorValueType::FP32_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); @@ -549,18 +539,14 @@ float payload_to_float(const std::vector &data, SensorValueType sensor_ 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 @@ -568,32 +554,16 @@ float payload_to_float(const std::vector &data, SensorValueType sensor_ 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; case SensorValueType::RAW: - result = NAN; - break; default: break; } - return result; + return value; } } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 88e2bba9ad..09395f29b3 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -195,7 +195,7 @@ inline bool coil_from_vector(int coil, const std::vector &data) { */ template N mask_and_shift_by_rightbit(N data, uint32_t mask) { auto result = (mask & data); - if (result == 0) { + if (result == 0 || mask == 0xFFFFFFFF) { return result; } for (size_t pos = 0; pos < sizeof(N) << 3; pos++) { @@ -205,22 +205,23 @@ template N mask_and_shift_by_rightbit(N data, uint32_t mask) { return 0; } -/** convert float value to vector suitable for sending - * @param value float value to cconvert +/** Convert float value to vector suitable for sending + * @param data target for payload + * @param value float value to convert * @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); +void number_to_payload(std::vector &data, int64_t value, SensorValueType value_type); -/** convert vector response payload to float - * @param value float value to cconvert +/** Convert vector response payload to number. + * @param data payload with the data to convert * @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 + * @return 64-bit number of the payload */ -float payload_to_float(const std::vector &data, SensorValueType sensor_value_type, uint8_t offset, - uint32_t bitmask); +int64_t payload_to_number(const std::vector &data, SensorValueType sensor_value_type, uint8_t offset, + uint32_t bitmask); class ModbusController; @@ -348,11 +349,11 @@ class ModbusCommandItem { * @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 + * @param value uint16_t single register value to write * @return ModbusCommandItem with the prepared command */ static ModbusCommandItem create_write_single_command(ModbusController *modbusdevice, uint16_t start_address, - int16_t value); + uint16_t value); /** Create modbus write single registers command * Function 05 (05hex) Write Single Coil * @param modbusdevice pointer to the device to execute the command @@ -446,13 +447,36 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { uint16_t command_throttle_; }; -/** convert vector response payload to float - * @param value float value to cconvert +/** Convert vector response payload to float. + * @param data payload with data * @param item SensorItem object - * @return float version of the input + * @return float value of data */ 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); + int64_t number = payload_to_number(data, item.sensor_value_type, item.offset, item.bitmask); + + float float_value; + if (item.sensor_value_type == SensorValueType::FP32 || item.sensor_value_type == SensorValueType::FP32_R) { + float_value = bit_cast(static_cast(number)); + } else { + float_value = static_cast(number); + } + + return float_value; +} + +inline std::vector float_to_payload(float value, SensorValueType value_type) { + int64_t val; + + if (value_type == SensorValueType::FP32 || value_type == SensorValueType::FP32_R) { + val = bit_cast(value); + } else { + val = llroundf(value); + } + + std::vector data; + number_to_payload(data, val, value_type); + return data; } } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/select/__init__.py b/esphome/components/modbus_controller/select/__init__.py new file mode 100644 index 0000000000..9651b5fedb --- /dev/null +++ b/esphome/components/modbus_controller/select/__init__.py @@ -0,0 +1,142 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import select +from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA +from esphome.jsonschema import jschema_composite + +from .. import ( + SENSOR_VALUE_TYPE, + TYPE_REGISTER_MAP, + ModbusController, + SensorItem, + modbus_controller_ns, +) +from ..const import ( + CONF_FORCE_NEW_RANGE, + CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_COUNT, + CONF_SKIP_UPDATES, + CONF_USE_WRITE_MULTIPLE, + CONF_VALUE_TYPE, + CONF_WRITE_LAMBDA, +) + +DEPENDENCIES = ["modbus_controller"] +CODEOWNERS = ["@martgras", "@stegm"] +CONF_OPTIONSMAP = "optionsmap" + +ModbusSelect = modbus_controller_ns.class_( + "ModbusSelect", cg.Component, select.Select, SensorItem +) + + +@jschema_composite +def ensure_option_map(): + def validator(value): + cv.check_not_templatable(value) + option = cv.All(cv.string_strict) + mapping = cv.All(cv.int_range(-(2 ** 63), 2 ** 63 - 1)) + options_map_schema = cv.Schema({option: mapping}) + value = options_map_schema(value) + + all_values = list(value.values()) + unique_values = set(value.values()) + if len(all_values) != len(unique_values): + raise cv.Invalid("Mapping values must be unique.") + + return value + + return validator + + +def register_count_value_type_min(value): + reg_count = value.get(CONF_REGISTER_COUNT) + if reg_count is not None: + value_type = value[CONF_VALUE_TYPE] + min_register_count = TYPE_REGISTER_MAP[value_type] + if min_register_count > reg_count: + raise cv.Invalid( + f"Value type {value_type} needs at least {min_register_count} registers" + ) + return value + + +INTEGER_SENSOR_VALUE_TYPE = { + key: value for key, value in SENSOR_VALUE_TYPE.items() if not key.startswith("FP") +} + +CONFIG_SCHEMA = cv.All( + select.SELECT_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( + { + cv.GenerateID(): cv.declare_id(ModbusSelect), + cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), + cv.Required(CONF_ADDRESS): cv.positive_int, + cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum( + INTEGER_SENSOR_VALUE_TYPE + ), + cv.Optional(CONF_REGISTER_COUNT): cv.positive_int, + cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, + cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, + cv.Required(CONF_OPTIONSMAP): ensure_option_map(), + cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, + }, + ), + register_count_value_type_min, +) + + +async def to_code(config): + value_type = config[CONF_VALUE_TYPE] + reg_count = config.get(CONF_REGISTER_COUNT) + if reg_count is None: + reg_count = TYPE_REGISTER_MAP[value_type] + + options_map = config[CONF_OPTIONSMAP] + + var = cg.new_Pvariable( + config[CONF_ID], + value_type, + config[CONF_ADDRESS], + reg_count, + config[CONF_SKIP_UPDATES], + config[CONF_FORCE_NEW_RANGE], + list(options_map.values()), + ) + + await cg.register_component(var, config) + await select.register_select(var, config, options=list(options_map.keys())) + + parent = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) + cg.add(parent.add_sensor_item(var)) + cg.add(var.set_parent(parent)) + cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE])) + + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], + [ + (ModbusSelect.operator("const_ptr"), "item"), + (cg.int64, "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_)) + + if CONF_WRITE_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_WRITE_LAMBDA], + [ + (ModbusSelect.operator("const_ptr"), "item"), + (cg.std_string.operator("const").operator("ref"), "x"), + (cg.int64, "value"), + (cg.std_vector.template(cg.uint16).operator("ref"), "payload"), + ], + return_type=cg.optional.template(cg.int64), + ) + cg.add(var.set_write_template(template_)) diff --git a/esphome/components/modbus_controller/select/modbus_select.cpp b/esphome/components/modbus_controller/select/modbus_select.cpp new file mode 100644 index 0000000000..2c6b32f545 --- /dev/null +++ b/esphome/components/modbus_controller/select/modbus_select.cpp @@ -0,0 +1,86 @@ +#include "modbus_select.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace modbus_controller { + +static const char *const TAG = "modbus_controller.select"; + +void ModbusSelect::dump_config() { LOG_SELECT(TAG, "Modbus Controller Select", this); } + +void ModbusSelect::parse_and_publish(const std::vector &data) { + int64_t value = payload_to_number(data, this->sensor_value_type, this->offset, this->bitmask); + + ESP_LOGD(TAG, "New select value %lld from payload", value); + + optional new_state; + + if (this->transform_func_.has_value()) { + auto val = (*this->transform_func_)(this, value, data); + if (val.has_value()) { + new_state = *val; + ESP_LOGV(TAG, "lambda returned option %s", new_state->c_str()); + } + } + + if (!new_state.has_value()) { + auto map_it = std::find(this->mapping_.cbegin(), this->mapping_.cend(), value); + + if (map_it != this->mapping_.cend()) { + size_t idx = std::distance(this->mapping_.cbegin(), map_it); + new_state = this->traits.get_options()[idx]; + ESP_LOGV(TAG, "Found option %s for value %lld", new_state->c_str(), value); + } else { + ESP_LOGE(TAG, "No option found for mapping %lld", value); + } + } + + if (new_state.has_value()) { + this->publish_state(new_state.value()); + } +} + +void ModbusSelect::control(const std::string &value) { + auto options = this->traits.get_options(); + auto opt_it = std::find(options.cbegin(), options.cend(), value); + size_t idx = std::distance(options.cbegin(), opt_it); + optional mapval = this->mapping_[idx]; + ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, value.c_str()); + + std::vector data; + + if (this->write_transform_func_.has_value()) { + auto val = (*this->write_transform_func_)(this, value, *mapval, data); + if (val.has_value()) { + mapval = *val; + ESP_LOGV(TAG, "write_lambda returned mapping value %lld", *mapval); + } else { + ESP_LOGD(TAG, "Communication handled by write_lambda - exiting control"); + return; + } + } + + if (data.empty()) { + number_to_payload(data, *mapval, this->sensor_value_type); + } else { + ESP_LOGV(TAG, "Using payload from write lambda"); + } + + if (data.empty()) { + ESP_LOGW(TAG, "No payload was created for updating select"); + return; + } + + const uint16_t write_address = this->start_address + this->offset / 2; + ModbusCommandItem write_cmd; + if ((this->register_count == 1) && (!this->use_write_multiple_)) { + write_cmd = ModbusCommandItem::create_write_single_command(parent_, write_address, data[0]); + } else { + write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, write_address, this->register_count, data); + } + + parent_->queue_command(write_cmd); +} + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/select/modbus_select.h b/esphome/components/modbus_controller/select/modbus_select.h new file mode 100644 index 0000000000..0875194768 --- /dev/null +++ b/esphome/components/modbus_controller/select/modbus_select.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/components/select/select.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +class ModbusSelect : public Component, public select::Select, public SensorItem { + public: + ModbusSelect(SensorValueType sensor_value_type, uint16_t start_address, uint8_t register_count, uint8_t skip_updates, + bool force_new_range, std::vector mapping) { + this->register_type = ModbusRegisterType::HOLDING; // not configurable + this->sensor_value_type = sensor_value_type; + this->start_address = start_address; + this->offset = 0; // not configurable + this->bitmask = 0xFFFFFFFF; // not configurable + this->register_count = register_count; + this->response_bytes = 0; // not configurable + this->skip_updates = skip_updates; + this->force_new_range = force_new_range; + this->mapping_ = std::move(mapping); + } + + using transform_func_t = + std::function(ModbusSelect *const, int64_t, const std::vector &)>; + using write_transform_func_t = + std::function(ModbusSelect *const, const std::string &, int64_t, std::vector &)>; + + void set_parent(ModbusController *const parent) { this->parent_ = parent; } + void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } + 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 dump_config() override; + void parse_and_publish(const std::vector &data) override; + void control(const std::string &value) override; + + protected: + std::vector mapping_; + ModbusController *parent_; + bool use_write_multiple_{false}; + optional transform_func_; + optional write_transform_func_; +}; + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 937b6cceb4..82deec70ec 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -773,9 +773,11 @@ class MockObj(Expression): return MockObj(f"{self.base} &", "") if name == "ptr": return MockObj(f"{self.base} *", "") + if name == "const_ptr": + return MockObj(f"{self.base} *const", "") if name == "const": return MockObj(f"const {self.base}", "") - raise ValueError("Expected one of ref, ptr, const.") + raise ValueError("Expected one of ref, ptr, const_ptr, const.") @property def using(self) -> "MockObj": diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 110641d6c9..2323b2578f 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -15,6 +15,7 @@ uint16 = global_ns.namespace("uint16_t") uint32 = global_ns.namespace("uint32_t") uint64 = global_ns.namespace("uint64_t") int32 = global_ns.namespace("int32_t") +int64 = global_ns.namespace("int64_t") const_char_ptr = global_ns.namespace("const char *") NAN = global_ns.namespace("NAN") esphome_ns = global_ns # using namespace esphome; From 4aeacfd16e171f1914c9e60e881b9b760a8b1dea Mon Sep 17 00:00:00 2001 From: mckaymatthew Date: Tue, 8 Feb 2022 03:56:40 -0600 Subject: [PATCH 085/238] Add max9611 High Side Current Shunt ADC (#2705) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/max9611/__init__.py | 1 + esphome/components/max9611/max9611.cpp | 93 ++++++++++++++++++++++++++ esphome/components/max9611/max9611.h | 62 +++++++++++++++++ esphome/components/max9611/sensor.py | 92 +++++++++++++++++++++++++ tests/test1.yaml | 16 ++++- 6 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 esphome/components/max9611/__init__.py create mode 100644 esphome/components/max9611/max9611.cpp create mode 100644 esphome/components/max9611/max9611.h create mode 100644 esphome/components/max9611/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 3326b7d90c..165ce52485 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -94,6 +94,7 @@ esphome/components/lock/* @esphome/core esphome/components/logger/* @esphome/core esphome/components/ltr390/* @sjtrny esphome/components/max7219digit/* @rspaargaren +esphome/components/max9611/* @mckaymatthew esphome/components/mcp23008/* @jesserockz esphome/components/mcp23017/* @jesserockz esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz diff --git a/esphome/components/max9611/__init__.py b/esphome/components/max9611/__init__.py new file mode 100644 index 0000000000..9b0ea14b57 --- /dev/null +++ b/esphome/components/max9611/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@mckaymatthew"] diff --git a/esphome/components/max9611/max9611.cpp b/esphome/components/max9611/max9611.cpp new file mode 100644 index 0000000000..418a577948 --- /dev/null +++ b/esphome/components/max9611/max9611.cpp @@ -0,0 +1,93 @@ +#include "max9611.h" +#include "esphome/core/log.h" +#include "esphome/components/i2c/i2c_bus.h" +namespace esphome { +namespace max9611 { +using namespace esphome::i2c; +// Sign extend +// http://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend +template inline T signextend(const T x) { + struct { + T x : B; + } s; + return s.x = x; +} +// Map the gain register to in uV/LSB +const float gain_to_lsb(MAX9611Multiplexer gain) { + float lsb = 0.0; + if (gain == MAX9611_MULTIPLEXER_CSA_GAIN1) { + lsb = 107.50; + } else if (gain == MAX9611_MULTIPLEXER_CSA_GAIN4) { + lsb = 26.88; + } else if (gain == MAX9611_MULTIPLEXER_CSA_GAIN8) { + lsb = 13.44; + } + return lsb; +} +static const char *const TAG = "max9611"; +static const uint8_t SETUP_DELAY = 4; // Wait 2 integration periods. +static const float VOUT_LSB = 14.0 / 1000.0; // 14mV/LSB +static const float TEMP_LSB = 0.48; // 0.48C/LSB +static const float MICRO_VOLTS_PER_VOLT = 1000000.0; +void MAX9611Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up max9611..."); + // Perform dummy-read + uint8_t value; + this->read(&value, 1); + // Configuration Stage. + // First send an integration request with the specified gain + const uint8_t setup_dat[] = {CONTROL_REGISTER_1_ADRR, static_cast(gain_)}; + // Then send a request that samples all channels as fast as possible, using the last provided gain + const uint8_t fast_mode_dat[] = {CONTROL_REGISTER_1_ADRR, MAX9611Multiplexer::MAX9611_MULTIPLEXER_FAST_MODE}; + + if (this->write(reinterpret_cast(&setup_dat), sizeof(setup_dat)) != ErrorCode::ERROR_OK) { + ESP_LOGE(TAG, "Failed to setup Max9611 during GAIN SET"); + return; + } + delay(SETUP_DELAY); + if (this->write(reinterpret_cast(&fast_mode_dat), sizeof(fast_mode_dat)) != ErrorCode::ERROR_OK) { + ESP_LOGE(TAG, "Failed to setup Max9611 during FAST MODE SET"); + return; + } +} +void MAX9611Component::dump_config() { + ESP_LOGCONFIG(TAG, "Dump Config max9611..."); + ESP_LOGCONFIG(TAG, " CSA Gain Register: %x", gain_); + LOG_I2C_DEVICE(this); +} +void MAX9611Component::update() { + // Setup read from 0x0 register base + const uint8_t reg_base = 0x0; + const ErrorCode write_result = this->write(®_base, 1); + // Just read the entire register map in a bulk read, faster than individually querying register. + const ErrorCode read_result = this->read(register_map_, sizeof(register_map_)); + if (write_result != ErrorCode::ERROR_OK || read_result != ErrorCode::ERROR_OK) { + ESP_LOGW(TAG, "MAX9611 Update FAILED!"); + return; + } + uint16_t csa_register = ((register_map_[CSA_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[CSA_DATA_BYTE_LSB_ADRR])) >> 4; + uint16_t rs_register = ((register_map_[RS_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[RS_DATA_BYTE_LSB_ADRR])) >> 4; + uint16_t t_register = ((register_map_[TEMP_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[TEMP_DATA_BYTE_LSB_ADRR])) >> 7; + float voltage = rs_register * VOUT_LSB; + float shunt_voltage = (csa_register * gain_to_lsb(gain_)) / MICRO_VOLTS_PER_VOLT; + float temp = signextend(t_register) * TEMP_LSB; + float amps = shunt_voltage / current_resistor_; + float watts = amps * voltage; + + if (voltage_sensor_ != nullptr) { + voltage_sensor_->publish_state(voltage); + } + if (current_sensor_ != nullptr) { + current_sensor_->publish_state(amps); + } + if (watt_sensor_ != nullptr) { + watt_sensor_->publish_state(watts); + } + if (temperature_sensor_ != nullptr) { + temperature_sensor_->publish_state(temp); + } + + ESP_LOGD(TAG, "V: %f, A: %f, W: %f, Deg C: %f", voltage, amps, watts, temp); +} +} // namespace max9611 +} // namespace esphome diff --git a/esphome/components/max9611/max9611.h b/esphome/components/max9611/max9611.h new file mode 100644 index 0000000000..017f56b1a7 --- /dev/null +++ b/esphome/components/max9611/max9611.h @@ -0,0 +1,62 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace max9611 { + +enum MAX9611Multiplexer { + MAX9611_MULTIPLEXER_CSA_GAIN1 = 0b000, + MAX9611_MULTIPLEXER_CSA_GAIN4 = 0b001, + MAX9611_MULTIPLEXER_CSA_GAIN8 = 0b010, + MAX9611_MULTIPLEXER_RS = 0b011, + MAX9611_MULTIPLEXER_OUT = 0b100, + MAX9611_MULTIPLEXER_SET = 0b101, + MAX9611_MULTIPLEXER_TEMP = 0b110, + MAX9611_MULTIPLEXER_FAST_MODE = 0b111, +}; + +enum MAX9611RegisterMap { + CSA_DATA_BYTE_MSB_ADRR = 0x00, + CSA_DATA_BYTE_LSB_ADRR = 0x01, + RS_DATA_BYTE_MSB_ADRR = 0x02, + RS_DATA_BYTE_LSB_ADRR = 0x03, + OUT_DATA_BYTE_MSB_ADRR = 0x04, // Unused Op-Amp + OUT_DATA_BYTE_LSB_ADRR = 0x05, // Unused Op-Amp + SET_DATA_BYTE_MSB_ADRR = 0x06, // Unused Op-Amp + SET_DATA_BYTE_LSB_ADRR = 0x07, // Unused Op-Amp + TEMP_DATA_BYTE_MSB_ADRR = 0x08, + TEMP_DATA_BYTE_LSB_ADRR = 0x09, + CONTROL_REGISTER_1_ADRR = 0x0A, + CONTROL_REGISTER_2_ADRR = 0x0B, +}; + +class MAX9611Component : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void update() override; + void set_voltage_sensor(sensor::Sensor *vs) { voltage_sensor_ = vs; } + void set_current_sensor(sensor::Sensor *cs) { current_sensor_ = cs; } + void set_watt_sensor(sensor::Sensor *ws) { watt_sensor_ = ws; } + void set_temp_sensor(sensor::Sensor *ts) { temperature_sensor_ = ts; } + + void set_current_resistor(float r) { current_resistor_ = r; } + void set_gain(MAX9611Multiplexer g) { gain_ = g; } + + protected: + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *watt_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; + float current_resistor_; + uint8_t register_map_[0x0C]; + MAX9611Multiplexer gain_; +}; + +} // namespace max9611 +} // namespace esphome diff --git a/esphome/components/max9611/sensor.py b/esphome/components/max9611/sensor.py new file mode 100644 index 0000000000..246d332a86 --- /dev/null +++ b/esphome/components/max9611/sensor.py @@ -0,0 +1,92 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_SHUNT_RESISTANCE, + CONF_GAIN, + CONF_VOLTAGE, + CONF_CURRENT, + CONF_POWER, + CONF_TEMPERATURE, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, + UNIT_CELSIUS, + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, +) + +DEPENDENCIES = ["i2c"] +max9611_ns = cg.esphome_ns.namespace("max9611") +max9611Gain = max9611_ns.enum("MAX9611Multiplexer") +MAX9611_GAIN = { + "8X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN8, + "4X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN4, + "1X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN1, +} +MAX9611Component = max9611_ns.class_( + "MAX9611Component", cg.PollingComponent, i2c.I2CDevice +) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MAX9611Component), + cv.Required(CONF_SHUNT_RESISTANCE): cv.resistance, + cv.Required(CONF_GAIN): cv.enum(MAX9611_GAIN, upper=True), + cv.Optional(CONF_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=2, + 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, + ), + 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, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x70)) +) + + +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_current_resistor(config[CONF_SHUNT_RESISTANCE])) + cg.add(var.set_gain(config[CONF_GAIN])) + 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_watt_sensor(sens)) + if CONF_TEMPERATURE in config: + conf = config[CONF_TEMPERATURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_temp_sensor(sens)) diff --git a/tests/test1.yaml b/tests/test1.yaml index e97b3aed73..d8fea223dc 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1077,7 +1077,21 @@ sensor: cs_pin: mcp23xxx: mcp23017_hub number: 14 - + - platform: max9611 + i2c_id: i2c_bus + shunt_resistance: 0.2 ohm + gain: '1X' + voltage: + name: Max9611 Voltage + current: + name: Max9611 Current + power: + name: Max9611 Watts + temperature: + name: Max9611 Temp + update_interval: 1s + + esp32_touch: setup_mode: False iir_filter: 10ms From c66d0550e85518cbe87604de557bbc43129adb92 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 8 Feb 2022 22:56:56 +1300 Subject: [PATCH 086/238] Inkplate 6 PLUS (#3013) --- esphome/components/inkplate6/display.py | 1 + esphome/components/inkplate6/inkplate.cpp | 434 +++++++++++++--------- esphome/components/inkplate6/inkplate.h | 54 +-- 3 files changed, 281 insertions(+), 208 deletions(-) diff --git a/esphome/components/inkplate6/display.py b/esphome/components/inkplate6/display.py index dca764c6ed..a17f37c920 100644 --- a/esphome/components/inkplate6/display.py +++ b/esphome/components/inkplate6/display.py @@ -47,6 +47,7 @@ InkplateModel = inkplate6_ns.enum("InkplateModel") MODELS = { "inkplate_6": InkplateModel.INKPLATE_6, "inkplate_10": InkplateModel.INKPLATE_10, + "inkplate_6_plus": InkplateModel.INKPLATE_6_PLUS, } CONFIG_SCHEMA = cv.All( diff --git a/esphome/components/inkplate6/inkplate.cpp b/esphome/components/inkplate6/inkplate.cpp index 8b7890feb7..e6fb9b773c 100644 --- a/esphome/components/inkplate6/inkplate.cpp +++ b/esphome/components/inkplate6/inkplate.cpp @@ -13,6 +13,11 @@ namespace inkplate6 { static const char *const TAG = "inkplate"; void Inkplate6::setup() { + for (uint32_t i = 0; i < 256; i++) { + this->pin_lut_[i] = ((i & 0b00000011) << 4) | (((i & 0b00001100) >> 2) << 18) | (((i & 0b00010000) >> 4) << 23) | + (((i & 0b11100000) >> 5) << 25); + } + this->initialize_(); this->vcom_pin_->setup(); @@ -38,11 +43,21 @@ void Inkplate6::setup() { this->display_data_6_pin_->setup(); this->display_data_7_pin_->setup(); - this->clean(); - this->display(); + this->wakeup_pin_->digital_write(true); + delay(1); + this->write_bytes(0x09, { + 0b00011011, // Power up seq. + 0b00000000, // Power up delay (3mS per rail) + 0b00011011, // Power down seq. + 0b00000000, // Power down delay (6mS per rail) + }); + delay(1); + this->wakeup_pin_->digital_write(false); } + void Inkplate6::initialize_() { ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + ExternalRAMAllocator allocator32(ExternalRAMAllocator::ALLOW_FAILURE); uint32_t buffer_size = this->get_buffer_length_(); if (buffer_size == 0) return; @@ -53,6 +68,10 @@ void Inkplate6::initialize_() { allocator.deallocate(this->partial_buffer_2_, buffer_size * 2); if (this->buffer_ != nullptr) allocator.deallocate(this->buffer_, buffer_size); + if (this->glut_ != nullptr) + allocator32.deallocate(this->glut_, 256 * (this->model_ == INKPLATE_6_PLUS ? 9 : 8)); + if (this->glut2_ != nullptr) + allocator32.deallocate(this->glut2_, 256 * (this->model_ == INKPLATE_6_PLUS ? 9 : 8)); this->buffer_ = allocator.allocate(buffer_size); if (this->buffer_ == nullptr) { @@ -60,7 +79,34 @@ void Inkplate6::initialize_() { this->mark_failed(); return; } - if (!this->greyscale_) { + if (this->greyscale_) { + uint8_t glut_size = (this->model_ == INKPLATE_6_PLUS ? 9 : 8); + + this->glut_ = allocator32.allocate(256 * glut_size); + if (this->glut_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate glut!"); + this->mark_failed(); + return; + } + this->glut2_ = allocator32.allocate(256 * glut_size); + if (this->glut2_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate glut2!"); + this->mark_failed(); + return; + } + + for (int i = 0; i < glut_size; i++) { + for (uint32_t j = 0; j < 256; j++) { + uint8_t z = (waveform3Bit[j & 0x07][i] << 2) | (waveform3Bit[(j >> 4) & 0x07][i]); + this->glut_[i * 256 + j] = ((z & 0b00000011) << 4) | (((z & 0b00001100) >> 2) << 18) | + (((z & 0b00010000) >> 4) << 23) | (((z & 0b11100000) >> 5) << 25); + z = ((waveform3Bit[j & 0x07][i] << 2) | (waveform3Bit[(j >> 4) & 0x07][i])) << 4; + this->glut2_[i * 256 + j] = ((z & 0b00000011) << 4) | (((z & 0b00001100) >> 2) << 18) | + (((z & 0b00010000) >> 4) << 23) | (((z & 0b11100000) >> 5) << 25); + } + } + + } else { this->partial_buffer_ = allocator.allocate(buffer_size); if (this->partial_buffer_ == nullptr) { ESP_LOGE(TAG, "Could not allocate partial buffer for display!"); @@ -73,13 +119,16 @@ void Inkplate6::initialize_() { this->mark_failed(); return; } + memset(this->partial_buffer_, 0, buffer_size); memset(this->partial_buffer_2_, 0, buffer_size * 2); } memset(this->buffer_, 0, buffer_size); } + float Inkplate6::get_setup_priority() const { return setup_priority::PROCESSOR; } + size_t Inkplate6::get_buffer_length_() { if (this->greyscale_) { return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 2u; @@ -87,6 +136,7 @@ size_t Inkplate6::get_buffer_length_() { return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u; } } + void Inkplate6::update() { this->do_update_(); @@ -96,6 +146,7 @@ void Inkplate6::update() { this->display(); } + void HOT Inkplate6::draw_absolute_pixel_internal(int x, int y, Color color) { if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0) return; @@ -121,6 +172,7 @@ void HOT Inkplate6::draw_absolute_pixel_internal(int x, int y, Color color) { this->partial_buffer_[pos] = (~pixelMaskLUT[x_sub] & current) | (color.is_on() ? 0 : pixelMaskLUT[x_sub]); } } + void Inkplate6::dump_config() { LOG_DISPLAY("", "Inkplate", this); ESP_LOGCONFIG(TAG, " Greyscale: %s", YESNO(this->greyscale_)); @@ -150,44 +202,51 @@ void Inkplate6::dump_config() { LOG_UPDATE_INTERVAL(this); } + void Inkplate6::eink_off_() { ESP_LOGV(TAG, "Eink off called"); - if (panel_on_ == 0) + if (!panel_on_) return; - panel_on_ = 0; - this->gmod_pin_->digital_write(false); + panel_on_ = false; + this->oe_pin_->digital_write(false); + this->gmod_pin_->digital_write(false); - GPIO.out &= ~(get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()) | (1 << this->le_pin_->get_pin())); - + GPIO.out &= ~(this->get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()) | (1 << this->le_pin_->get_pin())); + this->ckv_pin_->digital_write(false); this->sph_pin_->digital_write(false); this->spv_pin_->digital_write(false); - this->powerup_pin_->digital_write(false); - this->wakeup_pin_->digital_write(false); this->vcom_pin_->digital_write(false); + this->write_byte(0x01, 0x6F); // Put TPS65186 into standby mode + + delay(100); // NOLINT + + this->write_byte(0x01, 0x4f); // Disable 3V3 to the panel + + if (this->model_ != INKPLATE_6_PLUS) + this->wakeup_pin_->digital_write(false); + pins_z_state_(); } + void Inkplate6::eink_on_() { ESP_LOGV(TAG, "Eink on called"); - if (panel_on_ == 1) + if (panel_on_) return; - panel_on_ = 1; - pins_as_outputs_(); + this->panel_on_ = true; + + this->pins_as_outputs_(); this->wakeup_pin_->digital_write(true); - this->powerup_pin_->digital_write(true); this->vcom_pin_->digital_write(true); - - this->write_byte(0x01, 0x3F); - - delay(40); - - this->write_byte(0x0D, 0x80); - delay(2); - this->read_register(0x00, nullptr, 0); + this->write_byte(0x01, 0b00101111); // Enable all rails + + delay(1); + + this->write_byte(0x01, 0b10101111); // Switch TPS65186 into active mode this->le_pin_->digital_write(false); this->oe_pin_->digital_write(false); @@ -196,8 +255,33 @@ void Inkplate6::eink_on_() { this->gmod_pin_->digital_write(true); this->spv_pin_->digital_write(true); this->ckv_pin_->digital_write(false); + this->oe_pin_->digital_write(false); + + uint32_t timer = millis(); + do { + delay(1); + } while (!this->read_power_status_() && ((millis() - timer) < 250)); + if ((millis() - timer) >= 250) { + ESP_LOGW(TAG, "Power supply not detected"); + this->wakeup_pin_->digital_write(false); + this->vcom_pin_->digital_write(false); + this->powerup_pin_->digital_write(false); + this->panel_on_ = false; + return; + } + this->oe_pin_->digital_write(true); } + +bool Inkplate6::read_power_status_() { + uint8_t data; + auto err = this->read_register(0x0F, &data, 1); + if (err == i2c::ERROR_OK) { + return data == 0b11111010; + } + return false; +} + void Inkplate6::fill(Color color) { ESP_LOGV(TAG, "Fill called"); uint32_t start_time = millis(); @@ -212,6 +296,7 @@ void Inkplate6::fill(Color color) { ESP_LOGV(TAG, "Fill finished (%ums)", millis() - start_time); } + void Inkplate6::display() { ESP_LOGV(TAG, "Display called"); uint32_t start_time = millis(); @@ -227,201 +312,185 @@ void Inkplate6::display() { } ESP_LOGV(TAG, "Display finished (full) (%ums)", millis() - start_time); } + void Inkplate6::display1b_() { ESP_LOGV(TAG, "Display1b called"); uint32_t start_time = millis(); memcpy(this->buffer_, this->partial_buffer_, this->get_buffer_length_()); - uint32_t send; uint8_t data; uint8_t buffer_value; const uint8_t *buffer_ptr; eink_on_(); - clean_fast_(0, 1); - clean_fast_(1, 5); - clean_fast_(2, 1); - clean_fast_(0, 5); - clean_fast_(2, 1); - clean_fast_(1, 12); - clean_fast_(2, 1); - clean_fast_(0, 11); + if (this->model_ == INKPLATE_6_PLUS) { + clean_fast_(0, 1); + clean_fast_(1, 15); + clean_fast_(2, 1); + clean_fast_(0, 5); + clean_fast_(2, 1); + clean_fast_(1, 15); + } else { + clean_fast_(0, 1); + clean_fast_(1, 21); + clean_fast_(2, 1); + clean_fast_(0, 12); + clean_fast_(2, 1); + clean_fast_(1, 21); + clean_fast_(2, 1); + clean_fast_(0, 12); + } uint32_t clock = (1 << this->cl_pin_->get_pin()); + uint32_t data_mask = this->get_data_pin_mask_(); ESP_LOGV(TAG, "Display1b start loops (%ums)", millis() - start_time); - for (int k = 0; k < 3; k++) { + + for (int k = 0; k < 4; k++) { buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1]; vscan_start_(); - for (int i = 0; i < this->get_height_internal(); i++) { + for (int i = 0, im = this->get_height_internal(); i < im; i++) { buffer_value = *(buffer_ptr--); - data = LUTB[(buffer_value >> 4) & 0x0F]; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25); - hscan_start_(send); - data = LUTB[buffer_value & 0x0F]; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; + data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value >> 4) & 0x0F] : LUTB[(buffer_value >> 4) & 0x0F]; + hscan_start_(this->pin_lut_[data]); + data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value) & 0x0F] : LUTB[buffer_value & 0x0F]; + GPIO.out_w1ts = this->pin_lut_[data] | clock; + GPIO.out_w1tc = data_mask | clock; for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) { buffer_value = *(buffer_ptr--); - data = LUTB[(buffer_value >> 4) & 0x0F]; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; - data = LUTB[buffer_value & 0x0F]; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; + data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value >> 4) & 0x0F] : LUTB[(buffer_value >> 4) & 0x0F]; + GPIO.out_w1ts = this->pin_lut_[data] | clock; + GPIO.out_w1tc = data_mask | clock; + data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value) & 0x0F] : LUTB[buffer_value & 0x0F]; + GPIO.out_w1ts = this->pin_lut_[data] | clock; + GPIO.out_w1tc = data_mask | clock; } - GPIO.out_w1ts = send; - GPIO.out_w1tc = get_data_pin_mask_() | clock; + GPIO.out_w1ts = clock; + GPIO.out_w1tc = data_mask | clock; vscan_end_(); } delayMicroseconds(230); } - ESP_LOGV(TAG, "Display1b first loop x %d (%ums)", 3, millis() - start_time); + ESP_LOGV(TAG, "Display1b first loop x %d (%ums)", 4, millis() - start_time); buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1]; vscan_start_(); - for (int i = 0; i < this->get_height_internal(); i++) { + for (int i = 0, im = this->get_height_internal(); i < im; i++) { buffer_value = *(buffer_ptr--); - data = LUT2[(buffer_value >> 4) & 0x0F]; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25); - hscan_start_(send); - data = LUT2[buffer_value & 0x0F]; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; + data = this->model_ == INKPLATE_6_PLUS ? LUTB[(buffer_value >> 4) & 0x0F] : LUT2[(buffer_value >> 4) & 0x0F]; + hscan_start_(this->pin_lut_[data] | clock); + data = this->model_ == INKPLATE_6_PLUS ? LUTB[buffer_value & 0x0F] : LUT2[buffer_value & 0x0F]; + GPIO.out_w1ts = this->pin_lut_[data] | clock; + GPIO.out_w1tc = data_mask | clock; + for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) { buffer_value = *(buffer_ptr--); - data = LUT2[(buffer_value >> 4) & 0x0F]; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; - data = LUT2[buffer_value & 0x0F]; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; + data = this->model_ == INKPLATE_6_PLUS ? LUTB[(buffer_value >> 4) & 0x0F] : LUT2[(buffer_value >> 4) & 0x0F]; + GPIO.out_w1ts = this->pin_lut_[data] | clock; + GPIO.out_w1tc = data_mask | clock; + data = this->model_ == INKPLATE_6_PLUS ? LUTB[buffer_value & 0x0F] : LUT2[buffer_value & 0x0F]; + GPIO.out_w1ts = this->pin_lut_[data] | clock; + GPIO.out_w1tc = data_mask | clock; } - GPIO.out_w1ts = send; - GPIO.out_w1tc = get_data_pin_mask_() | clock; + GPIO.out_w1ts = clock; + GPIO.out_w1tc = data_mask | clock; vscan_end_(); } delayMicroseconds(230); ESP_LOGV(TAG, "Display1b second loop (%ums)", millis() - start_time); - vscan_start_(); - for (int i = 0; i < this->get_height_internal(); i++) { - data = 0b00000000; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25); - hscan_start_(send); - send |= clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; - for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; + if (this->model_ == INKPLATE_6_PLUS) { + clean_fast_(2, 2); + clean_fast_(3, 1); + } else { + uint32_t send = this->pin_lut_[0]; + vscan_start_(); + for (int i = 0, im = this->get_height_internal(); i < im; i++) { + hscan_start_(send); + GPIO.out_w1ts = send | clock; + GPIO.out_w1tc = data_mask | clock; + for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) { + GPIO.out_w1ts = send | clock; + GPIO.out_w1tc = data_mask | clock; + GPIO.out_w1ts = send | clock; + GPIO.out_w1tc = data_mask | clock; + } + GPIO.out_w1ts = send | clock; + GPIO.out_w1tc = data_mask | clock; + vscan_end_(); } - GPIO.out_w1ts = clock; - GPIO.out_w1tc = get_data_pin_mask_() | clock; - vscan_end_(); + delayMicroseconds(230); + ESP_LOGV(TAG, "Display1b third loop (%ums)", millis() - start_time); } - delayMicroseconds(230); - ESP_LOGV(TAG, "Display1b third loop (%ums)", millis() - start_time); - vscan_start_(); eink_off_(); this->block_partial_ = false; this->partial_updates_ = 0; ESP_LOGV(TAG, "Display1b finished (%ums)", millis() - start_time); } + void Inkplate6::display3b_() { ESP_LOGV(TAG, "Display3b called"); uint32_t start_time = millis(); eink_on_(); - clean_fast_(0, 1); - clean_fast_(1, 12); - clean_fast_(2, 1); - clean_fast_(0, 11); - clean_fast_(2, 1); - clean_fast_(1, 12); - clean_fast_(2, 1); - clean_fast_(0, 11); + if (this->model_ == INKPLATE_6_PLUS) { + clean_fast_(0, 1); + clean_fast_(1, 15); + clean_fast_(2, 1); + clean_fast_(0, 5); + clean_fast_(2, 1); + clean_fast_(1, 15); + } else { + clean_fast_(0, 1); + clean_fast_(1, 21); + clean_fast_(2, 1); + clean_fast_(0, 12); + clean_fast_(2, 1); + clean_fast_(1, 21); + clean_fast_(2, 1); + clean_fast_(0, 12); + } uint32_t clock = (1 << this->cl_pin_->get_pin()); - for (int k = 0; k < 8; k++) { - const uint8_t *buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1]; - uint32_t send; - uint8_t pix1; - uint8_t pix2; - uint8_t pix3; - uint8_t pix4; - uint8_t pixel; - uint8_t pixel2; - + uint32_t data_mask = this->get_data_pin_mask_(); + uint32_t pos; + uint32_t data; + uint8_t glut_size = this->model_ == INKPLATE_6_PLUS ? 9 : 8; + for (int k = 0; k < glut_size; k++) { + pos = this->get_buffer_length_(); vscan_start_(); for (int i = 0; i < this->get_height_internal(); i++) { - pix1 = (*buffer_ptr--); - pix2 = (*buffer_ptr--); - pix3 = (*buffer_ptr--); - pix4 = (*buffer_ptr--); - pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) | - (waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0); - pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) | - (waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0); + data = this->glut2_[k * 256 + this->buffer_[--pos]]; + data |= this->glut_[k * 256 + this->buffer_[--pos]]; + hscan_start_(data); + data = this->glut2_[k * 256 + this->buffer_[--pos]]; + data |= this->glut_[k * 256 + this->buffer_[--pos]]; + GPIO.out_w1ts = data | clock; + GPIO.out_w1tc = data_mask | clock; - send = ((pixel & 0b00000011) << 4) | (((pixel & 0b00001100) >> 2) << 18) | (((pixel & 0b00010000) >> 4) << 23) | - (((pixel & 0b11100000) >> 5) << 25); - hscan_start_(send); - send = ((pixel2 & 0b00000011) << 4) | (((pixel2 & 0b00001100) >> 2) << 18) | - (((pixel2 & 0b00010000) >> 4) << 23) | (((pixel2 & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; - - for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) { - pix1 = (*buffer_ptr--); - pix2 = (*buffer_ptr--); - pix3 = (*buffer_ptr--); - pix4 = (*buffer_ptr--); - pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) | - (waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0); - pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) | - (waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0); - - send = ((pixel & 0b00000011) << 4) | (((pixel & 0b00001100) >> 2) << 18) | (((pixel & 0b00010000) >> 4) << 23) | - (((pixel & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; - - send = ((pixel2 & 0b00000011) << 4) | (((pixel2 & 0b00001100) >> 2) << 18) | - (((pixel2 & 0b00010000) >> 4) << 23) | (((pixel2 & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; + for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { + data = this->glut2_[k * 256 + this->buffer_[--pos]]; + data |= this->glut_[k * 256 + this->buffer_[--pos]]; + GPIO.out_w1ts = data | clock; + GPIO.out_w1tc = data_mask | clock; + data = this->glut2_[k * 256 + this->buffer_[--pos]]; + data |= this->glut_[k * 256 + this->buffer_[--pos]]; + GPIO.out_w1ts = data | clock; + GPIO.out_w1tc = data_mask | clock; } - GPIO.out_w1ts = send; - GPIO.out_w1tc = get_data_pin_mask_() | clock; + GPIO.out_w1ts = clock; + GPIO.out_w1tc = data_mask | clock; vscan_end_(); } delayMicroseconds(230); } - clean_fast_(2, 1); clean_fast_(3, 1); vscan_start_(); eink_off_(); ESP_LOGV(TAG, "Display3b finished (%ums)", millis() - start_time); } + bool Inkplate6::partial_update_() { ESP_LOGV(TAG, "Partial update called"); uint32_t start_time = millis(); @@ -432,16 +501,15 @@ bool Inkplate6::partial_update_() { this->partial_updates_++; - uint16_t pos = this->get_buffer_length_() - 1; - uint32_t send; + uint32_t pos = this->get_buffer_length_() - 1; uint8_t data; uint8_t diffw, diffb; uint32_t n = (this->get_buffer_length_() * 2) - 1; for (int i = 0, im = this->get_height_internal(); i < im; i++) { for (int j = 0, jm = (this->get_width_internal() / 8); j < jm; j++) { - diffw = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & ~(this->partial_buffer_[pos]); - diffb = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & this->partial_buffer_[pos]; + diffw = this->buffer_[pos] & ~(this->partial_buffer_[pos]); + diffb = ~(this->buffer_[pos]) & this->partial_buffer_[pos]; pos--; this->partial_buffer_2_[n--] = LUTW[diffw >> 4] & LUTB[diffb >> 4]; this->partial_buffer_2_[n--] = LUTW[diffw & 0x0F] & LUTB[diffb & 0x0F]; @@ -451,23 +519,20 @@ bool Inkplate6::partial_update_() { eink_on_(); uint32_t clock = (1 << this->cl_pin_->get_pin()); + uint32_t data_mask = this->get_data_pin_mask_(); for (int k = 0; k < 5; k++) { vscan_start_(); const uint8_t *data_ptr = &this->partial_buffer_2_[(this->get_buffer_length_() * 2) - 1]; for (int i = 0; i < this->get_height_internal(); i++) { data = *(data_ptr--); - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25); - hscan_start_(send); + hscan_start_(this->pin_lut_[data]); for (int j = 0, jm = (this->get_width_internal() / 4) - 1; j < jm; j++) { data = *(data_ptr--); - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; + GPIO.out_w1ts = this->pin_lut_[data] | clock; + GPIO.out_w1tc = data_mask | clock; } - GPIO.out_w1ts = send; - GPIO.out_w1tc = get_data_pin_mask_() | clock; + GPIO.out_w1ts = clock; + GPIO.out_w1tc = data_mask | clock; vscan_end_(); } delayMicroseconds(230); @@ -482,6 +547,7 @@ bool Inkplate6::partial_update_() { ESP_LOGV(TAG, "Partial update finished (%ums)", millis() - start_time); return true; } + void Inkplate6::vscan_start_() { this->ckv_pin_->digital_write(true); delayMicroseconds(7); @@ -505,30 +571,23 @@ void Inkplate6::vscan_start_() { delayMicroseconds(0); this->ckv_pin_->digital_write(true); } -void Inkplate6::vscan_write_() { - this->ckv_pin_->digital_write(false); - this->le_pin_->digital_write(true); - this->le_pin_->digital_write(false); - delayMicroseconds(0); + +void Inkplate6::hscan_start_(uint32_t d) { + uint8_t clock = (1 << this->cl_pin_->get_pin()); this->sph_pin_->digital_write(false); - this->cl_pin_->digital_write(true); - this->cl_pin_->digital_write(false); + GPIO.out_w1ts = d | clock; + GPIO.out_w1tc = this->get_data_pin_mask_() | clock; this->sph_pin_->digital_write(true); this->ckv_pin_->digital_write(true); } -void Inkplate6::hscan_start_(uint32_t d) { - this->sph_pin_->digital_write(false); - GPIO.out_w1ts = (d) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); - this->sph_pin_->digital_write(true); -} + void Inkplate6::vscan_end_() { this->ckv_pin_->digital_write(false); this->le_pin_->digital_write(true); this->le_pin_->digital_write(false); - delayMicroseconds(1); - this->ckv_pin_->digital_write(true); + delayMicroseconds(0); } + void Inkplate6::clean() { ESP_LOGV(TAG, "Clean called"); uint32_t start_time = millis(); @@ -542,6 +601,7 @@ void Inkplate6::clean() { clean_fast_(1, 10); // White to White ESP_LOGV(TAG, "Clean finished (%ums)", millis() - start_time); } + void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { ESP_LOGV(TAG, "Clean fast called with: (%d, %d)", c, rep); uint32_t start_time = millis(); @@ -568,14 +628,14 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { hscan_start_(send); GPIO.out_w1ts = send | clock; GPIO.out_w1tc = clock; - for (int j = 0, jm = this->get_width_internal() / 8; j < jm; j++) { + for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { GPIO.out_w1ts = clock; GPIO.out_w1tc = clock; GPIO.out_w1ts = clock; GPIO.out_w1tc = clock; } - GPIO.out_w1ts = clock; - GPIO.out_w1tc = get_data_pin_mask_() | clock; + GPIO.out_w1ts = send | clock; + GPIO.out_w1tc = clock; vscan_end_(); } delayMicroseconds(230); @@ -583,7 +643,10 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { } ESP_LOGV(TAG, "Clean fast finished (%ums)", millis() - start_time); } + void Inkplate6::pins_z_state_() { + this->cl_pin_->pin_mode(gpio::FLAG_INPUT); + this->le_pin_->pin_mode(gpio::FLAG_INPUT); this->ckv_pin_->pin_mode(gpio::FLAG_INPUT); this->sph_pin_->pin_mode(gpio::FLAG_INPUT); @@ -600,7 +663,10 @@ void Inkplate6::pins_z_state_() { this->display_data_6_pin_->pin_mode(gpio::FLAG_INPUT); this->display_data_7_pin_->pin_mode(gpio::FLAG_INPUT); } + void Inkplate6::pins_as_outputs_() { + this->cl_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->le_pin_->pin_mode(gpio::FLAG_OUTPUT); this->ckv_pin_->pin_mode(gpio::FLAG_OUTPUT); this->sph_pin_->pin_mode(gpio::FLAG_OUTPUT); diff --git a/esphome/components/inkplate6/inkplate.h b/esphome/components/inkplate6/inkplate.h index 6cbb24805d..9355154c5a 100644 --- a/esphome/components/inkplate6/inkplate.h +++ b/esphome/components/inkplate6/inkplate.h @@ -13,32 +13,28 @@ namespace inkplate6 { enum InkplateModel : uint8_t { INKPLATE_6 = 0, INKPLATE_10 = 1, + INKPLATE_6_PLUS = 2, }; class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public i2c::I2CDevice { public: - const uint8_t LUT2[16] = {0b10101010, 0b10101001, 0b10100110, 0b10100101, 0b10011010, 0b10011001, - 0b10010110, 0b10010101, 0b01101010, 0b01101001, 0b01100110, 0b01100101, - 0b01011010, 0b01011001, 0b01010110, 0b01010101}; - const uint8_t LUTW[16] = {0b11111111, 0b11111110, 0b11111011, 0b11111010, 0b11101111, 0b11101110, - 0b11101011, 0b11101010, 0b10111111, 0b10111110, 0b10111011, 0b10111010, - 0b10101111, 0b10101110, 0b10101011, 0b10101010}; - const uint8_t LUTB[16] = {0b11111111, 0b11111101, 0b11110111, 0b11110101, 0b11011111, 0b11011101, - 0b11010111, 0b11010101, 0b01111111, 0b01111101, 0b01110111, 0b01110101, - 0b01011111, 0b01011101, 0b01010111, 0b01010101}; - const uint8_t pixelMaskLUT[8] = {0b00000001, 0b00000010, 0b00000100, 0b00001000, - 0b00010000, 0b00100000, 0b01000000, 0b10000000}; - const uint8_t pixelMaskGLUT[2] = {0b00001111, 0b11110000}; - const uint8_t waveform3Bit[8][8] = {{0, 0, 0, 0, 1, 1, 1, 0}, {1, 2, 2, 2, 1, 1, 1, 0}, {0, 1, 2, 1, 1, 2, 1, 0}, - {0, 2, 1, 2, 1, 2, 1, 0}, {0, 0, 0, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 2, 0}, + const uint8_t LUT2[16] = {0xAA, 0xA9, 0xA6, 0xA5, 0x9A, 0x99, 0x96, 0x95, + 0x6A, 0x69, 0x66, 0x65, 0x5A, 0x59, 0x56, 0x55}; + const uint8_t LUTW[16] = {0xFF, 0xFE, 0xFB, 0xFA, 0xEF, 0xEE, 0xEB, 0xEA, + 0xBF, 0xBE, 0xBB, 0xBA, 0xAF, 0xAE, 0xAB, 0xAA}; + const uint8_t LUTB[16] = {0xFF, 0xFD, 0xF7, 0xF5, 0xDF, 0xDD, 0xD7, 0xD5, + 0x7F, 0x7D, 0x77, 0x75, 0x5F, 0x5D, 0x57, 0x55}; + + const uint8_t pixelMaskLUT[8] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80}; + const uint8_t pixelMaskGLUT[2] = {0x0F, 0xF0}; + + const uint8_t waveform3Bit[8][8] = {{0, 1, 1, 0, 0, 1, 1, 0}, {0, 1, 2, 1, 1, 2, 1, 0}, {1, 1, 1, 2, 2, 1, 0, 0}, + {0, 0, 0, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 2, 0}, {2, 2, 1, 1, 2, 1, 2, 0}, {1, 1, 1, 2, 1, 2, 2, 0}, {0, 0, 0, 0, 0, 0, 2, 0}}; - const uint32_t waveform[50] = { - 0x00000008, 0x00000008, 0x00200408, 0x80281888, 0x60a81898, 0x60a8a8a8, 0x60a8a8a8, 0x6068a868, 0x6868a868, - 0x6868a868, 0x68686868, 0x6a686868, 0x5a686868, 0x5a686868, 0x5a586a68, 0x5a5a6a68, 0x5a5a6a68, 0x55566a68, - 0x55565a64, 0x55555654, 0x55555556, 0x55555556, 0x55555556, 0x55555516, 0x55555596, 0x15555595, 0x95955595, - 0x95959595, 0x95949495, 0x94949495, 0x94949495, 0xa4949494, 0x9494a4a4, 0x84a49494, 0x84948484, 0x84848484, - 0x84848484, 0x84848484, 0xa5a48484, 0xa9a4a4a8, 0xa9a8a8a8, 0xa5a9a9a4, 0xa5a5a5a4, 0xa1a5a5a1, 0xa9a9a9a9, - 0xa9a9a9a9, 0xa9a9a9a9, 0xa9a9a9a9, 0x15151515, 0x11111111}; + const uint8_t waveform3Bit6Plus[8][9] = {{0, 0, 0, 0, 0, 2, 1, 1, 0}, {0, 0, 2, 1, 1, 1, 2, 1, 0}, + {0, 2, 2, 2, 1, 1, 2, 1, 0}, {0, 0, 2, 2, 2, 1, 2, 1, 0}, + {0, 0, 0, 0, 2, 2, 2, 1, 0}, {0, 0, 2, 1, 2, 1, 1, 2, 0}, + {0, 0, 2, 2, 2, 1, 1, 2, 0}, {0, 0, 0, 0, 2, 2, 2, 2, 0}}; void set_greyscale(bool greyscale) { this->greyscale_ = greyscale; @@ -88,6 +84,8 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public bool get_partial_updating() { return this->partial_updating_; } uint8_t get_temperature() { return this->temperature_; } + void block_partial() { this->block_partial_ = true; } + protected: void draw_absolute_pixel_internal(int x, int y, Color color) override; void display1b_(); @@ -99,10 +97,10 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public void hscan_start_(uint32_t d); void vscan_end_(); void vscan_start_(); - void vscan_write_(); void eink_off_(); void eink_on_(); + bool read_power_status_(); void setup_pins_(); void pins_z_state_(); @@ -113,6 +111,8 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public return 800; } else if (this->model_ == INKPLATE_10) { return 1200; + } else if (this->model_ == INKPLATE_6_PLUS) { + return 1024; } return 0; } @@ -122,6 +122,8 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public return 600; } else if (this->model_ == INKPLATE_10) { return 825; + } else if (this->model_ == INKPLATE_6_PLUS) { + return 758; } return 0; } @@ -141,16 +143,20 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public return data; } - uint8_t panel_on_ = 0; + bool panel_on_{false}; uint8_t temperature_; uint8_t *partial_buffer_{nullptr}; uint8_t *partial_buffer_2_{nullptr}; + uint32_t *glut_{nullptr}; + uint32_t *glut2_{nullptr}; + uint32_t pin_lut_[256]; + uint32_t full_update_every_; uint32_t partial_updates_{0}; - bool block_partial_; + bool block_partial_{true}; bool greyscale_; bool partial_updating_; From 9826726a72624f78bfb77fec7656e1474ef2158b Mon Sep 17 00:00:00 2001 From: Andrej Komelj Date: Tue, 8 Feb 2022 10:58:38 +0100 Subject: [PATCH 087/238] Implement MQTT discovery object_id generator (#3114) --- esphome/components/mqtt/__init__.py | 22 ++++++++++++++++++++-- esphome/components/mqtt/mqtt_client.cpp | 4 +++- esphome/components/mqtt/mqtt_client.h | 12 ++++++++++-- esphome/components/mqtt/mqtt_component.cpp | 7 +++++-- esphome/components/mqtt/mqtt_const.h | 2 ++ esphome/const.py | 1 + 6 files changed, 41 insertions(+), 7 deletions(-) diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 88e5d43509..901b77474d 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -16,6 +16,7 @@ from esphome.const import ( CONF_DISCOVERY_PREFIX, CONF_DISCOVERY_RETAIN, CONF_DISCOVERY_UNIQUE_ID_GENERATOR, + CONF_DISCOVERY_OBJECT_ID_GENERATOR, CONF_ID, CONF_KEEPALIVE, CONF_LEVEL, @@ -105,6 +106,12 @@ MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = { "mac": MQTTDiscoveryUniqueIdGenerator.MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR, } +MQTTDiscoveryObjectIdGenerator = mqtt_ns.enum("MQTTDiscoveryObjectIdGenerator") +MQTT_DISCOVERY_OBJECT_ID_GENERATOR_OPTIONS = { + "none": MQTTDiscoveryObjectIdGenerator.MQTT_NONE_OBJECT_ID_GENERATOR, + "device_name": MQTTDiscoveryObjectIdGenerator.MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR, +} + def validate_config(value): # Populate default fields @@ -166,6 +173,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DISCOVERY_UNIQUE_ID_GENERATOR, default="legacy"): cv.enum( MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS ), + cv.Optional(CONF_DISCOVERY_OBJECT_ID_GENERATOR, default="none"): cv.enum( + MQTT_DISCOVERY_OBJECT_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, @@ -245,19 +255,27 @@ async def to_code(config): discovery_retain = config[CONF_DISCOVERY_RETAIN] discovery_prefix = config[CONF_DISCOVERY_PREFIX] discovery_unique_id_generator = config[CONF_DISCOVERY_UNIQUE_ID_GENERATOR] + discovery_object_id_generator = config[CONF_DISCOVERY_OBJECT_ID_GENERATOR] if not discovery: cg.add(var.disable_discovery()) elif discovery == "CLEAN": cg.add( var.set_discovery_info( - discovery_prefix, discovery_unique_id_generator, discovery_retain, True + discovery_prefix, + discovery_unique_id_generator, + discovery_object_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_unique_id_generator, discovery_retain + discovery_prefix, + discovery_unique_id_generator, + discovery_object_id_generator, + discovery_retain, ) ) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 67063d4c72..148316672a 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -538,9 +538,11 @@ 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, MQTTDiscoveryUniqueIdGenerator unique_id_generator, - bool retain, bool clean) { + MQTTDiscoveryObjectIdGenerator object_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_.object_id_generator = object_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 a6a7025c6f..58a4fbe166 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -61,6 +61,12 @@ enum MQTTDiscoveryUniqueIdGenerator { MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR, }; +/// available discovery object_id generators +enum MQTTDiscoveryObjectIdGenerator { + MQTT_NONE_OBJECT_ID_GENERATOR = 0, + MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR, +}; + /** Internal struct for MQTT Home Assistant discovery * * See MQTT Discovery. @@ -70,6 +76,7 @@ struct MQTTDiscoveryInfo { bool retain; ///< Whether to retain discovery messages. bool clean; MQTTDiscoveryUniqueIdGenerator unique_id_generator; + MQTTDiscoveryObjectIdGenerator object_id_generator; }; enum MQTTClientState { @@ -106,10 +113,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 object_id_generator Controls how ObjectId is generated. * @param retain Whether to retain discovery messages. */ - void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, bool retain, - bool clean = false); + void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, + MQTTDiscoveryObjectIdGenerator object_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 341ac50e37..cf228efd1b 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -111,12 +111,11 @@ bool MQTTComponent::send_discovery_() { 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(); + const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info(); if (!unique_id.empty()) { root[MQTT_UNIQUE_ID] = unique_id; } else { - 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())); @@ -129,6 +128,10 @@ bool MQTTComponent::send_discovery_() { } } + const std::string &node_name = App.get_name(); + if (discovery_info.object_id_generator == MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR) + root[MQTT_OBJECT_ID] = node_name + "_" + this->get_default_object_id_(); + JsonObject device_info = root.createNestedObject(MQTT_DEVICE); device_info[MQTT_DEVICE_IDENTIFIERS] = get_mac_address(); device_info[MQTT_DEVICE_NAME] = node_name; diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h index 52ca0ed7c0..7f74197ab4 100644 --- a/esphome/components/mqtt/mqtt_const.h +++ b/esphome/components/mqtt/mqtt_const.h @@ -107,6 +107,7 @@ 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_OBJECT_ID = "obj_id"; 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"; @@ -360,6 +361,7 @@ 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_OBJECT_ID = "object_id"; 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"; diff --git a/esphome/const.py b/esphome/const.py index 61b152654a..dcd6fea31f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -168,6 +168,7 @@ CONF_DIRECTION = "direction" CONF_DIRECTION_OUTPUT = "direction_output" CONF_DISABLED_BY_DEFAULT = "disabled_by_default" CONF_DISCOVERY = "discovery" +CONF_DISCOVERY_OBJECT_ID_GENERATOR = "discovery_object_id_generator" CONF_DISCOVERY_PREFIX = "discovery_prefix" CONF_DISCOVERY_RETAIN = "discovery_retain" CONF_DISCOVERY_UNIQUE_ID_GENERATOR = "discovery_unique_id_generator" From 88d72f8c9a8694213b8206bd1fed316c3f481709 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 9 Feb 2022 08:04:44 +1300 Subject: [PATCH 088/238] Fix files CI after merging (#3175) --- esphome/components/max9611/max9611.cpp | 2 +- esphome/components/modbus_controller/select/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/max9611/max9611.cpp b/esphome/components/max9611/max9611.cpp index 418a577948..70a94c4ad9 100644 --- a/esphome/components/max9611/max9611.cpp +++ b/esphome/components/max9611/max9611.cpp @@ -13,7 +13,7 @@ template inline T signextend(const T x) { return s.x = x; } // Map the gain register to in uV/LSB -const float gain_to_lsb(MAX9611Multiplexer gain) { +float gain_to_lsb(MAX9611Multiplexer gain) { float lsb = 0.0; if (gain == MAX9611_MULTIPLEXER_CSA_GAIN1) { lsb = 107.50; diff --git a/esphome/components/modbus_controller/select/__init__.py b/esphome/components/modbus_controller/select/__init__.py index 9651b5fedb..7d03064fa5 100644 --- a/esphome/components/modbus_controller/select/__init__.py +++ b/esphome/components/modbus_controller/select/__init__.py @@ -35,7 +35,7 @@ def ensure_option_map(): def validator(value): cv.check_not_templatable(value) option = cv.All(cv.string_strict) - mapping = cv.All(cv.int_range(-(2 ** 63), 2 ** 63 - 1)) + mapping = cv.All(cv.int_range(-(2**63), 2**63 - 1)) options_map_schema = cv.Schema({option: mapping}) value = options_map_schema(value) From f43e04e15aeec45d1e98e72bf1a57367d0e77403 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 9 Feb 2022 11:42:00 +1300 Subject: [PATCH 089/238] Try fix canbus config validation (#3173) --- esphome/components/canbus/__init__.py | 32 +++++++++++++-------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/esphome/components/canbus/__init__.py b/esphome/components/canbus/__init__.py index 3a3cece579..808b31d1d2 100644 --- a/esphome/components/canbus/__init__.py +++ b/esphome/components/canbus/__init__.py @@ -14,10 +14,14 @@ CONF_BIT_RATE = "bit_rate" CONF_ON_FRAME = "on_frame" -def validate_id(id_value, id_ext): - if not id_ext: - if id_value > 0x7FF: - raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)") +def validate_id(config): + if CONF_CAN_ID in config: + id_value = config[CONF_CAN_ID] + id_ext = config[CONF_USE_EXTENDED_ID] + if not id_ext: + if id_value > 0x7FF: + raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)") + return config def validate_raw_data(value): @@ -67,23 +71,18 @@ CANBUS_SCHEMA = cv.Schema( cv.Optional(CONF_ON_FRAME): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger), - cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), + cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, - cv.Optional(CONF_ON_FRAME): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger), - cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), - cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, - } - ), - } + }, + validate_id, ), - } + }, ).extend(cv.COMPONENT_SCHEMA) +CANBUS_SCHEMA.add_extra(validate_id) + async def setup_canbus_core_(var, config): - validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID]) await cg.register_component(var, config) cg.add(var.set_can_id([config[CONF_CAN_ID]])) cg.add(var.set_use_extended_id([config[CONF_USE_EXTENDED_ID]])) @@ -92,7 +91,6 @@ async def setup_canbus_core_(var, config): for conf in config.get(CONF_ON_FRAME, []): can_id = conf[CONF_CAN_ID] ext_id = conf[CONF_USE_EXTENDED_ID] - validate_id(can_id, ext_id) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id) await cg.register_component(trigger, conf) await automation.build_automation( @@ -117,11 +115,11 @@ async def register_canbus(var, config): cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, cv.Required(CONF_DATA): cv.templatable(validate_raw_data), }, + validate_id, key=CONF_DATA, ), ) async def canbus_action_to_code(config, action_id, template_arg, args): - validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID]) var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_CANBUS_ID]) From 64f798d4b2e92a8f22b35606ea1c9cca10e13c76 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Feb 2022 11:42:44 +1300 Subject: [PATCH 090/238] Bump pytest from 6.2.5 to 7.0.0 (#3163) 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 45d8c02099..62d1ff31af 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -4,7 +4,7 @@ black==22.1.0 pre-commit # Unit tests -pytest==6.2.5 +pytest==7.0.0 pytest-cov==3.0.0 pytest-mock==3.7.0 pytest-asyncio==0.17.2 From 3bf042dce943e6c6549f62d8d77636ff94e8fbe1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Feb 2022 12:57:12 +1300 Subject: [PATCH 091/238] Bump pytest-asyncio from 0.17.2 to 0.18.0 (#3168) 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 62d1ff31af..1e5a5c2ebc 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -7,6 +7,6 @@ pre-commit pytest==7.0.0 pytest-cov==3.0.0 pytest-mock==3.7.0 -pytest-asyncio==0.17.2 +pytest-asyncio==0.18.0 asyncmock==0.4.2 hypothesis==5.49.0 From e7dd6c52ac9ef348cce2fa1e6362fb6c7535cfd3 Mon Sep 17 00:00:00 2001 From: Borys Pierov Date: Wed, 9 Feb 2022 05:29:32 -0500 Subject: [PATCH 092/238] Allow to set manufacturer data for BLEAdvertising (#3179) --- esphome/components/esp32_ble/ble_advertising.cpp | 5 +++++ esphome/components/esp32_ble/ble_advertising.h | 1 + 2 files changed, 6 insertions(+) diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index 31b1f4c383..2083bf5f08 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -42,6 +42,11 @@ void BLEAdvertising::remove_service_uuid(ESPBTUUID uuid) { this->advertising_uuids_.end()); } +void BLEAdvertising::set_manufacturer_data(uint8_t *data, uint16_t size) { + this->advertising_data_.p_manufacturer_data = data; + this->advertising_data_.manufacturer_len = size; +} + void BLEAdvertising::start() { int num_services = this->advertising_uuids_.size(); if (num_services == 0) { diff --git a/esphome/components/esp32_ble/ble_advertising.h b/esphome/components/esp32_ble/ble_advertising.h index 01e2ba1295..079bd6c14c 100644 --- a/esphome/components/esp32_ble/ble_advertising.h +++ b/esphome/components/esp32_ble/ble_advertising.h @@ -20,6 +20,7 @@ class BLEAdvertising { void remove_service_uuid(ESPBTUUID uuid); void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; } void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; } + void set_manufacturer_data(uint8_t *data, uint16_t size); void start(); void stop(); From 5c220651355ca63e4040d80c0aef196093945c4c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 9 Feb 2022 23:46:20 +1300 Subject: [PATCH 093/238] Change most references from hassio to ha-addon (#3178) --- .github/workflows/release.yml | 4 +-- docker/Dockerfile | 2 +- .../etc/cont-init.d/10-requirements.sh | 0 .../etc/cont-init.d/20-nginx.sh | 0 .../etc/cont-init.d/30-dirs.sh | 0 .../etc/nginx/includes/mime.types | 0 .../etc/nginx/includes/proxy_params.conf | 0 .../etc/nginx/includes/server_params.conf | 0 .../etc/nginx/includes/ssl_params.conf | 0 .../etc/nginx/nginx.conf | 0 .../etc/nginx/servers/direct-ssl.disabled | 2 +- .../etc/nginx/servers/direct.disabled | 2 +- .../etc/nginx/servers/ingress.conf | 4 +-- .../etc/services.d/esphome/finish | 0 .../etc/services.d/esphome/run | 4 +-- .../etc/services.d/nginx/finish | 0 .../etc/services.d/nginx/run | 0 esphome/__main__.py | 2 +- esphome/core/__init__.py | 6 ++-- esphome/dashboard/dashboard.py | 30 +++++++++---------- esphome/helpers.py | 4 +-- requirements.txt | 2 +- script/ci-custom.py | 8 ++++- tests/unit_tests/test_helpers.py | 8 ++--- 24 files changed, 42 insertions(+), 36 deletions(-) rename docker/{hassio-rootfs => ha-addon-rootfs}/etc/cont-init.d/10-requirements.sh (100%) rename docker/{hassio-rootfs => ha-addon-rootfs}/etc/cont-init.d/20-nginx.sh (100%) rename docker/{hassio-rootfs => ha-addon-rootfs}/etc/cont-init.d/30-dirs.sh (100%) rename docker/{hassio-rootfs => ha-addon-rootfs}/etc/nginx/includes/mime.types (100%) rename docker/{hassio-rootfs => ha-addon-rootfs}/etc/nginx/includes/proxy_params.conf (100%) rename docker/{hassio-rootfs => ha-addon-rootfs}/etc/nginx/includes/server_params.conf (100%) rename docker/{hassio-rootfs => ha-addon-rootfs}/etc/nginx/includes/ssl_params.conf (100%) rename docker/{hassio-rootfs => ha-addon-rootfs}/etc/nginx/nginx.conf (100%) rename docker/{hassio-rootfs => ha-addon-rootfs}/etc/nginx/servers/direct-ssl.disabled (93%) rename docker/{hassio-rootfs => ha-addon-rootfs}/etc/nginx/servers/direct.disabled (85%) rename docker/{hassio-rootfs => ha-addon-rootfs}/etc/nginx/servers/ingress.conf (79%) rename docker/{hassio-rootfs => ha-addon-rootfs}/etc/services.d/esphome/finish (100%) rename docker/{hassio-rootfs => ha-addon-rootfs}/etc/services.d/esphome/run (96%) rename docker/{hassio-rootfs => ha-addon-rootfs}/etc/services.d/nginx/finish (100%) rename docker/{hassio-rootfs => ha-addon-rootfs}/etc/services.d/nginx/run (100%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d6895becc0..02a55494e9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -137,7 +137,7 @@ jobs: --build-type "${{ matrix.build_type }}" \ manifest - deploy-hassio-repo: + deploy-ha-addon-repo: if: github.repository == 'esphome/esphome' && github.event_name == 'release' runs-on: ubuntu-latest needs: [deploy-docker] @@ -150,5 +150,5 @@ jobs: -u ":$TOKEN" \ -X POST \ -H "Accept: application/vnd.github.v3+json" \ - https://api.github.com/repos/esphome/hassio/actions/workflows/bump-version.yml/dispatches \ + https://api.github.com/repos/esphome/home-assistant-addon/actions/workflows/bump-version.yml/dispatches \ -d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}" diff --git a/docker/Dockerfile b/docker/Dockerfile index 0eebbe827e..5d9decbf1b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -102,7 +102,7 @@ RUN \ ARG BUILD_VERSION=dev # Copy root filesystem -COPY docker/hassio-rootfs/ / +COPY docker/ha-addon-rootfs/ / # First install requirements to leverage caching when requirements don't change COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / diff --git a/docker/hassio-rootfs/etc/cont-init.d/10-requirements.sh b/docker/ha-addon-rootfs/etc/cont-init.d/10-requirements.sh similarity index 100% rename from docker/hassio-rootfs/etc/cont-init.d/10-requirements.sh rename to docker/ha-addon-rootfs/etc/cont-init.d/10-requirements.sh diff --git a/docker/hassio-rootfs/etc/cont-init.d/20-nginx.sh b/docker/ha-addon-rootfs/etc/cont-init.d/20-nginx.sh similarity index 100% rename from docker/hassio-rootfs/etc/cont-init.d/20-nginx.sh rename to docker/ha-addon-rootfs/etc/cont-init.d/20-nginx.sh diff --git a/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh b/docker/ha-addon-rootfs/etc/cont-init.d/30-dirs.sh similarity index 100% rename from docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh rename to docker/ha-addon-rootfs/etc/cont-init.d/30-dirs.sh diff --git a/docker/hassio-rootfs/etc/nginx/includes/mime.types b/docker/ha-addon-rootfs/etc/nginx/includes/mime.types similarity index 100% rename from docker/hassio-rootfs/etc/nginx/includes/mime.types rename to docker/ha-addon-rootfs/etc/nginx/includes/mime.types diff --git a/docker/hassio-rootfs/etc/nginx/includes/proxy_params.conf b/docker/ha-addon-rootfs/etc/nginx/includes/proxy_params.conf similarity index 100% rename from docker/hassio-rootfs/etc/nginx/includes/proxy_params.conf rename to docker/ha-addon-rootfs/etc/nginx/includes/proxy_params.conf diff --git a/docker/hassio-rootfs/etc/nginx/includes/server_params.conf b/docker/ha-addon-rootfs/etc/nginx/includes/server_params.conf similarity index 100% rename from docker/hassio-rootfs/etc/nginx/includes/server_params.conf rename to docker/ha-addon-rootfs/etc/nginx/includes/server_params.conf diff --git a/docker/hassio-rootfs/etc/nginx/includes/ssl_params.conf b/docker/ha-addon-rootfs/etc/nginx/includes/ssl_params.conf similarity index 100% rename from docker/hassio-rootfs/etc/nginx/includes/ssl_params.conf rename to docker/ha-addon-rootfs/etc/nginx/includes/ssl_params.conf diff --git a/docker/hassio-rootfs/etc/nginx/nginx.conf b/docker/ha-addon-rootfs/etc/nginx/nginx.conf similarity index 100% rename from docker/hassio-rootfs/etc/nginx/nginx.conf rename to docker/ha-addon-rootfs/etc/nginx/nginx.conf diff --git a/docker/hassio-rootfs/etc/nginx/servers/direct-ssl.disabled b/docker/ha-addon-rootfs/etc/nginx/servers/direct-ssl.disabled similarity index 93% rename from docker/hassio-rootfs/etc/nginx/servers/direct-ssl.disabled rename to docker/ha-addon-rootfs/etc/nginx/servers/direct-ssl.disabled index c2b8d6567d..4ebc435dbb 100644 --- a/docker/hassio-rootfs/etc/nginx/servers/direct-ssl.disabled +++ b/docker/ha-addon-rootfs/etc/nginx/servers/direct-ssl.disabled @@ -10,7 +10,7 @@ server { ssl_certificate_key /ssl/%%keyfile%%; # Clear Hass.io Ingress header - proxy_set_header X-Hassio-Ingress ""; + proxy_set_header X-HA-Ingress ""; # Redirect http requests to https on the same port. # https://rageagainstshell.com/2016/11/redirect-http-to-https-on-the-same-port-in-nginx/ diff --git a/docker/hassio-rootfs/etc/nginx/servers/direct.disabled b/docker/ha-addon-rootfs/etc/nginx/servers/direct.disabled similarity index 85% rename from docker/hassio-rootfs/etc/nginx/servers/direct.disabled rename to docker/ha-addon-rootfs/etc/nginx/servers/direct.disabled index 51f57cab88..80300fc6aa 100644 --- a/docker/hassio-rootfs/etc/nginx/servers/direct.disabled +++ b/docker/ha-addon-rootfs/etc/nginx/servers/direct.disabled @@ -4,7 +4,7 @@ server { include /etc/nginx/includes/server_params.conf; include /etc/nginx/includes/proxy_params.conf; # Clear Hass.io Ingress header - proxy_set_header X-Hassio-Ingress ""; + proxy_set_header X-HA-Ingress ""; location / { proxy_pass http://esphome; diff --git a/docker/hassio-rootfs/etc/nginx/servers/ingress.conf b/docker/ha-addon-rootfs/etc/nginx/servers/ingress.conf similarity index 79% rename from docker/hassio-rootfs/etc/nginx/servers/ingress.conf rename to docker/ha-addon-rootfs/etc/nginx/servers/ingress.conf index 3a800d97e7..9d0d2d3e66 100644 --- a/docker/hassio-rootfs/etc/nginx/servers/ingress.conf +++ b/docker/ha-addon-rootfs/etc/nginx/servers/ingress.conf @@ -3,8 +3,8 @@ server { include /etc/nginx/includes/server_params.conf; include /etc/nginx/includes/proxy_params.conf; - # Set Hass.io Ingress header - proxy_set_header X-Hassio-Ingress "YES"; + # Set Home Assistant Ingress header + proxy_set_header X-HA-Ingress "YES"; location / { # Only allow from Hass.io supervisor diff --git a/docker/hassio-rootfs/etc/services.d/esphome/finish b/docker/ha-addon-rootfs/etc/services.d/esphome/finish similarity index 100% rename from docker/hassio-rootfs/etc/services.d/esphome/finish rename to docker/ha-addon-rootfs/etc/services.d/esphome/finish diff --git a/docker/hassio-rootfs/etc/services.d/esphome/run b/docker/ha-addon-rootfs/etc/services.d/esphome/run similarity index 96% rename from docker/hassio-rootfs/etc/services.d/esphome/run rename to docker/ha-addon-rootfs/etc/services.d/esphome/run index a0f20d63d6..2c821bf4ee 100755 --- a/docker/hassio-rootfs/etc/services.d/esphome/run +++ b/docker/ha-addon-rootfs/etc/services.d/esphome/run @@ -4,7 +4,7 @@ # Runs the ESPHome dashboard # ============================================================================== -export ESPHOME_IS_HASSIO=true +export ESPHOME_IS_HA_ADDON=true if bashio::config.true 'leave_front_door_open'; then export DISABLE_HA_AUTHENTICATION=true @@ -32,4 +32,4 @@ export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache" export PLATFORMIO_GLOBALLIB_DIR=/piolibs bashio::log.info "Starting ESPHome dashboard..." -exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --hassio +exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon diff --git a/docker/hassio-rootfs/etc/services.d/nginx/finish b/docker/ha-addon-rootfs/etc/services.d/nginx/finish similarity index 100% rename from docker/hassio-rootfs/etc/services.d/nginx/finish rename to docker/ha-addon-rootfs/etc/services.d/nginx/finish diff --git a/docker/hassio-rootfs/etc/services.d/nginx/run b/docker/ha-addon-rootfs/etc/services.d/nginx/run similarity index 100% rename from docker/hassio-rootfs/etc/services.d/nginx/run rename to docker/ha-addon-rootfs/etc/services.d/nginx/run diff --git a/esphome/__main__.py b/esphome/__main__.py index 6f57791480..a64f096d54 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -661,7 +661,7 @@ def parse_args(argv): "--open-ui", help="Open the dashboard UI in a browser.", action="store_true" ) parser_dashboard.add_argument( - "--hassio", help=argparse.SUPPRESS, action="store_true" + "--ha-addon", help=argparse.SUPPRESS, action="store_true" ) parser_dashboard.add_argument( "--socket", help="Make the dashboard serve under a unix socket", type=str diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index addecf1326..a4867915bb 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -20,7 +20,7 @@ from esphome.coroutine import FakeEventLoop as _FakeEventLoop # pylint: disable=unused-import from esphome.coroutine import coroutine, coroutine_with_priority # noqa -from esphome.helpers import ensure_unique_string, is_hassio +from esphome.helpers import ensure_unique_string, is_ha_addon from esphome.util import OrderedDict if TYPE_CHECKING: @@ -568,12 +568,12 @@ class EsphomeCore: return self.relative_build_path("src", *path) def relative_pioenvs_path(self, *path): - if is_hassio(): + if is_ha_addon(): return os.path.join("/data", self.name, ".pioenvs", *path) return self.relative_build_path(".pioenvs", *path) def relative_piolibdeps_path(self, *path): - if is_hassio(): + if is_ha_addon(): return os.path.join("/data", self.name, ".piolibdeps", *path) return self.relative_build_path(".piolibdeps", *path) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index c68d037fe6..0ace97f10e 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -55,13 +55,13 @@ class DashboardSettings: self.password_hash = "" self.username = "" self.using_password = False - self.on_hassio = False + self.on_ha_addon = False self.cookie_secret = None def parse_args(self, args): - self.on_hassio = args.hassio + self.on_ha_addon = args.ha_addon password = args.password or os.getenv("PASSWORD", "") - if not self.on_hassio: + if not self.on_ha_addon: self.username = args.username or os.getenv("USERNAME", "") self.using_password = bool(password) if self.using_password: @@ -77,14 +77,14 @@ class DashboardSettings: return get_bool_env("ESPHOME_DASHBOARD_USE_PING") @property - def using_hassio_auth(self): - if not self.on_hassio: + def using_ha_addon_auth(self): + if not self.on_ha_addon: return False return not get_bool_env("DISABLE_HA_AUTHENTICATION") @property def using_auth(self): - return self.using_password or self.using_hassio_auth + return self.using_password or self.using_ha_addon_auth def check_password(self, username, password): if not self.using_auth: @@ -138,10 +138,10 @@ def authenticated(func): def is_authenticated(request_handler): - if settings.on_hassio: + if settings.on_ha_addon: # Handle ingress - disable auth on ingress port - # X-Hassio-Ingress is automatically stripped on the non-ingress server in nginx - header = request_handler.request.headers.get("X-Hassio-Ingress", "NO") + # X-HA-Ingress is automatically stripped on the non-ingress server in nginx + header = request_handler.request.headers.get("X-HA-Ingress", "NO") if str(header) == "YES": return True if settings.using_auth: @@ -792,23 +792,23 @@ class LoginHandler(BaseHandler): self.render( "login.template.html", error=error, - hassio=settings.using_hassio_auth, + ha_addon=settings.using_ha_addon_auth, has_username=bool(settings.username), **template_args(), ) - def post_hassio_login(self): + def post_ha_addon_login(self): import requests headers = { - "X-HASSIO-KEY": os.getenv("HASSIO_TOKEN"), + "Authentication": f"Bearer {os.getenv('SUPERVISOR_TOKEN')}", } data = { "username": self.get_argument("username", ""), "password": self.get_argument("password", ""), } try: - req = requests.post("http://hassio/auth", headers=headers, data=data) + req = requests.post("http://supervisor/auth", headers=headers, data=data) if req.status_code == 200: self.set_secure_cookie("authenticated", cookie_authenticated_yes) self.redirect("/") @@ -835,8 +835,8 @@ class LoginHandler(BaseHandler): self.render_login_page(error=error_str) def post(self): - if settings.using_hassio_auth: - self.post_hassio_login() + if settings.using_ha_addon_auth: + self.post_ha_addon_login() else: self.post_native_login() diff --git a/esphome/helpers.py b/esphome/helpers.py index 289abe5459..76158a1bfd 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -144,8 +144,8 @@ def get_bool_env(var, default=False): return bool(os.getenv(var, default)) -def is_hassio(): - return get_bool_env("ESPHOME_IS_HASSIO") +def is_ha_addon(): + return get_bool_env("ESPHOME_IS_HA_ADDON") def walk_files(path): diff --git a/requirements.txt b/requirements.txt index 2cbebb11f5..a33461b79b 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==20220116.0 +esphome-dashboard==20220209.0 aioesphomeapi==10.8.1 zeroconf==0.37.0 diff --git a/script/ci-custom.py b/script/ci-custom.py index 7bbaaf1c79..d1efa22d85 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -222,7 +222,13 @@ def lint_ext_check(fname): @lint_file_check( - exclude=["**.sh", "docker/hassio-rootfs/**", "docker/*.py", "script/*", "setup.py"] + exclude=[ + "**.sh", + "docker/ha-addon-rootfs/**", + "docker/*.py", + "script/*", + "setup.py", + ] ) def lint_executable_bit(fname): ex = EXECUTABLE_BIT[fname] diff --git a/tests/unit_tests/test_helpers.py b/tests/unit_tests/test_helpers.py index 00a6b08133..f883b8b44f 100644 --- a/tests/unit_tests/test_helpers.py +++ b/tests/unit_tests/test_helpers.py @@ -124,13 +124,13 @@ def test_get_bool_env(monkeypatch, var, value, default, expected): @pytest.mark.parametrize("value, expected", ((None, False), ("Yes", True))) -def test_is_hassio(monkeypatch, value, expected): +def test_is_ha_addon(monkeypatch, value, expected): if value is None: - monkeypatch.delenv("ESPHOME_IS_HASSIO", raising=False) + monkeypatch.delenv("ESPHOME_IS_HA_ADDON", raising=False) else: - monkeypatch.setenv("ESPHOME_IS_HASSIO", value) + monkeypatch.setenv("ESPHOME_IS_HA_ADDON", value) - actual = helpers.is_hassio() + actual = helpers.is_ha_addon() assert actual == expected From b48490badce8c14d14dbc3f0664c463c8b01974c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 9 Feb 2022 23:47:36 +1300 Subject: [PATCH 094/238] Bump version to 2022.3.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index dcd6fea31f..1494920385 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.2.0-dev" +__version__ = "2022.3.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 35e6a13cd1230519464aa80f42e2ac951335f3cb Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 9 Feb 2022 14:56:42 +0100 Subject: [PATCH 095/238] Remove unused obj attribute from AssignmentExpression (#3145) --- esphome/cpp_generator.py | 23 +++++++++++------------ tests/unit_tests/test_cpp_generator.py | 8 ++++---- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 82deec70ec..4ff16ba703 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -62,14 +62,13 @@ class RawExpression(Expression): class AssignmentExpression(Expression): - __slots__ = ("type", "modifier", "name", "rhs", "obj") + __slots__ = ("type", "modifier", "name", "rhs") - def __init__(self, type_, modifier, name, rhs, obj): + def __init__(self, type_, modifier, name, rhs): self.type = type_ self.modifier = modifier self.name = name self.rhs = safe_exp(rhs) - self.obj = obj def __str__(self): if self.type is None: @@ -427,8 +426,8 @@ class LineComment(Statement): class ProgmemAssignmentExpression(AssignmentExpression): __slots__ = () - def __init__(self, type_, name, rhs, obj): - super().__init__(type_, "", name, rhs, obj) + def __init__(self, type_, name, rhs): + super().__init__(type_, "", name, rhs) def __str__(self): return f"static const {self.type} {self.name}[] PROGMEM = {self.rhs}" @@ -437,8 +436,8 @@ class ProgmemAssignmentExpression(AssignmentExpression): class StaticConstAssignmentExpression(AssignmentExpression): __slots__ = () - def __init__(self, type_, name, rhs, obj): - super().__init__(type_, "", name, rhs, obj) + def __init__(self, type_, name, rhs): + super().__init__(type_, "", name, rhs) def __str__(self): return f"static const {self.type} {self.name}[] = {self.rhs}" @@ -447,7 +446,7 @@ class StaticConstAssignmentExpression(AssignmentExpression): def progmem_array(id_, rhs) -> "MockObj": rhs = safe_exp(rhs) obj = MockObj(id_, ".") - assignment = ProgmemAssignmentExpression(id_.type, id_, rhs, obj) + assignment = ProgmemAssignmentExpression(id_.type, id_, rhs) CORE.add(assignment) CORE.register_variable(id_, obj) return obj @@ -456,7 +455,7 @@ def progmem_array(id_, rhs) -> "MockObj": def static_const_array(id_, rhs) -> "MockObj": rhs = safe_exp(rhs) obj = MockObj(id_, ".") - assignment = StaticConstAssignmentExpression(id_.type, id_, rhs, obj) + assignment = StaticConstAssignmentExpression(id_.type, id_, rhs) CORE.add(assignment) CORE.register_variable(id_, obj) return obj @@ -484,7 +483,7 @@ def variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": obj = MockObj(id_, ".") if type_ is not None: id_.type = type_ - assignment = AssignmentExpression(id_.type, "", id_, rhs, obj) + assignment = AssignmentExpression(id_.type, "", id_, rhs) CORE.add(assignment) CORE.register_variable(id_, obj) return obj @@ -507,7 +506,7 @@ def new_variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj id_.type = type_ decl = VariableDeclarationExpression(id_.type, "", id_) CORE.add_global(decl) - assignment = AssignmentExpression(None, "", id_, rhs, obj) + assignment = AssignmentExpression(None, "", id_, rhs) CORE.add(assignment) CORE.register_variable(id_, obj) return obj @@ -529,7 +528,7 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": id_.type = type_ decl = VariableDeclarationExpression(id_.type, "*", id_) CORE.add_global(decl) - assignment = AssignmentExpression(None, None, id_, rhs, obj) + assignment = AssignmentExpression(None, None, id_, rhs) CORE.add(assignment) CORE.register_variable(id_, obj) return obj diff --git a/tests/unit_tests/test_cpp_generator.py b/tests/unit_tests/test_cpp_generator.py index 5a8087ffa9..331c500c04 100644 --- a/tests/unit_tests/test_cpp_generator.py +++ b/tests/unit_tests/test_cpp_generator.py @@ -13,9 +13,9 @@ class TestExpressions: "target, expected", ( (cg.RawExpression("foo && bar"), "foo && bar"), - (cg.AssignmentExpression(None, None, "foo", "bar", None), 'foo = "bar"'), - (cg.AssignmentExpression(ct.float_, "*", "foo", 1, None), "float *foo = 1"), - (cg.AssignmentExpression(ct.float_, "", "foo", 1, None), "float foo = 1"), + (cg.AssignmentExpression(None, None, "foo", "bar"), 'foo = "bar"'), + (cg.AssignmentExpression(ct.float_, "*", "foo", 1), "float *foo = 1"), + (cg.AssignmentExpression(ct.float_, "", "foo", 1), "float foo = 1"), (cg.VariableDeclarationExpression(ct.int32, "*", "foo"), "int32_t *foo"), (cg.VariableDeclarationExpression(ct.int32, "", "foo"), "int32_t foo"), (cg.ParameterExpression(ct.std_string, "foo"), "std::string foo"), @@ -274,7 +274,7 @@ class TestStatements: "// Help help\n// I'm being repressed", ), ( - cg.ProgmemAssignmentExpression(ct.uint16, "foo", "bar", None), + cg.ProgmemAssignmentExpression(ct.uint16, "foo", "bar"), 'static const uint16_t foo[] PROGMEM = "bar"', ), ), From 2622e59b0b6004f754e8ec7d0ce6129ef4b0b80c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 9 Feb 2022 14:57:00 +0100 Subject: [PATCH 096/238] Remove spurious Zeroconf instance from api client (#3143) --- esphome/components/api/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index b2920f239b..c777c3be9d 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -21,7 +21,6 @@ async def async_run_logs(config, address): 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( address, port, From 335512e2329c915563332a3b3bf83deca73d8a28 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Feb 2022 20:13:02 +0100 Subject: [PATCH 097/238] Bump aioesphomeapi from 10.8.1 to 10.8.2 (#3182) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a33461b79b..0c408f90b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.4 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 esphome-dashboard==20220209.0 -aioesphomeapi==10.8.1 +aioesphomeapi==10.8.2 zeroconf==0.37.0 # esp-idf requires this, but doesn't bundle it by default From ad6c5ff11d8bdcb8b180554b20c914838dce2799 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 10 Feb 2022 11:12:05 +1300 Subject: [PATCH 098/238] Clamp rotary_encoder restored value to min and max (#3184) --- esphome/components/rotary_encoder/rotary_encoder.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index c5227b41a7..c5e9cec596 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -138,6 +138,8 @@ void RotaryEncoderSensor::setup() { initial_value = 0; break; } + initial_value = clamp(initial_value, this->store_.min_value, this->store_.max_value); + this->store_.counter = initial_value; this->store_.last_read = initial_value; From 40e06c9819f17409615d4f4eec5cfe4dc9a3776d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 10 Feb 2022 21:55:11 +1300 Subject: [PATCH 099/238] Raise minimum python version to 3.8 (#3176) --- .github/workflows/ci.yml | 2 +- .gitignore | 1 + .pre-commit-config.yaml | 5 + esphome/__main__.py | 6 +- esphome/components/lcd_gpio/display.py | 2 +- esphome/components/remote_base/__init__.py | 36 ++-- esphome/components/tuya/climate/__init__.py | 32 +-- esphome/components/web_server/__init__.py | 4 +- esphome/components/wifi/__init__.py | 4 +- esphome/dashboard/dashboard.py | 2 +- requirements_test.txt | 1 + script/build_jsonschema.py | 2 +- script/ci-custom.py | 4 +- script/clang-format | 61 +++--- script/clang-tidy | 217 ++++++++++++-------- script/helpers.py | 11 +- script/lint-python | 33 ++- setup.py | 10 +- 18 files changed, 253 insertions(+), 180 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9473dc87dc..c2ad769156 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,7 +80,7 @@ jobs: uses: actions/setup-python@v2 id: python with: - python-version: '3.7' + python-version: '3.8' - name: Cache virtualenv uses: actions/cache@v2 diff --git a/.gitignore b/.gitignore index 57b8478bd7..110437c368 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,7 @@ venv/ ENV/ env.bak/ venv.bak/ +venv-*/ # mypy .mypy_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e38717fe5b..9549d5cedc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,3 +25,8 @@ repos: - --branch=dev - --branch=release - --branch=beta + - repo: https://github.com/asottile/pyupgrade + rev: v2.31.0 + hooks: + - id: pyupgrade + args: [--py38-plus] diff --git a/esphome/__main__.py b/esphome/__main__.py index a64f096d54..85cf4ede85 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -778,10 +778,10 @@ def run_esphome(argv): _LOGGER.warning("Please instead use:") _LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion)) - if sys.version_info < (3, 7, 0): + if sys.version_info < (3, 8, 0): _LOGGER.error( - "You're running ESPHome with Python <3.7. ESPHome is no longer compatible " - "with this Python version. Please reinstall ESPHome with Python 3.7+" + "You're running ESPHome with Python <3.8. ESPHome is no longer compatible " + "with this Python version. Please reinstall ESPHome with Python 3.8+" ) return 1 diff --git a/esphome/components/lcd_gpio/display.py b/esphome/components/lcd_gpio/display.py index 9fb635eafa..bfef402058 100644 --- a/esphome/components/lcd_gpio/display.py +++ b/esphome/components/lcd_gpio/display.py @@ -43,7 +43,7 @@ async def to_code(config): await lcd_base.setup_lcd_display(var, config) pins_ = [] for conf in config[CONF_DATA_PINS]: - pins_.append((await cg.gpio_pin_expression(conf))) + pins_.append(await cg.gpio_pin_expression(conf)) cg.add(var.set_data_pins(*pins_)) enable = await cg.gpio_pin_expression(config[CONF_ENABLE_PIN]) cg.add(var.set_enable_pin(enable)) diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 72a91a99dd..9bf03aaf28 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -847,7 +847,7 @@ async def rc_switch_raw_action(var, config, args): config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol ) cg.add(var.set_protocol(proto)) - cg.add(var.set_code((await cg.templatable(config[CONF_CODE], args, cg.std_string)))) + cg.add(var.set_code(await cg.templatable(config[CONF_CODE], args, cg.std_string))) @register_binary_sensor( @@ -868,13 +868,11 @@ async def rc_switch_type_a_action(var, config, args): config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol ) cg.add(var.set_protocol(proto)) + cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.std_string))) cg.add( - var.set_group((await cg.templatable(config[CONF_GROUP], args, cg.std_string))) + var.set_device(await cg.templatable(config[CONF_DEVICE], args, cg.std_string)) ) - cg.add( - var.set_device((await cg.templatable(config[CONF_DEVICE], args, cg.std_string))) - ) - cg.add(var.set_state((await cg.templatable(config[CONF_STATE], args, bool)))) + cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, bool))) @register_binary_sensor( @@ -897,13 +895,9 @@ async def rc_switch_type_b_action(var, config, args): config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol ) cg.add(var.set_protocol(proto)) - cg.add( - var.set_address((await cg.templatable(config[CONF_ADDRESS], args, cg.uint8))) - ) - cg.add( - var.set_channel((await cg.templatable(config[CONF_CHANNEL], args, cg.uint8))) - ) - cg.add(var.set_state((await cg.templatable(config[CONF_STATE], args, bool)))) + cg.add(var.set_address(await cg.templatable(config[CONF_ADDRESS], args, cg.uint8))) + cg.add(var.set_channel(await cg.templatable(config[CONF_CHANNEL], args, cg.uint8))) + cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, bool))) @register_binary_sensor( @@ -932,11 +926,11 @@ async def rc_switch_type_c_action(var, config, args): ) cg.add(var.set_protocol(proto)) cg.add( - var.set_family((await cg.templatable(config[CONF_FAMILY], args, cg.std_string))) + var.set_family(await cg.templatable(config[CONF_FAMILY], args, cg.std_string)) ) - cg.add(var.set_group((await cg.templatable(config[CONF_GROUP], args, cg.uint8)))) - cg.add(var.set_device((await cg.templatable(config[CONF_DEVICE], args, cg.uint8)))) - cg.add(var.set_state((await cg.templatable(config[CONF_STATE], args, bool)))) + cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.uint8))) + cg.add(var.set_device(await cg.templatable(config[CONF_DEVICE], args, cg.uint8))) + cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, bool))) @register_binary_sensor( @@ -959,11 +953,9 @@ async def rc_switch_type_d_action(var, config, args): config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol ) cg.add(var.set_protocol(proto)) - cg.add( - var.set_group((await cg.templatable(config[CONF_GROUP], args, cg.std_string))) - ) - cg.add(var.set_device((await cg.templatable(config[CONF_DEVICE], args, cg.uint8)))) - cg.add(var.set_state((await cg.templatable(config[CONF_STATE], args, bool)))) + cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.std_string))) + cg.add(var.set_device(await cg.templatable(config[CONF_DEVICE], args, cg.uint8))) + cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, bool))) @register_trigger("rc_switch", RCSwitchTrigger, RCSwitchData) diff --git a/esphome/components/tuya/climate/__init__.py b/esphome/components/tuya/climate/__init__.py index 275a87edd3..7d4b37ad22 100644 --- a/esphome/components/tuya/climate/__init__.py +++ b/esphome/components/tuya/climate/__init__.py @@ -36,31 +36,25 @@ def validate_temperature_multipliers(value): or CONF_TARGET_TEMPERATURE_MULTIPLIER in value ): raise cv.Invalid( - ( - f"Cannot have {CONF_TEMPERATURE_MULTIPLIER} at the same time as " - f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} and " - f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}" - ) + f"Cannot have {CONF_TEMPERATURE_MULTIPLIER} at the same time as " + f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} and " + f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}" ) if ( CONF_CURRENT_TEMPERATURE_MULTIPLIER in value and CONF_TARGET_TEMPERATURE_MULTIPLIER not in value ): raise cv.Invalid( - ( - f"{CONF_TARGET_TEMPERATURE_MULTIPLIER} required if using " - f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER}" - ) + f"{CONF_TARGET_TEMPERATURE_MULTIPLIER} required if using " + f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER}" ) if ( CONF_TARGET_TEMPERATURE_MULTIPLIER in value and CONF_CURRENT_TEMPERATURE_MULTIPLIER not in value ): raise cv.Invalid( - ( - f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} required if using " - f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}" - ) + f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} required if using " + f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}" ) keys = ( CONF_TEMPERATURE_MULTIPLIER, @@ -76,18 +70,14 @@ def validate_active_state_values(value): if CONF_ACTIVE_STATE_DATAPOINT not in value: if CONF_ACTIVE_STATE_COOLING_VALUE in value: raise cv.Invalid( - ( - f"{CONF_ACTIVE_STATE_DATAPOINT} required if using " - f"{CONF_ACTIVE_STATE_COOLING_VALUE}" - ) + f"{CONF_ACTIVE_STATE_DATAPOINT} required if using " + f"{CONF_ACTIVE_STATE_COOLING_VALUE}" ) else: if value[CONF_SUPPORTS_COOL] and CONF_ACTIVE_STATE_COOLING_VALUE not in value: raise cv.Invalid( - ( - f"{CONF_ACTIVE_STATE_COOLING_VALUE} required if using " - f"{CONF_ACTIVE_STATE_DATAPOINT} and device supports cooling" - ) + f"{CONF_ACTIVE_STATE_COOLING_VALUE} required if using " + f"{CONF_ACTIVE_STATE_DATAPOINT} and device supports cooling" ) return value diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 1e5f341717..eaa20ccbb4 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -77,11 +77,11 @@ async def to_code(config): if CONF_CSS_INCLUDE in config: cg.add_define("WEBSERVER_CSS_INCLUDE") path = CORE.relative_config_path(config[CONF_CSS_INCLUDE]) - with open(file=path, mode="r", encoding="utf-8") as myfile: + with open(file=path, encoding="utf-8") as myfile: cg.add(var.set_css_include(myfile.read())) if CONF_JS_INCLUDE in config: cg.add_define("WEBSERVER_JS_INCLUDE") path = CORE.relative_config_path(config[CONF_JS_INCLUDE]) - with open(file=path, mode="r", encoding="utf-8") as myfile: + with open(file=path, 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/wifi/__init__.py b/esphome/components/wifi/__init__.py index a24791b458..20f43cb450 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -225,11 +225,11 @@ def _validate(config): if CONF_MANUAL_IP in config: use_address = str(config[CONF_MANUAL_IP][CONF_STATIC_IP]) elif CONF_NETWORKS in config: - ips = set( + ips = { 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." diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 0ace97f10e..af68f2ae08 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -733,7 +733,7 @@ class EditRequestHandler(BaseHandler): content = "" if os.path.isfile(filename): # pylint: disable=no-value-for-parameter - with open(file=filename, mode="r", encoding="utf-8") as f: + with open(file=filename, encoding="utf-8") as f: content = f.read() self.write(content) diff --git a/requirements_test.txt b/requirements_test.txt index 1e5a5c2ebc..46713adbb9 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,7 @@ pylint==2.12.2 flake8==4.0.1 black==22.1.0 +pyupgrade==2.31.0 pre-commit # Unit tests diff --git a/script/build_jsonschema.py b/script/build_jsonschema.py index 7a3257411c..7673519916 100644 --- a/script/build_jsonschema.py +++ b/script/build_jsonschema.py @@ -170,7 +170,7 @@ def get_logger_tags(): ] for x in os.walk(CORE_COMPONENTS_PATH): for y in glob.glob(os.path.join(x[0], "*.cpp")): - with open(y, "r") as file: + with open(y) as file: data = file.read() match = pattern.search(data) if match: diff --git a/script/ci-custom.py b/script/ci-custom.py index d1efa22d85..2703e7d311 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -281,9 +281,7 @@ def highlight(s): ], ) def lint_no_defines(fname, match): - s = highlight( - "static const uint8_t {} = {};".format(match.group(1), match.group(2)) - ) + s = highlight(f"static const uint8_t {match.group(1)} = {match.group(2)};") return ( "#define macros for integer constants are not allowed, please use " "{} style instead (replace uint8_t with the appropriate " diff --git a/script/clang-format b/script/clang-format index 515df4c027..ae807262f1 100755 --- a/script/clang-format +++ b/script/clang-format @@ -17,14 +17,14 @@ def run_format(args, queue, lock, failed_files): """Takes filenames out of queue and runs clang-format on them.""" while True: path = queue.get() - invocation = ['clang-format-11'] + invocation = ["clang-format-11"] if args.inplace: - invocation.append('-i') + invocation.append("-i") else: - invocation.extend(['--dry-run', '-Werror']) + invocation.extend(["--dry-run", "-Werror"]) invocation.append(path) - proc = subprocess.run(invocation, capture_output=True, encoding='utf-8') + proc = subprocess.run(invocation, capture_output=True, encoding="utf-8") if proc.returncode != 0: with lock: print_error_for_file(path, proc.stderr) @@ -33,28 +33,36 @@ def run_format(args, queue, lock, failed_files): def progress_bar_show(value): - return value if value is not None else '' + return value if value is not None else "" def main(): colorama.init() parser = argparse.ArgumentParser() - parser.add_argument('-j', '--jobs', type=int, - default=multiprocessing.cpu_count(), - help='number of format instances to be run in parallel.') - parser.add_argument('files', nargs='*', default=[], - help='files to be processed (regex on path)') - parser.add_argument('-i', '--inplace', action='store_true', - help='reformat files in-place') - parser.add_argument('-c', '--changed', action='store_true', - help='only run on changed files') + parser.add_argument( + "-j", + "--jobs", + type=int, + default=multiprocessing.cpu_count(), + help="number of format instances to be run in parallel.", + ) + parser.add_argument( + "files", nargs="*", default=[], help="files to be processed (regex on path)" + ) + parser.add_argument( + "-i", "--inplace", action="store_true", help="reformat files in-place" + ) + parser.add_argument( + "-c", "--changed", action="store_true", help="only run on changed files" + ) args = parser.parse_args() try: - get_output('clang-format-11', '-version') + get_output("clang-format-11", "-version") except: - print(""" + print( + """ Oops. It looks like clang-format is not installed. Please check you can run "clang-format-11 -version" in your terminal and install @@ -62,16 +70,17 @@ def main(): Note you can also upload your code as a pull request on GitHub and see the CI check output to apply clang-format. - """) + """ + ) return 1 files = [] - for path in git_ls_files(['*.cpp', '*.h', '*.tcc']): + for path in git_ls_files(["*.cpp", "*.h", "*.tcc"]): files.append(os.path.relpath(path, os.getcwd())) if args.files: # Match against files specified on command-line - file_name_re = re.compile('|'.join(args.files)) + file_name_re = re.compile("|".join(args.files)) files = [p for p in files if file_name_re.search(p)] if args.changed: @@ -84,14 +93,16 @@ def main(): task_queue = queue.Queue(args.jobs) lock = threading.Lock() for _ in range(args.jobs): - t = threading.Thread(target=run_format, - args=(args, task_queue, lock, failed_files)) + t = threading.Thread( + target=run_format, args=(args, task_queue, lock, failed_files) + ) t.daemon = True t.start() # Fill the queue with files. - with click.progressbar(files, width=30, file=sys.stderr, - item_show_func=progress_bar_show) as bar: + with click.progressbar( + files, width=30, file=sys.stderr, item_show_func=progress_bar_show + ) as bar: for name in bar: task_queue.put(name) @@ -100,11 +111,11 @@ def main(): except KeyboardInterrupt: print() - print('Ctrl-C detected, goodbye.') + print("Ctrl-C detected, goodbye.") os.kill(0, 9) sys.exit(len(failed_files)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/script/clang-tidy b/script/clang-tidy index 8a7d229887..327b593008 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -1,7 +1,17 @@ #!/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, root_path, basepath +from helpers import ( + print_error_for_file, + get_output, + filter_grep, + build_all_include, + temp_header_file, + git_ls_files, + filter_changed, + load_idedata, + root_path, + basepath, +) import argparse import click import colorama @@ -20,67 +30,81 @@ def clang_options(idedata): cmd = [] # extract target architecture from triplet in g++ filename - triplet = os.path.basename(idedata['cxx_path'])[:-4] + 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__') + cmd.append("-m32") + cmd.append("-D__XTENSA__") else: - cmd.append(f'--target={triplet}') + cmd.append(f"--target={triplet}") # set flags - cmd.extend([ - # disable built-in include directories from the host - '-nostdinc', - '-nostdinc++', - # replace pgmspace.h, as it uses GNU extensions clang doesn't support - # https://github.com/earlephilhower/newlib-xtensa/pull/18 - '-D_PGMSPACE_H_', - '-Dpgm_read_byte(s)=(*(const uint8_t *)(s))', - '-Dpgm_read_byte_near(s)=(*(const uint8_t *)(s))', - '-Dpgm_read_word(s)=(*(const uint16_t *)(s))', - '-Dpgm_read_dword(s)=(*(const uint32_t *)(s))', - '-DPROGMEM=', - '-DPGM_P=const char *', - '-DPSTR(s)=(s)', - # this next one is also needed with upstream pgmspace.h - # suppress warning about identifier naming in expansion of this macro - '-DPSTRN(s, n)=(s)', - # suppress warning about attribute cannot be applied to type - # https://github.com/esp8266/Arduino/pull/8258 - '-Ddeprecated(x)=', - # 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', - ]) + cmd.extend( + [ + # disable built-in include directories from the host + "-nostdinc", + "-nostdinc++", + # replace pgmspace.h, as it uses GNU extensions clang doesn't support + # https://github.com/earlephilhower/newlib-xtensa/pull/18 + "-D_PGMSPACE_H_", + "-Dpgm_read_byte(s)=(*(const uint8_t *)(s))", + "-Dpgm_read_byte_near(s)=(*(const uint8_t *)(s))", + "-Dpgm_read_word(s)=(*(const uint16_t *)(s))", + "-Dpgm_read_dword(s)=(*(const uint32_t *)(s))", + "-DPROGMEM=", + "-DPGM_P=const char *", + "-DPSTR(s)=(s)", + # this next one is also needed with upstream pgmspace.h + # suppress warning about identifier naming in expansion of this macro + "-DPSTRN(s, n)=(s)", + # suppress warning about attribute cannot be applied to type + # https://github.com/esp8266/Arduino/pull/8258 + "-Ddeprecated(x)=", + # 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(' ') - if flag not in ('-free', '-fipa-pta', '-fstrict-volatile-bitfields', - '-mlongcalls', '-mtext-section-literals', - '-mfix-esp32-psram-cache-issue', '-mfix-esp32-psram-cache-strategy=memw')) + cmd.extend( + flag + for flag in idedata["cxx_flags"].split(" ") + if flag + not in ( + "-free", + "-fipa-pta", + "-fstrict-volatile-bitfields", + "-mlongcalls", + "-mtext-section-literals", + "-mfix-esp32-psram-cache-issue", + "-mfix-esp32-psram-cache-strategy=memw", + ) + ) # defines - cmd.extend(f'-D{define}' for define in idedata['defines']) + cmd.extend(f"-D{define}" for define in idedata["defines"]) # 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']: + for directory in idedata["includes"]["toolchain"]: if directory.startswith(toolchain_dir): - cmd.extend(['-isystem', directory]) + cmd.extend(["-isystem", directory]) # add library include directories using -isystem to suppress their errors - for directory in sorted(set(idedata['includes']['build'])): + for directory in sorted(set(idedata["includes"]["build"])): # 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]) + 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]) + cmd.extend(["-I", root_path]) return cmd @@ -88,28 +112,28 @@ def clang_options(idedata): def run_tidy(args, options, tmpdir, queue, lock, failed_files): while True: path = queue.get() - invocation = ['clang-tidy-11'] + invocation = ["clang-tidy-11"] if tmpdir is not None: - invocation.append('--export-fixes') + invocation.append("--export-fixes") # Get a temporary file. We immediately close the handle so clang-tidy can # overwrite it. - (handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir) + (handle, name) = tempfile.mkstemp(suffix=".yaml", dir=tmpdir) os.close(handle) invocation.append(name) if args.quiet: - invocation.append('--quiet') + invocation.append("--quiet") if sys.stdout.isatty(): - invocation.append('--use-color') + invocation.append("--use-color") invocation.append(f"--header-filter={os.path.abspath(basepath)}/.*") invocation.append(os.path.abspath(path)) - invocation.append('--') + invocation.append("--") invocation.extend(options) - proc = subprocess.run(invocation, capture_output=True, encoding='utf-8') + proc = subprocess.run(invocation, capture_output=True, encoding="utf-8") if proc.returncode != 0: with lock: print_error_for_file(path, proc.stdout) @@ -119,43 +143,60 @@ def run_tidy(args, options, tmpdir, queue, lock, failed_files): def progress_bar_show(value): if value is None: - return '' + return "" def split_list(a, n): k, m = divmod(len(a), n) - return [a[i * k + min(i, m):(i + 1) * k + min(i + 1, m)] for i in range(n)] + return [a[i * k + min(i, m) : (i + 1) * k + min(i + 1, m)] for i in range(n)] def main(): colorama.init() parser = argparse.ArgumentParser() - 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-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') - parser.add_argument('-q', '--quiet', action='store_false', - help='run clang-tidy in quiet mode') - parser.add_argument('-c', '--changed', action='store_true', - help='only run on changed files') - parser.add_argument('-g', '--grep', help='only run on files containing value') - parser.add_argument('--split-num', type=int, help='split the files into X jobs.', - default=None) - parser.add_argument('--split-at', type=int, help='which split is this? starts at 1', - default=None) - parser.add_argument('--all-headers', action='store_true', - help='create a dummy file that checks all headers') + 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-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") + parser.add_argument( + "-q", "--quiet", action="store_false", help="run clang-tidy in quiet mode" + ) + parser.add_argument( + "-c", "--changed", action="store_true", help="only run on changed files" + ) + parser.add_argument("-g", "--grep", help="only run on files containing value") + parser.add_argument( + "--split-num", type=int, help="split the files into X jobs.", default=None + ) + parser.add_argument( + "--split-at", type=int, help="which split is this? starts at 1", default=None + ) + parser.add_argument( + "--all-headers", + action="store_true", + help="create a dummy file that checks all headers", + ) args = parser.parse_args() try: - get_output('clang-tidy-11', '-version') + get_output("clang-tidy-11", "-version") except: - print(""" + print( + """ Oops. It looks like clang-tidy-11 is not installed. Please check you can run "clang-tidy-11 -version" in your terminal and install @@ -163,19 +204,20 @@ def main(): Note you can also upload your code as a pull request on GitHub and see the CI check output to apply clang-tidy. - """) + """ + ) return 1 idedata = load_idedata(args.environment) options = clang_options(idedata) files = [] - for path in git_ls_files(['*.cpp']): + for path in git_ls_files(["*.cpp"]): files.append(os.path.relpath(path, os.getcwd())) if args.files: # Match against files specified on command-line - file_name_re = re.compile('|'.join(args.files)) + file_name_re = re.compile("|".join(args.files)) files = [p for p in files if file_name_re.search(p)] if args.changed: @@ -202,14 +244,17 @@ def main(): task_queue = queue.Queue(args.jobs) lock = threading.Lock() for _ in range(args.jobs): - t = threading.Thread(target=run_tidy, - args=(args, options, tmpdir, task_queue, lock, failed_files)) + t = threading.Thread( + target=run_tidy, + args=(args, options, tmpdir, task_queue, lock, failed_files), + ) t.daemon = True t.start() # Fill the queue with files. - with click.progressbar(files, width=30, file=sys.stderr, - item_show_func=progress_bar_show) as bar: + with click.progressbar( + files, width=30, file=sys.stderr, item_show_func=progress_bar_show + ) as bar: for name in bar: task_queue.put(name) @@ -218,21 +263,21 @@ def main(): except KeyboardInterrupt: print() - print('Ctrl-C detected, goodbye.') + print("Ctrl-C detected, goodbye.") if tmpdir: shutil.rmtree(tmpdir) os.kill(0, 9) if args.fix and failed_files: - print('Applying fixes ...') + print("Applying fixes ...") try: - subprocess.call(['clang-apply-replacements-11', tmpdir]) + subprocess.call(["clang-apply-replacements-11", tmpdir]) except: - print('Error applying fixes.\n', file=sys.stderr) + print("Error applying fixes.\n", file=sys.stderr) raise sys.exit(len(failed_files)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/script/helpers.py b/script/helpers.py index abf970b8a2..c042362aeb 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -12,13 +12,16 @@ temp_header_file = os.path.join(temp_folder, "all-include.cpp") def styled(color, msg, reset=True): - prefix = ''.join(color) if isinstance(color, tuple) else color - suffix = colorama.Style.RESET_ALL if reset else '' + prefix = "".join(color) if isinstance(color, tuple) else color + suffix = colorama.Style.RESET_ALL if reset else "" return prefix + msg + suffix def print_error_for_file(file, body): - print(styled(colorama.Fore.GREEN, "### File ") + styled((colorama.Fore.GREEN, colorama.Style.BRIGHT), file)) + print( + styled(colorama.Fore.GREEN, "### File ") + + styled((colorama.Fore.GREEN, colorama.Style.BRIGHT), file) + ) print() if body is not None: print(body) @@ -100,7 +103,7 @@ def filter_changed(files): def filter_grep(files, value): matched = [] for file in files: - with open(file, "r") as handle: + with open(file) as handle: contents = handle.read() if value in contents: matched.append(file) diff --git a/script/lint-python b/script/lint-python index 8ee038a661..90b5dcd59f 100755 --- a/script/lint-python +++ b/script/lint-python @@ -1,7 +1,13 @@ #!/usr/bin/env python3 -from __future__ import print_function -from helpers import styled, print_error_for_file, 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 @@ -34,6 +40,12 @@ def main(): parser.add_argument( "-c", "--changed", action="store_true", help="Only run on changed files" ) + parser.add_argument( + "-a", + "--apply", + action="store_true", + help="Apply changes to files where possible", + ) args = parser.parse_args() files = [] @@ -56,7 +68,7 @@ def main(): errors = 0 - cmd = ["black", "--verbose", "--check"] + files + cmd = ["black", "--verbose"] + ([] if args.apply else ["--check"]) + files print("Running black...") print() log = get_err(*cmd) @@ -97,6 +109,21 @@ def main(): print_error(file_, linno, msg) errors += 1 + PYUPGRADE_TARGET = "--py38-plus" + cmd = ["pyupgrade", PYUPGRADE_TARGET] + files + print() + print("Running pyupgrade...") + print() + log = get_err(*cmd) + for line in log.splitlines(): + REWRITING = "Rewriting" + if line.startswith(REWRITING): + file_ = line[len(REWRITING) + 1 :] + print_error( + file_, None, f"Please run pyupgrade {PYUPGRADE_TARGET} on this file" + ) + errors += 1 + sys.exit(errors) diff --git a/setup.py b/setup.py index 967eadd70f..941c8089ec 100755 --- a/setup.py +++ b/setup.py @@ -17,11 +17,11 @@ PROJECT_EMAIL = "esphome@nabucasa.com" PROJECT_GITHUB_USERNAME = "esphome" PROJECT_GITHUB_REPOSITORY = "esphome" -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) +PYPI_URL = f"https://pypi.python.org/pypi/{PROJECT_PACKAGE_NAME}" +GITHUB_PATH = f"{PROJECT_GITHUB_USERNAME}/{PROJECT_GITHUB_REPOSITORY}" +GITHUB_URL = f"https://github.com/{GITHUB_PATH}" -DOWNLOAD_URL = "{}/archive/{}.zip".format(GITHUB_URL, const.__version__) +DOWNLOAD_URL = f"{GITHUB_URL}/archive/{const.__version__}.zip" here = os.path.abspath(os.path.dirname(__file__)) @@ -74,7 +74,7 @@ setup( zip_safe=False, platforms="any", test_suite="tests", - python_requires=">=3.7,<4.0", + python_requires=">=3.8,<4.0", install_requires=REQUIRES, keywords=["home", "automation"], entry_points={"console_scripts": ["esphome = esphome.__main__:main"]}, From 72e716cdf154f062e646feae514bd05e09134773 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 11 Feb 2022 09:06:00 +0100 Subject: [PATCH 100/238] Make generating combined binary output verbose (#3127) --- esphome/components/esp32/post_build.py.script | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/esphome/components/esp32/post_build.py.script b/esphome/components/esp32/post_build.py.script index 7feaf9e8e5..2bb1a6c3d6 100644 --- a/esphome/components/esp32/post_build.py.script +++ b/esphome/components/esp32/post_build.py.script @@ -1,13 +1,16 @@ # Source https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1005864664 import esptool +from SCons.Script import ARGUMENTS # pylint: disable=E0602 Import("env") # noqa def esp32_create_combined_bin(source, target, env): - print("Generating combined binary for serial flashing") + verbose = bool(int(ARGUMENTS.get("PIOVERBOSE", "0"))) + if verbose: + print("Generating combined binary for serial flashing") app_offset = 0x10000 new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin") @@ -24,18 +27,21 @@ def esp32_create_combined_bin(source, target, env): "--flash_size", flash_size, ] - print(" Offset | File") + if verbose: + print(" Offset | File") for section in sections: sect_adr, sect_file = section.split(" ", 1) - print(f" - {sect_adr} | {sect_file}") + if verbose: + 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() + if verbose: + print(f" - {hex(app_offset)} | {firmware_name}") + print() + print(f"Using esptool.py arguments: {' '.join(cmd)}") + print() esptool.main(cmd) From 3a678844516b8b8ffbf98514c1b794bb2f88b1b1 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 11 Feb 2022 09:06:06 +0100 Subject: [PATCH 101/238] Improve dallas timing (#3181) * Improve dallas timing * Format --- .../components/dallas/dallas_component.cpp | 66 ++++--- esphome/components/dallas/esp_one_wire.cpp | 177 ++++++++++-------- esphome/core/helpers.cpp | 2 + 3 files changed, 149 insertions(+), 96 deletions(-) diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 1eed2ebf78..56526e98bb 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -32,6 +32,11 @@ void DallasComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up DallasComponent..."); pin_->setup(); + + // clear bus with 480µs high, otherwise initial reset in search_vec() fails + pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + delayMicroseconds(480); + one_wire_ = new ESPOneWire(pin_); // NOLINT(cppcoreguidelines-owning-memory) std::vector raw_sensors; @@ -99,20 +104,22 @@ void DallasComponent::update() { this->status_clear_warning(); bool result; - if (!this->one_wire_->reset()) { - result = false; - } else { - result = true; - this->one_wire_->skip(); - this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION); + { + InterruptLock lock; + result = this->one_wire_->reset(); } - if (!result) { ESP_LOGE(TAG, "Requesting conversion failed"); this->status_set_warning(); return; } + { + InterruptLock lock; + this->one_wire_->skip(); + this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION); + } + for (auto *sensor : this->sensors_) { this->set_timeout(sensor->get_address_name(), sensor->millis_to_wait_for_conversion(), [this, sensor] { bool res = sensor->read_scratch_pad(); @@ -152,16 +159,26 @@ const std::string &DallasTemperatureSensor::get_address_name() { } bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() { auto *wire = this->parent_->one_wire_; - if (!wire->reset()) { - return false; + + { + InterruptLock lock; + + if (!wire->reset()) { + return false; + } } - wire->select(this->address_); - wire->write8(DALLAS_COMMAND_READ_SCRATCH_PAD); + { + InterruptLock lock; - for (unsigned char &i : this->scratch_pad_) { - i = wire->read8(); + wire->select(this->address_); + wire->write8(DALLAS_COMMAND_READ_SCRATCH_PAD); + + for (unsigned char &i : this->scratch_pad_) { + i = wire->read8(); + } } + return true; } bool DallasTemperatureSensor::setup_sensor() { @@ -200,17 +217,20 @@ bool DallasTemperatureSensor::setup_sensor() { } 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(); + { + 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(); - // 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/esp_one_wire.cpp b/esphome/components/dallas/esp_one_wire.cpp index 6dc085a0bf..885846e5e5 100644 --- a/esphome/components/dallas/esp_one_wire.cpp +++ b/esphome/components/dallas/esp_one_wire.cpp @@ -15,8 +15,6 @@ ESPOneWire::ESPOneWire(InternalGPIOPin *pin) { pin_ = pin->to_isr(); } bool HOT IRAM_ATTR ESPOneWire::reset() { // See reset here: // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html - InterruptLock lock; - // Wait for communication to clear (delay G) pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); uint8_t retries = 125; @@ -43,16 +41,18 @@ bool HOT IRAM_ATTR ESPOneWire::reset() { } void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) { - // See write 1/0 bit here: - // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html - InterruptLock lock; - // 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; + // from datasheet: + // write 0 low time: t_low0: min=60µs, max=120µs + // write 1 low time: t_low1: min=1µs, max=15µs + // time slot: t_slot: min=60µs, max=120µs + // recovery time: t_rec: min=1µs + // ds18b20 appears to read the bus after roughly 14µs + uint32_t delay0 = bit ? 6 : 60; + uint32_t delay1 = bit ? 54 : 5; // delay A/C delayMicroseconds(delay0); @@ -63,72 +63,100 @@ void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) { } bool HOT IRAM_ATTR ESPOneWire::read_bit() { - // See read bit here: - // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html - InterruptLock lock; - - // drive bus low, delay A + // drive bus low pin_.pin_mode(gpio::FLAG_OUTPUT); pin_.digital_write(false); + + // note: for reading we'll need very accurate timing, as the + // timing for the digital_read() is tight; according to the datasheet, + // we should read at the end of 16µs starting from the bus low + // typically, the ds18b20 pulls the line high after 11µs for a logical 1 + // and 29µs for a logical 0 + + uint32_t start = micros(); + // datasheet says >1µs delayMicroseconds(3); // release bus, delay E pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); - delayMicroseconds(10); + + // Unfortunately some frameworks have different characteristics than others + // esp32 arduino appears to pull the bus low only after the digital_write(false), + // whereas on esp-idf it already happens during the pin_mode(OUTPUT) + // manually correct for this with these constants. + +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + uint32_t timing_constant = 14; +#elif defined(USE_ESP32_FRAMEWORK_ESP_IDF) + uint32_t timing_constant = 12; +#else + uint32_t timing_constant = 14; +#endif + + // measure from start value directly, to get best accurate timing no matter + // how long pin_mode/delayMicroseconds took + while (micros() - start < timing_constant) + ; // sample bus to read bit from peer bool r = pin_.digital_read(); - // delay F - delayMicroseconds(53); + // read slot is at least 60µs; get as close to 60µs to spend less time with interrupts locked + uint32_t now = micros(); + if (now - start < 60) + delayMicroseconds(60 - (now - start)); + return r; } -void ESPOneWire::write8(uint8_t val) { +void IRAM_ATTR ESPOneWire::write8(uint8_t val) { for (uint8_t i = 0; i < 8; i++) { this->write_bit(bool((1u << i) & val)); } } -void ESPOneWire::write64(uint64_t val) { +void IRAM_ATTR ESPOneWire::write64(uint64_t val) { for (uint8_t i = 0; i < 64; i++) { this->write_bit(bool((1ULL << i) & val)); } } -uint8_t ESPOneWire::read8() { +uint8_t IRAM_ATTR ESPOneWire::read8() { uint8_t ret = 0; for (uint8_t i = 0; i < 8; i++) { ret |= (uint8_t(this->read_bit()) << i); } return ret; } -uint64_t ESPOneWire::read64() { +uint64_t IRAM_ATTR ESPOneWire::read64() { uint64_t ret = 0; for (uint8_t i = 0; i < 8; i++) { ret |= (uint64_t(this->read_bit()) << i); } return ret; } -void ESPOneWire::select(uint64_t address) { +void IRAM_ATTR ESPOneWire::select(uint64_t address) { this->write8(ONE_WIRE_ROM_SELECT); this->write64(address); } -void ESPOneWire::reset_search() { +void IRAM_ATTR ESPOneWire::reset_search() { this->last_discrepancy_ = 0; this->last_device_flag_ = false; this->last_family_discrepancy_ = 0; this->rom_number_ = 0; } -uint64_t ESPOneWire::search() { +uint64_t IRAM_ATTR ESPOneWire::search() { if (this->last_device_flag_) { return 0u; } - if (!this->reset()) { - // Reset failed or no devices present - this->reset_search(); - return 0u; + { + InterruptLock lock; + if (!this->reset()) { + // Reset failed or no devices present + this->reset_search(); + return 0u; + } } uint8_t id_bit_number = 1; @@ -137,58 +165,61 @@ uint64_t ESPOneWire::search() { bool search_result = false; uint8_t rom_byte_mask = 1; - // Initiate search - this->write8(ONE_WIRE_ROM_SEARCH); - do { - // read bit - bool id_bit = this->read_bit(); - // read its complement - bool cmp_id_bit = this->read_bit(); + { + InterruptLock lock; + // Initiate search + this->write8(ONE_WIRE_ROM_SEARCH); + do { + // read bit + bool id_bit = this->read_bit(); + // read its complement + bool cmp_id_bit = this->read_bit(); - if (id_bit && cmp_id_bit) { - // No devices participating in search - break; - } - - bool branch; - - if (id_bit != cmp_id_bit) { - // only chose one branch, the other one doesn't have any devices. - branch = id_bit; - } else { - // there are devices with both 0s and 1s at this bit - if (id_bit_number < this->last_discrepancy_) { - branch = (this->rom_number8_()[rom_byte_number] & rom_byte_mask) > 0; - } else { - branch = id_bit_number == this->last_discrepancy_; + if (id_bit && cmp_id_bit) { + // No devices participating in search + break; } - if (!branch) { - last_zero = id_bit_number; - if (last_zero < 9) { - this->last_discrepancy_ = last_zero; + bool branch; + + if (id_bit != cmp_id_bit) { + // only chose one branch, the other one doesn't have any devices. + branch = id_bit; + } else { + // there are devices with both 0s and 1s at this bit + if (id_bit_number < this->last_discrepancy_) { + branch = (this->rom_number8_()[rom_byte_number] & rom_byte_mask) > 0; + } else { + branch = id_bit_number == this->last_discrepancy_; + } + + if (!branch) { + last_zero = id_bit_number; + if (last_zero < 9) { + this->last_discrepancy_ = last_zero; + } } } - } - if (branch) { - // set bit - this->rom_number8_()[rom_byte_number] |= rom_byte_mask; - } else { - // clear bit - this->rom_number8_()[rom_byte_number] &= ~rom_byte_mask; - } + if (branch) { + // set bit + this->rom_number8_()[rom_byte_number] |= rom_byte_mask; + } else { + // clear bit + this->rom_number8_()[rom_byte_number] &= ~rom_byte_mask; + } - // choose/announce branch - this->write_bit(branch); - id_bit_number++; - rom_byte_mask <<= 1; - if (rom_byte_mask == 0u) { - // go to next byte - rom_byte_number++; - rom_byte_mask = 1; - } - } while (rom_byte_number < 8); // loop through all bytes + // choose/announce branch + this->write_bit(branch); + id_bit_number++; + rom_byte_mask <<= 1; + if (rom_byte_mask == 0u) { + // go to next byte + rom_byte_number++; + rom_byte_mask = 1; + } + } while (rom_byte_number < 8); // loop through all bytes + } if (id_bit_number >= 65) { this->last_discrepancy_ = last_zero; @@ -217,7 +248,7 @@ std::vector ESPOneWire::search_vec() { return res; } -void ESPOneWire::skip() { +void IRAM_ATTR ESPOneWire::skip() { this->write8(0xCC); // skip ROM } diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 6d399c4064..a346cd7e0b 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -328,6 +328,8 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } #elif defined(USE_ESP32) +// only affects the executing core +// so should not be used as a mutex lock, only to get accurate timing IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } #endif From cc0c1c08b94b47a5872a78185ab7f17ee41d9719 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Feb 2022 13:57:46 +0100 Subject: [PATCH 102/238] Bump platformio from 5.2.4 to 5.2.5 (#3188) * Bump platformio from 5.2.4 to 5.2.5 Bumps [platformio](https://github.com/platformio/platformio) from 5.2.4 to 5.2.5. - [Release notes](https://github.com/platformio/platformio/releases) - [Changelog](https://github.com/platformio/platformio-core/blob/develop/HISTORY.rst) - [Commits](https://github.com/platformio/platformio/commits) --- updated-dependencies: - dependency-name: platformio dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update requirements.txt Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Otto Winter --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0c408f90b5..acbf1d9984 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.4 # When updating platformio, also update Dockerfile +platformio==5.2.5 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 esphome-dashboard==20220209.0 aioesphomeapi==10.8.2 -zeroconf==0.37.0 +zeroconf==0.38.3 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From b1cefb7e3e9538c3e7f5fdb13cba55a4cb5664fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Feb 2022 13:58:10 +0100 Subject: [PATCH 103/238] Bump pytest-asyncio from 0.18.0 to 0.18.1 (#3187) Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.18.0 to 0.18.1. - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.18.0...v0.18.1) --- updated-dependencies: - dependency-name: pytest-asyncio dependency-type: direct:production update-type: version-update:semver-patch ... 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 46713adbb9..71b2e434cb 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -8,6 +8,6 @@ pre-commit pytest==7.0.0 pytest-cov==3.0.0 pytest-mock==3.7.0 -pytest-asyncio==0.18.0 +pytest-asyncio==0.18.1 asyncmock==0.4.2 hypothesis==5.49.0 From 0ec84be5dacb1a9d0c3cc5c5da5de9a3e9b14df9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Feb 2022 22:23:06 +0100 Subject: [PATCH 104/238] Bump pytest from 7.0.0 to 7.0.1 (#3189) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 71b2e434cb..afc4fd9d2a 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ pyupgrade==2.31.0 pre-commit # Unit tests -pytest==7.0.0 +pytest==7.0.1 pytest-cov==3.0.0 pytest-mock==3.7.0 pytest-asyncio==0.18.1 From a13a1225b7201c71d0db5b4dc921e00c5dcc07e5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 15 Feb 2022 11:57:47 +1300 Subject: [PATCH 105/238] Allow framework version validator to be maximum version (#3197) --- esphome/components/fastled_clockless/light.py | 7 +++- esphome/components/fastled_spi/light.py | 7 +++- esphome/config_validation.py | 35 ++++++++++++++----- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/esphome/components/fastled_clockless/light.py b/esphome/components/fastled_clockless/light.py index acf9488ae3..dc456d4959 100644 --- a/esphome/components/fastled_clockless/light.py +++ b/esphome/components/fastled_clockless/light.py @@ -49,7 +49,12 @@ CONFIG_SCHEMA = cv.All( } ), _validate, - cv.only_with_arduino, + cv.require_framework_version( + esp8266_arduino=cv.Version(2, 7, 4), + esp32_arduino=cv.Version(99, 0, 0), + max_version=True, + extra_message="Please see note on documentation for FastLED", + ), ) diff --git a/esphome/components/fastled_spi/light.py b/esphome/components/fastled_spi/light.py index a729fc015a..b3ce1722ee 100644 --- a/esphome/components/fastled_spi/light.py +++ b/esphome/components/fastled_spi/light.py @@ -33,7 +33,12 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DATA_RATE): cv.frequency, } ), - cv.only_with_arduino, + cv.require_framework_version( + esp8266_arduino=cv.Version(2, 7, 4), + esp32_arduino=cv.Version(99, 0, 0), + max_version=True, + extra_message="Please see note on documentation for FastLED", + ), ) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 2e7a4e5677..8e1c63a54e 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1713,30 +1713,49 @@ def require_framework_version( esp_idf=None, esp32_arduino=None, esp8266_arduino=None, + max_version=False, + extra_message=None, ): def validator(value): core_data = CORE.data[KEY_CORE] framework = core_data[KEY_TARGET_FRAMEWORK] if framework == "esp-idf": if esp_idf is None: - raise Invalid("This feature is incompatible with esp-idf") + msg = "This feature is incompatible with esp-idf" + if extra_message: + msg += f". {extra_message}" + raise Invalid(msg) required = esp_idf elif CORE.is_esp32 and framework == "arduino": if esp32_arduino is None: - raise Invalid( - "This feature is incompatible with ESP32 using arduino framework" - ) + msg = "This feature is incompatible with ESP32 using arduino framework" + if extra_message: + msg += f". {extra_message}" + raise Invalid(msg) required = esp32_arduino elif CORE.is_esp8266 and framework == "arduino": if esp8266_arduino is None: - raise Invalid("This feature is incompatible with ESP8266") + msg = "This feature is incompatible with ESP8266" + if extra_message: + msg += f". {extra_message}" + raise Invalid(msg) required = esp8266_arduino else: raise NotImplementedError + + if max_version: + if core_data[KEY_FRAMEWORK_VERSION] > required: + msg = f"This feature requires framework version {required} or lower" + if extra_message: + msg += f". {extra_message}" + raise Invalid(msg) + return value + if core_data[KEY_FRAMEWORK_VERSION] < required: - raise Invalid( - f"This feature requires at least framework version {required}" - ) + msg = f"This feature requires at least framework version {required}" + if extra_message: + msg += f". {extra_message}" + raise Invalid(msg) return value return validator From 113232ebb6d7c0109f483d5cb0992739a8c416fd Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Tue, 15 Feb 2022 01:01:50 -0300 Subject: [PATCH 106/238] add sim800l diagnostics (#3136) --- esphome/components/sim800l/__init__.py | 6 +++- esphome/components/sim800l/binary_sensor.py | 36 +++++++++++++++++++++ esphome/components/sim800l/sensor.py | 33 +++++++++++++++++++ esphome/components/sim800l/sim800l.cpp | 34 +++++++++++++++---- esphome/components/sim800l/sim800l.h | 24 +++++++++++++- 5 files changed, 125 insertions(+), 8 deletions(-) create mode 100644 esphome/components/sim800l/binary_sensor.py create mode 100644 esphome/components/sim800l/sensor.py diff --git a/esphome/components/sim800l/__init__.py b/esphome/components/sim800l/__init__.py index 4143627084..564b685b37 100644 --- a/esphome/components/sim800l/__init__.py +++ b/esphome/components/sim800l/__init__.py @@ -1,7 +1,10 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.const import CONF_ID, CONF_TRIGGER_ID +from esphome.const import ( + CONF_ID, + CONF_TRIGGER_ID, +) from esphome.components import uart DEPENDENCIES = ["uart"] @@ -20,6 +23,7 @@ Sim800LReceivedMessageTrigger = sim800l_ns.class_( Sim800LSendSmsAction = sim800l_ns.class_("Sim800LSendSmsAction", automation.Action) Sim800LDialAction = sim800l_ns.class_("Sim800LDialAction", automation.Action) +CONF_SIM800L_ID = "sim800l_id" CONF_ON_SMS_RECEIVED = "on_sms_received" CONF_RECIPIENT = "recipient" CONF_MESSAGE = "message" diff --git a/esphome/components/sim800l/binary_sensor.py b/esphome/components/sim800l/binary_sensor.py new file mode 100644 index 0000000000..7cee04374b --- /dev/null +++ b/esphome/components/sim800l/binary_sensor.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + DEVICE_CLASS_CONNECTIVITY, + ENTITY_CATEGORY_DIAGNOSTIC, +) +from . import CONF_SIM800L_ID, Sim800LComponent + +DEPENDENCIES = ["sim800l"] + +CONF_REGISTERED = "registered" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_SIM800L_ID): cv.use_id(Sim800LComponent), + cv.Optional(CONF_REGISTERED): 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, + } + ), +} + + +async def to_code(config): + sim800l_component = await cg.get_variable(config[CONF_SIM800L_ID]) + + if CONF_REGISTERED in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_REGISTERED]) + cg.add(sim800l_component.set_registered_binary_sensor(sens)) diff --git a/esphome/components/sim800l/sensor.py b/esphome/components/sim800l/sensor.py new file mode 100644 index 0000000000..156bd6a040 --- /dev/null +++ b/esphome/components/sim800l/sensor.py @@ -0,0 +1,33 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + DEVICE_CLASS_SIGNAL_STRENGTH, + ENTITY_CATEGORY_DIAGNOSTIC, + STATE_CLASS_MEASUREMENT, + UNIT_DECIBEL_MILLIWATT, +) +from . import CONF_SIM800L_ID, Sim800LComponent + +DEPENDENCIES = ["sim800l"] + +CONF_RSSI = "rssi" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_SIM800L_ID): cv.use_id(Sim800LComponent), + cv.Optional(CONF_RSSI): 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, + ), +} + + +async def to_code(config): + sim800l_component = await cg.get_variable(config[CONF_SIM800L_ID]) + + if CONF_RSSI in config: + sens = await sensor.new_sensor(config[CONF_RSSI]) + cg.add(sim800l_component.set_rssi_sensor(sens)) diff --git a/esphome/components/sim800l/sim800l.cpp b/esphome/components/sim800l/sim800l.cpp index eb6d62ca33..709e241491 100644 --- a/esphome/components/sim800l/sim800l.cpp +++ b/esphome/components/sim800l/sim800l.cpp @@ -117,7 +117,7 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->state_ = STATE_CREG; } } - this->registered_ = registered; + set_registered_(registered); break; } case STATE_CSQ: @@ -128,8 +128,17 @@ 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_ = parse_number(message.substr(6, comma - 6)).value_or(0); - ESP_LOGD(TAG, "RSSI: %d", this->rssi_); + int rssi = parse_number(message.substr(6, comma - 6)).value_or(0); + +#ifdef USE_SENSOR + if (this->rssi_sensor_ != nullptr) { + this->rssi_sensor_->publish_state(rssi); + } else { + ESP_LOGD(TAG, "RSSI: %d", rssi); + } +#else + ESP_LOGD(TAG, "RSSI: %d", rssi); +#endif } } this->expect_ack_ = true; @@ -201,7 +210,7 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->write(26); this->state_ = STATE_SENDINGSMS3; } else { - this->registered_ = false; + set_registered_(false); this->state_ = STATE_INIT; this->send_cmd_("AT+CMEE=2"); this->write(26); @@ -226,7 +235,7 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->state_ = STATE_INIT; this->dial_pending_ = false; } else { - this->registered_ = false; + this->set_registered_(false); this->state_ = STATE_INIT; this->send_cmd_("AT+CMEE=2"); this->write(26); @@ -277,7 +286,12 @@ void Sim800LComponent::send_sms(const std::string &recipient, const std::string } void Sim800LComponent::dump_config() { ESP_LOGCONFIG(TAG, "SIM800L:"); - ESP_LOGCONFIG(TAG, " RSSI: %d dB", this->rssi_); +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "Registered", this->registered_binary_sensor_); +#endif +#ifdef USE_SENSOR + LOG_SENSOR(" ", "Rssi", this->rssi_sensor_); +#endif } void Sim800LComponent::dial(const std::string &recipient) { ESP_LOGD(TAG, "Dialing %s", recipient.c_str()); @@ -286,5 +300,13 @@ void Sim800LComponent::dial(const std::string &recipient) { this->update(); } +void Sim800LComponent::set_registered_(bool registered) { + this->registered_ = registered; +#ifdef USE_BINARY_SENSOR + if (this->registered_binary_sensor_ != nullptr) + this->registered_binary_sensor_->publish_state(registered); +#endif +} + } // namespace sim800l } // namespace esphome diff --git a/esphome/components/sim800l/sim800l.h b/esphome/components/sim800l/sim800l.h index 4f738b0a8c..3535b96283 100644 --- a/esphome/components/sim800l/sim800l.h +++ b/esphome/components/sim800l/sim800l.h @@ -2,7 +2,14 @@ #include +#include "esphome/core/defines.h" #include "esphome/core/component.h" +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif #include "esphome/components/uart/uart.h" #include "esphome/core/automation.h" @@ -42,6 +49,14 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { void update() override; void loop() override; void dump_config() override; +#ifdef USE_BINARY_SENSOR + void set_registered_binary_sensor(binary_sensor::BinarySensor *registered_binary_sensor) { + registered_binary_sensor_ = registered_binary_sensor; + } +#endif +#ifdef USE_SENSOR + void set_rssi_sensor(sensor::Sensor *rssi_sensor) { rssi_sensor_ = rssi_sensor; } +#endif void add_on_sms_received_callback(std::function callback) { this->callback_.add(std::move(callback)); } @@ -51,7 +66,15 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { protected: void send_cmd_(const std::string &message); void parse_cmd_(std::string message); + void set_registered_(bool registered); +#ifdef USE_BINARY_SENSOR + binary_sensor::BinarySensor *registered_binary_sensor_{nullptr}; +#endif + +#ifdef USE_SENSOR + sensor::Sensor *rssi_sensor_{nullptr}; +#endif std::string sender_; char read_buffer_[SIM800L_READ_BUFFER_LENGTH]; size_t read_pos_{0}; @@ -60,7 +83,6 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { bool expect_ack_{false}; sim800l::State state_{STATE_IDLE}; bool registered_{false}; - int rssi_{0}; std::string recipient_; std::string outgoing_message_; From ce073a704be3631c0a2966f97d5445ec037578d9 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Tue, 15 Feb 2022 20:54:21 +0100 Subject: [PATCH 107/238] Fix strlcpy() uses to make long SSIDs and passwords work (#3199) Co-authored-by: Maurice Makaay --- esphome/components/wifi/wifi_component_esp32_arduino.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index e1332e3181..83381f3424 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -161,8 +161,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv417wifi_sta_config_t wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - strlcpy(reinterpret_cast(conf.sta.ssid), ap.get_ssid().c_str(), sizeof(conf.sta.ssid)); - strlcpy(reinterpret_cast(conf.sta.password), ap.get_password().c_str(), sizeof(conf.sta.password)); + strncpy(reinterpret_cast(conf.sta.ssid), ap.get_ssid().c_str(), sizeof(conf.sta.ssid)); + strncpy(reinterpret_cast(conf.sta.password), ap.get_password().c_str(), sizeof(conf.sta.password)); // The weakest authmode to accept in the fast scan mode if (ap.get_password().empty()) { @@ -709,7 +709,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - strlcpy(reinterpret_cast(conf.ap.ssid), ap.get_ssid().c_str(), sizeof(conf.ap.ssid)); + strncpy(reinterpret_cast(conf.ap.ssid), ap.get_ssid().c_str(), sizeof(conf.ap.ssid)); conf.ap.channel = ap.get_channel().value_or(1); conf.ap.ssid_hidden = ap.get_ssid().size(); conf.ap.max_connection = 5; @@ -720,7 +720,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { *conf.ap.password = 0; } else { conf.ap.authmode = WIFI_AUTH_WPA2_PSK; - strlcpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.ssid)); + strncpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.ssid)); } #if ESP_IDF_VERSION_MAJOR >= 4 From 41f84447cc31adcf7f0a651ac8bf5962b7c863cb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Feb 2022 09:11:46 +1300 Subject: [PATCH 108/238] Update HA addon token (#3200) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 02a55494e9..f5281c2fb7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -143,7 +143,7 @@ jobs: needs: [deploy-docker] steps: - env: - TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }} + TOKEN: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }} run: | TAG="${GITHUB_REF#refs/tags/}" curl \ From 51cb5da7f025ba10a23bff76d96756744ceca75b Mon Sep 17 00:00:00 2001 From: Stewart Date: Wed, 16 Feb 2022 15:50:10 +0000 Subject: [PATCH 109/238] Fix missed ARDUINO_VERSION_CODE to USE_ARDUINO_VERSION_CODE changes (#3206) Co-authored-by: Stewart Morgan --- esphome/components/debug/debug_component.cpp | 6 +++--- esphome/components/debug/debug_component.h | 4 ++-- esphome/components/ota/ota_backend_arduino_esp8266.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index a2697084bd..97d5aeb8a8 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -53,9 +53,9 @@ void DebugComponent::dump_config() { #ifdef USE_SENSOR LOG_SENSOR(" ", "Free space on heap", this->free_sensor_); LOG_SENSOR(" ", "Largest free heap block", this->block_sensor_); -#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) +#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) LOG_SENSOR(" ", "Heap fragmentation", this->fragmentation_sensor_); -#endif // defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) +#endif // defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) #endif // USE_SENSOR ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); @@ -316,7 +316,7 @@ void DebugComponent::update() { #endif } -#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) +#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) if (this->fragmentation_sensor_ != nullptr) { // NOLINTNEXTLINE(readability-static-accessed-through-instance) this->fragmentation_sensor_->publish_state(ESP.getHeapFragmentation()); diff --git a/esphome/components/debug/debug_component.h b/esphome/components/debug/debug_component.h index f966b4fafc..4dc1659616 100644 --- a/esphome/components/debug/debug_component.h +++ b/esphome/components/debug/debug_component.h @@ -28,7 +28,7 @@ class DebugComponent : public PollingComponent { #ifdef USE_SENSOR void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; } void set_block_sensor(sensor::Sensor *block_sensor) { block_sensor_ = block_sensor; } -#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) +#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) void set_fragmentation_sensor(sensor::Sensor *fragmentation_sensor) { fragmentation_sensor_ = fragmentation_sensor; } #endif void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; } @@ -42,7 +42,7 @@ class DebugComponent : public PollingComponent { sensor::Sensor *free_sensor_{nullptr}; sensor::Sensor *block_sensor_{nullptr}; -#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) +#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) sensor::Sensor *fragmentation_sensor_{nullptr}; #endif sensor::Sensor *loop_time_sensor_{nullptr}; diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.h b/esphome/components/ota/ota_backend_arduino_esp8266.h index 329f2cf0f2..7937c665b0 100644 --- a/esphome/components/ota/ota_backend_arduino_esp8266.h +++ b/esphome/components/ota/ota_backend_arduino_esp8266.h @@ -17,7 +17,7 @@ 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) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) bool supports_compression() override { return true; } #else bool supports_compression() override { return false; } From 4e24551b90958bb71ab8172fde34de869b8b6d6b Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 16 Feb 2022 22:25:04 +0100 Subject: [PATCH 110/238] Docker move deps install into base (#3207) --- docker/Dockerfile | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 5d9decbf1b..d97eed65f7 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,9 +5,11 @@ # One of "docker", "hassio" ARG BASEIMGTYPE=docker +# https://github.com/hassio-addons/addon-debian-base/releases 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 +# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye FROM debian:bullseye-20220125-slim AS base-docker-amd64 FROM debian:bullseye-20220125-slim AS base-docker-arm64 FROM debian:bullseye-20220125-slim AS base-docker-armv7 @@ -52,16 +54,16 @@ RUN \ && mkdir -p /piolibs - -# ======================= docker-type image ======================= -FROM base AS docker - # First install requirements to leverage caching when requirements don't change COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / RUN \ pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ && /platformio_install_deps.py /platformio.ini + +# ======================= docker-type image ======================= +FROM base AS docker + # Copy esphome and install COPY . /esphome RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome @@ -104,12 +106,6 @@ ARG BUILD_VERSION=dev # Copy root filesystem COPY docker/ha-addon-rootfs/ / -# First install requirements to leverage caching when requirements don't change -COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / -RUN \ - pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ - && /platformio_install_deps.py /platformio.ini - # Copy esphome and install COPY . /esphome RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome @@ -147,10 +143,8 @@ RUN \ /var/{cache,log}/* \ /var/lib/apt/lists/* -COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini / -RUN \ - pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \ - && /platformio_install_deps.py /platformio.ini +COPY requirements_test.txt / +RUN pip3 install --no-cache-dir -r /requirements_test.txt VOLUME ["/esphome"] WORKDIR /esphome From c123804294b5aa81c15cbd2fd0ca1b02a7316e99 Mon Sep 17 00:00:00 2001 From: Stewart Date: Thu, 17 Feb 2022 00:53:26 +0000 Subject: [PATCH 111/238] Set entity-category to diagnostic for debug component (#3209) Co-authored-by: Stewart Morgan Co-authored-by: root --- esphome/components/debug/sensor.py | 29 +++++++++++++++++++++---- esphome/components/debug/text_sensor.py | 6 +++-- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/esphome/components/debug/sensor.py b/esphome/components/debug/sensor.py index deea6fd5ed..f7ea07d138 100644 --- a/esphome/components/debug/sensor.py +++ b/esphome/components/debug/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_FRAGMENTATION, CONF_BLOCK, CONF_LOOP_TIME, + ENTITY_CATEGORY_DIAGNOSTIC, UNIT_MILLISECOND, UNIT_PERCENT, UNIT_BYTES, @@ -18,14 +19,34 @@ DEPENDENCIES = ["debug"] CONFIG_SCHEMA = { cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent), - cv.Optional(CONF_FREE): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0), - cv.Optional(CONF_BLOCK): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0), + cv.Optional(CONF_FREE): sensor.sensor_schema( + unit_of_measurement=UNIT_BYTES, + icon=ICON_COUNTER, + accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_BLOCK): sensor.sensor_schema( + unit_of_measurement=UNIT_BYTES, + icon=ICON_COUNTER, + accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), cv.Optional(CONF_FRAGMENTATION): cv.All( cv.only_on_esp8266, cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)), - sensor.sensor_schema(UNIT_PERCENT, ICON_COUNTER, 1), + sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_COUNTER, + accuracy_decimals=1, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + ), + cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLISECOND, + icon=ICON_TIMER, + accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), - cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema(UNIT_MILLISECOND, ICON_TIMER, 0), } diff --git a/esphome/components/debug/text_sensor.py b/esphome/components/debug/text_sensor.py index f8d1016fbf..11e6354f57 100644 --- a/esphome/components/debug/text_sensor.py +++ b/esphome/components/debug/text_sensor.py @@ -1,7 +1,7 @@ from esphome.components import text_sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_DEVICE +from esphome.const import CONF_DEVICE, ENTITY_CATEGORY_DIAGNOSTIC from . import CONF_DEBUG_ID, DebugComponent @@ -11,7 +11,9 @@ DEPENDENCIES = ["debug"] CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent), - cv.Optional(CONF_DEVICE): text_sensor.text_sensor_schema(), + cv.Optional(CONF_DEVICE): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC + ), } ) From ffa19426d79b92f2e16f4c5a71eb25481d0d4a61 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 17 Feb 2022 16:56:44 +1300 Subject: [PATCH 112/238] Remove redundant name from binary_sensor constructor (#3213) --- esphome/components/binary_sensor/__init__.py | 3 +-- esphome/components/binary_sensor/binary_sensor.cpp | 3 +-- esphome/components/binary_sensor/binary_sensor.h | 5 ----- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 1eab76d54e..c6065ddae4 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -22,7 +22,6 @@ from esphome.const import ( CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, - CONF_NAME, CONF_MQTT_ID, DEVICE_CLASS_EMPTY, DEVICE_CLASS_BATTERY, @@ -443,7 +442,7 @@ async def register_binary_sensor(var, config): async def new_binary_sensor(config): - var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME]) + var = cg.new_Pvariable(config[CONF_ID]) await register_binary_sensor(var, config) return var diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index 71422609d7..02735feaae 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -42,8 +42,7 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) { } } std::string BinarySensor::device_class() { return ""; } -BinarySensor::BinarySensor(const std::string &name) : EntityBase(name), state(false) {} -BinarySensor::BinarySensor() : BinarySensor("") {} +BinarySensor::BinarySensor() : state(false) {} void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } std::string BinarySensor::get_device_class() { if (this->device_class_.has_value()) diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index 591f444387..b5d1244bce 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -26,11 +26,6 @@ namespace binary_sensor { class BinarySensor : public EntityBase { public: explicit BinarySensor(); - /** Construct a binary sensor with the specified name - * - * @param name Name of this binary sensor. - */ - explicit BinarySensor(const std::string &name); /** Add a callback to be notified of state changes. * From 5a0b8328d81264e38ca9d486f857b36a7cbdadb2 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 17 Feb 2022 04:59:46 +0100 Subject: [PATCH 113/238] ESP8266 early init for pins (#3144) --- esphome/components/esp8266/__init__.py | 16 ++++++-- esphome/components/esp8266/const.py | 1 + esphome/components/esp8266/core.cpp | 10 +++++ esphome/components/esp8266/core.h | 14 +++++++ esphome/components/esp8266/gpio.py | 54 ++++++++++++++++++++++++-- 5 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 esphome/components/esp8266/core.h diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 3c83400c1d..7b1be32e38 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -17,11 +17,16 @@ 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 .const import ( + CONF_RESTORE_FROM_FLASH, + KEY_BOARD, + KEY_ESP8266, + KEY_PIN_INITIAL_STATES, + esp8266_ns, +) from .boards import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS -# force import gpio to register pin schema -from .gpio import esp8266_pin_to_code # noqa +from .gpio import PinInitialState, add_pin_initial_states_array CODEOWNERS = ["@esphome/core"] @@ -37,6 +42,9 @@ def set_core_data(config): config[CONF_FRAMEWORK][CONF_VERSION] ) CORE.data[KEY_ESP8266][KEY_BOARD] = config[CONF_BOARD] + CORE.data[KEY_ESP8266][KEY_PIN_INITIAL_STATES] = [ + PinInitialState() for _ in range(16) + ] return config @@ -221,6 +229,8 @@ async def to_code(config): if ld_script is not None: cg.add_platformio_option("board_build.ldscript", ld_script) + CORE.add_job(add_pin_initial_states_array) + # Called by writer.py def copy_files(): diff --git a/esphome/components/esp8266/const.py b/esphome/components/esp8266/const.py index 16a050360c..70429297e0 100644 --- a/esphome/components/esp8266/const.py +++ b/esphome/components/esp8266/const.py @@ -2,6 +2,7 @@ import esphome.codegen as cg KEY_ESP8266 = "esp8266" KEY_BOARD = "board" +KEY_PIN_INITIAL_STATES = "pin_initial_states" CONF_RESTORE_FROM_FLASH = "restore_from_flash" # esp8266 namespace is already defined by arduino, manually prefix esphome diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp index 828d71a3bd..a9460f51f2 100644 --- a/esphome/components/esp8266/core.cpp +++ b/esphome/components/esp8266/core.cpp @@ -1,5 +1,6 @@ #ifdef USE_ESP8266 +#include "core.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "preferences.h" @@ -53,6 +54,15 @@ extern "C" void resetPins() { // NOLINT // however, not strictly needed as we set up the pins properly // ourselves and this causes pins to toggle during reboot. force_link_symbols(); + + for (int i = 0; i < 16; i++) { + uint8_t mode = ESPHOME_ESP8266_GPIO_INITIAL_MODE[i]; + uint8_t level = ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[i]; + if (mode != 255) + pinMode(i, mode); // NOLINT + if (level != 255) + digitalWrite(i, level); // NOLINT + } } } // namespace esphome diff --git a/esphome/components/esp8266/core.h b/esphome/components/esp8266/core.h new file mode 100644 index 0000000000..ac33305669 --- /dev/null +++ b/esphome/components/esp8266/core.h @@ -0,0 +1,14 @@ +#pragma once + +#ifdef USE_ESP8266 + +#include + +extern const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_MODE[16]; +extern const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[16]; + +namespace esphome { +namespace esp8266 {} // namespace esp8266 +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/esp8266/gpio.py b/esphome/components/esp8266/gpio.py index fa5c94dff5..cf33ec126b 100644 --- a/esphome/components/esp8266/gpio.py +++ b/esphome/components/esp8266/gpio.py @@ -1,4 +1,6 @@ import logging +from dataclasses import dataclass +from typing import List from esphome.const import ( CONF_ID, @@ -12,12 +14,12 @@ from esphome.const import ( CONF_PULLUP, ) from esphome import pins -from esphome.core import CORE +from esphome.core import CORE, coroutine_with_priority import esphome.config_validation as cv import esphome.codegen as cg from . import boards -from .const import KEY_BOARD, KEY_ESP8266, esp8266_ns +from .const import KEY_BOARD, KEY_ESP8266, KEY_PIN_INITIAL_STATES, esp8266_ns _LOGGER = logging.getLogger(__name__) @@ -160,11 +162,57 @@ ESP8266_PIN_SCHEMA = cv.All( ) +@dataclass +class PinInitialState: + mode = 255 + level: int = 255 + + @pins.PIN_SCHEMA_REGISTRY.register("esp8266", ESP8266_PIN_SCHEMA) async def esp8266_pin_to_code(config): var = cg.new_Pvariable(config[CONF_ID]) num = config[CONF_NUMBER] + mode = config[CONF_MODE] cg.add(var.set_pin(num)) cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + cg.add(var.set_flags(pins.gpio_flags_expr(mode))) + if num < 16: + initial_state: PinInitialState = CORE.data[KEY_ESP8266][KEY_PIN_INITIAL_STATES][ + num + ] + if mode[CONF_INPUT]: + if mode[CONF_PULLDOWN]: + initial_state.mode = cg.global_ns.INPUT_PULLDOWN_16 + elif mode[CONF_PULLUP]: + initial_state.mode = cg.global_ns.INPUT_PULLUP + else: + initial_state.mode = cg.global_ns.INPUT + elif mode[CONF_OUTPUT]: + if mode[CONF_OPEN_DRAIN]: + initial_state.mode = cg.global_ns.OUTPUT_OPEN_DRAIN + else: + initial_state.mode = cg.global_ns.OUTPUT + initial_state.level = int(config[CONF_INVERTED]) + return var + + +@coroutine_with_priority(-999.0) +async def add_pin_initial_states_array(): + # Add includes at the very end, so that they override everything + initial_states: List[PinInitialState] = CORE.data[KEY_ESP8266][ + KEY_PIN_INITIAL_STATES + ] + initial_modes_s = ", ".join(str(x.mode) for x in initial_states) + initial_levels_s = ", ".join(str(x.level) for x in initial_states) + + cg.add_global( + cg.RawExpression( + f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_MODE[16] = {{{initial_modes_s}}}" + ) + ) + cg.add_global( + cg.RawExpression( + f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[16] = {{{initial_levels_s}}}" + ) + ) From c054fb8a2c5a60da3961728222d66ebbadfb50c3 Mon Sep 17 00:00:00 2001 From: Felix Storm Date: Thu, 17 Feb 2022 05:00:14 +0100 Subject: [PATCH 114/238] CAN bus: read all queued messages (#3194) --- esphome/components/canbus/canbus.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/components/canbus/canbus.cpp b/esphome/components/canbus/canbus.cpp index 731682c277..d9f6ded85d 100644 --- a/esphome/components/canbus/canbus.cpp +++ b/esphome/components/canbus/canbus.cpp @@ -56,13 +56,15 @@ void Canbus::add_trigger(CanbusTrigger *trigger) { void Canbus::loop() { struct CanFrame can_message; - // readmessage - if (this->read_message(&can_message) == canbus::ERROR_OK) { + // read all messages until queue is empty + int message_counter = 0; + while (this->read_message(&can_message) == canbus::ERROR_OK) { + message_counter++; if (can_message.use_extended_id) { - ESP_LOGD(TAG, "received can message extended can_id=0x%x size=%d", can_message.can_id, + ESP_LOGD(TAG, "received can message (#%d) extended can_id=0x%x size=%d", message_counter, can_message.can_id, can_message.can_data_length_code); } else { - ESP_LOGD(TAG, "received can message std can_id=0x%x size=%d", can_message.can_id, + ESP_LOGD(TAG, "received can message (#%d) std can_id=0x%x size=%d", message_counter, can_message.can_id, can_message.can_data_length_code); } From 38259c96c9c5e49154be9f25a89e68741034071a Mon Sep 17 00:00:00 2001 From: Felix Storm Date: Thu, 17 Feb 2022 05:00:31 +0100 Subject: [PATCH 115/238] CAN bus: support bit mask for on_frame can_id (#3196) --- esphome/components/canbus/__init__.py | 16 ++++++++--- esphome/components/canbus/canbus.cpp | 5 ++-- esphome/components/canbus/canbus.h | 8 +++--- tests/test1.yaml | 38 +++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 9 deletions(-) diff --git a/esphome/components/canbus/__init__.py b/esphome/components/canbus/__init__.py index 808b31d1d2..5f614eb0a4 100644 --- a/esphome/components/canbus/__init__.py +++ b/esphome/components/canbus/__init__.py @@ -8,6 +8,7 @@ CODEOWNERS = ["@mvturnho", "@danielschramm"] IS_PLATFORM_COMPONENT = True CONF_CAN_ID = "can_id" +CONF_CAN_ID_MASK = "can_id_mask" CONF_USE_EXTENDED_ID = "use_extended_id" CONF_CANBUS_ID = "canbus_id" CONF_BIT_RATE = "bit_rate" @@ -38,7 +39,7 @@ canbus_ns = cg.esphome_ns.namespace("canbus") CanbusComponent = canbus_ns.class_("CanbusComponent", cg.Component) CanbusTrigger = canbus_ns.class_( "CanbusTrigger", - automation.Trigger.template(cg.std_vector.template(cg.uint8)), + automation.Trigger.template(cg.std_vector.template(cg.uint8), cg.uint32), cg.Component, ) CanSpeed = canbus_ns.enum("CAN_SPEED") @@ -72,6 +73,9 @@ CANBUS_SCHEMA = cv.Schema( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger), cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), + cv.Optional(CONF_CAN_ID_MASK, default=0x1FFFFFFF): cv.int_range( + min=0, max=0x1FFFFFFF + ), cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, }, validate_id, @@ -90,11 +94,16 @@ async def setup_canbus_core_(var, config): for conf in config.get(CONF_ON_FRAME, []): can_id = conf[CONF_CAN_ID] + can_id_mask = conf[CONF_CAN_ID_MASK] ext_id = conf[CONF_USE_EXTENDED_ID] - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id) + trigger = cg.new_Pvariable( + conf[CONF_TRIGGER_ID], var, can_id, can_id_mask, ext_id + ) await cg.register_component(trigger, conf) await automation.build_automation( - trigger, [(cg.std_vector.template(cg.uint8), "x")], conf + trigger, + [(cg.std_vector.template(cg.uint8), "x"), (cg.uint32, "can_id")], + conf, ) @@ -126,7 +135,6 @@ async def canbus_action_to_code(config, action_id, template_arg, args): if CONF_CAN_ID in config: can_id = await cg.templatable(config[CONF_CAN_ID], args, cg.uint32) cg.add(var.set_can_id(can_id)) - use_extended_id = await cg.templatable( config[CONF_USE_EXTENDED_ID], args, cg.uint32 ) diff --git a/esphome/components/canbus/canbus.cpp b/esphome/components/canbus/canbus.cpp index d9f6ded85d..14dc1544cf 100644 --- a/esphome/components/canbus/canbus.cpp +++ b/esphome/components/canbus/canbus.cpp @@ -78,8 +78,9 @@ void Canbus::loop() { // fire all triggers for (auto *trigger : this->triggers_) { - if ((trigger->can_id_ == can_message.can_id) && (trigger->use_extended_id_ == can_message.use_extended_id)) { - trigger->trigger(data); + if ((trigger->can_id_ == (can_message.can_id & trigger->can_id_mask_)) && + (trigger->use_extended_id_ == can_message.use_extended_id)) { + trigger->trigger(data, can_message.can_id); } } } diff --git a/esphome/components/canbus/canbus.h b/esphome/components/canbus/canbus.h index 37adf0bc9c..0491e8d3c1 100644 --- a/esphome/components/canbus/canbus.h +++ b/esphome/components/canbus/canbus.h @@ -116,17 +116,19 @@ template class CanbusSendAction : public Action, public P std::vector data_static_{}; }; -class CanbusTrigger : public Trigger>, public Component { +class CanbusTrigger : public Trigger, uint32_t>, public Component { friend class Canbus; public: - explicit CanbusTrigger(Canbus *parent, const std::uint32_t can_id, const bool use_extended_id) - : parent_(parent), can_id_(can_id), use_extended_id_(use_extended_id){}; + explicit CanbusTrigger(Canbus *parent, const std::uint32_t can_id, const std::uint32_t can_id_mask, + const bool use_extended_id) + : parent_(parent), can_id_(can_id), can_id_mask_(can_id_mask), use_extended_id_(use_extended_id){}; void setup() override { this->parent_->add_trigger(this); } protected: Canbus *parent_; uint32_t can_id_; + uint32_t can_id_mask_; bool use_extended_id_; }; diff --git a/tests/test1.yaml b/tests/test1.yaml index d8fea223dc..54343f59c7 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2551,6 +2551,25 @@ canbus: lambda: "return x[0] == 0x11;" then: light.toggle: ${roomname}_lights + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } - platform: esp32_can id: esp32_internal_can rx_pin: GPIO04 @@ -2570,6 +2589,25 @@ canbus: lambda: "return x[0] == 0x11;" then: light.toggle: ${roomname}_lights + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } teleinfo: id: myteleinfo From 36ddd9dd69bfed26cf67c67aad88aeb4572665df Mon Sep 17 00:00:00 2001 From: wilberforce Date: Thu, 17 Feb 2022 17:02:10 +1300 Subject: [PATCH 116/238] Simplify captive portal to compressed single page (#2872) --- .../components/captive_portal/captive_index.h | 107 ++++++++++++++++++ .../captive_portal/captive_portal.cpp | 90 +++------------ .../captive_portal/captive_portal.h | 2 +- esphome/components/captive_portal/index.html | 55 --------- esphome/components/captive_portal/lock.svg | 1 - .../components/captive_portal/stylesheet.css | 58 ---------- .../captive_portal/wifi-strength-1.svg | 1 - .../captive_portal/wifi-strength-2.svg | 1 - .../captive_portal/wifi-strength-3.svg | 1 - .../captive_portal/wifi-strength-4.svg | 1 - 10 files changed, 125 insertions(+), 192 deletions(-) create mode 100644 esphome/components/captive_portal/captive_index.h delete mode 100644 esphome/components/captive_portal/index.html delete mode 100644 esphome/components/captive_portal/lock.svg delete mode 100644 esphome/components/captive_portal/stylesheet.css delete mode 100644 esphome/components/captive_portal/wifi-strength-1.svg delete mode 100644 esphome/components/captive_portal/wifi-strength-2.svg delete mode 100644 esphome/components/captive_portal/wifi-strength-3.svg delete mode 100644 esphome/components/captive_portal/wifi-strength-4.svg diff --git a/esphome/components/captive_portal/captive_index.h b/esphome/components/captive_portal/captive_index.h new file mode 100644 index 0000000000..bf2e6e6e8b --- /dev/null +++ b/esphome/components/captive_portal/captive_index.h @@ -0,0 +1,107 @@ +#pragma once +// Generated from https://github.com/esphome/esphome-webserver +#include "esphome/core/hal.h" +namespace esphome { + +namespace captive_portal { + +const uint8_t INDEX_GZ[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xdd, 0x58, 0x09, 0x6f, 0xdc, 0x36, 0x16, 0xfe, 0x2b, + 0xac, 0x92, 0x74, 0x34, 0x8d, 0xc5, 0xd1, 0x31, 0x97, 0x35, 0xd2, 0x14, 0x89, 0x37, 0x45, 0x0b, 0x24, 0x69, 0x00, + 0xbb, 0x5d, 0x14, 0x69, 0x00, 0x73, 0x24, 0x6a, 0xc4, 0x58, 0xa2, 0x54, 0x91, 0x9a, 0x23, 0x83, 0xd9, 0xdf, 0xde, + 0x47, 0x52, 0x73, 0x38, 0x6b, 0x2f, 0x90, 0x62, 0x8b, 0xa2, 0x4d, 0x6c, 0x9a, 0xc7, 0x3b, 0x3f, 0xf2, 0xf1, 0x3d, + 0x2a, 0xfa, 0x2a, 0xad, 0x12, 0xb9, 0xad, 0x29, 0xca, 0x65, 0x59, 0xcc, 0x23, 0xd5, 0xa2, 0x82, 0xf0, 0x65, 0x4c, + 0x39, 0x8c, 0x28, 0x49, 0xe7, 0x51, 0x49, 0x25, 0x41, 0x49, 0x4e, 0x1a, 0x41, 0x65, 0xfc, 0xd3, 0xcd, 0x77, 0xce, + 0x14, 0x0d, 0xe6, 0x51, 0xc1, 0xf8, 0x1d, 0x6a, 0x68, 0x11, 0xb3, 0xa4, 0xe2, 0x28, 0x6f, 0x68, 0x16, 0xa7, 0x44, + 0x92, 0x90, 0x95, 0x64, 0x49, 0x15, 0x81, 0x66, 0xe3, 0xa4, 0xa4, 0xf1, 0x8a, 0xd1, 0x75, 0x5d, 0x35, 0x12, 0x01, + 0xa5, 0xa4, 0x5c, 0xc6, 0xd6, 0x9a, 0xa5, 0x32, 0x8f, 0x53, 0xba, 0x62, 0x09, 0x75, 0xf4, 0xe0, 0x82, 0x71, 0x26, + 0x19, 0x29, 0x1c, 0x91, 0x90, 0x82, 0xc6, 0xde, 0x45, 0x2b, 0x68, 0xa3, 0x07, 0x64, 0x01, 0x63, 0x5e, 0x59, 0x20, + 0x52, 0x24, 0x0d, 0xab, 0x25, 0x52, 0xf6, 0xc6, 0x65, 0x95, 0xb6, 0x05, 0x9d, 0x67, 0x2d, 0x4f, 0x24, 0x03, 0x0b, + 0x84, 0xcd, 0xfb, 0xbb, 0x82, 0x4a, 0x44, 0xe3, 0x37, 0x44, 0xe6, 0xb8, 0x24, 0x1b, 0xdb, 0x74, 0x18, 0xb7, 0xfd, + 0x6f, 0x6c, 0xfe, 0xdc, 0x73, 0xdd, 0xfe, 0x85, 0x6e, 0xdc, 0xfe, 0x00, 0xfe, 0xce, 0x1a, 0x2a, 0xdb, 0x86, 0x23, + 0x62, 0xdf, 0x46, 0x35, 0x50, 0xa2, 0x34, 0xb6, 0x4a, 0xcf, 0xc7, 0xae, 0x3b, 0x45, 0xde, 0x25, 0xf6, 0x47, 0x8e, + 0xe7, 0xe1, 0xc0, 0xf1, 0x46, 0xc9, 0xc4, 0x19, 0x21, 0x6f, 0x08, 0x8d, 0xef, 0xe3, 0x11, 0x72, 0x3f, 0x59, 0x28, + 0x63, 0x45, 0x11, 0x5b, 0xbc, 0xe2, 0xd4, 0x42, 0x42, 0x36, 0xd5, 0x1d, 0x8d, 0xad, 0xa4, 0x6d, 0x1a, 0xf0, 0xee, + 0xaa, 0x2a, 0xaa, 0x06, 0xac, 0xfd, 0x95, 0xa3, 0x7b, 0xff, 0xbe, 0x58, 0x87, 0x6c, 0x08, 0x17, 0x59, 0xd5, 0x94, + 0xb1, 0xa5, 0x41, 0xb1, 0x9f, 0xee, 0xe8, 0x1e, 0xa9, 0xa6, 0x7f, 0xb6, 0xe8, 0x54, 0x0d, 0x5b, 0x32, 0x1e, 0x5b, + 0x9e, 0x8f, 0xbc, 0x29, 0xe8, 0xbd, 0xed, 0xef, 0x8f, 0xa0, 0x10, 0x05, 0x4a, 0xe7, 0x66, 0x65, 0xbf, 0xbf, 0x8d, + 0xc4, 0x6a, 0x89, 0x36, 0x65, 0xc1, 0x45, 0x6c, 0xe5, 0x52, 0xd6, 0xe1, 0x60, 0xb0, 0x5e, 0xaf, 0xf1, 0x3a, 0xc0, + 0x55, 0xb3, 0x1c, 0xf8, 0xae, 0xeb, 0x0e, 0x80, 0xc2, 0x42, 0x66, 0x7f, 0x2c, 0x7f, 0x68, 0xa1, 0x9c, 0xb2, 0x65, + 0x2e, 0x75, 0x7f, 0xfe, 0x74, 0xc7, 0xf7, 0x91, 0xa2, 0x98, 0xdf, 0x7e, 0x38, 0xd3, 0xd2, 0x9c, 0x69, 0xe1, 0xdf, + 0x12, 0xdb, 0x3a, 0xb8, 0xda, 0x7b, 0xa3, 0x8c, 0x9a, 0x10, 0x1f, 0xf9, 0xc8, 0xd5, 0xff, 0x7d, 0x47, 0xf5, 0xbb, + 0x91, 0xf3, 0xd9, 0x08, 0x9d, 0x8d, 0xe0, 0xaf, 0x02, 0xd0, 0x2f, 0xc7, 0xce, 0xe5, 0x91, 0xdf, 0x53, 0xeb, 0x2b, + 0xcf, 0x3d, 0x4d, 0x28, 0xa6, 0xef, 0xc7, 0xe7, 0x63, 0xc7, 0xff, 0x59, 0x11, 0x68, 0xf4, 0x8f, 0x5c, 0x8e, 0x9f, + 0x7b, 0x3f, 0x8f, 0xc9, 0x08, 0x8d, 0xba, 0x99, 0x91, 0xa3, 0xfa, 0xc7, 0x91, 0xd6, 0x85, 0x46, 0x2b, 0x20, 0x2b, + 0x9d, 0xb1, 0x33, 0x22, 0x01, 0x0a, 0x3a, 0xab, 0xa0, 0x07, 0xd3, 0x63, 0xe0, 0x3e, 0x9b, 0x73, 0x82, 0x4f, 0xbd, + 0xc1, 0xdc, 0xea, 0x87, 0x96, 0x75, 0x82, 0xa1, 0x3a, 0x87, 0x01, 0x7f, 0xac, 0xe0, 0xdc, 0x59, 0x56, 0x7f, 0x6f, + 0x7d, 0x2b, 0xc8, 0x8a, 0x5a, 0x71, 0x1c, 0x43, 0xa8, 0xb5, 0x25, 0x9c, 0x10, 0x5c, 0x54, 0x09, 0x51, 0x2c, 0x58, + 0x50, 0xd2, 0x24, 0xf9, 0xd7, 0x5f, 0xdb, 0xc7, 0xa5, 0x25, 0x95, 0xaf, 0x0a, 0xaa, 0xba, 0xe2, 0xe5, 0xf6, 0x86, + 0x2c, 0xdf, 0x42, 0x00, 0xd9, 0x16, 0x11, 0x2c, 0xa5, 0x56, 0xff, 0xbd, 0xfb, 0x01, 0x0b, 0xb9, 0x2d, 0x28, 0x4e, + 0x99, 0xa8, 0x0b, 0xb2, 0x8d, 0xad, 0x05, 0xc8, 0xba, 0xb3, 0xfa, 0x17, 0x19, 0x95, 0x49, 0x6e, 0x5b, 0x03, 0x08, + 0xb1, 0x8c, 0x2d, 0xf1, 0x47, 0x51, 0x71, 0xab, 0x8f, 0x65, 0x4e, 0xb9, 0x6d, 0x1f, 0x2c, 0x54, 0xf6, 0x71, 0xbd, + 0x64, 0x3f, 0xb4, 0x74, 0xb4, 0x41, 0x32, 0xa9, 0x42, 0x0e, 0xab, 0xe0, 0xbd, 0x38, 0xce, 0x2e, 0xaa, 0x74, 0xfb, + 0x88, 0x79, 0xb9, 0x67, 0x6c, 0x63, 0x9c, 0xd3, 0xe6, 0x86, 0x6e, 0xe0, 0xb8, 0xfc, 0x9b, 0x7d, 0xc7, 0xd0, 0x5b, + 0x2a, 0xd7, 0x55, 0x73, 0x27, 0x42, 0x64, 0x3d, 0x37, 0xe2, 0x66, 0x26, 0x42, 0x39, 0x26, 0xb5, 0xc0, 0xa2, 0x80, + 0xf0, 0xb7, 0xbd, 0x3e, 0xc4, 0x6a, 0x7d, 0xdf, 0x14, 0x83, 0xe2, 0x6d, 0x94, 0xb2, 0x15, 0x4a, 0x0a, 0x22, 0xe0, + 0xb8, 0x72, 0x23, 0xcb, 0x42, 0x87, 0xb8, 0xaa, 0x78, 0x02, 0xfc, 0x77, 0xb1, 0xf5, 0x00, 0x76, 0x2f, 0xb7, 0x3f, + 0xa4, 0x76, 0x4f, 0x00, 0x6a, 0xbd, 0x3e, 0x5e, 0x91, 0xa2, 0xa5, 0x28, 0x46, 0x32, 0x67, 0xe2, 0x64, 0xe2, 0xec, + 0x51, 0xb6, 0x5a, 0xdc, 0x01, 0x57, 0x06, 0xcb, 0xc2, 0xee, 0x5b, 0xc7, 0x38, 0x8e, 0x88, 0xb9, 0xe5, 0xac, 0x27, + 0xd6, 0x67, 0x36, 0x39, 0x05, 0xcd, 0xa4, 0x75, 0x16, 0xf0, 0x4f, 0x77, 0x70, 0x1b, 0xe1, 0x06, 0xf4, 0xf7, 0xf7, + 0xa7, 0xd9, 0x48, 0xd4, 0x84, 0x7f, 0xce, 0xaa, 0x6c, 0xd4, 0x81, 0x85, 0x55, 0x4f, 0x45, 0x17, 0x10, 0x9d, 0x74, + 0x0e, 0xc8, 0xb1, 0xff, 0x74, 0x07, 0x71, 0xa6, 0x8e, 0xce, 0xdd, 0x49, 0x68, 0x34, 0x00, 0x84, 0xe6, 0xb7, 0xfb, + 0x7e, 0xff, 0xe4, 0xce, 0x6f, 0x2d, 0x6d, 0xb6, 0xd7, 0xb4, 0xa0, 0x89, 0xac, 0x1a, 0xdb, 0x7a, 0x02, 0x9a, 0xe0, + 0x24, 0x68, 0xbf, 0xbf, 0xbf, 0x79, 0xf3, 0x3a, 0xae, 0x6c, 0xda, 0xbf, 0x78, 0x8c, 0x5a, 0xdd, 0xea, 0xef, 0xe1, + 0x56, 0xff, 0x4f, 0xdc, 0x53, 0xf7, 0x7a, 0xef, 0x03, 0xb0, 0x1a, 0xaf, 0x4f, 0x97, 0xbb, 0xba, 0x00, 0x9e, 0xc3, + 0x25, 0x72, 0x61, 0x3d, 0x17, 0xb6, 0x33, 0x1e, 0xf5, 0x41, 0x3d, 0xfc, 0x80, 0xe9, 0xfa, 0x7a, 0x86, 0x6b, 0x5a, + 0x1d, 0xd1, 0xf9, 0x37, 0xbb, 0x45, 0xb5, 0x71, 0x04, 0xfb, 0xc4, 0xf8, 0x32, 0x64, 0x3c, 0xa7, 0x0d, 0x93, 0x7b, + 0x30, 0x17, 0x6e, 0xfa, 0xba, 0x95, 0xbb, 0x9a, 0xa4, 0xa9, 0x5a, 0x19, 0xd5, 0x9b, 0x59, 0x06, 0x79, 0x41, 0x51, + 0xd2, 0xd0, 0xa3, 0xe5, 0xde, 0xac, 0xeb, 0x2b, 0x28, 0xbc, 0x1c, 0x3d, 0xdb, 0xab, 0x83, 0xb7, 0x93, 0xb0, 0x65, + 0x0e, 0x29, 0xd8, 0x92, 0x87, 0x09, 0xd8, 0x4d, 0x1b, 0xc3, 0x94, 0x91, 0x92, 0x15, 0xdb, 0x50, 0xc0, 0x65, 0xe8, + 0x40, 0xc2, 0x60, 0xd9, 0x7e, 0xd1, 0x4a, 0x59, 0x71, 0xd0, 0xdd, 0xa4, 0xb4, 0x09, 0xdd, 0x99, 0xe9, 0x38, 0x0d, + 0x49, 0x59, 0x2b, 0x42, 0x1c, 0x34, 0xb4, 0x9c, 0x2d, 0x48, 0x72, 0xb7, 0x6c, 0xaa, 0x96, 0xa7, 0x4e, 0xa2, 0x6e, + 0xeb, 0xf0, 0x89, 0x97, 0x91, 0x80, 0x26, 0xb3, 0x6e, 0x94, 0x65, 0xd9, 0x0c, 0x90, 0xa0, 0x8e, 0xb9, 0xfc, 0x42, + 0x1f, 0x0f, 0x15, 0xdb, 0x99, 0x99, 0xd8, 0x57, 0x13, 0xc6, 0x46, 0x48, 0x25, 0xcf, 0x66, 0x07, 0x77, 0xdc, 0x19, + 0xa4, 0x01, 0x01, 0x42, 0x6a, 0x88, 0x7f, 0x30, 0x73, 0x5f, 0x12, 0xc6, 0xcf, 0xad, 0x57, 0x67, 0x65, 0xd6, 0x85, + 0x2f, 0xc0, 0xa2, 0xd5, 0xe8, 0x20, 0x9e, 0x41, 0xa2, 0x32, 0xb9, 0x30, 0xf4, 0xc7, 0x6e, 0xbd, 0xd9, 0xe3, 0xee, + 0x8c, 0xec, 0x0e, 0xd4, 0x59, 0x41, 0x37, 0xb3, 0x8f, 0xad, 0x90, 0x2c, 0xdb, 0x3a, 0x5d, 0x2e, 0x0d, 0xe1, 0xbc, + 0x40, 0x0e, 0x5d, 0x00, 0x29, 0xa5, 0x7c, 0xa6, 0x75, 0x38, 0x4c, 0xd2, 0x52, 0x74, 0x38, 0x1d, 0xc5, 0xe8, 0x53, + 0x7a, 0x5f, 0xd6, 0xff, 0xa2, 0x56, 0xc7, 0x71, 0x57, 0x92, 0x06, 0x72, 0x8b, 0xb3, 0xa8, 0x00, 0xd3, 0x32, 0x74, + 0x26, 0xb0, 0x57, 0xdd, 0x94, 0x12, 0x06, 0x9e, 0x83, 0x99, 0xfa, 0x6e, 0x3a, 0xe0, 0xed, 0xd5, 0x1b, 0x24, 0xaa, + 0x82, 0xa5, 0x1d, 0x9d, 0x26, 0x41, 0xee, 0x11, 0x1e, 0x0f, 0xb6, 0x1b, 0xa9, 0xb9, 0x03, 0xd4, 0xc3, 0x6c, 0x4a, + 0x3c, 0xf7, 0x81, 0x1d, 0x49, 0xb3, 0xcc, 0x5f, 0x64, 0x47, 0xa4, 0x54, 0xaa, 0xdd, 0xb3, 0xee, 0x54, 0xf8, 0x43, + 0x10, 0x70, 0xd8, 0x1b, 0xe8, 0xef, 0x99, 0x8e, 0x8b, 0xdd, 0x99, 0x14, 0x7d, 0x52, 0xc3, 0xb6, 0x29, 0xec, 0x87, + 0x4e, 0xee, 0xb3, 0xe0, 0xea, 0x94, 0x09, 0x7b, 0x8f, 0x67, 0xc2, 0x1e, 0x52, 0xb5, 0xcb, 0xcb, 0x6a, 0x13, 0xf7, + 0x74, 0x4e, 0x1a, 0xc2, 0x4f, 0xef, 0x59, 0xf0, 0x0a, 0xf8, 0xff, 0x2f, 0x29, 0xee, 0x0f, 0xa7, 0xb7, 0x2f, 0x48, + 0x6d, 0x5f, 0x98, 0xd5, 0x8c, 0x77, 0xca, 0x79, 0xe8, 0x41, 0xfa, 0x62, 0x58, 0xb0, 0xa5, 0xf7, 0x67, 0x40, 0xfb, + 0xdf, 0x38, 0x06, 0x2f, 0xbc, 0x29, 0xbe, 0x44, 0xba, 0x31, 0x10, 0xe1, 0x60, 0x8a, 0x26, 0x57, 0x43, 0x3c, 0xf4, + 0x90, 0xaa, 0x9a, 0xc6, 0x68, 0x82, 0xa7, 0x40, 0x30, 0xc6, 0xc1, 0x04, 0x26, 0x90, 0xef, 0xe1, 0xd1, 0x6b, 0x3f, + 0xc0, 0xe3, 0x11, 0x50, 0xf9, 0x2e, 0x0e, 0x7c, 0x64, 0x68, 0xc7, 0xd8, 0x07, 0x71, 0x8a, 0x24, 0x28, 0x01, 0xe8, + 0x24, 0xc0, 0xee, 0x04, 0xc4, 0x8d, 0xb1, 0x7b, 0x89, 0xa7, 0x63, 0x34, 0xc5, 0x13, 0x80, 0x0e, 0x0f, 0x47, 0x85, + 0x33, 0xc2, 0x1e, 0x4c, 0x07, 0x63, 0x32, 0xc5, 0xc3, 0x00, 0xe9, 0xc6, 0xc0, 0x31, 0x01, 0x11, 0x0e, 0x76, 0xbd, + 0xd7, 0x01, 0xf6, 0x27, 0xa0, 0x77, 0x38, 0x7c, 0x01, 0x62, 0x2f, 0x87, 0xc8, 0xb4, 0x06, 0x5e, 0x50, 0x30, 0x7a, + 0x0c, 0x34, 0xff, 0x9f, 0x0b, 0x1a, 0x40, 0xe2, 0xa1, 0x00, 0x5f, 0x42, 0xec, 0x7a, 0x8a, 0xdf, 0xb4, 0x06, 0x37, + 0xcf, 0x43, 0xee, 0x1f, 0xc6, 0x2c, 0xf8, 0xe7, 0x62, 0xe6, 0x29, 0x04, 0xa0, 0x0b, 0xba, 0x41, 0x0e, 0xd2, 0x8d, + 0xd1, 0x0d, 0xcc, 0xd3, 0xab, 0x4b, 0x34, 0x05, 0xae, 0xf1, 0x14, 0x5d, 0xa2, 0x91, 0x42, 0x17, 0xd8, 0x87, 0x86, + 0xc9, 0x01, 0xa6, 0x2f, 0x84, 0x71, 0xf8, 0x37, 0x86, 0xf1, 0x31, 0x9f, 0xfe, 0xc6, 0x2e, 0xfd, 0x15, 0x57, 0x10, + 0x94, 0x63, 0xba, 0x0c, 0x8b, 0x06, 0xe6, 0x15, 0xaf, 0xaa, 0x28, 0x78, 0x94, 0x43, 0x35, 0x02, 0xef, 0x7a, 0x0f, + 0xb1, 0x34, 0xce, 0xbd, 0xf9, 0xbd, 0x2a, 0x1d, 0x28, 0xbd, 0x79, 0xa4, 0xd3, 0xf9, 0xfc, 0x26, 0xa7, 0xe8, 0xd5, + 0xf5, 0x3b, 0x78, 0x08, 0x16, 0x05, 0xe2, 0xd5, 0x1a, 0xde, 0x9b, 0x5b, 0x24, 0x2b, 0xf5, 0x82, 0xe7, 0x50, 0x2a, + 0xaa, 0x2e, 0x3c, 0x20, 0x50, 0x57, 0x2c, 0x60, 0x8c, 0xa3, 0x45, 0x33, 0x7f, 0x57, 0x50, 0x22, 0x28, 0x5a, 0xb2, + 0x15, 0x45, 0x4c, 0x42, 0x1d, 0x50, 0x52, 0x24, 0x99, 0x6a, 0x8e, 0x8c, 0x9a, 0xee, 0x6d, 0x25, 0x69, 0x88, 0xae, + 0xaa, 0x7a, 0xab, 0x85, 0x24, 0x39, 0xe1, 0x4b, 0x9a, 0x1e, 0x84, 0x29, 0xea, 0x6d, 0xd5, 0x36, 0xe8, 0x97, 0x17, + 0x6f, 0x5e, 0xab, 0x87, 0x36, 0x45, 0x4e, 0xa7, 0x6c, 0x23, 0xd1, 0x8f, 0x37, 0x2f, 0x50, 0x5b, 0xc3, 0xa6, 0x53, + 0x63, 0x5b, 0xb5, 0xa2, 0xcd, 0x1a, 0x2a, 0x4b, 0xaa, 0x48, 0x40, 0xb9, 0xa0, 0x52, 0x42, 0xa1, 0x21, 0x30, 0x94, + 0xce, 0xda, 0x13, 0x53, 0x75, 0x83, 0xbb, 0x20, 0x7e, 0xde, 0x95, 0xd7, 0x51, 0x1e, 0x18, 0xd7, 0xaf, 0x3b, 0x6a, + 0x70, 0x3d, 0x98, 0x47, 0xea, 0x39, 0x8d, 0x88, 0x7e, 0x84, 0xc4, 0x83, 0x35, 0xcb, 0x98, 0x7a, 0xb8, 0xcd, 0x23, + 0x5d, 0x8f, 0x2a, 0x09, 0xaa, 0x24, 0x32, 0x5f, 0x34, 0x74, 0xaf, 0xa0, 0x7c, 0x09, 0xaf, 0x64, 0xd8, 0x70, 0xa8, + 0x50, 0x12, 0x9a, 0x57, 0x05, 0x54, 0x40, 0xf1, 0xf5, 0xf5, 0x0f, 0xff, 0x52, 0x9f, 0x3f, 0xc0, 0xcf, 0x13, 0x27, + 0x3c, 0x29, 0x0c, 0xa3, 0xea, 0x74, 0x7c, 0xe3, 0xa1, 0xf9, 0x90, 0x51, 0xc3, 0x7b, 0x00, 0xfc, 0x4e, 0xef, 0x49, + 0x79, 0x77, 0x98, 0xec, 0x24, 0xe9, 0x5f, 0x5d, 0xd9, 0x1a, 0x26, 0xd1, 0x2e, 0x4a, 0x26, 0xe7, 0xd7, 0x60, 0x60, + 0x34, 0x30, 0x0b, 0xe0, 0x9c, 0x72, 0xc0, 0xd0, 0xe6, 0x1d, 0x0f, 0xec, 0xa8, 0x42, 0xec, 0x27, 0x8d, 0x98, 0xd9, + 0x60, 0xed, 0x65, 0x49, 0x65, 0x5e, 0xa5, 0xf1, 0xbb, 0x1f, 0xaf, 0x6f, 0x8e, 0x1e, 0x77, 0xb0, 0x52, 0x9e, 0x98, + 0x0f, 0x2c, 0x6d, 0x21, 0x59, 0x4d, 0x1a, 0xa9, 0xc5, 0x3a, 0x2a, 0xce, 0x0e, 0x1e, 0xe9, 0x75, 0xbd, 0x33, 0xda, + 0xa9, 0x8e, 0x71, 0x30, 0x47, 0x0f, 0xd9, 0x78, 0xd0, 0xfd, 0x99, 0x95, 0x03, 0x73, 0x14, 0x07, 0xe6, 0x5c, 0x0e, + 0xf4, 0xe7, 0xa7, 0xdf, 0x01, 0xf1, 0x69, 0xfc, 0xac, 0x8e, 0x12, 0x00, 0x00}; + +} // namespace captive_portal +} // namespace esphome diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index d4e37f62f2..3bfdea0ab5 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -4,60 +4,27 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/components/wifi/wifi_component.h" +#include "captive_index.h" namespace esphome { namespace captive_portal { static const char *const TAG = "captive_portal"; -void CaptivePortal::handle_index(AsyncWebServerRequest *request) { - AsyncResponseStream *stream = request->beginResponseStream("text/html"); - stream->print(F("")); - stream->print(App.get_name().c_str()); - stream->print(F("")); - stream->print(F("")); - stream->print(F("")); - stream->print(F("

WiFi Networks

")); - - if (request->hasArg("save")) { - stream->print(F("
The ESP will now try to connect to the network...
Please give it some " - "time to connect.
Note: Copy the changed network to your YAML file - the next OTA update will " - "overwrite these settings.
")); - } +void CaptivePortal::handle_config(AsyncWebServerRequest *request) { + AsyncResponseStream *stream = request->beginResponseStream("application/json"); + stream->addHeader("cache-control", "public, max-age=0, must-revalidate"); + stream->printf(R"({"name":"%s","aps":[{})", App.get_name().c_str()); for (auto &scan : wifi::global_wifi_component->get_scan_result()) { if (scan.get_is_hidden()) continue; - stream->print(F("")); + // Assumes no " in ssid, possible unicode isses? + stream->printf(R"(,{"ssid":"%s","rssi":%d,"lock":%d})", scan.get_ssid().c_str(), scan.get_rssi(), + scan.get_with_auth()); } - - stream->print(F("

WiFi Settings







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

OTA Update

")); - stream->print(F("
")); + stream->print(F("]}")); request->send(stream); } void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { @@ -68,7 +35,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { 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"); + request->redirect("/?save"); } void CaptivePortal::setup() {} @@ -98,44 +65,21 @@ void CaptivePortal::start() { this->active_ = true; } -const char STYLESHEET_CSS[] PROGMEM = - R"(*{box-sizing:inherit}div,input{padding:5px;font-size:1em}input{width:95%}body{text-align:center;font-family:sans-serif}button{border:0;border-radius:.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;padding:0}.main{text-align:left;display:inline-block;min-width:260px}.network{display:flex;justify-content:space-between;align-items:center}.network-left{display:flex;align-items:center}.network-ssid{margin-bottom:-7px;margin-left:10px}.info{border:1px solid;margin:10px 0;padding:15px 10px;color:#4f8a10;background-color:#dff2bf})"; -const char LOCK_SVG[] PROGMEM = - R"()"; - void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { if (req->url() == "/") { - this->handle_index(req); + AsyncWebServerResponse *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ)); + response->addHeader("Content-Encoding", "gzip"); + req->send(response); + return; + } else if (req->url() == "/config.json") { + this->handle_config(req); return; } else if (req->url() == "/wifisave") { this->handle_wifisave(req); return; - } else if (req->url() == "/stylesheet.css") { - req->send_P(200, "text/css", STYLESHEET_CSS); - return; - } else if (req->url() == "/lock.svg") { - req->send_P(200, "image/svg+xml", LOCK_SVG); - return; } - - AsyncResponseStream *stream = req->beginResponseStream("image/svg+xml"); - stream->print(F("url() == "/wifi-strength-4.svg") { - stream->print(F("3z")); - } else { - if (req->url() == "/wifi-strength-1.svg") { - stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-5.1 6.36a8.43 8.43 0 0 0-7.22-.01L3.27 7.4")); - } else if (req->url() == "/wifi-strength-2.svg") { - stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-3.21 3.98a11.32 11.32 0 0 0-11 0L3.27 7.4")); - } else if (req->url() == "/wifi-strength-3.svg") { - stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-1.94 2.43A13.6 13.6 0 0 0 12 8C9 8 6.68 9 5.21 9.84l-1.94-2.")); - } - stream->print(F("4A16.94 16.94 0 0 1 12 5z")); - } - stream->print(F("\"/>")); - req->send(stream); } + CaptivePortal::CaptivePortal(web_server_base::WebServerBase *base) : base_(base) { global_captive_portal = this; } float CaptivePortal::get_setup_priority() const { // Before WiFi diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h index b308de42b7..0e68bc9cef 100644 --- a/esphome/components/captive_portal/captive_portal.h +++ b/esphome/components/captive_portal/captive_portal.h @@ -58,7 +58,7 @@ class CaptivePortal : public AsyncWebHandler, public Component { return false; } - void handle_index(AsyncWebServerRequest *request); + void handle_config(AsyncWebServerRequest *request); void handle_wifisave(AsyncWebServerRequest *request); diff --git a/esphome/components/captive_portal/index.html b/esphome/components/captive_portal/index.html deleted file mode 100644 index 627bf81215..0000000000 --- a/esphome/components/captive_portal/index.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - {{ App.get_name() }} - - - - -
-

WiFi Networks

-
- The ESP will now try to connect to the network...
- Please give it some time to connect.
- Note: Copy the changed network to your YAML file - the next OTA update will overwrite these settings. -
- - - -

WiFi Settings

-
-
-
-
- -
-

-
- -

OTA Update

-
- - -
-
- - diff --git a/esphome/components/captive_portal/lock.svg b/esphome/components/captive_portal/lock.svg deleted file mode 100644 index 743a1cc55a..0000000000 --- a/esphome/components/captive_portal/lock.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/esphome/components/captive_portal/stylesheet.css b/esphome/components/captive_portal/stylesheet.css deleted file mode 100644 index 73f82f05f1..0000000000 --- a/esphome/components/captive_portal/stylesheet.css +++ /dev/null @@ -1,58 +0,0 @@ -* { - box-sizing: inherit; -} - -div, input { - padding: 5px; - font-size: 1em; -} - -input { - width: 95%; -} - -body { - text-align: center; - font-family: sans-serif; -} - -button { - border: 0; - border-radius: 0.3rem; - background-color: #1fa3ec; - color: #fff; - line-height: 2.4rem; - font-size: 1.2rem; - width: 100%; - padding: 0; -} - -.main { - text-align: left; - display: inline-block; - min-width: 260px; -} - -.network { - display: flex; - justify-content: space-between; - align-items: center; -} - -.network-left { - display: flex; - align-items: center; -} - -.network-ssid { - margin-bottom: -7px; - margin-left: 10px; -} - -.info { - border: 1px solid; - margin: 10px 0px; - padding: 15px 10px; - color: #4f8a10; - background-color: #dff2bf; -} diff --git a/esphome/components/captive_portal/wifi-strength-1.svg b/esphome/components/captive_portal/wifi-strength-1.svg deleted file mode 100644 index 189a38193c..0000000000 --- a/esphome/components/captive_portal/wifi-strength-1.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/esphome/components/captive_portal/wifi-strength-2.svg b/esphome/components/captive_portal/wifi-strength-2.svg deleted file mode 100644 index 9b4b2d2396..0000000000 --- a/esphome/components/captive_portal/wifi-strength-2.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/esphome/components/captive_portal/wifi-strength-3.svg b/esphome/components/captive_portal/wifi-strength-3.svg deleted file mode 100644 index 44b7532bb7..0000000000 --- a/esphome/components/captive_portal/wifi-strength-3.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/esphome/components/captive_portal/wifi-strength-4.svg b/esphome/components/captive_portal/wifi-strength-4.svg deleted file mode 100644 index a22b0b8281..0000000000 --- a/esphome/components/captive_portal/wifi-strength-4.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 958ad0d750378e6ca47bd45bbd9a44cf391de5cc Mon Sep 17 00:00:00 2001 From: Roi Tagar Date: Thu, 17 Feb 2022 06:03:54 +0200 Subject: [PATCH 117/238] HttpRequestComponent::get_string - avoid copy (#2988) --- esphome/components/http_request/http_request.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index 64f3f97de9..4e1cfe94b3 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -120,10 +120,16 @@ void HttpRequestComponent::close() { } const char *HttpRequestComponent::get_string() { - // 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(); +#if defined(ESP32) + // The static variable is here because HTTPClient::getString() returns a String on ESP32, + // and we need something to keep a buffer alive. + static String str; +#else + // However on ESP8266, HTTPClient::getString() returns a String& to a member variable. + // Leaving this the default so that any new platform either doesn't copy, or encounters a compilation error. + auto & +#endif + str = this->client_.getString(); return str.c_str(); } From 34c229fd33b57dc08b7953c8d0b39cf0fab3bd1f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 17 Feb 2022 11:56:14 +0100 Subject: [PATCH 118/238] Fix platformio docker version mismstch (#3215) --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index d97eed65f7..3de497faba 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -45,7 +45,7 @@ RUN \ # Ubuntu python3-pip is missing wheel pip3 install --no-cache-dir \ wheel==0.37.1 \ - platformio==5.2.4 \ + platformio==5.2.5 \ # Change some platformio settings && platformio settings set enable_telemetry No \ && platformio settings set check_libraries_interval 1000000 \ From 953f0569fbf060cc066a9f05b702824a04f5c2d1 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 17 Feb 2022 12:07:36 +0100 Subject: [PATCH 119/238] Docker ha-addon switch to nginx-light (#3218) --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 3de497faba..65e831f89b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -95,7 +95,7 @@ RUN \ apt-get update \ # Use pinned versions so that we get updates with build caching && apt-get install -y --no-install-recommends \ - nginx=1.18.0-6.1 \ + nginx-light=1.18.0-6.1 \ && rm -rf \ /tmp/* \ /var/{cache,log}/* \ From 8cb9be7560ff370da8d70c098e5e90170f75ee62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Panella?= Date: Thu, 17 Feb 2022 14:14:10 -0600 Subject: [PATCH 120/238] Analog threshold (#3190) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + .../components/analog_threshold/__init__.py | 1 + .../analog_threshold_binary_sensor.cpp | 40 +++++++++++++++++ .../analog_threshold_binary_sensor.h | 29 ++++++++++++ .../analog_threshold/binary_sensor.py | 44 +++++++++++++++++++ tests/test1.yaml | 15 +++++++ 6 files changed, 130 insertions(+) create mode 100644 esphome/components/analog_threshold/__init__.py create mode 100644 esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp create mode 100644 esphome/components/analog_threshold/analog_threshold_binary_sensor.h create mode 100644 esphome/components/analog_threshold/binary_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 165ce52485..f533ff5c47 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -19,6 +19,7 @@ esphome/components/airthings_wave_mini/* @ncareau esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/am43/* @buxtronix esphome/components/am43/cover/* @buxtronix +esphome/components/analog_threshold/* @ianchi esphome/components/animation/* @syndlex esphome/components/anova/* @buxtronix esphome/components/api/* @OttoWinter diff --git a/esphome/components/analog_threshold/__init__.py b/esphome/components/analog_threshold/__init__.py new file mode 100644 index 0000000000..9ae2df986d --- /dev/null +++ b/esphome/components/analog_threshold/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@ianchi"] diff --git a/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp b/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp new file mode 100644 index 0000000000..f679b9994f --- /dev/null +++ b/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp @@ -0,0 +1,40 @@ +#include "analog_threshold_binary_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace analog_threshold { + +static const char *const TAG = "analog_threshold.binary_sensor"; + +void AnalogThresholdBinarySensor::setup() { + float sensor_value = this->sensor_->get_state(); + + // TRUE state is defined to be when sensor is >= threshold + // so when undefined sensor value initialize to FALSE + if (std::isnan(sensor_value)) { + this->publish_initial_state(false); + } else { + this->publish_initial_state(sensor_value >= (this->lower_threshold_ + this->upper_threshold_) / 2.0f); + } +} + +void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) { + this->sensor_ = analog_sensor; + + this->sensor_->add_on_state_callback([this](float sensor_value) { + // if there is an invalid sensor reading, ignore the change and keep the current state + if (!std::isnan(sensor_value)) { + this->publish_state(sensor_value >= (this->state ? this->lower_threshold_ : this->upper_threshold_)); + } + }); +} + +void AnalogThresholdBinarySensor::dump_config() { + LOG_BINARY_SENSOR("", "Analog Threshold Binary Sensor", this); + LOG_SENSOR(" ", "Sensor", this->sensor_); + ESP_LOGCONFIG(TAG, " Upper threshold: %.11f", this->upper_threshold_); + ESP_LOGCONFIG(TAG, " Lower threshold: %.11f", this->lower_threshold_); +} + +} // namespace analog_threshold +} // namespace esphome diff --git a/esphome/components/analog_threshold/analog_threshold_binary_sensor.h b/esphome/components/analog_threshold/analog_threshold_binary_sensor.h new file mode 100644 index 0000000000..619aef1075 --- /dev/null +++ b/esphome/components/analog_threshold/analog_threshold_binary_sensor.h @@ -0,0 +1,29 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace analog_threshold { + +class AnalogThresholdBinarySensor : public Component, public binary_sensor::BinarySensor { + public: + void dump_config() override; + void setup() override; + + float get_setup_priority() const override { return setup_priority::DATA; } + + void set_sensor(sensor::Sensor *analog_sensor); + void set_upper_threshold(float threshold) { this->upper_threshold_ = threshold; } + void set_lower_threshold(float threshold) { this->lower_threshold_ = threshold; } + + protected: + sensor::Sensor *sensor_{nullptr}; + + float upper_threshold_; + float lower_threshold_; +}; + +} // namespace analog_threshold +} // namespace esphome diff --git a/esphome/components/analog_threshold/binary_sensor.py b/esphome/components/analog_threshold/binary_sensor.py new file mode 100644 index 0000000000..ef4a6044bf --- /dev/null +++ b/esphome/components/analog_threshold/binary_sensor.py @@ -0,0 +1,44 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor, sensor +from esphome.const import ( + CONF_SENSOR_ID, + CONF_THRESHOLD, +) + +analog_threshold_ns = cg.esphome_ns.namespace("analog_threshold") + +AnalogThresholdBinarySensor = analog_threshold_ns.class_( + "AnalogThresholdBinarySensor", binary_sensor.BinarySensor, cg.Component +) + +CONF_UPPER = "upper" +CONF_LOWER = "lower" + +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(AnalogThresholdBinarySensor), + cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor), + cv.Required(CONF_THRESHOLD): cv.Any( + cv.float_, + cv.Schema( + {cv.Required(CONF_UPPER): cv.float_, cv.Required(CONF_LOWER): cv.float_} + ), + ), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + await cg.register_component(var, config) + + sens = await cg.get_variable(config[CONF_SENSOR_ID]) + cg.add(var.set_sensor(sens)) + + if isinstance(config[CONF_THRESHOLD], float): + cg.add(var.set_upper_threshold(config[CONF_THRESHOLD])) + cg.add(var.set_lower_threshold(config[CONF_THRESHOLD])) + else: + cg.add(var.set_upper_threshold(config[CONF_THRESHOLD][CONF_UPPER])) + cg.add(var.set_lower_threshold(config[CONF_THRESHOLD][CONF_LOWER])) diff --git a/tests/test1.yaml b/tests/test1.yaml index 54343f59c7..e2d5fdc0c5 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1307,6 +1307,21 @@ binary_sensor: ] - platform: as3935 name: "Storm Alert" + - platform: analog_threshold + name: Analog Trheshold 1 + sensor_id: template_sensor + threshold: + upper: 110 + lower: 90 + filters: + - delayed_on: 0s + - delayed_off: 10s + - platform: analog_threshold + name: Analog Trheshold 2 + sensor_id: template_sensor + threshold: 100 + filters: + - invert: pca9685: frequency: 500 From ccce4b19e8dd26f674c9b8d816627f191444c1df Mon Sep 17 00:00:00 2001 From: mipa87 <62723159+mipa87@users.noreply.github.com> Date: Thu, 17 Feb 2022 21:47:31 +0100 Subject: [PATCH 121/238] Fix pm1006 polling component definition (#3210) --- esphome/components/pm1006/sensor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/pm1006/sensor.py b/esphome/components/pm1006/sensor.py index 1e648be199..2df9edbf45 100644 --- a/esphome/components/pm1006/sensor.py +++ b/esphome/components/pm1006/sensor.py @@ -16,7 +16,9 @@ CODEOWNERS = ["@habbie"] DEPENDENCIES = ["uart"] pm1006_ns = cg.esphome_ns.namespace("pm1006") -PM1006Component = pm1006_ns.class_("PM1006Component", uart.UARTDevice, cg.Component) +PM1006Component = pm1006_ns.class_( + "PM1006Component", uart.UARTDevice, cg.PollingComponent +) CONFIG_SCHEMA = cv.All( From 140db85d21d5030b9a4fd8f61f848e6433b302a4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 18 Feb 2022 10:11:22 +1300 Subject: [PATCH 122/238] Add LONG LONG flag for arduinojson (#3212) --- esphome/components/json/json_util.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/json/json_util.h b/esphome/components/json/json_util.h index 57fe6107d8..2299a4cfed 100644 --- a/esphome/components/json/json_util.h +++ b/esphome/components/json/json_util.h @@ -4,9 +4,10 @@ #include "esphome/core/helpers.h" -#undef ARDUINOJSON_ENABLE_STD_STRING #define ARDUINOJSON_ENABLE_STD_STRING 1 // NOLINT +#define ARDUINOJSON_USE_LONG_LONG 1 // NOLINT + #include namespace esphome { From 3b8bb09ae3709d844b6bc3aecc4e0b32c5b5d166 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 18 Feb 2022 10:27:20 +1300 Subject: [PATCH 123/238] Add class as first positional arg to sensor_schema (#3216) --- esphome/components/adc/sensor.py | 2 +- esphome/components/ads1115/sensor.py | 2 +- esphome/components/as3935/sensor.py | 3 - esphome/components/bh1750/sensor.py | 6 +- .../components/binary_sensor_map/sensor.py | 8 +- esphome/components/bl0940/sensor.py | 47 ++-- .../components/ble_client/sensor/__init__.py | 8 +- esphome/components/ble_rssi/sensor.py | 6 +- esphome/components/cd74hc4067/sensor.py | 4 +- esphome/components/ct_clamp/sensor.py | 6 +- esphome/components/dallas/sensor.py | 6 +- esphome/components/daly_bms/sensor.py | 138 +++++----- esphome/components/demo/__init__.py | 11 +- esphome/components/dsmr/sensor.py | 251 +++++++++--------- esphome/components/duty_cycle/sensor.py | 12 +- esphome/components/esp32_hall/sensor.py | 25 +- esphome/components/fingerprint_grow/sensor.py | 7 - esphome/components/gps/__init__.py | 6 - esphome/components/hmc5883l/sensor.py | 2 - .../homeassistant/sensor/__init__.py | 7 +- esphome/components/hrxl_maxsonar_wr/sensor.py | 26 +- esphome/components/hx711/sensor.py | 6 +- esphome/components/ltr390/sensor.py | 20 +- esphome/components/max31855/sensor.py | 6 +- esphome/components/max31856/sensor.py | 6 +- esphome/components/max31865/sensor.py | 6 +- esphome/components/max6675/sensor.py | 10 +- esphome/components/mcp9808/sensor.py | 10 +- .../mqtt_subscribe/sensor/__init__.py | 8 +- esphome/components/nextion/sensor/__init__.py | 2 +- esphome/components/ntc/sensor.py | 6 +- esphome/components/pid/sensor/__init__.py | 6 +- .../components/pipsolar/sensor/__init__.py | 153 +++++++---- esphome/components/pmsx003/sensor.py | 67 +++-- esphome/components/pulse_counter/sensor.py | 6 +- esphome/components/pulse_meter/sensor.py | 5 +- esphome/components/pulse_width/sensor.py | 6 +- esphome/components/qmc5883l/sensor.py | 2 - esphome/components/resistance/sensor.py | 6 +- esphome/components/rotary_encoder/sensor.py | 8 +- esphome/components/ruuvitag/sensor.py | 3 - esphome/components/sdp3x/sensor.py | 6 +- esphome/components/sensor/__init__.py | 5 + esphome/components/sgp40/sensor.py | 6 +- esphome/components/sts3x/sensor.py | 10 +- esphome/components/sun/sensor/__init__.py | 8 +- .../components/teleinfo/sensor/__init__.py | 14 +- .../components/template/sensor/__init__.py | 7 +- esphome/components/tmp102/sensor.py | 10 +- esphome/components/tmp117/sensor.py | 10 +- esphome/components/tof10120/sensor.py | 6 +- .../components/total_daily_energy/sensor.py | 6 +- esphome/components/tsl2561/sensor.py | 6 +- esphome/components/tsl2591/sensor.py | 36 +-- esphome/components/tx20/sensor.py | 2 - esphome/components/ultrasonic/sensor.py | 6 +- esphome/components/uptime/sensor.py | 27 +- esphome/components/vl53l0x/sensor.py | 6 +- esphome/components/wifi_signal/sensor.py | 27 +- .../components/xiaomi_cgpr1/binary_sensor.py | 7 +- .../xiaomi_mjyd02yla/binary_sensor.py | 4 +- esphome/core/defines.h | 1 + 62 files changed, 506 insertions(+), 629 deletions(-) diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index c812e67a68..5443b9875a 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -133,6 +133,7 @@ ADCSensor = adc_ns.class_( CONFIG_SCHEMA = cv.All( sensor.sensor_schema( + ADCSensor, unit_of_measurement=UNIT_VOLT, accuracy_decimals=2, device_class=DEVICE_CLASS_VOLTAGE, @@ -140,7 +141,6 @@ CONFIG_SCHEMA = cv.All( ) .extend( { - 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( diff --git a/esphome/components/ads1115/sensor.py b/esphome/components/ads1115/sensor.py index da33a39041..190e641ca3 100644 --- a/esphome/components/ads1115/sensor.py +++ b/esphome/components/ads1115/sensor.py @@ -52,6 +52,7 @@ ADS1115Sensor = ads1115_ns.class_( CONF_ADS1115_ID = "ads1115_id" CONFIG_SCHEMA = ( sensor.sensor_schema( + ADS1115Sensor, unit_of_measurement=UNIT_VOLT, accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, @@ -59,7 +60,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(ADS1115Sensor), cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component), cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"), cv.Required(CONF_GAIN): validate_gain, diff --git a/esphome/components/as3935/sensor.py b/esphome/components/as3935/sensor.py index 271a29e0fc..5a3967ed7e 100644 --- a/esphome/components/as3935/sensor.py +++ b/esphome/components/as3935/sensor.py @@ -4,7 +4,6 @@ from esphome.components import sensor from esphome.const import ( CONF_DISTANCE, CONF_LIGHTNING_ENERGY, - STATE_CLASS_NONE, UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, ICON_FLASH, @@ -20,12 +19,10 @@ CONFIG_SCHEMA = cv.Schema( unit_of_measurement=UNIT_KILOMETER, icon=ICON_SIGNAL_DISTANCE_VARIANT, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_LIGHTNING_ENERGY): sensor.sensor_schema( icon=ICON_FLASH, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/bh1750/sensor.py b/esphome/components/bh1750/sensor.py index 156c7bb375..904e716eb8 100644 --- a/esphome/components/bh1750/sensor.py +++ b/esphome/components/bh1750/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, CONF_RESOLUTION, DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT, @@ -27,6 +26,7 @@ BH1750Sensor = bh1750_ns.class_( CONF_MEASUREMENT_TIME = "measurement_time" CONFIG_SCHEMA = ( sensor.sensor_schema( + BH1750Sensor, unit_of_measurement=UNIT_LUX, accuracy_decimals=1, device_class=DEVICE_CLASS_ILLUMINANCE, @@ -34,7 +34,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(BH1750Sensor), cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum( BH1750_RESOLUTIONS, float=True ), @@ -52,9 +51,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) await i2c.register_i2c_device(var, config) cg.add(var.set_resolution(config[CONF_RESOLUTION])) diff --git a/esphome/components/binary_sensor_map/sensor.py b/esphome/components/binary_sensor_map/sensor.py index 946e2f9e62..7ddf0ecf2a 100644 --- a/esphome/components/binary_sensor_map/sensor.py +++ b/esphome/components/binary_sensor_map/sensor.py @@ -3,14 +3,12 @@ import esphome.config_validation as cv from esphome.components import sensor, binary_sensor from esphome.const import ( - CONF_ID, CONF_CHANNELS, CONF_VALUE, CONF_TYPE, ICON_CHECK_CIRCLE_OUTLINE, CONF_BINARY_SENSOR, CONF_GROUP, - STATE_CLASS_NONE, ) DEPENDENCIES = ["binary_sensor"] @@ -33,12 +31,11 @@ entry = { CONFIG_SCHEMA = cv.typed_schema( { CONF_GROUP: sensor.sensor_schema( + BinarySensorMap, icon=ICON_CHECK_CIRCLE_OUTLINE, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, ).extend( { - cv.GenerateID(): cv.declare_id(BinarySensorMap), cv.Required(CONF_CHANNELS): cv.All( cv.ensure_list(entry), cv.Length(min=1) ), @@ -50,9 +47,8 @@ CONFIG_SCHEMA = cv.typed_schema( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) constant = SENSOR_MAP_TYPES[config[CONF_TYPE]] cg.add(var.set_sensor_type(constant)) diff --git a/esphome/components/bl0940/sensor.py b/esphome/components/bl0940/sensor.py index ce630b7408..9f516a8691 100644 --- a/esphome/components/bl0940/sensor.py +++ b/esphome/components/bl0940/sensor.py @@ -12,9 +12,7 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_AMPERE, UNIT_CELSIUS, UNIT_KILOWATT_HOURS, @@ -35,38 +33,39 @@ CONFIG_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 + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CURRENT): sensor.sensor_schema( - UNIT_AMPERE, - ICON_EMPTY, - 2, - DEVICE_CLASS_CURRENT, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_POWER): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ENERGY): sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 0, - DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ENERGY, ), cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 0, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_NONE, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_EXTERNAL_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 0, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_NONE, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/ble_client/sensor/__init__.py b/esphome/components/ble_client/sensor/__init__.py index 4aa6a92ba5..71cfb03ae0 100644 --- a/esphome/components/ble_client/sensor/__init__.py +++ b/esphome/components/ble_client/sensor/__init__.py @@ -2,9 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, ble_client, esp32_ble_tracker from esphome.const import ( - CONF_ID, CONF_LAMBDA, - STATE_CLASS_NONE, CONF_TRIGGER_ID, CONF_SERVICE_UUID, ) @@ -31,12 +29,11 @@ BLESensorNotifyTrigger = ble_client_ns.class_( CONFIG_SCHEMA = cv.All( sensor.sensor_schema( + BLESensor, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, ) .extend( { - cv.GenerateID(): cv.declare_id(BLESensor), cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid, cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid, @@ -57,7 +54,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) 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])) @@ -124,7 +121,6 @@ async def to_code(config): await cg.register_component(var, config) await ble_client.register_ble_node(var, config) cg.add(var.set_enable_notify(config[CONF_NOTIFY])) - await sensor.register_sensor(var, config) for conf in config.get(CONF_ON_NOTIFY, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await ble_client.register_ble_node(trigger, config) diff --git a/esphome/components/ble_rssi/sensor.py b/esphome/components/ble_rssi/sensor.py index 0c4308b11a..fd2c2e5cb1 100644 --- a/esphome/components/ble_rssi/sensor.py +++ b/esphome/components/ble_rssi/sensor.py @@ -4,7 +4,6 @@ from esphome.components import sensor, esp32_ble_tracker from esphome.const import ( CONF_SERVICE_UUID, CONF_MAC_ADDRESS, - CONF_ID, DEVICE_CLASS_SIGNAL_STRENGTH, STATE_CLASS_MEASUREMENT, UNIT_DECIBEL, @@ -19,6 +18,7 @@ BLERSSISensor = ble_rssi_ns.class_( CONFIG_SCHEMA = cv.All( sensor.sensor_schema( + BLERSSISensor, unit_of_measurement=UNIT_DECIBEL, accuracy_decimals=0, device_class=DEVICE_CLASS_SIGNAL_STRENGTH, @@ -26,7 +26,6 @@ CONFIG_SCHEMA = cv.All( ) .extend( { - cv.GenerateID(): cv.declare_id(BLERSSISensor), cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, } @@ -38,10 +37,9 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await esp32_ble_tracker.register_ble_device(var, config) - await sensor.register_sensor(var, config) if CONF_MAC_ADDRESS in config: cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) diff --git a/esphome/components/cd74hc4067/sensor.py b/esphome/components/cd74hc4067/sensor.py index 7c7cf9ccb7..3eee34b85e 100644 --- a/esphome/components/cd74hc4067/sensor.py +++ b/esphome/components/cd74hc4067/sensor.py @@ -25,6 +25,7 @@ CONF_CD74HC4067_ID = "cd74hc4067_id" CONFIG_SCHEMA = ( sensor.sensor_schema( + CD74HC4067Sensor, unit_of_measurement=UNIT_VOLT, accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, @@ -33,7 +34,6 @@ CONFIG_SCHEMA = ( ) .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), @@ -47,8 +47,8 @@ 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) + await cg.register_component(var, config) cg.add(var.set_pin(config[CONF_NUMBER])) sens = await cg.get_variable(config[CONF_SENSOR]) diff --git a/esphome/components/ct_clamp/sensor.py b/esphome/components/ct_clamp/sensor.py index 049905d0a7..18ea5877d2 100644 --- a/esphome/components/ct_clamp/sensor.py +++ b/esphome/components/ct_clamp/sensor.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome.components import sensor, voltage_sampler from esphome.const import ( CONF_SENSOR, - CONF_ID, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT, UNIT_AMPERE, @@ -19,6 +18,7 @@ CTClampSensor = ct_clamp_ns.class_("CTClampSensor", sensor.Sensor, cg.PollingCom CONFIG_SCHEMA = ( sensor.sensor_schema( + CTClampSensor, unit_of_measurement=UNIT_AMPERE, accuracy_decimals=2, device_class=DEVICE_CLASS_CURRENT, @@ -26,7 +26,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(CTClampSensor), cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler), cv.Optional( CONF_SAMPLE_DURATION, default="200ms" @@ -38,9 +37,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) sens = await cg.get_variable(config[CONF_SENSOR]) cg.add(var.set_source(sens)) diff --git a/esphome/components/dallas/sensor.py b/esphome/components/dallas/sensor.py index 14ad0efa7b..9288f0a3a6 100644 --- a/esphome/components/dallas/sensor.py +++ b/esphome/components/dallas/sensor.py @@ -9,7 +9,6 @@ from esphome.const import ( DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, - CONF_ID, ) from . import DallasComponent, dallas_ns @@ -17,13 +16,13 @@ DallasTemperatureSensor = dallas_ns.class_("DallasTemperatureSensor", sensor.Sen CONFIG_SCHEMA = cv.All( sensor.sensor_schema( + DallasTemperatureSensor, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ).extend( { - cv.GenerateID(): cv.declare_id(DallasTemperatureSensor), cv.GenerateID(CONF_DALLAS_ID): cv.use_id(DallasComponent), cv.Optional(CONF_ADDRESS): cv.hex_int, cv.Optional(CONF_INDEX): cv.positive_int, @@ -36,7 +35,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): hub = await cg.get_variable(config[CONF_DALLAS_ID]) - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) if CONF_ADDRESS in config: cg.add(var.set_address(config[CONF_ADDRESS])) @@ -49,4 +48,3 @@ async def to_code(config): cg.add(var.set_parent(hub)) cg.add(hub.register_sensor(var)) - await sensor.register_sensor(var, config) diff --git a/esphome/components/daly_bms/sensor.py b/esphome/components/daly_bms/sensor.py index 1d0ee89914..0ba68d3786 100644 --- a/esphome/components/daly_bms/sensor.py +++ b/esphome/components/daly_bms/sensor.py @@ -11,14 +11,11 @@ from esphome.const import ( 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, @@ -70,109 +67,94 @@ CONFIG_SCHEMA = cv.All( { 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, + unit_of_measurement=UNIT_VOLT, + icon=ICON_FLASH, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CURRENT): sensor.sensor_schema( - UNIT_AMPERE, - ICON_CURRENT_DC, - 1, - DEVICE_CLASS_CURRENT, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_AMPERE, + icon=ICON_CURRENT_DC, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, - ICON_PERCENT, - 1, - DEVICE_CLASS_BATTERY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + icon=ICON_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_MAX_CELL_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, - ICON_FLASH, - 2, - DEVICE_CLASS_VOLTAGE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT, + icon=ICON_FLASH, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_MAX_CELL_VOLTAGE_NUMBER): sensor.sensor_schema( - UNIT_EMPTY, - ICON_COUNTER, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + icon=ICON_COUNTER, + accuracy_decimals=0, ), cv.Optional(CONF_MIN_CELL_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, - ICON_FLASH, - 2, - DEVICE_CLASS_VOLTAGE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT, + icon=ICON_FLASH, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_MIN_CELL_VOLTAGE_NUMBER): sensor.sensor_schema( - UNIT_EMPTY, - ICON_COUNTER, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + icon=ICON_COUNTER, + accuracy_decimals=0, ), cv.Optional(CONF_MAX_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_THERMOMETER_CHEVRON_UP, - 0, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER_CHEVRON_UP, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_MAX_TEMPERATURE_PROBE_NUMBER): sensor.sensor_schema( - UNIT_EMPTY, - ICON_COUNTER, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + icon=ICON_COUNTER, + accuracy_decimals=0, ), cv.Optional(CONF_MIN_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_THERMOMETER_CHEVRON_DOWN, - 0, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER_CHEVRON_DOWN, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_MIN_TEMPERATURE_PROBE_NUMBER): sensor.sensor_schema( - UNIT_EMPTY, - ICON_COUNTER, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + icon=ICON_COUNTER, + accuracy_decimals=0, ), cv.Optional(CONF_REMAINING_CAPACITY): sensor.sensor_schema( - UNIT_AMPERE_HOUR, - ICON_GAUGE, - 2, - DEVICE_CLASS_VOLTAGE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_AMPERE_HOUR, + icon=ICON_GAUGE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CELLS_NUMBER): sensor.sensor_schema( - UNIT_EMPTY, - ICON_COUNTER, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + icon=ICON_COUNTER, + accuracy_decimals=0, ), cv.Optional(CONF_TEMPERATURE_1): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_THERMOMETER, - 0, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TEMPERATURE_2): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_THERMOMETER, - 0, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/demo/__init__.py b/esphome/components/demo/__init__.py index f20a96ebd4..77b1680d26 100644 --- a/esphome/components/demo/__init__.py +++ b/esphome/components/demo/__init__.py @@ -337,12 +337,8 @@ CONFIG_SCHEMA = cv.Schema( }, ], ): [ - sensor.sensor_schema(accuracy_decimals=0) - .extend(cv.polling_component_schema("60s")) - .extend( - { - cv.GenerateID(): cv.declare_id(DemoSensor), - } + sensor.sensor_schema(DemoSensor, accuracy_decimals=0).extend( + cv.polling_component_schema("60s") ) ], cv.Optional( @@ -427,9 +423,8 @@ async def to_code(config): cg.add(var.set_type(conf[CONF_TYPE])) for conf in config[CONF_SENSORS]: - var = cg.new_Pvariable(conf[CONF_ID]) + var = await sensor.new_sensor(conf) await cg.register_component(var, conf) - await sensor.register_sensor(var, conf) for conf in config[CONF_SWITCHES]: var = cg.new_Pvariable(conf[CONF_ID]) diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index d809d0d105..bb4722655c 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -2,19 +2,16 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( + CONF_ID, DEVICE_CLASS_CURRENT, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_ENERGY, DEVICE_CLASS_GAS, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, UNIT_CUBIC_METER, - UNIT_EMPTY, UNIT_KILOWATT, UNIT_KILOWATT_HOURS, UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, @@ -30,202 +27,214 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_DSMR_ID): cv.use_id(Dsmr), cv.Optional("energy_delivered_lux"): sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_delivered_tariff1"): sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_delivered_tariff2"): sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_returned_lux"): sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_returned_tariff1"): sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_returned_tariff2"): sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("total_imported_energy"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, - ICON_EMPTY, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, ), cv.Optional("total_exported_energy"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, - ICON_EMPTY, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, ), cv.Optional("power_delivered"): sensor.sensor_schema( - UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_returned"): sensor.sensor_schema( - UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_POWER, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_POWER, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_threshold"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 3, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=3, ), cv.Optional("electricity_switch_position"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 3, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=3, ), cv.Optional("electricity_failures"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, ), cv.Optional("electricity_long_failures"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, ), cv.Optional("electricity_sags_l1"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, ), cv.Optional("electricity_sags_l2"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, ), cv.Optional("electricity_sags_l3"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, ), cv.Optional("electricity_swells_l1"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, ), cv.Optional("electricity_swells_l2"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, ), cv.Optional("electricity_swells_l3"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, ), cv.Optional("current_l1"): sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("current_l2"): sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("current_l3"): sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_delivered_l1"): sensor.sensor_schema( - UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_delivered_l2"): sensor.sensor_schema( - UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_delivered_l3"): sensor.sensor_schema( - UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_returned_l1"): sensor.sensor_schema( - UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_returned_l2"): sensor.sensor_schema( - UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_returned_l3"): sensor.sensor_schema( - UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l1"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l2"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l3"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l1"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l2"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l3"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l1"): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l2"): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l3"): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("gas_delivered"): sensor.sensor_schema( - UNIT_CUBIC_METER, - ICON_EMPTY, - 3, - DEVICE_CLASS_GAS, - STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_CUBIC_METER, + accuracy_decimals=3, + device_class=DEVICE_CLASS_GAS, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("gas_delivered_be"): sensor.sensor_schema( - UNIT_CUBIC_METER, - ICON_EMPTY, - 3, - DEVICE_CLASS_GAS, - STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_CUBIC_METER, + accuracy_decimals=3, + device_class=DEVICE_CLASS_GAS, + state_class=STATE_CLASS_TOTAL_INCREASING, ), } ).extend(cv.COMPONENT_SCHEMA) @@ -238,10 +247,10 @@ async def to_code(config): for key, conf in config.items(): if not isinstance(conf, dict): continue - id = conf.get("id") + id = conf[CONF_ID] if id and id.type == sensor.Sensor: - s = await sensor.new_sensor(conf) - cg.add(getattr(hub, f"set_{key}")(s)) + sens = await sensor.new_sensor(conf) + cg.add(getattr(hub, f"set_{key}")(sens)) sensors.append(f"F({key})") if sensors: diff --git a/esphome/components/duty_cycle/sensor.py b/esphome/components/duty_cycle/sensor.py index 6a367328e6..3dcdf7a818 100644 --- a/esphome/components/duty_cycle/sensor.py +++ b/esphome/components/duty_cycle/sensor.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import sensor from esphome.const import ( - CONF_ID, CONF_PIN, STATE_CLASS_MEASUREMENT, UNIT_PERCENT, @@ -17,25 +16,20 @@ DutyCycleSensor = duty_cycle_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + DutyCycleSensor, unit_of_measurement=UNIT_PERCENT, icon=ICON_PERCENT, accuracy_decimals=1, state_class=STATE_CLASS_MEASUREMENT, ) - .extend( - { - cv.GenerateID(): cv.declare_id(DutyCycleSensor), - cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema), - } - ) + .extend({cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema)}) .extend(cv.polling_component_schema("60s")) ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) pin = await cg.gpio_pin_expression(config[CONF_PIN]) cg.add(var.set_pin(pin)) diff --git a/esphome/components/esp32_hall/sensor.py b/esphome/components/esp32_hall/sensor.py index a752da2c97..0c94224ef8 100644 --- a/esphome/components/esp32_hall/sensor.py +++ b/esphome/components/esp32_hall/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( - CONF_ID, STATE_CLASS_MEASUREMENT, UNIT_MICROTESLA, ICON_MAGNET, @@ -15,23 +14,15 @@ ESP32HallSensor = esp32_hall_ns.class_( "ESP32HallSensor", sensor.Sensor, cg.PollingComponent ) -CONFIG_SCHEMA = ( - sensor.sensor_schema( - unit_of_measurement=UNIT_MICROTESLA, - icon=ICON_MAGNET, - accuracy_decimals=1, - state_class=STATE_CLASS_MEASUREMENT, - ) - .extend( - { - cv.GenerateID(): cv.declare_id(ESP32HallSensor), - } - ) - .extend(cv.polling_component_schema("60s")) -) +CONFIG_SCHEMA = sensor.sensor_schema( + ESP32HallSensor, + unit_of_measurement=UNIT_MICROTESLA, + icon=ICON_MAGNET, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, +).extend(cv.polling_component_schema("60s")) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) diff --git a/esphome/components/fingerprint_grow/sensor.py b/esphome/components/fingerprint_grow/sensor.py index 4ae670743d..ed4e431dcc 100644 --- a/esphome/components/fingerprint_grow/sensor.py +++ b/esphome/components/fingerprint_grow/sensor.py @@ -14,7 +14,6 @@ from esphome.const import ( ICON_DATABASE, ICON_FINGERPRINT, ICON_SECURITY, - STATE_CLASS_NONE, ) from . import CONF_FINGERPRINT_GROW_ID, FingerprintGrowComponent @@ -26,36 +25,30 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_FINGERPRINT_COUNT): sensor.sensor_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/gps/__init__.py b/esphome/components/gps/__init__.py index e485373175..d4cf79b49e 100644 --- a/esphome/components/gps/__init__.py +++ b/esphome/components/gps/__init__.py @@ -11,7 +11,6 @@ from esphome.const import ( CONF_ALTITUDE, CONF_SATELLITES, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_DEGREES, UNIT_KILOMETER_PER_HOUR, UNIT_METER, @@ -35,27 +34,22 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_LATITUDE): sensor.sensor_schema( unit_of_measurement=UNIT_DEGREES, accuracy_decimals=6, - state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_LONGITUDE): sensor.sensor_schema( unit_of_measurement=UNIT_DEGREES, accuracy_decimals=6, - state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_SPEED): sensor.sensor_schema( unit_of_measurement=UNIT_KILOMETER_PER_HOUR, accuracy_decimals=6, - state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_COURSE): sensor.sensor_schema( unit_of_measurement=UNIT_DEGREES, accuracy_decimals=2, - state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_ALTITUDE): sensor.sensor_schema( unit_of_measurement=UNIT_METER, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_SATELLITES): sensor.sensor_schema( accuracy_decimals=0, diff --git a/esphome/components/hmc5883l/sensor.py b/esphome/components/hmc5883l/sensor.py index 9d8701079e..26e8e2b60c 100644 --- a/esphome/components/hmc5883l/sensor.py +++ b/esphome/components/hmc5883l/sensor.py @@ -8,7 +8,6 @@ from esphome.const import ( CONF_RANGE, ICON_MAGNET, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_MICROTESLA, UNIT_DEGREES, ICON_SCREEN_ROTATION, @@ -88,7 +87,6 @@ heading_schema = sensor.sensor_schema( unit_of_measurement=UNIT_DEGREES, icon=ICON_SCREEN_ROTATION, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ) CONFIG_SCHEMA = ( diff --git a/esphome/components/homeassistant/sensor/__init__.py b/esphome/components/homeassistant/sensor/__init__.py index cf29db8bb8..28fee9f7f6 100644 --- a/esphome/components/homeassistant/sensor/__init__.py +++ b/esphome/components/homeassistant/sensor/__init__.py @@ -5,7 +5,6 @@ from esphome.const import ( CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_ID, - STATE_CLASS_NONE, ) from .. import homeassistant_ns @@ -15,12 +14,8 @@ HomeassistantSensor = homeassistant_ns.class_( "HomeassistantSensor", sensor.Sensor, cg.Component ) -CONFIG_SCHEMA = sensor.sensor_schema( - accuracy_decimals=1, - state_class=STATE_CLASS_NONE, -).extend( +CONFIG_SCHEMA = sensor.sensor_schema(HomeassistantSensor, accuracy_decimals=1,).extend( { - cv.GenerateID(): cv.declare_id(HomeassistantSensor), cv.Required(CONF_ENTITY_ID): cv.entity_id, cv.Optional(CONF_ATTRIBUTE): cv.string, } diff --git a/esphome/components/hrxl_maxsonar_wr/sensor.py b/esphome/components/hrxl_maxsonar_wr/sensor.py index dd43bd84a7..a78ae574b1 100644 --- a/esphome/components/hrxl_maxsonar_wr/sensor.py +++ b/esphome/components/hrxl_maxsonar_wr/sensor.py @@ -1,8 +1,6 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import sensor, uart from esphome.const import ( - CONF_ID, STATE_CLASS_MEASUREMENT, UNIT_METER, ICON_ARROW_EXPAND_VERTICAL, @@ -16,24 +14,16 @@ HrxlMaxsonarWrComponent = hrxlmaxsonarwr_ns.class_( "HrxlMaxsonarWrComponent", sensor.Sensor, cg.Component, uart.UARTDevice ) -CONFIG_SCHEMA = ( - sensor.sensor_schema( - unit_of_measurement=UNIT_METER, - icon=ICON_ARROW_EXPAND_VERTICAL, - accuracy_decimals=3, - state_class=STATE_CLASS_MEASUREMENT, - ) - .extend( - { - cv.GenerateID(): cv.declare_id(HrxlMaxsonarWrComponent), - } - ) - .extend(uart.UART_DEVICE_SCHEMA) -) +CONFIG_SCHEMA = sensor.sensor_schema( + HrxlMaxsonarWrComponent, + unit_of_measurement=UNIT_METER, + icon=ICON_ARROW_EXPAND_VERTICAL, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, +).extend(uart.UART_DEVICE_SCHEMA) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/hx711/sensor.py b/esphome/components/hx711/sensor.py index cd06cc770f..88a0bb85b7 100644 --- a/esphome/components/hx711/sensor.py +++ b/esphome/components/hx711/sensor.py @@ -5,7 +5,6 @@ from esphome.components import sensor from esphome.const import ( CONF_CLK_PIN, CONF_GAIN, - CONF_ID, ICON_SCALE, STATE_CLASS_MEASUREMENT, ) @@ -24,13 +23,13 @@ GAINS = { CONFIG_SCHEMA = ( sensor.sensor_schema( + HX711Sensor, icon=ICON_SCALE, accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT, ) .extend( { - cv.GenerateID(): cv.declare_id(HX711Sensor), cv.Required(CONF_DOUT_PIN): pins.gpio_input_pin_schema, cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_GAIN, default=128): cv.enum(GAINS, int=True), @@ -41,9 +40,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) dout_pin = await cg.gpio_pin_expression(config[CONF_DOUT_PIN]) cg.add(var.set_dout_pin(dout_pin)) diff --git a/esphome/components/ltr390/sensor.py b/esphome/components/ltr390/sensor.py index 0e70f7bb1b..0a765dbe3d 100644 --- a/esphome/components/ltr390/sensor.py +++ b/esphome/components/ltr390/sensor.py @@ -52,16 +52,28 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(LTR390Component), cv.Optional(CONF_LIGHT): sensor.sensor_schema( - UNIT_LUX, ICON_BRIGHTNESS_5, 1, DEVICE_CLASS_ILLUMINANCE + unit_of_measurement=UNIT_LUX, + icon=ICON_BRIGHTNESS_5, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, ), cv.Optional(CONF_AMBIENT_LIGHT): sensor.sensor_schema( - UNIT_COUNTS, ICON_BRIGHTNESS_5, 1, DEVICE_CLASS_ILLUMINANCE + unit_of_measurement=UNIT_COUNTS, + icon=ICON_BRIGHTNESS_5, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, ), cv.Optional(CONF_UV_INDEX): sensor.sensor_schema( - UNIT_UVI, ICON_BRIGHTNESS_5, 5, DEVICE_CLASS_ILLUMINANCE + unit_of_measurement=UNIT_UVI, + icon=ICON_BRIGHTNESS_5, + accuracy_decimals=5, + device_class=DEVICE_CLASS_ILLUMINANCE, ), cv.Optional(CONF_UV): sensor.sensor_schema( - UNIT_COUNTS, ICON_BRIGHTNESS_5, 1, DEVICE_CLASS_ILLUMINANCE + unit_of_measurement=UNIT_COUNTS, + icon=ICON_BRIGHTNESS_5, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, ), cv.Optional(CONF_GAIN, default="X3"): cv.enum(GAIN_OPTIONS), cv.Optional(CONF_RESOLUTION, default=18): cv.enum(RES_OPTIONS), diff --git a/esphome/components/max31855/sensor.py b/esphome/components/max31855/sensor.py index c7732dfbe3..0cdedb5464 100644 --- a/esphome/components/max31855/sensor.py +++ b/esphome/components/max31855/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, spi from esphome.const import ( - CONF_ID, CONF_REFERENCE_TEMPERATURE, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, @@ -16,13 +15,13 @@ MAX31855Sensor = max31855_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + MAX31855Sensor, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, ) .extend( { - cv.GenerateID(): cv.declare_id(MAX31855Sensor), cv.Optional(CONF_REFERENCE_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=2, @@ -37,10 +36,9 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await spi.register_spi_device(var, config) - await sensor.register_sensor(var, config) if CONF_REFERENCE_TEMPERATURE in config: tc_ref = await sensor.new_sensor(config[CONF_REFERENCE_TEMPERATURE]) cg.add(var.set_reference_sensor(tc_ref)) diff --git a/esphome/components/max31856/sensor.py b/esphome/components/max31856/sensor.py index 083d2ac30c..71f1f3bfa5 100644 --- a/esphome/components/max31856/sensor.py +++ b/esphome/components/max31856/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, spi from esphome.const import ( - CONF_ID, CONF_MAINS_FILTER, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, @@ -22,6 +21,7 @@ FILTER = { CONFIG_SCHEMA = ( sensor.sensor_schema( + MAX31856Sensor, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, @@ -29,7 +29,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(MAX31856Sensor), cv.Optional(CONF_MAINS_FILTER, default="60HZ"): cv.enum( FILTER, upper=True, space="" ), @@ -41,8 +40,7 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await spi.register_spi_device(var, config) - await sensor.register_sensor(var, config) cg.add(var.set_filter(config[CONF_MAINS_FILTER])) diff --git a/esphome/components/max31865/sensor.py b/esphome/components/max31865/sensor.py index 33d9c42be3..6eb8bd400d 100644 --- a/esphome/components/max31865/sensor.py +++ b/esphome/components/max31865/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, spi from esphome.const import ( - CONF_ID, CONF_MAINS_FILTER, CONF_REFERENCE_RESISTANCE, CONF_RTD_NOMINAL_RESISTANCE, @@ -25,6 +24,7 @@ FILTER = { CONFIG_SCHEMA = ( sensor.sensor_schema( + MAX31865Sensor, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=2, device_class=DEVICE_CLASS_TEMPERATURE, @@ -32,7 +32,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(MAX31865Sensor), cv.Required(CONF_REFERENCE_RESISTANCE): cv.All( cv.resistance, cv.Range(min=100, max=10000) ), @@ -51,10 +50,9 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await spi.register_spi_device(var, config) - await sensor.register_sensor(var, config) cg.add(var.set_reference_resistance(config[CONF_REFERENCE_RESISTANCE])) cg.add(var.set_nominal_resistance(config[CONF_RTD_NOMINAL_RESISTANCE])) cg.add(var.set_filter(config[CONF_MAINS_FILTER])) diff --git a/esphome/components/max6675/sensor.py b/esphome/components/max6675/sensor.py index dff8360226..23fc86d2c2 100644 --- a/esphome/components/max6675/sensor.py +++ b/esphome/components/max6675/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, spi from esphome.const import ( - CONF_ID, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, @@ -15,23 +14,18 @@ MAX6675Sensor = max6675_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + MAX6675Sensor, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ) - .extend( - { - cv.GenerateID(): cv.declare_id(MAX6675Sensor), - } - ) .extend(cv.polling_component_schema("60s")) .extend(spi.spi_device_schema()) ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await spi.register_spi_device(var, config) - await sensor.register_sensor(var, config) diff --git a/esphome/components/mcp9808/sensor.py b/esphome/components/mcp9808/sensor.py index c7f6226e0b..2d7874fe20 100644 --- a/esphome/components/mcp9808/sensor.py +++ b/esphome/components/mcp9808/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, @@ -18,23 +17,18 @@ MCP9808Sensor = mcp9808_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + MCP9808Sensor, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ) - .extend( - { - cv.GenerateID(): cv.declare_id(MCP9808Sensor), - } - ) .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x18)) ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - await sensor.register_sensor(var, config) diff --git a/esphome/components/mqtt_subscribe/sensor/__init__.py b/esphome/components/mqtt_subscribe/sensor/__init__.py index 420d4f152c..6fe0c48ae0 100644 --- a/esphome/components/mqtt_subscribe/sensor/__init__.py +++ b/esphome/components/mqtt_subscribe/sensor/__init__.py @@ -2,10 +2,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import mqtt, sensor from esphome.const import ( - CONF_ID, CONF_QOS, CONF_TOPIC, - STATE_CLASS_NONE, ) from .. import mqtt_subscribe_ns @@ -18,12 +16,11 @@ MQTTSubscribeSensor = mqtt_subscribe_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + MQTTSubscribeSensor, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ) .extend( { - cv.GenerateID(): cv.declare_id(MQTTSubscribeSensor), cv.GenerateID(CONF_MQTT_PARENT_ID): cv.use_id(mqtt.MQTTClientComponent), cv.Required(CONF_TOPIC): cv.subscribe_topic, cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, @@ -34,9 +31,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) parent = await cg.get_variable(config[CONF_MQTT_PARENT_ID]) cg.add(var.set_parent(parent)) diff --git a/esphome/components/nextion/sensor/__init__.py b/esphome/components/nextion/sensor/__init__.py index 8a32adc1f6..b022007ddd 100644 --- a/esphome/components/nextion/sensor/__init__.py +++ b/esphome/components/nextion/sensor/__init__.py @@ -44,11 +44,11 @@ def _validate(config): CONFIG_SCHEMA = cv.All( sensor.sensor_schema( + NextionSensor, accuracy_decimals=2, ) .extend( { - cv.GenerateID(): cv.declare_id(NextionSensor), cv.Optional(CONF_PRECISION, default=0): cv.int_range(min=0, max=8), cv.Optional(CONF_WAVE_CHANNEL_ID): CheckWaveID, cv.Optional(CONF_COMPONENT_ID): cv.uint8_t, diff --git a/esphome/components/ntc/sensor.py b/esphome/components/ntc/sensor.py index 660208635c..ba8d3df9d8 100644 --- a/esphome/components/ntc/sensor.py +++ b/esphome/components/ntc/sensor.py @@ -5,7 +5,6 @@ import esphome.codegen as cg from esphome.components import sensor from esphome.const import ( CONF_CALIBRATION, - CONF_ID, CONF_REFERENCE_RESISTANCE, CONF_REFERENCE_TEMPERATURE, CONF_SENSOR, @@ -117,6 +116,7 @@ def process_calibration(value): CONFIG_SCHEMA = ( sensor.sensor_schema( + NTC, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, @@ -124,7 +124,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(NTC), cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), cv.Required(CONF_CALIBRATION): process_calibration, } @@ -134,9 +133,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) sens = await cg.get_variable(config[CONF_SENSOR]) cg.add(var.set_sensor(sens)) diff --git a/esphome/components/pid/sensor/__init__.py b/esphome/components/pid/sensor/__init__.py index d1007fcbc4..d1c65dfb39 100644 --- a/esphome/components/pid/sensor/__init__.py +++ b/esphome/components/pid/sensor/__init__.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( - CONF_ID, STATE_CLASS_MEASUREMENT, UNIT_PERCENT, ICON_GAUGE, @@ -29,6 +28,7 @@ PID_CLIMATE_SENSOR_TYPES = { CONF_CLIMATE_ID = "climate_id" CONFIG_SCHEMA = ( sensor.sensor_schema( + PIDClimateSensor, unit_of_measurement=UNIT_PERCENT, icon=ICON_GAUGE, accuracy_decimals=1, @@ -36,7 +36,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(PIDClimateSensor), cv.GenerateID(CONF_CLIMATE_ID): cv.use_id(PIDClimate), cv.Required(CONF_TYPE): cv.enum(PID_CLIMATE_SENSOR_TYPES, upper=True), } @@ -47,8 +46,7 @@ CONFIG_SCHEMA = ( async def to_code(config): parent = await cg.get_variable(config[CONF_CLIMATE_ID]) - var = cg.new_Pvariable(config[CONF_ID]) - await sensor.register_sensor(var, config) + var = await sensor.new_sensor(config) await cg.register_component(var, config) cg.add(var.set_parent(parent)) diff --git a/esphome/components/pipsolar/sensor/__init__.py b/esphome/components/pipsolar/sensor/__init__.py index a206e41988..3a6f94d6ac 100644 --- a/esphome/components/pipsolar/sensor/__init__.py +++ b/esphome/components/pipsolar/sensor/__init__.py @@ -3,18 +3,15 @@ import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( DEVICE_CLASS_CURRENT, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, ICON_CURRENT_AC, - ICON_EMPTY, UNIT_AMPERE, UNIT_CELSIUS, UNIT_HERTZ, UNIT_PERCENT, UNIT_VOLT, - UNIT_EMPTY, UNIT_VOLT_AMPS, UNIT_WATT, CONF_BUS_VOLTAGE, @@ -74,134 +71,196 @@ CONF_PV_CHARGING_POWER = "pv_charging_power" TYPES = { CONF_GRID_RATING_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_GRID_RATING_CURRENT: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, ), CONF_AC_OUTPUT_RATING_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_AC_OUTPUT_RATING_FREQUENCY: sensor.sensor_schema( - UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_HERTZ, + icon=ICON_CURRENT_AC, + accuracy_decimals=1, ), CONF_AC_OUTPUT_RATING_CURRENT: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, ), CONF_AC_OUTPUT_RATING_APPARENT_POWER: sensor.sensor_schema( - UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_VOLT_AMPS, + accuracy_decimals=1, ), CONF_AC_OUTPUT_RATING_ACTIVE_POWER: sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, ), CONF_BATTERY_RATING_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_RECHARGE_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_UNDER_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_BULK_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_FLOAT_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_TYPE: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_CURRENT_MAX_AC_CHARGING_CURRENT: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, ), CONF_CURRENT_MAX_CHARGING_CURRENT: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, ), CONF_INPUT_VOLTAGE_RANGE: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_OUTPUT_SOURCE_PRIORITY: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_CHARGER_SOURCE_PRIORITY: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_PARALLEL_MAX_NUM: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_MACHINE_TYPE: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, + ), + CONF_TOPOLOGY: sensor.sensor_schema( + accuracy_decimals=1, ), - CONF_TOPOLOGY: sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY), CONF_OUTPUT_MODE: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_BATTERY_REDISCHARGE_VOLTAGE: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_PV_OK_CONDITION_FOR_PARALLEL: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_PV_POWER_BALANCE: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_GRID_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_GRID_FREQUENCY: sensor.sensor_schema( - UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_HERTZ, + icon=ICON_CURRENT_AC, + accuracy_decimals=1, ), CONF_AC_OUTPUT_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_AC_OUTPUT_FREQUENCY: sensor.sensor_schema( - UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_HERTZ, + icon=ICON_CURRENT_AC, + accuracy_decimals=1, ), CONF_AC_OUTPUT_APPARENT_POWER: sensor.sensor_schema( - UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_VOLT_AMPS, + accuracy_decimals=1, ), CONF_AC_OUTPUT_ACTIVE_POWER: sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, ), CONF_OUTPUT_LOAD_PERCENT: sensor.sensor_schema( - UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, ), CONF_BUS_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_CHARGING_CURRENT: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, ), CONF_BATTERY_CAPACITY_PERCENT: sensor.sensor_schema( - UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, ), CONF_INVERTER_HEAT_SINK_TEMPERATURE: sensor.sensor_schema( - UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, ), CONF_PV_INPUT_CURRENT_FOR_BATTERY: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, ), CONF_PV_INPUT_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_VOLTAGE_SCC: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_DISCHARGE_CURRENT: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, ), CONF_BATTERY_VOLTAGE_OFFSET_FOR_FANS_ON: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_EEPROM_VERSION: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_PV_CHARGING_POWER: sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, ), } diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index 56a91d22fc..b731e48e31 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -22,7 +22,6 @@ from esphome.const import ( DEVICE_CLASS_PM1, DEVICE_CLASS_PM10, DEVICE_CLASS_PM25, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, ICON_CHEMICAL_WEAPON, @@ -75,22 +74,22 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(PMSX003Component), cv.Required(CONF_TYPE): cv.enum(PMSX003_TYPES, upper=True), cv.Optional(CONF_PM_1_0_STD): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_PM1, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, + device_class=DEVICE_CLASS_PM1, ), cv.Optional(CONF_PM_2_5_STD): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_PM25, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, + device_class=DEVICE_CLASS_PM25, ), cv.Optional(CONF_PM_10_0_STD): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_PM10, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, + device_class=DEVICE_CLASS_PM10, ), cv.Optional(CONF_PM_1_0): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, @@ -111,40 +110,34 @@ CONFIG_SCHEMA = ( state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_0_3UM): sensor.sensor_schema( - UNIT_COUNT_DECILITRE, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, + unit_of_measurement=UNIT_COUNT_DECILITRE, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, ), cv.Optional(CONF_PM_0_5UM): sensor.sensor_schema( - UNIT_COUNT_DECILITRE, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, + unit_of_measurement=UNIT_COUNT_DECILITRE, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, ), cv.Optional(CONF_PM_1_0UM): sensor.sensor_schema( - UNIT_COUNT_DECILITRE, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, + unit_of_measurement=UNIT_COUNT_DECILITRE, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, ), cv.Optional(CONF_PM_2_5UM): sensor.sensor_schema( - UNIT_COUNT_DECILITRE, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, + unit_of_measurement=UNIT_COUNT_DECILITRE, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, ), cv.Optional(CONF_PM_5_0UM): sensor.sensor_schema( - UNIT_COUNT_DECILITRE, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, + unit_of_measurement=UNIT_COUNT_DECILITRE, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, ), cv.Optional(CONF_PM_10_0UM): sensor.sensor_schema( - UNIT_COUNT_DECILITRE, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, + unit_of_measurement=UNIT_COUNT_DECILITRE, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, diff --git a/esphome/components/pulse_counter/sensor.py b/esphome/components/pulse_counter/sensor.py index c7b89d41b0..6dcb974a1f 100644 --- a/esphome/components/pulse_counter/sensor.py +++ b/esphome/components/pulse_counter/sensor.py @@ -5,7 +5,6 @@ from esphome.components import sensor from esphome.const import ( CONF_COUNT_MODE, CONF_FALLING_EDGE, - CONF_ID, CONF_INTERNAL_FILTER, CONF_PIN, CONF_RISING_EDGE, @@ -66,6 +65,7 @@ def validate_count_mode(value): CONFIG_SCHEMA = ( sensor.sensor_schema( + PulseCounterSensor, unit_of_measurement=UNIT_PULSES_PER_MINUTE, icon=ICON_PULSE, accuracy_decimals=2, @@ -73,7 +73,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(PulseCounterSensor), cv.Required(CONF_PIN): validate_pulse_counter_pin, cv.Optional( CONF_COUNT_MODE, @@ -104,9 +103,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) pin = await cg.gpio_pin_expression(config[CONF_PIN]) cg.add(var.set_pin(pin)) diff --git a/esphome/components/pulse_meter/sensor.py b/esphome/components/pulse_meter/sensor.py index 454cb3a69d..fa753b5b05 100644 --- a/esphome/components/pulse_meter/sensor.py +++ b/esphome/components/pulse_meter/sensor.py @@ -50,13 +50,13 @@ def validate_pulse_meter_pin(value): CONFIG_SCHEMA = sensor.sensor_schema( + PulseMeterSensor, unit_of_measurement=UNIT_PULSES_PER_MINUTE, icon=ICON_PULSE, accuracy_decimals=2, state_class=STATE_CLASS_MEASUREMENT, ).extend( { - cv.GenerateID(): cv.declare_id(PulseMeterSensor), cv.Required(CONF_PIN): validate_pulse_meter_pin, cv.Optional(CONF_INTERNAL_FILTER, default="13us"): validate_internal_filter, cv.Optional(CONF_TIMEOUT, default="5min"): validate_timeout, @@ -71,9 +71,8 @@ CONFIG_SCHEMA = sensor.sensor_schema( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) pin = await cg.gpio_pin_expression(config[CONF_PIN]) cg.add(var.set_pin(pin)) diff --git a/esphome/components/pulse_width/sensor.py b/esphome/components/pulse_width/sensor.py index b090647627..47d70166d3 100644 --- a/esphome/components/pulse_width/sensor.py +++ b/esphome/components/pulse_width/sensor.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import sensor from esphome.const import ( - CONF_ID, CONF_PIN, STATE_CLASS_MEASUREMENT, UNIT_SECOND, @@ -18,6 +17,7 @@ PulseWidthSensor = pulse_width_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + PulseWidthSensor, unit_of_measurement=UNIT_SECOND, icon=ICON_TIMER, accuracy_decimals=3, @@ -25,7 +25,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(PulseWidthSensor), cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema), } ) @@ -34,9 +33,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) pin = await cg.gpio_pin_expression(config[CONF_PIN]) cg.add(var.set_pin(pin)) diff --git a/esphome/components/qmc5883l/sensor.py b/esphome/components/qmc5883l/sensor.py index 27d1df5b29..100ac93520 100644 --- a/esphome/components/qmc5883l/sensor.py +++ b/esphome/components/qmc5883l/sensor.py @@ -8,7 +8,6 @@ from esphome.const import ( CONF_RANGE, ICON_MAGNET, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_MICROTESLA, UNIT_DEGREES, ICON_SCREEN_ROTATION, @@ -79,7 +78,6 @@ heading_schema = sensor.sensor_schema( unit_of_measurement=UNIT_DEGREES, icon=ICON_SCREEN_ROTATION, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ) CONFIG_SCHEMA = ( diff --git a/esphome/components/resistance/sensor.py b/esphome/components/resistance/sensor.py index 329192e902..55e7ddfc81 100644 --- a/esphome/components/resistance/sensor.py +++ b/esphome/components/resistance/sensor.py @@ -6,7 +6,6 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_OHM, ICON_FLASH, - CONF_ID, ) resistance_ns = cg.esphome_ns.namespace("resistance") @@ -24,6 +23,7 @@ CONFIGURATIONS = { CONFIG_SCHEMA = ( sensor.sensor_schema( + ResistanceSensor, unit_of_measurement=UNIT_OHM, icon=ICON_FLASH, accuracy_decimals=1, @@ -31,7 +31,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(ResistanceSensor), cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), cv.Required(CONF_CONFIGURATION): cv.enum(CONFIGURATIONS, upper=True), cv.Required(CONF_RESISTOR): cv.resistance, @@ -43,9 +42,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) sens = await cg.get_variable(config[CONF_SENSOR]) cg.add(var.set_sensor(sens)) diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py index cd747264b3..ae6b0ae3bf 100644 --- a/esphome/components/rotary_encoder/sensor.py +++ b/esphome/components/rotary_encoder/sensor.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_RESOLUTION, CONF_MIN_VALUE, CONF_MAX_VALUE, - STATE_CLASS_NONE, UNIT_STEPS, ICON_ROTATE_RIGHT, CONF_VALUE, @@ -65,14 +64,13 @@ def validate_min_max_value(config): CONFIG_SCHEMA = cv.All( sensor.sensor_schema( + RotaryEncoderSensor, unit_of_measurement=UNIT_STEPS, icon=ICON_ROTATE_RIGHT, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, ) .extend( { - cv.GenerateID(): cv.declare_id(RotaryEncoderSensor), cv.Required(CONF_PIN_A): cv.All(pins.internal_gpio_input_pin_schema), cv.Required(CONF_PIN_B): cv.All(pins.internal_gpio_input_pin_schema), cv.Optional(CONF_PIN_RESET): pins.internal_gpio_output_pin_schema, @@ -105,9 +103,9 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) + pin_a = await cg.gpio_pin_expression(config[CONF_PIN_A]) cg.add(var.set_pin_a(pin_a)) pin_b = await cg.gpio_pin_expression(config[CONF_PIN_B]) diff --git a/esphome/components/ruuvitag/sensor.py b/esphome/components/ruuvitag/sensor.py index 2bb9549195..a46daf88ac 100644 --- a/esphome/components/ruuvitag/sensor.py +++ b/esphome/components/ruuvitag/sensor.py @@ -21,7 +21,6 @@ from esphome.const import ( DEVICE_CLASS_VOLTAGE, ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_CELSIUS, UNIT_PERCENT, UNIT_VOLT, @@ -108,13 +107,11 @@ CONFIG_SCHEMA = ( 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/sdp3x/sensor.py b/esphome/components/sdp3x/sensor.py index 45f5cc4d9a..66ee475b11 100644 --- a/esphome/components/sdp3x/sensor.py +++ b/esphome/components/sdp3x/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, DEVICE_CLASS_PRESSURE, STATE_CLASS_MEASUREMENT, UNIT_HECTOPASCAL, @@ -24,6 +23,7 @@ CONF_MEASUREMENT_MODE = "measurement_mode" CONFIG_SCHEMA = ( sensor.sensor_schema( + SDP3XComponent, unit_of_measurement=UNIT_HECTOPASCAL, accuracy_decimals=3, device_class=DEVICE_CLASS_PRESSURE, @@ -31,7 +31,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(SDP3XComponent), cv.Optional( CONF_MEASUREMENT_MODE, default="differential_pressure" ): cv.enum(MEASUREMENT_MODE), @@ -43,8 +42,7 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(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])) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 14a15da2f1..577596f6ce 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -58,6 +58,7 @@ from esphome.const import ( DEVICE_CLASS_VOLTAGE, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_generator import MockObjClass from esphome.cpp_helpers import setup_entity from esphome.util import Registry @@ -223,6 +224,8 @@ _UNDEF = object() def sensor_schema( + class_: MockObjClass = _UNDEF, + *, unit_of_measurement: str = _UNDEF, icon: str = _UNDEF, accuracy_decimals: int = _UNDEF, @@ -231,6 +234,8 @@ def sensor_schema( entity_category: str = _UNDEF, ) -> cv.Schema: schema = SENSOR_SCHEMA + if class_ is not _UNDEF: + schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)}) if unit_of_measurement is not _UNDEF: schema = schema.extend( { diff --git a/esphome/components/sgp40/sensor.py b/esphome/components/sgp40/sensor.py index 7b96f867af..6f27b54fb0 100644 --- a/esphome/components/sgp40/sensor.py +++ b/esphome/components/sgp40/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, ICON_RADIATOR, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, STATE_CLASS_MEASUREMENT, @@ -25,6 +24,7 @@ CONF_VOC_BASELINE = "voc_baseline" CONFIG_SCHEMA = ( sensor.sensor_schema( + SGP40Component, icon=ICON_RADIATOR, accuracy_decimals=0, device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, @@ -32,7 +32,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(SGP40Component), cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean, cv.Optional(CONF_VOC_BASELINE): cv.hex_uint16_t, cv.Optional(CONF_COMPENSATION): cv.Schema( @@ -49,10 +48,9 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - await sensor.register_sensor(var, config) if CONF_COMPENSATION in config: compensation_config = config[CONF_COMPENSATION] diff --git a/esphome/components/sts3x/sensor.py b/esphome/components/sts3x/sensor.py index b02c835ef8..aa7573aaf2 100644 --- a/esphome/components/sts3x/sensor.py +++ b/esphome/components/sts3x/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, @@ -18,23 +17,18 @@ STS3XComponent = sts3x_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + STS3XComponent, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ) - .extend( - { - cv.GenerateID(): cv.declare_id(STS3XComponent), - } - ) .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x4A)) ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/sun/sensor/__init__.py b/esphome/components/sun/sensor/__init__.py index 236acfadef..10c0237327 100644 --- a/esphome/components/sun/sensor/__init__.py +++ b/esphome/components/sun/sensor/__init__.py @@ -2,10 +2,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( - STATE_CLASS_NONE, UNIT_DEGREES, ICON_WEATHER_SUNSET, - CONF_ID, CONF_TYPE, ) from .. import sun_ns, CONF_SUN_ID, Sun @@ -21,14 +19,13 @@ TYPES = { CONFIG_SCHEMA = ( sensor.sensor_schema( + SunSensor, unit_of_measurement=UNIT_DEGREES, icon=ICON_WEATHER_SUNSET, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ) .extend( { - cv.GenerateID(): cv.declare_id(SunSensor), cv.GenerateID(CONF_SUN_ID): cv.use_id(Sun), cv.Required(CONF_TYPE): cv.enum(TYPES, lower=True), } @@ -38,9 +35,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) cg.add(var.set_type(config[CONF_TYPE])) paren = await cg.get_variable(config[CONF_SUN_ID]) diff --git a/esphome/components/teleinfo/sensor/__init__.py b/esphome/components/teleinfo/sensor/__init__.py index aa875be157..ff2d81c95e 100644 --- a/esphome/components/teleinfo/sensor/__init__.py +++ b/esphome/components/teleinfo/sensor/__init__.py @@ -1,5 +1,4 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import sensor from esphome.const import CONF_ID, ICON_FLASH, UNIT_WATT_HOURS @@ -13,13 +12,12 @@ from .. import ( TeleInfoSensor = teleinfo_ns.class_("TeleInfoSensor", sensor.Sensor, cg.Component) -CONFIG_SCHEMA = ( - sensor.sensor_schema( - unit_of_measurement=UNIT_WATT_HOURS, icon=ICON_FLASH, accuracy_decimals=0 - ) - .extend({cv.GenerateID(): cv.declare_id(TeleInfoSensor)}) - .extend(TELEINFO_LISTENER_SCHEMA) -) +CONFIG_SCHEMA = sensor.sensor_schema( + TeleInfoSensor, + unit_of_measurement=UNIT_WATT_HOURS, + icon=ICON_FLASH, + accuracy_decimals=0, +).extend(TELEINFO_LISTENER_SCHEMA) async def to_code(config): diff --git a/esphome/components/template/sensor/__init__.py b/esphome/components/template/sensor/__init__.py index 75fb505d91..9ed7a83848 100644 --- a/esphome/components/template/sensor/__init__.py +++ b/esphome/components/template/sensor/__init__.py @@ -6,7 +6,6 @@ from esphome.const import ( CONF_ID, CONF_LAMBDA, CONF_STATE, - STATE_CLASS_NONE, ) from .. import template_ns @@ -16,12 +15,11 @@ TemplateSensor = template_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + TemplateSensor, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ) .extend( { - cv.GenerateID(): cv.declare_id(TemplateSensor), cv.Optional(CONF_LAMBDA): cv.returning_lambda, } ) @@ -30,9 +28,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) if CONF_LAMBDA in config: template_ = await cg.process_lambda( diff --git a/esphome/components/tmp102/sensor.py b/esphome/components/tmp102/sensor.py index c5ffbb8df5..57d0afd5a1 100644 --- a/esphome/components/tmp102/sensor.py +++ b/esphome/components/tmp102/sensor.py @@ -11,7 +11,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, @@ -27,23 +26,18 @@ TMP102Component = tmp102_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + TMP102Component, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ) - .extend( - { - cv.GenerateID(): cv.declare_id(TMP102Component), - } - ) .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x48)) ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - await sensor.register_sensor(var, config) diff --git a/esphome/components/tmp117/sensor.py b/esphome/components/tmp117/sensor.py index 054864dd83..fb97258bc1 100644 --- a/esphome/components/tmp117/sensor.py +++ b/esphome/components/tmp117/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, CONF_UPDATE_INTERVAL, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, @@ -19,16 +18,12 @@ TMP117Component = tmp117_ns.class_( CONFIG_SCHEMA = cv.All( sensor.sensor_schema( + TMP117Component, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ) - .extend( - { - cv.GenerateID(): cv.declare_id(TMP117Component), - } - ) .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x48)) ) @@ -77,10 +72,9 @@ def determine_config_register(polling_period): async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - await sensor.register_sensor(var, config) update_period = config[CONF_UPDATE_INTERVAL].total_seconds cg.add(var.set_config(determine_config_register(update_period))) diff --git a/esphome/components/tof10120/sensor.py b/esphome/components/tof10120/sensor.py index 2d3add2399..21d6d48659 100644 --- a/esphome/components/tof10120/sensor.py +++ b/esphome/components/tof10120/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, STATE_CLASS_MEASUREMENT, UNIT_METER, ICON_ARROW_EXPAND_VERTICAL, @@ -18,19 +17,18 @@ TOF10120Sensor = tof10120_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + TOF10120Sensor, unit_of_measurement=UNIT_METER, icon=ICON_ARROW_EXPAND_VERTICAL, accuracy_decimals=3, state_class=STATE_CLASS_MEASUREMENT, ) - .extend({cv.GenerateID(): cv.declare_id(TOF10120Sensor)}) .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x52)) ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index 1af8db8332..3698563aff 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -40,12 +40,12 @@ def inherit_accuracy_decimals(decimals, config): CONFIG_SCHEMA = ( sensor.sensor_schema( + TotalDailyEnergy, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ) .extend( { - cv.GenerateID(): cv.declare_id(TotalDailyEnergy), cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), cv.Optional(CONF_RESTORE, default=True): cv.boolean, @@ -82,10 +82,8 @@ FINAL_VALIDATE_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) sens = await cg.get_variable(config[CONF_POWER_ID]) cg.add(var.set_parent(sens)) diff --git a/esphome/components/tsl2561/sensor.py b/esphome/components/tsl2561/sensor.py index cf3837cb4d..fb2c00697b 100644 --- a/esphome/components/tsl2561/sensor.py +++ b/esphome/components/tsl2561/sensor.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( CONF_GAIN, - CONF_ID, CONF_INTEGRATION_TIME, DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT, @@ -40,6 +39,7 @@ TSL2561Sensor = tsl2561_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + TSL2561Sensor, unit_of_measurement=UNIT_LUX, accuracy_decimals=1, device_class=DEVICE_CLASS_ILLUMINANCE, @@ -47,7 +47,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(TSL2561Sensor), cv.Optional( CONF_INTEGRATION_TIME, default="402ms" ): validate_integration_time, @@ -61,10 +60,9 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - await sensor.register_sensor(var, config) cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) cg.add(var.set_gain(config[CONF_GAIN])) diff --git a/esphome/components/tsl2591/sensor.py b/esphome/components/tsl2591/sensor.py index 1ec37b5f93..63a0733365 100644 --- a/esphome/components/tsl2591/sensor.py +++ b/esphome/components/tsl2591/sensor.py @@ -35,10 +35,8 @@ from esphome.const import ( CONF_DEVICE_FACTOR, CONF_GLASS_ATTENUATION_FACTOR, ICON_BRIGHTNESS_6, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT, - UNIT_EMPTY, UNIT_LUX, ) @@ -87,32 +85,26 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(TSL2591Component), cv.Optional(CONF_INFRARED): sensor.sensor_schema( - UNIT_EMPTY, - ICON_BRIGHTNESS_6, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + icon=ICON_BRIGHTNESS_6, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_VISIBLE): sensor.sensor_schema( - UNIT_EMPTY, - ICON_BRIGHTNESS_6, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + icon=ICON_BRIGHTNESS_6, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_FULL_SPECTRUM): sensor.sensor_schema( - UNIT_EMPTY, - ICON_BRIGHTNESS_6, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + icon=ICON_BRIGHTNESS_6, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CALCULATED_LUX): sensor.sensor_schema( - UNIT_LUX, - ICON_BRIGHTNESS_6, - 4, - DEVICE_CLASS_ILLUMINANCE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_LUX, + icon=ICON_BRIGHTNESS_6, + accuracy_decimals=4, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional( CONF_INTEGRATION_TIME, default="100ms" diff --git a/esphome/components/tx20/sensor.py b/esphome/components/tx20/sensor.py index 84df82b5e6..f8a0b08d99 100644 --- a/esphome/components/tx20/sensor.py +++ b/esphome/components/tx20/sensor.py @@ -8,7 +8,6 @@ from esphome.const import ( CONF_PIN, CONF_WIND_DIRECTION_DEGREES, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_KILOMETER_PER_HOUR, ICON_WEATHER_WINDY, ICON_SIGN_DIRECTION, @@ -31,7 +30,6 @@ CONFIG_SCHEMA = cv.Schema( unit_of_measurement=UNIT_DEGREES, icon=ICON_SIGN_DIRECTION, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ), cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema), } diff --git a/esphome/components/ultrasonic/sensor.py b/esphome/components/ultrasonic/sensor.py index f7026e884c..afbd1128c2 100644 --- a/esphome/components/ultrasonic/sensor.py +++ b/esphome/components/ultrasonic/sensor.py @@ -4,7 +4,6 @@ from esphome import pins from esphome.components import sensor from esphome.const import ( CONF_ECHO_PIN, - CONF_ID, CONF_TRIGGER_PIN, CONF_TIMEOUT, STATE_CLASS_MEASUREMENT, @@ -21,6 +20,7 @@ UltrasonicSensorComponent = ultrasonic_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + UltrasonicSensorComponent, unit_of_measurement=UNIT_METER, icon=ICON_ARROW_EXPAND_VERTICAL, accuracy_decimals=2, @@ -28,7 +28,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(UltrasonicSensorComponent), cv.Required(CONF_TRIGGER_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_ECHO_PIN): pins.internal_gpio_input_pin_schema, cv.Optional(CONF_TIMEOUT, default="2m"): cv.distance, @@ -42,9 +41,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) trigger = await cg.gpio_pin_expression(config[CONF_TRIGGER_PIN]) cg.add(var.set_trigger_pin(trigger)) diff --git a/esphome/components/uptime/sensor.py b/esphome/components/uptime/sensor.py index 16a1e4c125..103bc3a666 100644 --- a/esphome/components/uptime/sensor.py +++ b/esphome/components/uptime/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg 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, @@ -12,24 +11,16 @@ from esphome.const import ( uptime_ns = cg.esphome_ns.namespace("uptime") UptimeSensor = uptime_ns.class_("UptimeSensor", sensor.Sensor, cg.PollingComponent) -CONFIG_SCHEMA = ( - sensor.sensor_schema( - unit_of_measurement=UNIT_SECOND, - icon=ICON_TIMER, - accuracy_decimals=0, - state_class=STATE_CLASS_TOTAL_INCREASING, - entity_category=ENTITY_CATEGORY_DIAGNOSTIC, - ) - .extend( - { - cv.GenerateID(): cv.declare_id(UptimeSensor), - } - ) - .extend(cv.polling_component_schema("60s")) -) +CONFIG_SCHEMA = sensor.sensor_schema( + UptimeSensor, + unit_of_measurement=UNIT_SECOND, + icon=ICON_TIMER, + accuracy_decimals=0, + state_class=STATE_CLASS_TOTAL_INCREASING, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, +).extend(cv.polling_component_schema("60s")) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) diff --git a/esphome/components/vl53l0x/sensor.py b/esphome/components/vl53l0x/sensor.py index 0ce3197366..7b485e3887 100644 --- a/esphome/components/vl53l0x/sensor.py +++ b/esphome/components/vl53l0x/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, STATE_CLASS_MEASUREMENT, UNIT_METER, ICON_ARROW_EXPAND_VERTICAL, @@ -41,6 +40,7 @@ def check_timeout(value): CONFIG_SCHEMA = cv.All( sensor.sensor_schema( + VL53L0XSensor, unit_of_measurement=UNIT_METER, icon=ICON_ARROW_EXPAND_VERTICAL, accuracy_decimals=2, @@ -48,7 +48,6 @@ CONFIG_SCHEMA = cv.All( ) .extend( { - cv.GenerateID(): cv.declare_id(VL53L0XSensor), cv.Optional(CONF_SIGNAL_RATE_LIMIT, default=0.25): cv.float_range( min=0.0, max=512.0, min_included=False, max_included=False ), @@ -64,7 +63,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) cg.add(var.set_signal_rate_limit(config[CONF_SIGNAL_RATE_LIMIT])) cg.add(var.set_long_range(config[CONF_LONG_RANGE])) @@ -74,5 +73,4 @@ async def to_code(config): enable = await cg.gpio_pin_expression(config[CONF_ENABLE_PIN]) cg.add(var.set_enable_pin(enable)) - await sensor.register_sensor(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/wifi_signal/sensor.py b/esphome/components/wifi_signal/sensor.py index 2097c21bd7..77fabf272e 100644 --- a/esphome/components/wifi_signal/sensor.py +++ b/esphome/components/wifi_signal/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( - CONF_ID, DEVICE_CLASS_SIGNAL_STRENGTH, ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, @@ -15,24 +14,16 @@ WiFiSignalSensor = wifi_signal_ns.class_( "WiFiSignalSensor", sensor.Sensor, cg.PollingComponent ) -CONFIG_SCHEMA = ( - 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( - { - cv.GenerateID(): cv.declare_id(WiFiSignalSensor), - } - ) - .extend(cv.polling_component_schema("60s")) -) +CONFIG_SCHEMA = sensor.sensor_schema( + WiFiSignalSensor, + 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(cv.polling_component_schema("60s")) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) diff --git a/esphome/components/xiaomi_cgpr1/binary_sensor.py b/esphome/components/xiaomi_cgpr1/binary_sensor.py index 7f0aac873d..4297b7fc3b 100644 --- a/esphome/components/xiaomi_cgpr1/binary_sensor.py +++ b/esphome/components/xiaomi_cgpr1/binary_sensor.py @@ -7,12 +7,10 @@ from esphome.const import ( CONF_DEVICE_CLASS, CONF_MAC_ADDRESS, CONF_ID, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MOTION, ENTITY_CATEGORY_DIAGNOSTIC, - ICON_EMPTY, UNIT_PERCENT, CONF_IDLE_TIME, CONF_ILLUMINANCE, @@ -52,11 +50,12 @@ CONFIG_SCHEMA = cv.All( 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 + unit_of_measurement=UNIT_LUX, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, ), } ) diff --git a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py index 1bedae26cf..6a7d6aae79 100644 --- a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py +++ b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py @@ -12,7 +12,6 @@ from esphome.const import ( DEVICE_CLASS_ILLUMINANCE, ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_PERCENT, CONF_IDLE_TIME, CONF_ILLUMINANCE, @@ -22,7 +21,7 @@ from esphome.const import ( ) DEPENDENCIES = ["esp32_ble_tracker"] -AUTO_LOAD = ["xiaomi_ble"] +AUTO_LOAD = ["xiaomi_ble", "sensor"] xiaomi_mjyd02yla_ns = cg.esphome_ns.namespace("xiaomi_mjyd02yla") XiaomiMJYD02YLA = xiaomi_mjyd02yla_ns.class_( @@ -45,7 +44,6 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_MINUTE, icon=ICON_TIMELAPSE, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 574a8dcafe..aabb5510f4 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -57,6 +57,7 @@ // ESP32-specific feature flags #ifdef USE_ESP32 +#define USE_ESP32_BLE_CLIENT #define USE_ESP32_BLE_SERVER #define USE_ESP32_CAMERA #define USE_ESP32_IGNORE_EFUSE_MAC_CRC From 7a242bb4ed9cb78f8a78be3e8cc091c895caa52e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 18 Feb 2022 10:39:59 +1300 Subject: [PATCH 124/238] Binary Sensor codegen tidyup (#3217) --- esphome/components/apds9960/binary_sensor.py | 11 +++-- esphome/components/as3935/binary_sensor.py | 2 +- esphome/components/binary_sensor/__init__.py | 40 ++++++++++++++++++- .../components/ble_presence/binary_sensor.py | 8 ++-- esphome/components/ble_scanner/text_sensor.py | 2 +- esphome/components/cap1188/binary_sensor.py | 8 ++-- .../custom/binary_sensor/__init__.py | 2 +- esphome/components/daly_bms/binary_sensor.py | 18 ++------- esphome/components/demo/__init__.py | 11 ++--- .../components/esp32_touch/binary_sensor.py | 3 +- .../fingerprint_grow/binary_sensor.py | 2 +- .../components/gpio/binary_sensor/__init__.py | 20 +++++----- .../homeassistant/binary_sensor/__init__.py | 22 +++++----- .../binary_sensor/__init__.py | 4 +- esphome/components/mpr121/binary_sensor.py | 8 ++-- .../nextion/binary_sensor/__init__.py | 4 +- .../nextion/text_sensor/__init__.py | 2 +- .../pipsolar/binary_sensor/__init__.py | 11 ++--- esphome/components/pn532/binary_sensor.py | 8 ++-- esphome/components/rc522/binary_sensor.py | 8 ++-- esphome/components/rdm6300/binary_sensor.py | 8 ++-- esphome/components/remote_base/__init__.py | 2 +- esphome/components/sim800l/binary_sensor.py | 14 ++----- esphome/components/status/binary_sensor.py | 20 +++------- .../sx1509/binary_sensor/__init__.py | 8 ++-- .../teleinfo/text_sensor/__init__.py | 2 +- .../template/binary_sensor/__init__.py | 18 +++++---- esphome/components/text_sensor/__init__.py | 7 ++-- .../touchscreen/binary_sensor/__init__.py | 11 +++-- .../components/ttp229_bsf/binary_sensor.py | 8 ++-- .../components/ttp229_lsf/binary_sensor.py | 8 ++-- .../components/tuya/binary_sensor/__init__.py | 23 ++++++----- esphome/components/wifi_info/text_sensor.py | 10 ++--- .../components/xiaomi_cgpr1/binary_sensor.py | 25 +++++------- .../xiaomi_mjyd02yla/binary_sensor.py | 24 +++++------ .../xiaomi_mue4094rt/binary_sensor.py | 18 +++++---- .../components/xiaomi_wx08zm/binary_sensor.py | 10 ++--- esphome/components/xpt2046/binary_sensor.py | 8 ++-- 38 files changed, 196 insertions(+), 222 deletions(-) diff --git a/esphome/components/apds9960/binary_sensor.py b/esphome/components/apds9960/binary_sensor.py index 4a5c69f6a9..04dc6f4d5d 100644 --- a/esphome/components/apds9960/binary_sensor.py +++ b/esphome/components/apds9960/binary_sensor.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_DIRECTION, CONF_DEVICE_CLASS, DEVICE_CLASS_MOVING +from esphome.const import CONF_DIRECTION, DEVICE_CLASS_MOVING from . import APDS9960, CONF_APDS9960_ID DEPENDENCIES = ["apds9960"] @@ -13,13 +13,12 @@ DIRECTIONS = { "RIGHT": "set_right_direction", } -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_MOVING +).extend( { - cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True), cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), - cv.Optional( - CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING - ): binary_sensor.device_class, + cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True), } ) diff --git a/esphome/components/as3935/binary_sensor.py b/esphome/components/as3935/binary_sensor.py index 11b2ac812c..3081d2115f 100644 --- a/esphome/components/as3935/binary_sensor.py +++ b/esphome/components/as3935/binary_sensor.py @@ -5,7 +5,7 @@ from . import AS3935, CONF_AS3935_ID DEPENDENCIES = ["as3935"] -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema().extend( { cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935), } diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index c6065ddae4..40f95d72f9 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome.cpp_generator import MockObjClass from esphome.cpp_helpers import setup_entity from esphome import automation, core from esphome.automation import Condition, maybe_simple_id @@ -7,7 +8,9 @@ from esphome.components import mqtt from esphome.const import ( CONF_DELAY, CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, CONF_FILTERS, + CONF_ICON, CONF_ID, CONF_INVALID_COOLDOWN, CONF_INVERTED, @@ -314,7 +317,7 @@ def validate_multi_click_timing(value): return timings -device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") +validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( @@ -323,7 +326,7 @@ BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).ex cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( mqtt.MQTTBinarySensorComponent ), - cv.Optional(CONF_DEVICE_CLASS): device_class, + cv.Optional(CONF_DEVICE_CLASS): validate_device_class, cv.Optional(CONF_FILTERS): validate_filters, cv.Optional(CONF_ON_PRESS): automation.validate_automation( { @@ -376,6 +379,39 @@ BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).ex } ) +_UNDEF = object() + + +def binary_sensor_schema( + class_: MockObjClass = _UNDEF, + *, + icon: str = _UNDEF, + entity_category: str = _UNDEF, + device_class: str = _UNDEF, +) -> cv.Schema: + schema = BINARY_SENSOR_SCHEMA + if class_ is not _UNDEF: + schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)}) + 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 + } + ) + if device_class is not _UNDEF: + schema = schema.extend( + { + cv.Optional( + CONF_DEVICE_CLASS, default=device_class + ): validate_device_class + } + ) + return schema + async def setup_binary_sensor_core_(var, config): await setup_entity(var, config) diff --git a/esphome/components/ble_presence/binary_sensor.py b/esphome/components/ble_presence/binary_sensor.py index 2a242c3aca..67f2c3516f 100644 --- a/esphome/components/ble_presence/binary_sensor.py +++ b/esphome/components/ble_presence/binary_sensor.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_IBEACON_MAJOR, CONF_IBEACON_MINOR, CONF_IBEACON_UUID, - CONF_ID, ) DEPENDENCIES = ["esp32_ble_tracker"] @@ -30,9 +29,9 @@ def _validate(config): CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema(BLEPresenceDevice) + .extend( { - cv.GenerateID(): cv.declare_id(BLEPresenceDevice), cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t, @@ -48,10 +47,9 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) await esp32_ble_tracker.register_ble_device(var, config) - await binary_sensor.register_binary_sensor(var, config) if CONF_MAC_ADDRESS in config: cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) diff --git a/esphome/components/ble_scanner/text_sensor.py b/esphome/components/ble_scanner/text_sensor.py index 31dccdf119..743403c6a4 100644 --- a/esphome/components/ble_scanner/text_sensor.py +++ b/esphome/components/ble_scanner/text_sensor.py @@ -13,7 +13,7 @@ BLEScanner = ble_scanner_ns.class_( ) CONFIG_SCHEMA = cv.All( - text_sensor.text_sensor_schema(klass=BLEScanner) + text_sensor.text_sensor_schema(BLEScanner) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) .extend(cv.COMPONENT_SCHEMA) ) diff --git a/esphome/components/cap1188/binary_sensor.py b/esphome/components/cap1188/binary_sensor.py index c249eb7330..7950774340 100644 --- a/esphome/components/cap1188/binary_sensor.py +++ b/esphome/components/cap1188/binary_sensor.py @@ -1,15 +1,14 @@ 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 esphome.const import CONF_CHANNEL 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( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(CAP1188Channel).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), } @@ -17,8 +16,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) hub = await cg.get_variable(config[CONF_CAP1188_ID]) cg.add(var.set_channel(config[CONF_CHANNEL])) diff --git a/esphome/components/custom/binary_sensor/__init__.py b/esphome/components/custom/binary_sensor/__init__.py index 18d613d4c1..8d6d621b3a 100644 --- a/esphome/components/custom/binary_sensor/__init__.py +++ b/esphome/components/custom/binary_sensor/__init__.py @@ -11,7 +11,7 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(): cv.declare_id(CustomBinarySensorConstructor), cv.Required(CONF_LAMBDA): cv.returning_lambda, cv.Required(CONF_BINARY_SENSORS): cv.ensure_list( - binary_sensor.BINARY_SENSOR_SCHEMA + binary_sensor.binary_sensor_schema() ), } ) diff --git a/esphome/components/daly_bms/binary_sensor.py b/esphome/components/daly_bms/binary_sensor.py index 23330cd945..7b252b5e89 100644 --- a/esphome/components/daly_bms/binary_sensor.py +++ b/esphome/components/daly_bms/binary_sensor.py @@ -1,7 +1,6 @@ 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" @@ -18,18 +17,10 @@ CONFIG_SCHEMA = cv.All( 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), - } - ), + ): binary_sensor.binary_sensor_schema(), cv.Optional( CONF_DISCHARGING_MOS_ENABLED - ): binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor), - } - ), + ): binary_sensor.binary_sensor_schema(), } ).extend(cv.COMPONENT_SCHEMA) ) @@ -38,9 +29,8 @@ CONFIG_SCHEMA = cv.All( 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)) + var = await binary_sensor.new_binary_sensor(conf) + cg.add(getattr(hub, f"set_{key}_binary_sensor")(var)) async def to_code(config): diff --git a/esphome/components/demo/__init__.py b/esphome/components/demo/__init__.py index 77b1680d26..7aa742919a 100644 --- a/esphome/components/demo/__init__.py +++ b/esphome/components/demo/__init__.py @@ -132,12 +132,8 @@ CONFIG_SCHEMA = cv.Schema( }, ], ): [ - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema(DemoBinarySensor).extend( cv.polling_component_schema("60s") - ).extend( - { - cv.GenerateID(): cv.declare_id(DemoBinarySensor), - } ) ], cv.Optional( @@ -372,7 +368,7 @@ CONFIG_SCHEMA = cv.Schema( }, ], ): [ - text_sensor.text_sensor_schema(klass=DemoTextSensor).extend( + text_sensor.text_sensor_schema(DemoTextSensor).extend( cv.polling_component_schema("60s") ) ], @@ -382,9 +378,8 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): for conf in config[CONF_BINARY_SENSORS]: - var = cg.new_Pvariable(conf[CONF_ID]) + var = await binary_sensor.new_binary_sensor(conf) await cg.register_component(var, conf) - await binary_sensor.register_binary_sensor(var, conf) for conf in config[CONF_CLIMATES]: var = cg.new_Pvariable(conf[CONF_ID]) diff --git a/esphome/components/esp32_touch/binary_sensor.py b/esphome/components/esp32_touch/binary_sensor.py index bd3e06545d..326f559830 100644 --- a/esphome/components/esp32_touch/binary_sensor.py +++ b/esphome/components/esp32_touch/binary_sensor.py @@ -39,9 +39,8 @@ ESP32TouchBinarySensor = esp32_touch_ns.class_( "ESP32TouchBinarySensor", binary_sensor.BinarySensor ) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(ESP32TouchBinarySensor).extend( { - cv.GenerateID(): cv.declare_id(ESP32TouchBinarySensor), cv.GenerateID(CONF_ESP32_TOUCH_ID): cv.use_id(ESP32TouchComponent), cv.Required(CONF_PIN): validate_touch_pad, cv.Required(CONF_THRESHOLD): cv.uint16_t, diff --git a/esphome/components/fingerprint_grow/binary_sensor.py b/esphome/components/fingerprint_grow/binary_sensor.py index f432ef92cc..8572919e36 100644 --- a/esphome/components/fingerprint_grow/binary_sensor.py +++ b/esphome/components/fingerprint_grow/binary_sensor.py @@ -6,7 +6,7 @@ from . import CONF_FINGERPRINT_GROW_ID, FingerprintGrowComponent DEPENDENCIES = ["fingerprint_grow"] -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema().extend( { cv.GenerateID(CONF_FINGERPRINT_GROW_ID): cv.use_id(FingerprintGrowComponent), cv.Optional(CONF_ICON, default=ICON_KEY_PLUS): cv.icon, diff --git a/esphome/components/gpio/binary_sensor/__init__.py b/esphome/components/gpio/binary_sensor/__init__.py index 4d91b81a44..786c3f4b96 100644 --- a/esphome/components/gpio/binary_sensor/__init__.py +++ b/esphome/components/gpio/binary_sensor/__init__.py @@ -2,25 +2,27 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.components import binary_sensor -from esphome.const import CONF_ID, CONF_PIN +from esphome.const import CONF_PIN from .. import gpio_ns GPIOBinarySensor = gpio_ns.class_( "GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component ) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(GPIOBinarySensor), - cv.Required(CONF_PIN): pins.gpio_input_pin_schema, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + binary_sensor.binary_sensor_schema(GPIOBinarySensor) + .extend( + { + cv.Required(CONF_PIN): pins.gpio_input_pin_schema, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) - await binary_sensor.register_binary_sensor(var, config) pin = await cg.gpio_pin_expression(config[CONF_PIN]) cg.add(var.set_pin(pin)) diff --git a/esphome/components/homeassistant/binary_sensor/__init__.py b/esphome/components/homeassistant/binary_sensor/__init__.py index 4972466aac..a4f854c16e 100644 --- a/esphome/components/homeassistant/binary_sensor/__init__.py +++ b/esphome/components/homeassistant/binary_sensor/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_ID +from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID from .. import homeassistant_ns DEPENDENCIES = ["api"] @@ -9,19 +9,21 @@ HomeassistantBinarySensor = homeassistant_ns.class_( "HomeassistantBinarySensor", binary_sensor.BinarySensor, cg.Component ) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(HomeassistantBinarySensor), - cv.Required(CONF_ENTITY_ID): cv.entity_id, - cv.Optional(CONF_ATTRIBUTE): cv.string, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + binary_sensor.binary_sensor_schema(HomeassistantBinarySensor) + .extend( + { + cv.Required(CONF_ENTITY_ID): cv.entity_id, + cv.Optional(CONF_ATTRIBUTE): cv.string, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) - await binary_sensor.register_binary_sensor(var, config) cg.add(var.set_entity_id(config[CONF_ENTITY_ID])) if CONF_ATTRIBUTE in config: diff --git a/esphome/components/modbus_controller/binary_sensor/__init__.py b/esphome/components/modbus_controller/binary_sensor/__init__.py index 557d76479d..5315167479 100644 --- a/esphome/components/modbus_controller/binary_sensor/__init__.py +++ b/esphome/components/modbus_controller/binary_sensor/__init__.py @@ -29,11 +29,11 @@ ModbusBinarySensor = modbus_controller_ns.class_( ) CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA) + binary_sensor.binary_sensor_schema(ModbusBinarySensor) + .extend(cv.COMPONENT_SCHEMA) .extend(ModbusItemBaseSchema) .extend( { - cv.GenerateID(): cv.declare_id(ModbusBinarySensor), cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), } ), diff --git a/esphome/components/mpr121/binary_sensor.py b/esphome/components/mpr121/binary_sensor.py index 20b80e063e..131fbcfc5b 100644 --- a/esphome/components/mpr121/binary_sensor.py +++ b/esphome/components/mpr121/binary_sensor.py @@ -1,7 +1,7 @@ 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 esphome.const import CONF_CHANNEL from . import ( mpr121_ns, MPR121Component, @@ -13,9 +13,8 @@ from . import ( DEPENDENCIES = ["mpr121"] MPR121Channel = mpr121_ns.class_("MPR121Channel", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(MPR121Channel).extend( { - cv.GenerateID(): cv.declare_id(MPR121Channel), cv.GenerateID(CONF_MPR121_ID): cv.use_id(MPR121Component), cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=11), cv.Optional(CONF_TOUCH_THRESHOLD): cv.int_range(min=0x05, max=0x30), @@ -25,8 +24,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) hub = await cg.get_variable(config[CONF_MPR121_ID]) cg.add(var.set_channel(config[CONF_CHANNEL])) diff --git a/esphome/components/nextion/binary_sensor/__init__.py b/esphome/components/nextion/binary_sensor/__init__.py index 090fae3429..8b4a45cc60 100644 --- a/esphome/components/nextion/binary_sensor/__init__.py +++ b/esphome/components/nextion/binary_sensor/__init__.py @@ -20,9 +20,9 @@ NextionBinarySensor = nextion_ns.class_( ) CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema(NextionBinarySensor) + .extend( { - cv.GenerateID(): cv.declare_id(NextionBinarySensor), cv.Optional(CONF_PAGE_ID): cv.uint8_t, cv.Optional(CONF_COMPONENT_ID): cv.uint8_t, } diff --git a/esphome/components/nextion/text_sensor/__init__.py b/esphome/components/nextion/text_sensor/__init__.py index 9b8518d8c4..826ff2354e 100644 --- a/esphome/components/nextion/text_sensor/__init__.py +++ b/esphome/components/nextion/text_sensor/__init__.py @@ -17,7 +17,7 @@ NextionTextSensor = nextion_ns.class_( ) CONFIG_SCHEMA = ( - text_sensor.text_sensor_schema(klass=NextionTextSensor) + text_sensor.text_sensor_schema(NextionTextSensor) .extend(CONFIG_TEXT_COMPONENT_SCHEMA) .extend(cv.polling_component_schema("never")) ) diff --git a/esphome/components/pipsolar/binary_sensor/__init__.py b/esphome/components/pipsolar/binary_sensor/__init__.py index 5c6af3bffc..f4b34fd594 100644 --- a/esphome/components/pipsolar/binary_sensor/__init__.py +++ b/esphome/components/pipsolar/binary_sensor/__init__.py @@ -1,9 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import ( - CONF_ID, -) + from .. import PIPSOLAR_COMPONENT_SCHEMA, CONF_PIPSOLAR_ID DEPENDENCIES = ["uart"] @@ -130,7 +128,7 @@ TYPES = [ ] CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend( - {cv.Optional(type): binary_sensor.BINARY_SENSOR_SCHEMA for type in TYPES} + {cv.Optional(type): binary_sensor.binary_sensor_schema() for type in TYPES} ) @@ -139,6 +137,5 @@ async def to_code(config): for type in TYPES: if type in config: conf = config[type] - sens = cg.new_Pvariable(conf[CONF_ID]) - await binary_sensor.register_binary_sensor(sens, conf) - cg.add(getattr(paren, f"set_{type}")(sens)) + var = await binary_sensor.new_binary_sensor(conf) + cg.add(getattr(paren, f"set_{type}")(var)) diff --git a/esphome/components/pn532/binary_sensor.py b/esphome/components/pn532/binary_sensor.py index 9a5816b322..9bcae30750 100644 --- a/esphome/components/pn532/binary_sensor.py +++ b/esphome/components/pn532/binary_sensor.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_UID, CONF_ID +from esphome.const import CONF_UID from esphome.core import HexInt from . import pn532_ns, PN532, CONF_PN532_ID @@ -31,9 +31,8 @@ def validate_uid(value): PN532BinarySensor = pn532_ns.class_("PN532BinarySensor", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(PN532BinarySensor).extend( { - cv.GenerateID(): cv.declare_id(PN532BinarySensor), cv.GenerateID(CONF_PN532_ID): cv.use_id(PN532), cv.Required(CONF_UID): validate_uid, } @@ -41,8 +40,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) hub = await cg.get_variable(config[CONF_PN532_ID]) cg.add(hub.register_tag(var)) diff --git a/esphome/components/rc522/binary_sensor.py b/esphome/components/rc522/binary_sensor.py index 67d3068599..716c0eca76 100644 --- a/esphome/components/rc522/binary_sensor.py +++ b/esphome/components/rc522/binary_sensor.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_UID, CONF_ID +from esphome.const import CONF_UID from esphome.core import HexInt from . import rc522_ns, RC522, CONF_RC522_ID @@ -31,9 +31,8 @@ def validate_uid(value): RC522BinarySensor = rc522_ns.class_("RC522BinarySensor", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(RC522BinarySensor).extend( { - cv.GenerateID(): cv.declare_id(RC522BinarySensor), cv.GenerateID(CONF_RC522_ID): cv.use_id(RC522), cv.Required(CONF_UID): validate_uid, } @@ -41,8 +40,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) hub = await cg.get_variable(config[CONF_RC522_ID]) cg.add(hub.register_tag(var)) diff --git a/esphome/components/rdm6300/binary_sensor.py b/esphome/components/rdm6300/binary_sensor.py index c99a2bfc06..cd808b92cc 100644 --- a/esphome/components/rdm6300/binary_sensor.py +++ b/esphome/components/rdm6300/binary_sensor.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor, rdm6300 -from esphome.const import CONF_UID, CONF_ID +from esphome.const import CONF_UID from . import rdm6300_ns DEPENDENCIES = ["rdm6300"] @@ -11,9 +11,8 @@ RDM6300BinarySensor = rdm6300_ns.class_( "RDM6300BinarySensor", binary_sensor.BinarySensor ) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(RDM6300BinarySensor).extend( { - cv.GenerateID(): cv.declare_id(RDM6300BinarySensor), cv.GenerateID(CONF_RDM6300_ID): cv.use_id(rdm6300.RDM6300Component), cv.Required(CONF_UID): cv.uint32_t, } @@ -21,8 +20,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) hub = await cg.get_variable(config[CONF_RDM6300_ID]) cg.add(hub.register_card(var)) diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 9bf03aaf28..531761ee95 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -165,7 +165,7 @@ def declare_protocol(name): BINARY_SENSOR_REGISTRY = Registry( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema().extend( { cv.GenerateID(CONF_RECEIVER_ID): cv.use_id(RemoteReceiverBase), } diff --git a/esphome/components/sim800l/binary_sensor.py b/esphome/components/sim800l/binary_sensor.py index 7cee04374b..f046d031ea 100644 --- a/esphome/components/sim800l/binary_sensor.py +++ b/esphome/components/sim800l/binary_sensor.py @@ -2,8 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor from esphome.const import ( - CONF_DEVICE_CLASS, - CONF_ENTITY_CATEGORY, DEVICE_CLASS_CONNECTIVITY, ENTITY_CATEGORY_DIAGNOSTIC, ) @@ -15,15 +13,9 @@ CONF_REGISTERED = "registered" CONFIG_SCHEMA = { cv.GenerateID(CONF_SIM800L_ID): cv.use_id(Sim800LComponent), - cv.Optional(CONF_REGISTERED): 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, - } + cv.Optional(CONF_REGISTERED): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_CONNECTIVITY, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } diff --git a/esphome/components/status/binary_sensor.py b/esphome/components/status/binary_sensor.py index 9367706388..1f2b7c9d18 100644 --- a/esphome/components/status/binary_sensor.py +++ b/esphome/components/status/binary_sensor.py @@ -2,9 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor from esphome.const import ( - CONF_ENTITY_CATEGORY, - CONF_ID, - CONF_DEVICE_CLASS, DEVICE_CLASS_CONNECTIVITY, ENTITY_CATEGORY_DIAGNOSTIC, ) @@ -14,20 +11,13 @@ StatusBinarySensor = status_ns.class_( "StatusBinarySensor", binary_sensor.BinarySensor, cg.Component ) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(StatusBinarySensor), - 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, - } +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema( + StatusBinarySensor, + device_class=DEVICE_CLASS_CONNECTIVITY, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ).extend(cv.COMPONENT_SCHEMA) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) - await binary_sensor.register_binary_sensor(var, config) diff --git a/esphome/components/sx1509/binary_sensor/__init__.py b/esphome/components/sx1509/binary_sensor/__init__.py index 1560af8e99..bbf0e5d0bc 100644 --- a/esphome/components/sx1509/binary_sensor/__init__.py +++ b/esphome/components/sx1509/binary_sensor/__init__.py @@ -1,7 +1,7 @@ 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 SX1509Component, sx1509_ns, CONF_SX1509_ID CONF_ROW = "row" @@ -11,9 +11,8 @@ DEPENDENCIES = ["sx1509"] SX1509BinarySensor = sx1509_ns.class_("SX1509BinarySensor", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(SX1509BinarySensor).extend( { - cv.GenerateID(): cv.declare_id(SX1509BinarySensor), cv.GenerateID(CONF_SX1509_ID): cv.use_id(SX1509Component), cv.Required(CONF_ROW): cv.int_range(min=0, max=4), cv.Required(CONF_COL): cv.int_range(min=0, max=4), @@ -22,8 +21,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) hub = await cg.get_variable(config[CONF_SX1509_ID]) cg.add(var.set_row_col(config[CONF_ROW], config[CONF_COL])) diff --git a/esphome/components/teleinfo/text_sensor/__init__.py b/esphome/components/teleinfo/text_sensor/__init__.py index 848b08d742..df8e4c21fc 100644 --- a/esphome/components/teleinfo/text_sensor/__init__.py +++ b/esphome/components/teleinfo/text_sensor/__init__.py @@ -8,7 +8,7 @@ TeleInfoTextSensor = teleinfo_ns.class_( "TeleInfoTextSensor", text_sensor.TextSensor, cg.Component ) -CONFIG_SCHEMA = text_sensor.text_sensor_schema(klass=TeleInfoTextSensor).extend( +CONFIG_SCHEMA = text_sensor.text_sensor_schema(TeleInfoTextSensor).extend( TELEINFO_LISTENER_SCHEMA ) diff --git a/esphome/components/template/binary_sensor/__init__.py b/esphome/components/template/binary_sensor/__init__.py index 8f551e3215..4ce89503de 100644 --- a/esphome/components/template/binary_sensor/__init__.py +++ b/esphome/components/template/binary_sensor/__init__.py @@ -9,18 +9,20 @@ TemplateBinarySensor = template_ns.class_( "TemplateBinarySensor", binary_sensor.BinarySensor, cg.Component ) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(TemplateBinarySensor), - cv.Optional(CONF_LAMBDA): cv.returning_lambda, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + binary_sensor.binary_sensor_schema(TemplateBinarySensor) + .extend( + { + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) - await binary_sensor.register_binary_sensor(var, config) if CONF_LAMBDA in config: template_ = await cg.process_lambda( diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index de72579402..f2f382ceaa 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -137,13 +137,14 @@ _UNDEF = object() def text_sensor_schema( - klass: MockObjClass = _UNDEF, + class_: MockObjClass = _UNDEF, + *, icon: str = _UNDEF, entity_category: str = _UNDEF, ) -> cv.Schema: schema = TEXT_SENSOR_SCHEMA - if klass is not _UNDEF: - schema = schema.extend({cv.GenerateID(): cv.declare_id(klass)}) + if class_ is not _UNDEF: + schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)}) if icon is not _UNDEF: schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon}) if entity_category is not _UNDEF: diff --git a/esphome/components/touchscreen/binary_sensor/__init__.py b/esphome/components/touchscreen/binary_sensor/__init__.py index 9dba821d4d..73cbf1df7e 100644 --- a/esphome/components/touchscreen/binary_sensor/__init__.py +++ b/esphome/components/touchscreen/binary_sensor/__init__.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_ID from .. import touchscreen_ns, CONF_TOUCHSCREEN_ID, Touchscreen, TouchListener @@ -34,23 +33,23 @@ def validate_coords(config): CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema(TouchscreenBinarySensor) + .extend( { - cv.GenerateID(): cv.declare_id(TouchscreenBinarySensor), cv.GenerateID(CONF_TOUCHSCREEN_ID): cv.use_id(Touchscreen), cv.Required(CONF_X_MIN): cv.int_range(min=0, max=2000), cv.Required(CONF_X_MAX): cv.int_range(min=0, max=2000), cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=2000), cv.Required(CONF_Y_MAX): cv.int_range(min=0, max=2000), } - ).extend(cv.COMPONENT_SCHEMA), + ) + .extend(cv.COMPONENT_SCHEMA), validate_coords, ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) await cg.register_parented(var, config[CONF_TOUCHSCREEN_ID]) diff --git a/esphome/components/ttp229_bsf/binary_sensor.py b/esphome/components/ttp229_bsf/binary_sensor.py index 75540fe0e8..8a0c7fce48 100644 --- a/esphome/components/ttp229_bsf/binary_sensor.py +++ b/esphome/components/ttp229_bsf/binary_sensor.py @@ -1,15 +1,14 @@ 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 esphome.const import CONF_CHANNEL from . import ttp229_bsf_ns, TTP229BSFComponent, CONF_TTP229_ID DEPENDENCIES = ["ttp229_bsf"] TTP229BSFChannel = ttp229_bsf_ns.class_("TTP229BSFChannel", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(TTP229BSFChannel).extend( { - cv.GenerateID(): cv.declare_id(TTP229BSFChannel), cv.GenerateID(CONF_TTP229_ID): cv.use_id(TTP229BSFComponent), cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=15), } @@ -17,8 +16,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) cg.add(var.set_channel(config[CONF_CHANNEL])) hub = await cg.get_variable(config[CONF_TTP229_ID]) diff --git a/esphome/components/ttp229_lsf/binary_sensor.py b/esphome/components/ttp229_lsf/binary_sensor.py index b52a9e8575..5fba0096de 100644 --- a/esphome/components/ttp229_lsf/binary_sensor.py +++ b/esphome/components/ttp229_lsf/binary_sensor.py @@ -1,15 +1,14 @@ 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 esphome.const import CONF_CHANNEL from . import ttp229_lsf_ns, TTP229LSFComponent, CONF_TTP229_ID DEPENDENCIES = ["ttp229_lsf"] TTP229Channel = ttp229_lsf_ns.class_("TTP229Channel", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(TTP229Channel).extend( { - cv.GenerateID(): cv.declare_id(TTP229Channel), cv.GenerateID(CONF_TTP229_ID): cv.use_id(TTP229LSFComponent), cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=15), } @@ -17,8 +16,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) cg.add(var.set_channel(config[CONF_CHANNEL])) hub = await cg.get_variable(config[CONF_TTP229_ID]) diff --git a/esphome/components/tuya/binary_sensor/__init__.py b/esphome/components/tuya/binary_sensor/__init__.py index cd4a2db89f..856b5eb323 100644 --- a/esphome/components/tuya/binary_sensor/__init__.py +++ b/esphome/components/tuya/binary_sensor/__init__.py @@ -1,7 +1,8 @@ from esphome.components import binary_sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT +from esphome.const import CONF_SENSOR_DATAPOINT + from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] @@ -11,19 +12,21 @@ TuyaBinarySensor = tuya_ns.class_( "TuyaBinarySensor", binary_sensor.BinarySensor, cg.Component ) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(TuyaBinarySensor), - cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), - cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + binary_sensor.binary_sensor_schema(TuyaBinarySensor) + .extend( + { + 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]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) - await binary_sensor.register_binary_sensor(var, config) paren = await cg.get_variable(config[CONF_TUYA_ID]) cg.add(var.set_tuya_parent(paren)) diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index 58250c3759..54993d48ee 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -32,19 +32,19 @@ MacAddressWifiInfo = wifi_info_ns.class_( CONFIG_SCHEMA = cv.Schema( { cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema( - klass=IPAddressWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC + IPAddressWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ).extend(cv.polling_component_schema("1s")), cv.Optional(CONF_SCAN_RESULTS): text_sensor.text_sensor_schema( - klass=ScanResultsWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC + ScanResultsWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ).extend(cv.polling_component_schema("60s")), cv.Optional(CONF_SSID): text_sensor.text_sensor_schema( - klass=SSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC + SSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ).extend(cv.polling_component_schema("1s")), cv.Optional(CONF_BSSID): text_sensor.text_sensor_schema( - klass=BSSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC + BSSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ).extend(cv.polling_component_schema("1s")), cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema( - klass=MacAddressWifiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC + MacAddressWifiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ), } ) diff --git a/esphome/components/xiaomi_cgpr1/binary_sensor.py b/esphome/components/xiaomi_cgpr1/binary_sensor.py index 4297b7fc3b..1b878ca800 100644 --- a/esphome/components/xiaomi_cgpr1/binary_sensor.py +++ b/esphome/components/xiaomi_cgpr1/binary_sensor.py @@ -4,9 +4,7 @@ from esphome.components import sensor, binary_sensor, esp32_ble_tracker from esphome.const import ( CONF_BATTERY_LEVEL, CONF_BINDKEY, - CONF_DEVICE_CLASS, CONF_MAC_ADDRESS, - CONF_ID, DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MOTION, @@ -31,15 +29,11 @@ XiaomiCGPR1 = xiaomi_cgpr1_ns.class_( ) CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema(XiaomiCGPR1, device_class=DEVICE_CLASS_MOTION) + .extend( { - cv.GenerateID(): cv.declare_id(XiaomiCGPR1), cv.Required(CONF_BINDKEY): cv.bind_key, cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional( - CONF_DEVICE_CLASS, - default=DEVICE_CLASS_MOTION, - ): binary_sensor.device_class, cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, accuracy_decimals=0, @@ -64,21 +58,20 @@ CONFIG_SCHEMA = cv.All( ) -def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - yield cg.register_component(var, config) - yield esp32_ble_tracker.register_ble_device(var, config) - yield binary_sensor.register_binary_sensor(var, config) +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + 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)) cg.add(var.set_bindkey(config[CONF_BINDKEY])) if CONF_IDLE_TIME in config: - sens = yield sensor.new_sensor(config[CONF_IDLE_TIME]) + sens = await sensor.new_sensor(config[CONF_IDLE_TIME]) cg.add(var.set_idle_time(sens)) if CONF_BATTERY_LEVEL in config: - sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) + sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) cg.add(var.set_battery_level(sens)) if CONF_ILLUMINANCE in config: - sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE]) + sens = await sensor.new_sensor(config[CONF_ILLUMINANCE]) cg.add(var.set_illuminance(sens)) diff --git a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py index 6a7d6aae79..9a4b50df91 100644 --- a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py +++ b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py @@ -3,13 +3,13 @@ import esphome.config_validation as cv from esphome.components import sensor, binary_sensor, esp32_ble_tracker from esphome.const import ( CONF_MAC_ADDRESS, - CONF_ID, CONF_BINDKEY, - CONF_DEVICE_CLASS, CONF_LIGHT, CONF_BATTERY_LEVEL, DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_LIGHT, + DEVICE_CLASS_MOTION, ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_PERCENT, @@ -32,14 +32,13 @@ XiaomiMJYD02YLA = xiaomi_mjyd02yla_ns.class_( ) CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema( + XiaomiMJYD02YLA, device_class=DEVICE_CLASS_MOTION + ) + .extend( { - cv.GenerateID(): cv.declare_id(XiaomiMJYD02YLA), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Required(CONF_BINDKEY): cv.bind_key, - cv.Optional( - CONF_DEVICE_CLASS, default="motion" - ): binary_sensor.device_class, cv.Optional(CONF_IDLE_TIME): sensor.sensor_schema( unit_of_measurement=UNIT_MINUTE, icon=ICON_TIMELAPSE, @@ -58,12 +57,8 @@ CONFIG_SCHEMA = cv.All( device_class=DEVICE_CLASS_ILLUMINANCE, state_class=STATE_CLASS_MEASUREMENT, ), - cv.Optional(CONF_LIGHT): binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.Optional( - CONF_DEVICE_CLASS, default="light" - ): binary_sensor.device_class, - } + cv.Optional(CONF_LIGHT): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_LIGHT ), } ) @@ -73,10 +68,9 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) await esp32_ble_tracker.register_ble_device(var, config) - await binary_sensor.register_binary_sensor(var, config) cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) cg.add(var.set_bindkey(config[CONF_BINDKEY])) diff --git a/esphome/components/xiaomi_mue4094rt/binary_sensor.py b/esphome/components/xiaomi_mue4094rt/binary_sensor.py index 5d19263c9c..94d85213ff 100644 --- a/esphome/components/xiaomi_mue4094rt/binary_sensor.py +++ b/esphome/components/xiaomi_mue4094rt/binary_sensor.py @@ -1,7 +1,11 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor, esp32_ble_tracker -from esphome.const import CONF_MAC_ADDRESS, CONF_DEVICE_CLASS, CONF_TIMEOUT, CONF_ID +from esphome.const import ( + CONF_MAC_ADDRESS, + CONF_TIMEOUT, + DEVICE_CLASS_MOTION, +) DEPENDENCIES = ["esp32_ble_tracker"] @@ -16,13 +20,12 @@ XiaomiMUE4094RT = xiaomi_mue4094rt_ns.class_( ) CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema( + XiaomiMUE4094RT, device_class=DEVICE_CLASS_MOTION + ) + .extend( { - cv.GenerateID(): cv.declare_id(XiaomiMUE4094RT), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional( - CONF_DEVICE_CLASS, default="motion" - ): binary_sensor.device_class, cv.Optional( CONF_TIMEOUT, default="5s" ): cv.positive_time_period_milliseconds, @@ -34,10 +37,9 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) await esp32_ble_tracker.register_ble_device(var, config) - await binary_sensor.register_binary_sensor(var, config) cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) cg.add(var.set_time(config[CONF_TIMEOUT])) diff --git a/esphome/components/xiaomi_wx08zm/binary_sensor.py b/esphome/components/xiaomi_wx08zm/binary_sensor.py index 8667794923..504dff9d66 100644 --- a/esphome/components/xiaomi_wx08zm/binary_sensor.py +++ b/esphome/components/xiaomi_wx08zm/binary_sensor.py @@ -10,12 +10,11 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_PERCENT, ICON_BUG, - CONF_ID, ) DEPENDENCIES = ["esp32_ble_tracker"] -AUTO_LOAD = ["xiaomi_ble"] +AUTO_LOAD = ["xiaomi_ble", "sensor"] xiaomi_wx08zm_ns = cg.esphome_ns.namespace("xiaomi_wx08zm") XiaomiWX08ZM = xiaomi_wx08zm_ns.class_( @@ -26,9 +25,9 @@ XiaomiWX08ZM = xiaomi_wx08zm_ns.class_( ) CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema(XiaomiWX08ZM) + .extend( { - cv.GenerateID(): cv.declare_id(XiaomiWX08ZM), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TABLET): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, @@ -51,10 +50,9 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) await esp32_ble_tracker.register_ble_device(var, config) - await binary_sensor.register_binary_sensor(var, config) cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) diff --git a/esphome/components/xpt2046/binary_sensor.py b/esphome/components/xpt2046/binary_sensor.py index 6959d6c342..6ec09a2295 100644 --- a/esphome/components/xpt2046/binary_sensor.py +++ b/esphome/components/xpt2046/binary_sensor.py @@ -1,7 +1,7 @@ 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 ( xpt2046_ns, XPT2046Component, @@ -27,9 +27,8 @@ def validate_xpt2046_button(config): CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema(XPT2046Button).extend( { - cv.GenerateID(): cv.declare_id(XPT2046Button), cv.GenerateID(CONF_XPT2046_ID): cv.use_id(XPT2046Component), cv.Required(CONF_X_MIN): cv.int_range(min=0, max=4095), cv.Required(CONF_X_MAX): cv.int_range(min=0, max=4095), @@ -42,8 +41,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) hub = await cg.get_variable(config[CONF_XPT2046_ID]) cg.add( var.set_area( From 1a8f8adc2a2b0393cd0639d0cdf3b8088df41201 Mon Sep 17 00:00:00 2001 From: Michael Labuschke Date: Thu, 17 Feb 2022 23:00:03 +0100 Subject: [PATCH 125/238] Read all cell voltages from DalyBMS (#3203) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/daly_bms/daly_bms.cpp | 67 ++++++++++++++++++++++++ esphome/components/daly_bms/daly_bms.h | 33 ++++++++++++ esphome/components/daly_bms/sensor.py | 54 +++++++++++++++++++ 3 files changed, 154 insertions(+) diff --git a/esphome/components/daly_bms/daly_bms.cpp b/esphome/components/daly_bms/daly_bms.cpp index 44c05f0686..f2b4c0e92b 100644 --- a/esphome/components/daly_bms/daly_bms.cpp +++ b/esphome/components/daly_bms/daly_bms.cpp @@ -16,6 +16,7 @@ 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_CELL_VOLTAGE = 0x95; static const uint8_t DALY_REQUEST_TEMPERATURE = 0x96; void DalyBmsComponent::setup() {} @@ -31,6 +32,7 @@ void DalyBmsComponent::update() { 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_CELL_VOLTAGE); this->request_data_(DALY_REQUEST_TEMPERATURE); std::vector get_battery_level_data; @@ -166,6 +168,71 @@ void DalyBmsComponent::decode_data_(std::vector data) { } break; + case DALY_REQUEST_CELL_VOLTAGE: + switch (it[4]) { + case 1: + if (this->cell_1_voltage_) { + this->cell_1_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); + } + if (this->cell_2_voltage_) { + this->cell_2_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); + } + if (this->cell_3_voltage_) { + this->cell_3_voltage_->publish_state((float) encode_uint16(it[9], it[10]) / 1000); + } + break; + case 2: + if (this->cell_4_voltage_) { + this->cell_4_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); + } + if (this->cell_5_voltage_) { + this->cell_5_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); + } + if (this->cell_6_voltage_) { + this->cell_6_voltage_->publish_state((float) encode_uint16(it[9], it[10]) / 1000); + } + break; + case 3: + if (this->cell_7_voltage_) { + this->cell_7_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); + } + if (this->cell_8_voltage_) { + this->cell_8_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); + } + if (this->cell_9_voltage_) { + this->cell_9_voltage_->publish_state((float) encode_uint16(it[9], it[10]) / 1000); + } + break; + case 4: + if (this->cell_10_voltage_) { + this->cell_10_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); + } + if (this->cell_11_voltage_) { + this->cell_11_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); + } + if (this->cell_12_voltage_) { + this->cell_12_voltage_->publish_state((float) encode_uint16(it[9], it[10]) / 1000); + } + break; + case 5: + if (this->cell_13_voltage_) { + this->cell_13_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); + } + if (this->cell_14_voltage_) { + this->cell_14_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); + } + if (this->cell_15_voltage_) { + this->cell_15_voltage_->publish_state((float) encode_uint16(it[9], it[10]) / 1000); + } + break; + case 6: + if (this->cell_16_voltage_) { + this->cell_16_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); + } + break; + } + break; + default: break; } diff --git a/esphome/components/daly_bms/daly_bms.h b/esphome/components/daly_bms/daly_bms.h index b5d4c8ae39..90faab77f7 100644 --- a/esphome/components/daly_bms/daly_bms.h +++ b/esphome/components/daly_bms/daly_bms.h @@ -37,6 +37,23 @@ class DalyBmsComponent : public PollingComponent, public uart::UARTDevice { 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; } + void set_cell_1_voltage_sensor(sensor::Sensor *cell_1_voltage) { cell_1_voltage_ = cell_1_voltage; } + void set_cell_2_voltage_sensor(sensor::Sensor *cell_2_voltage) { cell_2_voltage_ = cell_2_voltage; } + void set_cell_3_voltage_sensor(sensor::Sensor *cell_3_voltage) { cell_3_voltage_ = cell_3_voltage; } + void set_cell_4_voltage_sensor(sensor::Sensor *cell_4_voltage) { cell_4_voltage_ = cell_4_voltage; } + void set_cell_5_voltage_sensor(sensor::Sensor *cell_5_voltage) { cell_5_voltage_ = cell_5_voltage; } + void set_cell_6_voltage_sensor(sensor::Sensor *cell_6_voltage) { cell_6_voltage_ = cell_6_voltage; } + void set_cell_7_voltage_sensor(sensor::Sensor *cell_7_voltage) { cell_7_voltage_ = cell_7_voltage; } + void set_cell_8_voltage_sensor(sensor::Sensor *cell_8_voltage) { cell_8_voltage_ = cell_8_voltage; } + void set_cell_9_voltage_sensor(sensor::Sensor *cell_9_voltage) { cell_9_voltage_ = cell_9_voltage; } + void set_cell_10_voltage_sensor(sensor::Sensor *cell_10_voltage) { cell_10_voltage_ = cell_10_voltage; } + void set_cell_11_voltage_sensor(sensor::Sensor *cell_11_voltage) { cell_11_voltage_ = cell_11_voltage; } + void set_cell_12_voltage_sensor(sensor::Sensor *cell_12_voltage) { cell_12_voltage_ = cell_12_voltage; } + void set_cell_13_voltage_sensor(sensor::Sensor *cell_13_voltage) { cell_13_voltage_ = cell_13_voltage; } + void set_cell_14_voltage_sensor(sensor::Sensor *cell_14_voltage) { cell_14_voltage_ = cell_14_voltage; } + void set_cell_15_voltage_sensor(sensor::Sensor *cell_15_voltage) { cell_15_voltage_ = cell_15_voltage; } + void set_cell_16_voltage_sensor(sensor::Sensor *cell_16_voltage) { cell_16_voltage_ = cell_16_voltage; } + // TEXT_SENSORS void set_status_text_sensor(text_sensor::TextSensor *status_text_sensor) { status_text_sensor_ = status_text_sensor; } // BINARY_SENSORS @@ -72,6 +89,22 @@ class DalyBmsComponent : public PollingComponent, public uart::UARTDevice { sensor::Sensor *cells_number_{nullptr}; sensor::Sensor *temperature_1_sensor_{nullptr}; sensor::Sensor *temperature_2_sensor_{nullptr}; + sensor::Sensor *cell_1_voltage_{nullptr}; + sensor::Sensor *cell_2_voltage_{nullptr}; + sensor::Sensor *cell_3_voltage_{nullptr}; + sensor::Sensor *cell_4_voltage_{nullptr}; + sensor::Sensor *cell_5_voltage_{nullptr}; + sensor::Sensor *cell_6_voltage_{nullptr}; + sensor::Sensor *cell_7_voltage_{nullptr}; + sensor::Sensor *cell_8_voltage_{nullptr}; + sensor::Sensor *cell_9_voltage_{nullptr}; + sensor::Sensor *cell_10_voltage_{nullptr}; + sensor::Sensor *cell_11_voltage_{nullptr}; + sensor::Sensor *cell_12_voltage_{nullptr}; + sensor::Sensor *cell_13_voltage_{nullptr}; + sensor::Sensor *cell_14_voltage_{nullptr}; + sensor::Sensor *cell_15_voltage_{nullptr}; + sensor::Sensor *cell_16_voltage_{nullptr}; text_sensor::TextSensor *status_text_sensor_{nullptr}; diff --git a/esphome/components/daly_bms/sensor.py b/esphome/components/daly_bms/sensor.py index 0ba68d3786..e2e8528317 100644 --- a/esphome/components/daly_bms/sensor.py +++ b/esphome/components/daly_bms/sensor.py @@ -36,6 +36,22 @@ CONF_REMAINING_CAPACITY = "remaining_capacity" CONF_TEMPERATURE_1 = "temperature_1" CONF_TEMPERATURE_2 = "temperature_2" +CONF_CELL_1_VOLTAGE = "cell_1_voltage" +CONF_CELL_2_VOLTAGE = "cell_2_voltage" +CONF_CELL_3_VOLTAGE = "cell_3_voltage" +CONF_CELL_4_VOLTAGE = "cell_4_voltage" +CONF_CELL_5_VOLTAGE = "cell_5_voltage" +CONF_CELL_6_VOLTAGE = "cell_6_voltage" +CONF_CELL_7_VOLTAGE = "cell_7_voltage" +CONF_CELL_8_VOLTAGE = "cell_8_voltage" +CONF_CELL_9_VOLTAGE = "cell_9_voltage" +CONF_CELL_10_VOLTAGE = "cell_10_voltage" +CONF_CELL_11_VOLTAGE = "cell_11_voltage" +CONF_CELL_12_VOLTAGE = "cell_12_voltage" +CONF_CELL_13_VOLTAGE = "cell_13_voltage" +CONF_CELL_14_VOLTAGE = "cell_14_voltage" +CONF_CELL_15_VOLTAGE = "cell_15_voltage" +CONF_CELL_16_VOLTAGE = "cell_16_voltage" ICON_CURRENT_DC = "mdi:current-dc" ICON_BATTERY_OUTLINE = "mdi:battery-outline" ICON_THERMOMETER_CHEVRON_UP = "mdi:thermometer-chevron-up" @@ -60,8 +76,30 @@ TYPES = [ CONF_REMAINING_CAPACITY, CONF_TEMPERATURE_1, CONF_TEMPERATURE_2, + CONF_CELL_1_VOLTAGE, + CONF_CELL_2_VOLTAGE, + CONF_CELL_3_VOLTAGE, + CONF_CELL_4_VOLTAGE, + CONF_CELL_5_VOLTAGE, + CONF_CELL_6_VOLTAGE, + CONF_CELL_7_VOLTAGE, + CONF_CELL_8_VOLTAGE, + CONF_CELL_9_VOLTAGE, + CONF_CELL_10_VOLTAGE, + CONF_CELL_11_VOLTAGE, + CONF_CELL_12_VOLTAGE, + CONF_CELL_13_VOLTAGE, + CONF_CELL_14_VOLTAGE, + CONF_CELL_15_VOLTAGE, + CONF_CELL_16_VOLTAGE, ] +CELL_VOLTAGE_SCHEMA = sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, +) + CONFIG_SCHEMA = cv.All( cv.Schema( { @@ -156,6 +194,22 @@ CONFIG_SCHEMA = cv.All( device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_CELL_1_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_2_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_3_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_4_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_5_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_6_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_7_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_8_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_9_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_10_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_11_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_12_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_13_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_14_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_15_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_16_VOLTAGE): CELL_VOLTAGE_SCHEMA, } ).extend(cv.COMPONENT_SCHEMA) ) From f137cc10f4124f0ab891e606fbbd096d2573c38e Mon Sep 17 00:00:00 2001 From: ImSorryButWho <91785526+ImSorryButWho@users.noreply.github.com> Date: Fri, 18 Feb 2022 16:22:41 -0500 Subject: [PATCH 126/238] Remote magiquest protocol (#2963) Co-authored-by: Aaron Hertz Co-authored-by: Otto Winter --- esphome/components/remote_base/__init__.py | 50 +++++++++++ .../remote_base/magiquest_protocol.cpp | 83 +++++++++++++++++++ .../remote_base/magiquest_protocol.h | 50 +++++++++++ esphome/const.py | 2 + tests/test1.yaml | 5 ++ 5 files changed, 190 insertions(+) create mode 100644 esphome/components/remote_base/magiquest_protocol.cpp create mode 100644 esphome/components/remote_base/magiquest_protocol.h diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 531761ee95..f1b3e32c18 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -27,6 +27,8 @@ from esphome.const import ( CONF_CARRIER_FREQUENCY, CONF_RC_CODE_1, CONF_RC_CODE_2, + CONF_MAGNITUDE, + CONF_WAND_ID, CONF_LEVEL, ) from esphome.core import coroutine @@ -391,6 +393,54 @@ async def lg_action(var, config, args): cg.add(var.set_nbits(template_)) +# MagiQuest +( + MagiQuestData, + MagiQuestBinarySensor, + MagiQuestTrigger, + MagiQuestAction, + MagiQuestDumper, +) = declare_protocol("MagiQuest") + +MAGIQUEST_SCHEMA = cv.Schema( + { + cv.Required(CONF_WAND_ID): cv.hex_uint32_t, + cv.Optional(CONF_MAGNITUDE, default=0xFFFF): cv.hex_uint16_t, + } +) + + +@register_binary_sensor("magiquest", MagiQuestBinarySensor, MAGIQUEST_SCHEMA) +def magiquest_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + MagiQuestData, + ("magnitude", config[CONF_MAGNITUDE]), + ("wand_id", config[CONF_WAND_ID]), + ) + ) + ) + + +@register_trigger("magiquest", MagiQuestTrigger, MagiQuestData) +def magiquest_trigger(var, config): + pass + + +@register_dumper("magiquest", MagiQuestDumper) +def magiquest_dumper(var, config): + pass + + +@register_action("magiquest", MagiQuestAction, MAGIQUEST_SCHEMA) +async def magiquest_action(var, config, args): + template_ = await cg.templatable(config[CONF_WAND_ID], args, cg.uint32) + cg.add(var.set_wand_id(template_)) + template_ = await cg.templatable(config[CONF_MAGNITUDE], args, cg.uint16) + cg.add(var.set_magnitude(template_)) + + # NEC NECData, NECBinarySensor, NECTrigger, NECAction, NECDumper = declare_protocol("NEC") NEC_SCHEMA = cv.Schema( diff --git a/esphome/components/remote_base/magiquest_protocol.cpp b/esphome/components/remote_base/magiquest_protocol.cpp new file mode 100644 index 0000000000..20b40ef201 --- /dev/null +++ b/esphome/components/remote_base/magiquest_protocol.cpp @@ -0,0 +1,83 @@ +#include "magiquest_protocol.h" +#include "esphome/core/log.h" + +/* Based on protocol analysis from + * https://arduino-irremote.github.io/Arduino-IRremote/ir__MagiQuest_8cpp_source.html + */ + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.magiquest"; + +static const uint32_t MAGIQUEST_UNIT = 288; // us +static const uint32_t MAGIQUEST_ONE_MARK = 2 * MAGIQUEST_UNIT; +static const uint32_t MAGIQUEST_ONE_SPACE = 2 * MAGIQUEST_UNIT; +static const uint32_t MAGIQUEST_ZERO_MARK = MAGIQUEST_UNIT; +static const uint32_t MAGIQUEST_ZERO_SPACE = 3 * MAGIQUEST_UNIT; + +void MagiQuestProtocol::encode(RemoteTransmitData *dst, const MagiQuestData &data) { + dst->reserve(101); // 2 start bits, 48 data bits, 1 stop bit + dst->set_carrier_frequency(38000); + + // 2 start bits + dst->item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE); + dst->item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE); + for (uint32_t mask = 1 << 31; mask; mask >>= 1) { + if (data.wand_id & mask) { + dst->item(MAGIQUEST_ONE_MARK, MAGIQUEST_ONE_SPACE); + } else { + dst->item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE); + } + } + + for (uint16_t mask = 1 << 15; mask; mask >>= 1) { + if (data.magnitude & mask) { + dst->item(MAGIQUEST_ONE_MARK, MAGIQUEST_ONE_SPACE); + } else { + dst->item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE); + } + } + + dst->mark(MAGIQUEST_UNIT); +} +optional MagiQuestProtocol::decode(RemoteReceiveData src) { + MagiQuestData data{ + .magnitude = 0, + .wand_id = 0, + }; + // Two start bits + if (!src.expect_item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE) || + !src.expect_item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE)) { + return {}; + } + + for (uint32_t mask = 1 << 31; mask; mask >>= 1) { + if (src.expect_item(MAGIQUEST_ONE_MARK, MAGIQUEST_ONE_SPACE)) { + data.wand_id |= mask; + } else if (src.expect_item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE)) { + data.wand_id &= ~mask; + } else { + return {}; + } + } + + for (uint16_t mask = 1 << 15; mask; mask >>= 1) { + if (src.expect_item(MAGIQUEST_ONE_MARK, MAGIQUEST_ONE_SPACE)) { + data.magnitude |= mask; + } else if (src.expect_item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE)) { + data.magnitude &= ~mask; + } else { + return {}; + } + } + + src.expect_mark(MAGIQUEST_UNIT); + return data; +} +void MagiQuestProtocol::dump(const MagiQuestData &data) { + ESP_LOGD(TAG, "Received MagiQuest: wand_id=0x%08X, magnitude=0x%04X", data.wand_id, data.magnitude); +} + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/magiquest_protocol.h b/esphome/components/remote_base/magiquest_protocol.h new file mode 100644 index 0000000000..909be346d0 --- /dev/null +++ b/esphome/components/remote_base/magiquest_protocol.h @@ -0,0 +1,50 @@ +#pragma once + +#include "remote_base.h" + +/* Based on protocol analysis from + * https://arduino-irremote.github.io/Arduino-IRremote/ir__MagiQuest_8cpp_source.html + */ + +namespace esphome { +namespace remote_base { + +struct MagiQuestData { + uint16_t magnitude; + uint32_t wand_id; + + bool operator==(const MagiQuestData &rhs) const { + // Treat 0xffff as a special, wildcard magnitude + // In testing, the wand never produces this value, and this allows us to match + // on just the wand_id if wanted. + if (rhs.wand_id != this->wand_id) { + return false; + } + return (this->wand_id == 0xffff || rhs.wand_id == 0xffff || this->wand_id == rhs.wand_id); + } +}; + +class MagiQuestProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const MagiQuestData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const MagiQuestData &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(MagiQuest) + +template class MagiQuestAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(uint16_t, magnitude) + TEMPLATABLE_VALUE(uint32_t, wand_id) + + void encode(RemoteTransmitData *dst, Ts... x) override { + MagiQuestData data{}; + data.magnitude = this->magnitude_.value(x...); + data.wand_id = this->wand_id_.value(x...); + MagiQuestProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 1494920385..721f78f26a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -344,6 +344,7 @@ CONF_LOOP_TIME = "loop_time" CONF_LOW = "low" CONF_LOW_VOLTAGE_REFERENCE = "low_voltage_reference" CONF_MAC_ADDRESS = "mac_address" +CONF_MAGNITUDE = "magnitude" CONF_MAINS_FILTER = "mains_filter" CONF_MAKE_ID = "make_id" CONF_MANUAL_IP = "manual_ip" @@ -735,6 +736,7 @@ CONF_VOLTAGE_DIVIDER = "voltage_divider" CONF_WAIT_TIME = "wait_time" CONF_WAIT_UNTIL = "wait_until" CONF_WAKEUP_PIN = "wakeup_pin" +CONF_WAND_ID = "wand_id" CONF_WARM_WHITE = "warm_white" CONF_WARM_WHITE_COLOR_TEMPERATURE = "warm_white_color_temperature" CONF_WATCHDOG_THRESHOLD = "watchdog_threshold" diff --git a/tests/test1.yaml b/tests/test1.yaml index e2d5fdc0c5..9b38d549b3 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1861,6 +1861,11 @@ switch: turn_on_action: remote_transmitter.transmit_jvc: data: 0x10EF + - platform: template + name: MagiQuest + turn_on_action: + remote_transmitter.transmit_magiquest: + wand_id: 0x01234567 - platform: template name: NEC id: living_room_lights_off From 231908fe9f50e33eab511a7365ec3b94bd3da53b Mon Sep 17 00:00:00 2001 From: Borys Pierov Date: Sat, 19 Feb 2022 03:45:32 -0500 Subject: [PATCH 127/238] Implement text_sensor based on ble_client (#3079) Co-authored-by: Otto Winter --- .../ble_client/text_sensor/__init__.py | 121 ++++++++++++++++ .../ble_client/text_sensor/automation.h | 38 +++++ .../text_sensor/ble_text_sensor.cpp | 137 ++++++++++++++++++ .../ble_client/text_sensor/ble_text_sensor.h | 47 ++++++ tests/test1.yaml | 12 ++ 5 files changed, 355 insertions(+) create mode 100644 esphome/components/ble_client/text_sensor/__init__.py create mode 100644 esphome/components/ble_client/text_sensor/automation.h create mode 100644 esphome/components/ble_client/text_sensor/ble_text_sensor.cpp create mode 100644 esphome/components/ble_client/text_sensor/ble_text_sensor.h diff --git a/esphome/components/ble_client/text_sensor/__init__.py b/esphome/components/ble_client/text_sensor/__init__.py new file mode 100644 index 0000000000..e1f97e4a01 --- /dev/null +++ b/esphome/components/ble_client/text_sensor/__init__.py @@ -0,0 +1,121 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor, ble_client, esp32_ble_tracker +from esphome.const import ( + CONF_ID, + CONF_TRIGGER_ID, + CONF_SERVICE_UUID, +) +from esphome import automation +from .. import ble_client_ns + +DEPENDENCIES = ["ble_client"] + +CONF_CHARACTERISTIC_UUID = "characteristic_uuid" +CONF_DESCRIPTOR_UUID = "descriptor_uuid" + +CONF_NOTIFY = "notify" +CONF_ON_NOTIFY = "on_notify" + +adv_data_t = cg.std_vector.template(cg.uint8) +adv_data_t_const_ref = adv_data_t.operator("ref").operator("const") + +BLETextSensor = ble_client_ns.class_( + "BLETextSensor", + text_sensor.TextSensor, + cg.PollingComponent, + ble_client.BLEClientNode, +) +BLETextSensorNotifyTrigger = ble_client_ns.class_( + "BLETextSensorNotifyTrigger", automation.Trigger.template(cg.std_string) +) + +CONFIG_SCHEMA = cv.All( + text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(BLETextSensor), + cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, + cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid, + cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid, + cv.Optional(CONF_NOTIFY, default=False): cv.boolean, + cv.Optional(CONF_ON_NOTIFY): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + BLETextSensorNotifyTrigger + ), + } + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(ble_client.BLE_CLIENT_SCHEMA) +) + + +async 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)) + + if CONF_DESCRIPTOR_UUID in config: + if len(config[CONF_DESCRIPTOR_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): + cg.add( + var.set_descr_uuid16( + esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID]) + ) + ) + elif len(config[CONF_DESCRIPTOR_UUID]) == len( + esp32_ble_tracker.bt_uuid32_format + ): + cg.add( + var.set_descr_uuid32( + esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID]) + ) + ) + elif len(config[CONF_DESCRIPTOR_UUID]) == len( + esp32_ble_tracker.bt_uuid128_format + ): + uuid128 = esp32_ble_tracker.as_reversed_hex_array( + config[CONF_DESCRIPTOR_UUID] + ) + cg.add(var.set_descr_uuid128(uuid128)) + + await cg.register_component(var, config) + await ble_client.register_ble_node(var, config) + cg.add(var.set_enable_notify(config[CONF_NOTIFY])) + await text_sensor.register_text_sensor(var, config) + for conf in config.get(CONF_ON_NOTIFY, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await ble_client.register_ble_node(trigger, config) + await automation.build_automation(trigger, [(cg.std_string, "x")], conf) diff --git a/esphome/components/ble_client/text_sensor/automation.h b/esphome/components/ble_client/text_sensor/automation.h new file mode 100644 index 0000000000..be85892c5a --- /dev/null +++ b/esphome/components/ble_client/text_sensor/automation.h @@ -0,0 +1,38 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/components/ble_client/text_sensor/ble_text_sensor.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace ble_client { + +class BLETextSensorNotifyTrigger : public Trigger, public BLETextSensor { + public: + explicit BLETextSensorNotifyTrigger(BLETextSensor *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) override { + switch (event) { + case ESP_GATTC_SEARCH_CMPL_EVT: { + this->sensor_->node_state = espbt::ClientState::ESTABLISHED; + break; + } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle) + break; + this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len)); + } + default: + break; + } + } + + protected: + BLETextSensor *sensor_; +}; + +} // namespace ble_client +} // namespace esphome + +#endif diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp new file mode 100644 index 0000000000..c4d175faa4 --- /dev/null +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -0,0 +1,137 @@ +#include "ble_text_sensor.h" + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/core/application.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace ble_client { + +static const char *const TAG = "ble_text_sensor"; + +static const std::string EMPTY = ""; + +uint32_t BLETextSensor::hash_base() { return 193967603UL; } + +void BLETextSensor::loop() {} + +void BLETextSensor::dump_config() { + LOG_TEXT_SENSOR("", "BLE Text Sensor", this); + 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()); + ESP_LOGCONFIG(TAG, " Descriptor UUID : %s", this->descr_uuid_.to_string().c_str()); + ESP_LOGCONFIG(TAG, " Notifications : %s", YESNO(this->notify_)); + LOG_UPDATE_INTERVAL(this); +} + +void BLETextSensor::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, "[%s] Connected successfully!", this->get_name().c_str()); + break; + } + break; + } + case ESP_GATTC_DISCONNECT_EVT: { + ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str()); + this->status_set_warning(); + this->publish_state(EMPTY); + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: { + this->handle = 0; + auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); + if (chr == nullptr) { + this->status_set_warning(); + this->publish_state(EMPTY); + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), + this->char_uuid_.to_string().c_str()); + break; + } + this->handle = chr->handle; + if (this->descr_uuid_.get_uuid().len > 0) { + auto *descr = chr->get_descriptor(this->descr_uuid_); + if (descr == nullptr) { + this->status_set_warning(); + this->publish_state(EMPTY); + ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s", + this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(), + this->descr_uuid_.to_string().c_str()); + break; + } + this->handle = descr->handle; + } + if (this->notify_) { + auto status = + esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle); + if (status) { + ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status); + } + } else { + this->node_state = espbt::ClientState::ESTABLISHED; + } + 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) { + this->status_clear_warning(); + this->publish_state(this->parse_data(param->read.value, param->read.value_len)); + } + break; + } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle) + 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)); + break; + } + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + this->node_state = espbt::ClientState::ESTABLISHED; + break; + } + default: + break; + } +} + +std::string BLETextSensor::parse_data(uint8_t *value, uint16_t value_len) { + std::string text(value, value + value_len); + return text; +} + +void BLETextSensor::update() { + if (this->node_state != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str()); + return; + } + if (this->handle == 0) { + ESP_LOGW(TAG, "[%s] Cannot poll, no service or characteristic found", this->get_name().c_str()); + return; + } + + auto status = + esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE); + if (status) { + this->status_set_warning(); + this->publish_state(EMPTY); + ESP_LOGW(TAG, "[%s] Error sending read request for sensor, status=%d", this->get_name().c_str(), status); + } +} + +} // namespace ble_client +} // namespace esphome +#endif diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.h b/esphome/components/ble_client/text_sensor/ble_text_sensor.h new file mode 100644 index 0000000000..37537307de --- /dev/null +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.h @@ -0,0 +1,47 @@ +#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/text_sensor/text_sensor.h" + +#ifdef USE_ESP32 +#include + +namespace esphome { +namespace ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLETextSensor : public text_sensor::TextSensor, public PollingComponent, public BLEClientNode { + public: + void loop() override; + void update() override; + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void set_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 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_enable_notify(bool notify) { this->notify_ = notify; } + std::string parse_data(uint8_t *value, uint16_t value_len); + uint16_t handle; + + protected: + uint32_t hash_base() override; + bool notify_; + espbt::ESPBTUUID service_uuid_; + espbt::ESPBTUUID char_uuid_; + espbt::ESPBTUUID descr_uuid_; +}; + +} // namespace ble_client +} // namespace esphome +#endif diff --git a/tests/test1.yaml b/tests/test1.yaml index 9b38d549b3..3d533f33fc 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2496,6 +2496,18 @@ globals: initial_value: "false" text_sensor: + - platform: ble_client + ble_client_id: ble_foo + name: 'Sensor Location' + service_uuid: '180d' + characteristic_uuid: '2a38' + descriptor_uuid: '2902' + notify: true + update_interval: never + on_notify: + then: + - lambda: |- + ESP_LOGD("green_btn", "Location changed: %s", x); - platform: mqtt_subscribe name: "MQTT Subscribe Text" topic: "the/topic" From 88fbb0ffbbc1207018e5488e95170069e5080a76 Mon Sep 17 00:00:00 2001 From: Arturo Casal Date: Sat, 19 Feb 2022 09:49:45 +0100 Subject: [PATCH 128/238] Add sensor support: MAX44009 (#3125) Co-authored-by: Otto Winter --- CODEOWNERS | 1 + esphome/components/max44009/__init__.py | 0 esphome/components/max44009/max44009.cpp | 144 +++++++++++++++++++++++ esphome/components/max44009/max44009.h | 37 ++++++ esphome/components/max44009/sensor.py | 53 +++++++++ tests/test1.yaml | 7 ++ 6 files changed, 242 insertions(+) create mode 100644 esphome/components/max44009/__init__.py create mode 100644 esphome/components/max44009/max44009.cpp create mode 100644 esphome/components/max44009/max44009.h create mode 100644 esphome/components/max44009/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index f533ff5c47..d2971e7c73 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -94,6 +94,7 @@ esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lock/* @esphome/core esphome/components/logger/* @esphome/core esphome/components/ltr390/* @sjtrny +esphome/components/max44009/* @berfenger esphome/components/max7219digit/* @rspaargaren esphome/components/max9611/* @mckaymatthew esphome/components/mcp23008/* @jesserockz diff --git a/esphome/components/max44009/__init__.py b/esphome/components/max44009/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/max44009/max44009.cpp b/esphome/components/max44009/max44009.cpp new file mode 100644 index 0000000000..6f12fb6583 --- /dev/null +++ b/esphome/components/max44009/max44009.cpp @@ -0,0 +1,144 @@ +#include "max44009.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace max44009 { + +static const char *const TAG = "max44009.sensor"; + +// REGISTERS +static const uint8_t MAX44009_REGISTER_CONFIGURATION = 0x02; +static const uint8_t MAX44009_LUX_READING_HIGH = 0x03; +static const uint8_t MAX44009_LUX_READING_LOW = 0x04; +// CONFIGURATION MASKS +static const uint8_t MAX44009_CFG_CONTINUOUS = 0x80; +// ERROR CODES +static const uint8_t MAX44009_OK = 0; +static const uint8_t MAX44009_ERROR_WIRE_REQUEST = -10; +static const uint8_t MAX44009_ERROR_OVERFLOW = -20; +static const uint8_t MAX44009_ERROR_HIGH_BYTE = -30; +static const uint8_t MAX44009_ERROR_LOW_BYTE = -31; + +void MAX44009Sensor::setup() { + ESP_LOGCONFIG(TAG, "Setting up MAX44009..."); + bool state_ok = false; + if (this->mode_ == MAX44009Mode::MAX44009_MODE_LOW_POWER) { + state_ok = this->set_low_power_mode(); + } else if (this->mode_ == MAX44009Mode::MAX44009_MODE_CONTINUOUS) { + state_ok = this->set_continuous_mode(); + } else { + /* + * Mode AUTO: Set mode depending on update interval + * - On low power mode, the IC measures lux intensity only once every 800ms + * regardless of integration time + * - On continuous mode, the IC continuously measures lux intensity + */ + if (this->get_update_interval() < 800) { + state_ok = this->set_continuous_mode(); + } else { + state_ok = this->set_low_power_mode(); + } + } + if (!state_ok) + this->mark_failed(); +} + +void MAX44009Sensor::dump_config() { + ESP_LOGCONFIG(TAG, "MAX44009:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with MAX44009 failed!"); + } +} + +float MAX44009Sensor::get_setup_priority() const { return setup_priority::DATA; } + +void MAX44009Sensor::update() { + // update sensor illuminance value + float lux = this->read_illuminance_(); + if (this->error_ != MAX44009_OK) { + this->status_set_warning(); + this->publish_state(NAN); + } else { + this->status_clear_warning(); + this->publish_state(lux); + } +} + +float MAX44009Sensor::read_illuminance_() { + uint8_t datahigh = this->read_(MAX44009_LUX_READING_HIGH); + if (error_ != MAX44009_OK) { + this->error_ = MAX44009_ERROR_HIGH_BYTE; + return this->error_; + } + uint8_t datalow = this->read_(MAX44009_LUX_READING_LOW); + if (error_ != MAX44009_OK) { + this->error_ = MAX44009_ERROR_LOW_BYTE; + return this->error_; + } + uint8_t exponent = datahigh >> 4; + if (exponent == 0x0F) { + this->error_ = MAX44009_ERROR_OVERFLOW; + return this->error_; + } + + return this->convert_to_lux_(datahigh, datalow); +} + +float MAX44009Sensor::convert_to_lux_(uint8_t data_high, uint8_t data_low) { + uint8_t exponent = data_high >> 4; + uint32_t mantissa = ((data_high & 0x0F) << 4) + (data_low & 0x0F); + return ((0x0001 << exponent) * 0.045) * mantissa; +} + +bool MAX44009Sensor::set_continuous_mode() { + uint8_t config = this->read_(MAX44009_REGISTER_CONFIGURATION); + if (this->error_ == MAX44009_OK) { + config |= MAX44009_CFG_CONTINUOUS; + this->write_(MAX44009_REGISTER_CONFIGURATION, config); + this->status_clear_warning(); + ESP_LOGV(TAG, "set to continuous mode"); + return true; + } else { + this->status_set_warning(); + return false; + } +} + +bool MAX44009Sensor::set_low_power_mode() { + uint8_t config = this->read_(MAX44009_REGISTER_CONFIGURATION); + if (this->error_ == MAX44009_OK) { + config &= ~MAX44009_CFG_CONTINUOUS; + this->write_(MAX44009_REGISTER_CONFIGURATION, config); + this->status_clear_warning(); + ESP_LOGV(TAG, "set to low power mode"); + return true; + } else { + this->status_set_warning(); + return false; + } +} + +uint8_t MAX44009Sensor::read_(uint8_t reg) { + uint8_t data = 0; + if (!this->read_byte(reg, &data)) { + this->error_ = MAX44009_ERROR_WIRE_REQUEST; + } else { + this->error_ = MAX44009_OK; + } + return data; +} + +void MAX44009Sensor::write_(uint8_t reg, uint8_t value) { + if (!this->write_byte(reg, value)) { + this->error_ = MAX44009_ERROR_WIRE_REQUEST; + } else { + this->error_ = MAX44009_OK; + } +} + +void MAX44009Sensor::set_mode(MAX44009Mode mode) { this->mode_ = mode; } + +} // namespace max44009 +} // namespace esphome diff --git a/esphome/components/max44009/max44009.h b/esphome/components/max44009/max44009.h new file mode 100644 index 0000000000..c85d1c1028 --- /dev/null +++ b/esphome/components/max44009/max44009.h @@ -0,0 +1,37 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace max44009 { + +enum MAX44009Mode { MAX44009_MODE_AUTO, MAX44009_MODE_LOW_POWER, MAX44009_MODE_CONTINUOUS }; + +/// This class implements support for the MAX44009 Illuminance i2c sensor. +class MAX44009Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { + public: + MAX44009Sensor() {} + + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + void set_mode(MAX44009Mode mode); + bool set_continuous_mode(); + bool set_low_power_mode(); + + protected: + /// Read the illuminance value + float read_illuminance_(); + float convert_to_lux_(uint8_t data_high, uint8_t data_low); + uint8_t read_(uint8_t reg); + void write_(uint8_t reg, uint8_t value); + + int error_; + MAX44009Mode mode_; +}; + +} // namespace max44009 +} // namespace esphome diff --git a/esphome/components/max44009/sensor.py b/esphome/components/max44009/sensor.py new file mode 100644 index 0000000000..498cccb77b --- /dev/null +++ b/esphome/components/max44009/sensor.py @@ -0,0 +1,53 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, i2c +from esphome.const import ( + CONF_ID, + CONF_MODE, + DEVICE_CLASS_ILLUMINANCE, + STATE_CLASS_MEASUREMENT, + UNIT_LUX, +) + +CODEOWNERS = ["@berfenger"] +DEPENDENCIES = ["i2c"] + +max44009_ns = cg.esphome_ns.namespace("max44009") +MAX44009Sensor = max44009_ns.class_( + "MAX44009Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice +) + +MAX44009Mode = max44009_ns.enum("MAX44009Mode") +MODE_OPTIONS = { + "auto": MAX44009Mode.MAX44009_MODE_AUTO, + "low_power": MAX44009Mode.MAX44009_MODE_LOW_POWER, + "continuous": MAX44009Mode.MAX44009_MODE_CONTINUOUS, +} + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + unit_of_measurement=UNIT_LUX, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.GenerateID(): cv.declare_id(MAX44009Sensor), + cv.Optional(CONF_MODE, default="low_power"): cv.enum( + MODE_OPTIONS, lower=True + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x4A)) +) + + +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 sensor.register_sensor(var, config) + + cg.add(var.set_mode(config[CONF_MODE])) diff --git a/tests/test1.yaml b/tests/test1.yaml index 3d533f33fc..8b99f86773 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -463,6 +463,13 @@ sensor: state_topic: livingroom/custom_state_topic measurement_duration: 31 i2c_id: i2c_bus + - platform: max44009 + name: "Outside Brightness 1" + internal: true + address: 0x4A + update_interval: 30s + mode: low_power + i2c_id: i2c_bus - platform: bme280 temperature: name: "Outside Temperature" From e445d6aada8c1710758b1b4c2210ac22ed988017 Mon Sep 17 00:00:00 2001 From: Peter Valkov Date: Sat, 19 Feb 2022 11:36:19 +0200 Subject: [PATCH 129/238] Fix for api disconnect detection. (#2909) Co-authored-by: Otto Winter --- esphome/components/api/api_connection.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index d9ce6cd79e..b998ef5929 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -105,6 +105,7 @@ void APIConnection::loop() { 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) { + ESP_LOGVV(TAG, "Sending keepalive PING..."); this->sent_ping_ = true; this->send_ping_request(PingRequest()); } @@ -908,7 +909,7 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) } return false; } - this->last_traffic_ = millis(); + // Do not set last_traffic_ on send return true; } void APIConnection::on_unauthenticated_access() { From ad2f857e15c801b9f673a6fe36621f7ea90f845d Mon Sep 17 00:00:00 2001 From: mknjc Date: Sat, 19 Feb 2022 10:59:53 +0100 Subject: [PATCH 130/238] [miscale] Add flag to clear last impedance reading if the newly received reading only contains weight (#3132) --- esphome/components/xiaomi_miscale/sensor.py | 3 +++ .../xiaomi_miscale/xiaomi_miscale.cpp | 25 +++++++++++-------- .../xiaomi_miscale/xiaomi_miscale.h | 2 ++ esphome/const.py | 1 + 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/esphome/components/xiaomi_miscale/sensor.py b/esphome/components/xiaomi_miscale/sensor.py index 517870cc01..a2a2f3bdfc 100644 --- a/esphome/components/xiaomi_miscale/sensor.py +++ b/esphome/components/xiaomi_miscale/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_OHM, CONF_IMPEDANCE, ICON_OMEGA, + CONF_CLEAR_IMPEDANCE, ) DEPENDENCIES = ["esp32_ble_tracker"] @@ -25,6 +26,7 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(XiaomiMiscale), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_CLEAR_IMPEDANCE, default=False): cv.boolean, cv.Optional(CONF_WEIGHT): sensor.sensor_schema( unit_of_measurement=UNIT_KILOGRAM, icon=ICON_SCALE_BATHROOM, @@ -50,6 +52,7 @@ async def to_code(config): await esp32_ble_tracker.register_ble_device(var, config) cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) + cg.add(var.set_clear_impedance(config[CONF_CLEAR_IMPEDANCE])) if CONF_WEIGHT in config: sens = await sensor.new_sensor(config[CONF_WEIGHT]) diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp index 6c3bd61cac..4ed5f587de 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp @@ -24,25 +24,30 @@ 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); - if (!res.has_value()) { + 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->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); + if (this->impedance_ != nullptr) { + if (res->version == 1) { + 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_->publish_state(*res->impedance); + } else { + if (clear_impedance_) + this->impedance_->publish_state(NAN); + } + } + } success = true; } diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.h b/esphome/components/xiaomi_miscale/xiaomi_miscale.h index 3e51405ddc..cff61f153b 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.h +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.h @@ -24,11 +24,13 @@ class XiaomiMiscale : public Component, public esp32_ble_tracker::ESPBTDeviceLis 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; } + void set_clear_impedance(bool clear_impedance) { clear_impedance_ = clear_impedance; } protected: uint64_t address_; sensor::Sensor *weight_{nullptr}; sensor::Sensor *impedance_{nullptr}; + bool clear_impedance_{false}; optional parse_header_(const esp32_ble_tracker::ServiceData &service_data); bool parse_message_(const std::vector &message, ParseResult &result); diff --git a/esphome/const.py b/esphome/const.py index 721f78f26a..dec68e0701 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -89,6 +89,7 @@ CONF_CHANGE_MODE_EVERY = "change_mode_every" CONF_CHANNEL = "channel" CONF_CHANNELS = "channels" CONF_CHIPSET = "chipset" +CONF_CLEAR_IMPEDANCE = "clear_impedance" CONF_CLIENT_ID = "client_id" CONF_CLK_PIN = "clk_pin" CONF_CLOCK_PIN = "clock_pin" From 125c693e3f54aabdda8901c575a1c1354e919143 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sat, 19 Feb 2022 11:41:34 +0100 Subject: [PATCH 131/238] Add ESP32 variant config validator function (#3088) * Add esp32_variant config validator function * Drop unused is_esp32c3 function Co-authored-by: Otto Winter --- esphome/components/esp32/__init__.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 1229675ad8..0b2c291ba2 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -65,8 +65,26 @@ def get_esp32_variant(): return CORE.data[KEY_ESP32][KEY_VARIANT] -def is_esp32c3(): - return get_esp32_variant() == VARIANT_ESP32C3 +def only_on_variant(*, supported=None, unsupported=None): + """Config validator for features only available on some ESP32 variants.""" + if supported is not None and not isinstance(supported, list): + supported = [supported] + if unsupported is not None and not isinstance(unsupported, list): + unsupported = [unsupported] + + def validator_(obj): + variant = get_esp32_variant() + if supported is not None and variant not in supported: + raise cv.Invalid( + f"This feature is only available on {', '.join(supported)}" + ) + if unsupported is not None and variant in unsupported: + raise cv.Invalid( + f"This feature is not available on {', '.join(unsupported)}" + ) + return obj + + return validator_ @dataclass From d594f43ebdd4a580ffe8c6933dd814093341cf19 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 19 Feb 2022 14:11:01 +0100 Subject: [PATCH 132/238] Publish NAN when dallas conversion failed (#3227) --- esphome/components/dallas/dallas_component.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 56526e98bb..b1d28b8b4c 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -111,6 +111,9 @@ void DallasComponent::update() { if (!result) { ESP_LOGE(TAG, "Requesting conversion failed"); this->status_set_warning(); + for (auto *sensor : this->sensors_) { + sensor->publish_state(NAN); + } return; } From 0c1520dd9cb8f9ed076619342e476a91f0c48a21 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 19 Feb 2022 14:11:45 +0100 Subject: [PATCH 133/238] Fix ESP8266 climate memaccess warning (#3226) --- esphome/components/climate/climate.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index ebea20ed1f..65b5ef4eb4 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -1,4 +1,5 @@ #include "climate.h" +#include "esphome/core/macros.h" namespace esphome { namespace climate { @@ -326,14 +327,17 @@ optional Climate::restore_state_() { return recovered; } void Climate::save_state_() { -#if defined(USE_ESP_IDF) && !defined(CLANG_TIDY) +#if (defined(USE_ESP_IDF) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \ + !defined(CLANG_TIDY) #pragma GCC diagnostic ignored "-Wclass-memaccess" +#define TEMP_IGNORE_MEMACCESS #endif ClimateDeviceRestoreState state{}; // initialize as zero to prevent random data on stack triggering erase memset(&state, 0, sizeof(ClimateDeviceRestoreState)); -#if USE_ESP_IDF && !defined(CLANG_TIDY) +#ifdef TEMP_IGNORE_MEMACCESS #pragma GCC diagnostic pop +#undef TEMP_IGNORE_MEMACCESS #endif state.mode = this->mode; From ae57ad0c810fbf646ae2e5c2f142cfd37cafe1ee Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 19 Feb 2022 14:42:54 +0100 Subject: [PATCH 134/238] Fix warning in test1.yaml (#3228) --- tests/test1.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test1.yaml b/tests/test1.yaml index 8b99f86773..7eb1b457db 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1097,8 +1097,8 @@ sensor: temperature: name: Max9611 Temp update_interval: 1s - - + + esp32_touch: setup_mode: False iir_filter: 10ms @@ -1317,18 +1317,18 @@ binary_sensor: - platform: analog_threshold name: Analog Trheshold 1 sensor_id: template_sensor - threshold: + threshold: upper: 110 lower: 90 - filters: + filters: - delayed_on: 0s - delayed_off: 10s - platform: analog_threshold name: Analog Trheshold 2 sensor_id: template_sensor threshold: 100 - filters: - - invert: + filters: + - invert: pca9685: frequency: 500 @@ -2514,7 +2514,7 @@ text_sensor: on_notify: then: - lambda: |- - ESP_LOGD("green_btn", "Location changed: %s", x); + ESP_LOGD("green_btn", "Location changed: %s", x.c_str()); - platform: mqtt_subscribe name: "MQTT Subscribe Text" topic: "the/topic" @@ -2582,7 +2582,7 @@ canbus: then: - lambda: |- std::string b(x.begin(), x.end()); - ESP_LOGD("canid 500", "%s", &b[0] ); + ESP_LOGD("canid 500", "%s", b.c_str()); - can_id: 23 then: - if: @@ -2620,7 +2620,7 @@ canbus: then: - lambda: |- std::string b(x.begin(), x.end()); - ESP_LOGD("canid 500", "%s", &b[0] ); + ESP_LOGD("canid 500", "%s", b.c_str() ); - can_id: 23 then: - if: From 34c9d8be50f56981a599a52ffa8e211c30d3a4bb Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 19 Feb 2022 14:46:27 +0100 Subject: [PATCH 135/238] Lint trailing whitespace (#3230) --- .github/ISSUE_TEMPLATE/config.yml | 1 - .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- CONTRIBUTING.md | 2 +- esphome/components/fujitsu_general/fujitsu_general.h | 10 +++++----- esphome/components/ili9341/ili9341_init.h | 8 ++++---- script/ci-custom.py | 5 +++++ tests/component_tests/button/test_button.yaml | 1 - 7 files changed, 17 insertions(+), 14 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 4add58dfbe..7f99701e39 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -9,4 +9,3 @@ contact_links: - name: Frequently Asked Question url: https://esphome.io/guides/faq.html about: Please view the FAQ for common questions and what to include in a bug report. - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 25411c19f5..f9c8cce0af 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,4 @@ -# What does this implement/fix? +# What does this implement/fix? Quick description and explanation of changes @@ -35,6 +35,6 @@ Quick description and explanation of changes ## Checklist: - [ ] The code change is tested and works locally. - [ ] Tests have been added to verify that the new code works (under `tests/` folder). - + If user exposed functionality or configuration variables are added/changed: - [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e96bb5745b..ec23656763 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ For a detailed guide, please see https://esphome.io/guides/contributing.html#con Things to note when contributing: - Please test your changes :) - - If a new feature is added or an existing user-facing feature is changed, you should also + - If a new feature is added or an existing user-facing feature is changed, you should also update the [docs](https://github.com/esphome/esphome-docs). See [contributing to esphome-docs](https://esphome.io/guides/contributing.html#contributing-to-esphomedocs) for more information. - Please also update the tests in the `tests/` folder. You can do so by just adding a line in one of the YAML files diff --git a/esphome/components/fujitsu_general/fujitsu_general.h b/esphome/components/fujitsu_general/fujitsu_general.h index 8dc7a3e484..ee83ae9d19 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.h +++ b/esphome/components/fujitsu_general/fujitsu_general.h @@ -17,29 +17,29 @@ const uint8_t FUJITSU_GENERAL_TEMP_MAX = 30; // Celsius * turn * on temp mode fan swing * * | | | | | | * - * + * * temperatures 1 1248 124 124 1 * auto auto 18 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000100 00000000 00000000 00000000 00000000 00000000 00000100 11110001 * auto auto 19 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10001100 00000000 00000000 00000000 00000000 00000000 00000100 11111110 * auto auto 30 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000111 00000000 00000000 00000000 00000000 00000000 00000100 11110011 - * + * * on flag: * on at 16 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000000 00100000 00000000 00000000 00000000 00000000 00000100 11010101 * down to 16 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 00000000 00100000 00000000 00000000 00000000 00000000 00000100 00110101 - * + * * mode options: * auto auto 30 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000111 00000000 00000000 00000000 00000000 00000000 00000100 11110011 * cool auto 30 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000111 10000000 00000000 00000000 00000000 00000000 00000100 01110011 * dry auto 30 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000111 01000000 00000000 00000000 00000000 00000000 00000100 10110011 * fan (auto) (30) 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000111 11000000 00000000 00000000 00000000 00000000 00000100 00110011 * heat auto 30 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000111 00100000 00000000 00000000 00000000 00000000 00000100 11010011 - * + * * fan options: * heat 30 high 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000111 00100000 10000000 00000000 00000000 00000000 00000100 01010011 * heat 30 med 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 00000111 00100000 01000000 00000000 00000000 00000000 00000100 01010011 * heat 30 low 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 00000111 00100000 11000000 00000000 00000000 00000000 00000100 10010011 * heat 30 quiet 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 00000111 00100000 00100000 00000000 00000000 00000000 00000100 00010011 - * + * * swing options: * heat 30 swing vert 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 00000111 00100000 00101000 00000000 00000000 00000000 00000100 00011101 * heat 30 noswing 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 00000111 00100000 00100000 00000000 00000000 00000000 00000100 00010011 diff --git a/esphome/components/ili9341/ili9341_init.h b/esphome/components/ili9341/ili9341_init.h index 9282895e2e..b4f67ff19a 100644 --- a/esphome/components/ili9341/ili9341_init.h +++ b/esphome/components/ili9341/ili9341_init.h @@ -25,10 +25,10 @@ static const uint8_t PROGMEM INITCMD_M5STACK[] = { 0xF2, 1, 0x00, // 3Gamma Function Disable ILI9341_GAMMASET , 1, 0x01, // Gamma curve selected ILI9341_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma - 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, + 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00, ILI9341_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma - 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, + 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F, ILI9341_SLPOUT , 0x80, // Exit Sleep ILI9341_DISPON , 0x80, // Display on @@ -55,10 +55,10 @@ static const uint8_t PROGMEM INITCMD_TFT[] = { 0xF2, 1, 0x00, // 3Gamma Function Disable ILI9341_GAMMASET , 1, 0x01, // Gamma curve selected ILI9341_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma - 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, + 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00, ILI9341_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma - 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, + 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F, ILI9341_SLPOUT , 0x80, // Exit Sleep ILI9341_DISPON , 0x80, // Display on diff --git a/script/ci-custom.py b/script/ci-custom.py index 2703e7d311..c0737da103 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -591,6 +591,11 @@ def lint_inclusive_language(fname, match): ) +@lint_re_check(r"[\t\r\f\v ]+$") +def lint_trailing_whitespace(fname, match): + return "Trailing whitespace detected" + + @lint_content_find_check( "ESP_LOG", include=["*.h", "*.tcc"], diff --git a/tests/component_tests/button/test_button.yaml b/tests/component_tests/button/test_button.yaml index 3eac129e8c..32d2e8d93b 100644 --- a/tests/component_tests/button/test_button.yaml +++ b/tests/component_tests/button/test_button.yaml @@ -18,4 +18,3 @@ button: name: wol_test_2 id: wol_2 internal: false - From d2b209234f7f5cbf18a1747d82ca5c54c060f3b3 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 19 Feb 2022 15:09:17 +0100 Subject: [PATCH 136/238] Improve ESP8266 iram usage (#3223) --- esphome/core/scheduler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 7f0ed0b17c..56f823556b 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -116,7 +116,7 @@ optional HOT Scheduler::next_schedule_in() { return 0; return next_time - now; } -void IRAM_ATTR HOT Scheduler::call() { +void HOT Scheduler::call() { const uint32_t now = this->millis_(); this->process_to_add(); From b8d10a62c2436430cc235342062215a6f77b3494 Mon Sep 17 00:00:00 2001 From: Tyler Bules Date: Sat, 19 Feb 2022 09:13:48 -0500 Subject: [PATCH 137/238] ESP32-C3 deep sleep fix (#3066) --- esphome/components/deep_sleep/__init__.py | 40 ++++++++++++++++++- .../deep_sleep/deep_sleep_component.cpp | 14 ++++++- .../deep_sleep/deep_sleep_component.h | 5 ++- tests/test1.yaml | 2 +- tests/test2.yaml | 2 +- 5 files changed, 58 insertions(+), 5 deletions(-) diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index ba4c2c0d7e..2a74d0c1bb 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -11,9 +11,39 @@ from esphome.const import ( CONF_WAKEUP_PIN, ) +from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32.const import ( + VARIANT_ESP32, + VARIANT_ESP32C3, +) + +WAKEUP_PINS = { + VARIANT_ESP32: [ + 0, + 2, + 4, + 12, + 13, + 14, + 15, + 25, + 26, + 27, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + ], + VARIANT_ESP32C3: [0, 1, 2, 3, 4, 5], +} + def validate_pin_number(value): - valid_pins = [0, 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39] + valid_pins = WAKEUP_PINS.get(get_esp32_variant(), WAKEUP_PINS[VARIANT_ESP32]) if value[CONF_NUMBER] not in valid_pins: raise cv.Invalid( f"Only pins {', '.join(str(x) for x in valid_pins)} support wakeup" @@ -21,6 +51,14 @@ def validate_pin_number(value): return value +def validate_config(config): + if get_esp32_variant() == VARIANT_ESP32C3 and CONF_ESP32_EXT1_WAKEUP in config: + raise cv.Invalid("ESP32-C3 does not support wakeup from touch.") + if get_esp32_variant() == VARIANT_ESP32C3 and CONF_TOUCH_WAKEUP in config: + raise cv.Invalid("ESP32-C3 does not support wakeup from ext1") + return config + + deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep") DeepSleepComponent = deep_sleep_ns.class_("DeepSleepComponent", cg.Component) EnterDeepSleepAction = deep_sleep_ns.class_("EnterDeepSleepAction", automation.Action) diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 7774014d3d..82751b538b 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -104,7 +104,7 @@ void DeepSleepComponent::begin_sleep(bool manual) { App.run_safe_shutdown_hooks(); -#ifdef USE_ESP32 +#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) if (this->sleep_duration_.has_value()) esp_sleep_enable_timer_wakeup(*this->sleep_duration_); if (this->wakeup_pin_ != nullptr) { @@ -126,6 +126,18 @@ void DeepSleepComponent::begin_sleep(bool manual) { esp_deep_sleep_start(); #endif +#ifdef USE_ESP32_VARIANT_ESP32C3 + 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()) { + level = !level; + } + esp_deep_sleep_enable_gpio_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level); + } +#endif + #ifdef USE_ESP8266 ESP.deepSleep(*this->sleep_duration_); // NOLINT(readability-static-accessed-through-instance) #endif diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index 59df199a9f..057d992427 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -57,13 +57,16 @@ class DeepSleepComponent : public Component { public: /// Set the duration in ms the component should sleep once it's in deep sleep mode. void set_sleep_duration(uint32_t time_ms); -#ifdef USE_ESP32 +#if defined(USE_ESP32) /** Set the pin to wake up to on the ESP32 once it's in deep sleep mode. * Use the inverted property to set the wakeup level. */ void set_wakeup_pin(InternalGPIOPin *pin) { this->wakeup_pin_ = pin; } void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode); +#endif + +#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) void set_ext1_wakeup(Ext1Wakeup ext1_wakeup); diff --git a/tests/test1.yaml b/tests/test1.yaml index 7eb1b457db..4e82d06485 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -262,7 +262,7 @@ power_supply: deep_sleep: run_duration: 20s sleep_duration: 50s - wakeup_pin: GPIO39 + wakeup_pin: GPIO2 wakeup_pin_mode: INVERT_WAKEUP ads1115: diff --git a/tests/test2.yaml b/tests/test2.yaml index 2a122b971f..76b9775c54 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -60,7 +60,7 @@ deep_sleep: gpio_wakeup_reason: 10s touch_wakeup_reason: 15s sleep_duration: 50s - wakeup_pin: GPIO39 + wakeup_pin: GPIO2 wakeup_pin_mode: INVERT_WAKEUP as3935_i2c: From debcaf6fb7602db12f98cb5c02fbed4b5e7591dc Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 19 Feb 2022 15:47:50 +0100 Subject: [PATCH 138/238] Add ESP32C3 and ESP32S2 support to dashboard (#3152) * Add ESP32C3 and ESP32S2 support to dashboard * Format * Fix tests --- esphome/components/esp32/__init__.py | 4 +-- esphome/storage_json.py | 12 ++++--- esphome/wizard.py | 49 +++++++++++++++++++++------- tests/unit_tests/test_wizard.py | 4 +-- 4 files changed, 50 insertions(+), 19 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 0b2c291ba2..478c9701b8 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -61,8 +61,8 @@ def set_core_data(config): return config -def get_esp32_variant(): - return CORE.data[KEY_ESP32][KEY_VARIANT] +def get_esp32_variant(core_obj=None): + return (core_obj or CORE).data[KEY_ESP32][KEY_VARIANT] def only_on_variant(*, supported=None, unsupported=None): diff --git a/esphome/storage_json.py b/esphome/storage_json.py index 207a3edf57..d561de4ce6 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -64,7 +64,7 @@ class StorageJSON: # 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 + # The type of hardware in use, like "ESP32", "ESP32C3", "ESP8266", etc. self.target_platform = target_platform # type: str # The absolute path to the platformio project self.build_path = build_path # type: str @@ -99,6 +99,11 @@ class StorageJSON: def from_esphome_core( esph, old ): # type: (CoreType, Optional[StorageJSON]) -> StorageJSON + hardware = esph.target_platform + if esph.is_esp32: + from esphome.components import esp32 + + hardware = esp32.get_esp32_variant(esph) return StorageJSON( storage_version=1, name=esph.name, @@ -107,15 +112,14 @@ class StorageJSON: src_version=1, address=esph.address, web_port=esph.web_port, - target_platform=esph.target_platform, + target_platform=hardware, build_path=esph.build_path, firmware_bin_path=esph.firmware_bin, loaded_integrations=list(esph.loaded_integrations), ) @staticmethod - def from_wizard(name, address, esp_platform): - # type: (str, str, str) -> StorageJSON + def from_wizard(name: str, address: str, esp_platform: str) -> "StorageJSON": return StorageJSON( storage_version=1, name=name, diff --git a/esphome/wizard.py b/esphome/wizard.py index c64ad3a583..34930ff66f 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -67,6 +67,27 @@ esp32: type: arduino """ +ESP32S2_CONFIG = """ +esp32: + board: {board} + framework: + type: esp-idf +""" + +ESP32C3_CONFIG = """ +esp32: + board: {board} + framework: + type: esp-idf +""" + +HARDWARE_BASE_CONFIGS = { + "ESP8266": ESP8266_CONFIG, + "ESP32": ESP32_CONFIG, + "ESP32S2": ESP32S2_CONFIG, + "ESP32C3": ESP32C3_CONFIG, +} + def sanitize_double_quotes(value): return value.replace("\\", "\\\\").replace('"', '\\"') @@ -83,11 +104,7 @@ def wizard_file(**kwargs): config = BASE_CONFIG.format(**kwargs) - config += ( - ESP8266_CONFIG.format(**kwargs) - if kwargs["platform"] == "ESP8266" - else ESP32_CONFIG.format(**kwargs) - ) + config += HARDWARE_BASE_CONFIGS[kwargs["platform"]].format(**kwargs) config += LOGGER_API_CONFIG @@ -119,16 +136,26 @@ def wizard_file(**kwargs): """ # pylint: disable=consider-using-f-string - config += """ + if kwargs["platform"] in ["ESP8266", "ESP32"]: + config += """ # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "{fallback_name}" password: "{fallback_psk}" captive_portal: -""".format( - **kwargs - ) + """.format( + **kwargs + ) + else: + config += """ + # Enable fallback hotspot in case wifi connection fails + ap: + ssid: "{fallback_name}" + password: "{fallback_psk}" + """.format( + **kwargs + ) return config @@ -147,10 +174,10 @@ def wizard_write(path, **kwargs): kwargs["platform"] = ( "ESP8266" if board in esp8266_boards.ESP8266_BOARD_PINS else "ESP32" ) - platform = kwargs["platform"] + hardware = kwargs["platform"] write_file(path, wizard_file(**kwargs)) - storage = StorageJSON.from_wizard(name, f"{name}.local", platform) + storage = StorageJSON.from_wizard(name, f"{name}.local", hardware) storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path)) storage.save(storage_path) diff --git a/tests/unit_tests/test_wizard.py b/tests/unit_tests/test_wizard.py index 59fcfbff60..79a5894075 100644 --- a/tests/unit_tests/test_wizard.py +++ b/tests/unit_tests/test_wizard.py @@ -3,14 +3,14 @@ import esphome.wizard as wz import pytest from esphome.components.esp8266.boards import ESP8266_BOARD_PINS -from mock import MagicMock +from unittest.mock import MagicMock @pytest.fixture def default_config(): return { "name": "test-name", - "platform": "test_platform", + "platform": "ESP8266", "board": "esp01_1m", "ssid": "test_ssid", "psk": "test_psk", From 5811389891e814b138eb8dd5236d730c9c0c581b Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 19 Feb 2022 15:49:20 +0100 Subject: [PATCH 139/238] BH1750 dynamically calculate options (#3214) * BH1750 dynamically calculate options * Fix tests * Fix NAN * Convert setup to new-style * Add myself as codeowner --- CODEOWNERS | 1 + esphome/components/bh1750/bh1750.cpp | 184 +++++++++++++++++++-------- esphome/components/bh1750/bh1750.h | 27 +--- esphome/components/bh1750/sensor.py | 24 +--- tests/test1.yaml | 2 - 5 files changed, 142 insertions(+), 96 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index d2971e7c73..61469c53fa 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -28,6 +28,7 @@ esphome/components/atc_mithermometer/* @ahpohl esphome/components/b_parasite/* @rbaron esphome/components/ballu/* @bazuchan esphome/components/bang_bang/* @OttoWinter +esphome/components/bh1750/* @OttoWinter esphome/components/binary_sensor/* @esphome/core esphome/components/bl0940/* @tobias- esphome/components/ble_client/* @buxtronix diff --git a/esphome/components/bh1750/bh1750.cpp b/esphome/components/bh1750/bh1750.cpp index 4e6bb3c563..de6d811ed2 100644 --- a/esphome/components/bh1750/bh1750.cpp +++ b/esphome/components/bh1750/bh1750.cpp @@ -9,18 +9,109 @@ static const char *const TAG = "bh1750.sensor"; static const uint8_t BH1750_COMMAND_POWER_ON = 0b00000001; static const uint8_t BH1750_COMMAND_MT_REG_HI = 0b01000000; // last 3 bits static const uint8_t BH1750_COMMAND_MT_REG_LO = 0b01100000; // last 5 bits +static const uint8_t BH1750_COMMAND_ONE_TIME_L = 0b00100011; +static const uint8_t BH1750_COMMAND_ONE_TIME_H = 0b00100000; +static const uint8_t BH1750_COMMAND_ONE_TIME_H2 = 0b00100001; + +/* +bh1750 properties: + +L-resolution mode: +- resolution 4lx (@ mtreg=69) +- measurement time: typ=16ms, max=24ms, scaled by MTreg value divided by 69 +- formula: counts / 1.2 * (69 / MTreg) lx +H-resolution mode: +- resolution 1lx (@ mtreg=69) +- measurement time: typ=120ms, max=180ms, scaled by MTreg value divided by 69 +- formula: counts / 1.2 * (69 / MTreg) lx +H-resolution mode2: +- resolution 0.5lx (@ mtreg=69) +- measurement time: typ=120ms, max=180ms, scaled by MTreg value divided by 69 +- formula: counts / 1.2 * (69 / MTreg) / 2 lx + +MTreg: +- min=31, default=69, max=254 + +-> only reason to use l-resolution is faster, but offers no higher range +-> below ~7000lx, makes sense to use H-resolution2 @ MTreg=254 +-> try to maximize MTreg to get lowest noise level +*/ void BH1750Sensor::setup() { ESP_LOGCONFIG(TAG, "Setting up BH1750 '%s'...", this->name_.c_str()); - if (!this->write_bytes(BH1750_COMMAND_POWER_ON, nullptr, 0)) { + uint8_t turn_on = BH1750_COMMAND_POWER_ON; + if (this->write(&turn_on, 1) != i2c::ERROR_OK) { this->mark_failed(); return; } +} - uint8_t mtreg_hi = (this->measurement_duration_ >> 5) & 0b111; - uint8_t mtreg_lo = (this->measurement_duration_ >> 0) & 0b11111; - this->write_bytes(BH1750_COMMAND_MT_REG_HI | mtreg_hi, nullptr, 0); - this->write_bytes(BH1750_COMMAND_MT_REG_LO | mtreg_lo, nullptr, 0); +void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function &f) { + // turn on (after one-shot sensor automatically powers down) + uint8_t turn_on = BH1750_COMMAND_POWER_ON; + if (this->write(&turn_on, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Turning on BH1750 failed"); + f(NAN); + return; + } + + if (active_mtreg_ != mtreg) { + // set mtreg + uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> 5) & 0b111); + uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> 0) & 0b11111); + if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Setting measurement time for BH1750 failed"); + active_mtreg_ = 0; + f(NAN); + return; + } + active_mtreg_ = mtreg; + } + + uint8_t cmd; + uint16_t meas_time; + switch (mode) { + case BH1750_MODE_L: + cmd = BH1750_COMMAND_ONE_TIME_L; + meas_time = 24 * mtreg / 69; + break; + case BH1750_MODE_H: + cmd = BH1750_COMMAND_ONE_TIME_H; + meas_time = 180 * mtreg / 69; + break; + case BH1750_MODE_H2: + cmd = BH1750_COMMAND_ONE_TIME_H2; + meas_time = 180 * mtreg / 69; + break; + default: + f(NAN); + return; + } + if (this->write(&cmd, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Starting measurement for BH1750 failed"); + f(NAN); + return; + } + + // probably not needed, but adjust for rounding + meas_time++; + + this->set_timeout("read", meas_time, [this, mode, mtreg, f]() { + uint16_t raw_value; + if (this->read(reinterpret_cast(&raw_value), 2) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Reading BH1750 data failed"); + f(NAN); + return; + } + raw_value = i2c::i2ctohs(raw_value); + + float lx = float(raw_value) / 1.2f; + lx *= 69.0f / mtreg; + if (mode == BH1750_MODE_H2) + lx /= 2.0f; + + f(lx); + }); } void BH1750Sensor::dump_config() { @@ -30,64 +121,49 @@ void BH1750Sensor::dump_config() { ESP_LOGE(TAG, "Communication with BH1750 failed!"); } - const char *resolution_s; - switch (this->resolution_) { - case BH1750_RESOLUTION_0P5_LX: - resolution_s = "0.5"; - break; - case BH1750_RESOLUTION_1P0_LX: - resolution_s = "1"; - break; - case BH1750_RESOLUTION_4P0_LX: - resolution_s = "4"; - break; - default: - resolution_s = "Unknown"; - break; - } - ESP_LOGCONFIG(TAG, " Resolution: %s", resolution_s); LOG_UPDATE_INTERVAL(this); } void BH1750Sensor::update() { - if (!this->write_bytes(this->resolution_, nullptr, 0)) - return; + // first do a quick measurement in L-mode with full range + // to find right range + this->read_lx_(BH1750_MODE_L, 31, [this](float val) { + if (std::isnan(val)) { + this->status_set_warning(); + this->publish_state(NAN); + return; + } - uint32_t wait = 0; - // use max conversion times - switch (this->resolution_) { - case BH1750_RESOLUTION_0P5_LX: - case BH1750_RESOLUTION_1P0_LX: - wait = 180; - break; - case BH1750_RESOLUTION_4P0_LX: - wait = 24; - break; - } + BH1750Mode use_mode; + uint8_t use_mtreg; + if (val <= 7000) { + use_mode = BH1750_MODE_H2; + use_mtreg = 254; + } else { + use_mode = BH1750_MODE_H; + // lx = counts / 1.2 * (69 / mtreg) + // -> mtreg = counts / 1.2 * (69 / lx) + // calculate for counts=50000 (allow some range to not saturate, but maximize mtreg) + // -> mtreg = 50000*(10/12)*(69/lx) + int ideal_mtreg = 50000 * 10 * 69 / (12 * (int) val); + use_mtreg = std::min(254, std::max(31, ideal_mtreg)); + } + ESP_LOGV(TAG, "L result: %f -> Calculated mode=%d, mtreg=%d", val, (int) use_mode, use_mtreg); - this->set_timeout("illuminance", wait, [this]() { this->read_data_(); }); + this->read_lx_(use_mode, use_mtreg, [this](float val) { + if (std::isnan(val)) { + this->status_set_warning(); + this->publish_state(NAN); + return; + } + ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), val); + this->status_clear_warning(); + this->publish_state(val); + }); + }); } 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) != i2c::ERROR_OK) { - this->status_set_warning(); - return; - } - raw_value = i2c::i2ctohs(raw_value); - - 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(); -} - -void BH1750Sensor::set_resolution(BH1750Resolution resolution) { this->resolution_ = resolution; } } // namespace bh1750 } // namespace esphome diff --git a/esphome/components/bh1750/bh1750.h b/esphome/components/bh1750/bh1750.h index c88fa10832..a31eb33609 100644 --- a/esphome/components/bh1750/bh1750.h +++ b/esphome/components/bh1750/bh1750.h @@ -7,29 +7,15 @@ namespace esphome { namespace bh1750 { -/// Enum listing all resolutions that can be used with the BH1750 -enum BH1750Resolution { - BH1750_RESOLUTION_4P0_LX = 0b00100011, // one-time low resolution mode - BH1750_RESOLUTION_1P0_LX = 0b00100000, // one-time high resolution mode 1 - BH1750_RESOLUTION_0P5_LX = 0b00100001, // one-time high resolution mode 2 +enum BH1750Mode { + BH1750_MODE_L, + BH1750_MODE_H, + BH1750_MODE_H2, }; /// This class implements support for the i2c-based BH1750 ambient light sensor. class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { public: - /** Set the resolution of this sensor. - * - * Possible values are: - * - * - `BH1750_RESOLUTION_4P0_LX` - * - `BH1750_RESOLUTION_1P0_LX` - * - `BH1750_RESOLUTION_0P5_LX` (default) - * - * @param resolution The new resolution of the sensor. - */ - void set_resolution(BH1750Resolution resolution); - void set_measurement_duration(uint8_t measurement_duration) { measurement_duration_ = measurement_duration; } - // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) void setup() override; @@ -38,10 +24,9 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c: float get_setup_priority() const override; protected: - void read_data_(); + void read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function &f); - BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX}; - uint8_t measurement_duration_; + uint8_t active_mtreg_{0}; }; } // namespace bh1750 diff --git a/esphome/components/bh1750/sensor.py b/esphome/components/bh1750/sensor.py index 904e716eb8..69778f49ce 100644 --- a/esphome/components/bh1750/sensor.py +++ b/esphome/components/bh1750/sensor.py @@ -2,28 +2,20 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_RESOLUTION, DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT, UNIT_LUX, - CONF_MEASUREMENT_DURATION, ) DEPENDENCIES = ["i2c"] +CODEOWNERS = ["@OttoWinter"] bh1750_ns = cg.esphome_ns.namespace("bh1750") -BH1750Resolution = bh1750_ns.enum("BH1750Resolution") -BH1750_RESOLUTIONS = { - 4.0: BH1750Resolution.BH1750_RESOLUTION_4P0_LX, - 1.0: BH1750Resolution.BH1750_RESOLUTION_1P0_LX, - 0.5: BH1750Resolution.BH1750_RESOLUTION_0P5_LX, -} BH1750Sensor = bh1750_ns.class_( "BH1750Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice ) -CONF_MEASUREMENT_TIME = "measurement_time" CONFIG_SCHEMA = ( sensor.sensor_schema( BH1750Sensor, @@ -34,14 +26,11 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum( - BH1750_RESOLUTIONS, float=True + cv.Optional("resolution"): cv.invalid( + "The 'resolution' option has been removed. The optimal value is now dynamically calculated." ), - cv.Optional(CONF_MEASUREMENT_DURATION, default=69): cv.int_range( - min=31, max=254 - ), - cv.Optional(CONF_MEASUREMENT_TIME): cv.invalid( - "The 'measurement_time' option has been replaced with 'measurement_duration' in 1.18.0" + cv.Optional("measurement_duration"): cv.invalid( + "The 'measurement_duration' option has been removed. The optimal value is now dynamically calculated." ), } ) @@ -54,6 +43,3 @@ async def to_code(config): var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - - cg.add(var.set_resolution(config[CONF_RESOLUTION])) - cg.add(var.set_measurement_duration(config[CONF_MEASUREMENT_DURATION])) diff --git a/tests/test1.yaml b/tests/test1.yaml index 4e82d06485..2acb420fb5 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -456,12 +456,10 @@ sensor: name: "Living Room Brightness 3" internal: true address: 0x23 - resolution: 1.0 update_interval: 30s retain: False availability: state_topic: livingroom/custom_state_topic - measurement_duration: 31 i2c_id: i2c_bus - platform: max44009 name: "Outside Brightness 1" From 8dae7f82252ed6815910090c413437865d1c3656 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 19 Feb 2022 15:57:52 +0100 Subject: [PATCH 140/238] Bump esphome-dashboard from 20220209.0 to 20220219.0 (#3231) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index acbf1d9984..427045af02 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.5 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20220209.0 +esphome-dashboard==20220219.0 aioesphomeapi==10.8.2 zeroconf==0.38.3 From f59dbe4a88aa21bc829e2dd46cca3f14fc295f81 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 20 Feb 2022 21:13:37 +0100 Subject: [PATCH 141/238] Add copy integration (#3241) --- CODEOWNERS | 1 + esphome/components/copy/__init__.py | 5 ++ .../components/copy/binary_sensor/__init__.py | 41 ++++++++++++++ .../copy/binary_sensor/copy_binary_sensor.cpp | 18 +++++++ .../copy/binary_sensor/copy_binary_sensor.h | 21 ++++++++ esphome/components/copy/button/__init__.py | 42 +++++++++++++++ .../components/copy/button/copy_button.cpp | 14 +++++ esphome/components/copy/button/copy_button.h | 22 ++++++++ esphome/components/copy/cover/__init__.py | 38 +++++++++++++ esphome/components/copy/cover/copy_cover.cpp | 50 +++++++++++++++++ esphome/components/copy/cover/copy_cover.h | 25 +++++++++ esphome/components/copy/fan/__init__.py | 36 +++++++++++++ esphome/components/copy/fan/copy_fan.cpp | 53 +++++++++++++++++++ esphome/components/copy/fan/copy_fan.h | 26 +++++++++ esphome/components/copy/lock/__init__.py | 36 +++++++++++++ esphome/components/copy/lock/copy_lock.cpp | 29 ++++++++++ esphome/components/copy/lock/copy_lock.h | 23 ++++++++ esphome/components/copy/number/__init__.py | 38 +++++++++++++ .../components/copy/number/copy_number.cpp | 29 ++++++++++ esphome/components/copy/number/copy_number.h | 23 ++++++++ esphome/components/copy/select/__init__.py | 36 +++++++++++++ .../components/copy/select/copy_select.cpp | 27 ++++++++++ esphome/components/copy/select/copy_select.h | 23 ++++++++ esphome/components/copy/sensor/__init__.py | 45 ++++++++++++++++ .../components/copy/sensor/copy_sensor.cpp | 18 +++++++ esphome/components/copy/sensor/copy_sensor.h | 21 ++++++++ esphome/components/copy/switch/__init__.py | 38 +++++++++++++ .../components/copy/switch/copy_switch.cpp | 26 +++++++++ esphome/components/copy/switch/copy_switch.h | 23 ++++++++ .../components/copy/text_sensor/__init__.py | 37 +++++++++++++ .../copy/text_sensor/copy_text_sensor.cpp | 18 +++++++ .../copy/text_sensor/copy_text_sensor.h | 21 ++++++++ esphome/const.py | 1 + tests/test1.yaml | 9 ++++ tests/test4.yaml | 21 ++++++++ 35 files changed, 934 insertions(+) create mode 100644 esphome/components/copy/__init__.py create mode 100644 esphome/components/copy/binary_sensor/__init__.py create mode 100644 esphome/components/copy/binary_sensor/copy_binary_sensor.cpp create mode 100644 esphome/components/copy/binary_sensor/copy_binary_sensor.h create mode 100644 esphome/components/copy/button/__init__.py create mode 100644 esphome/components/copy/button/copy_button.cpp create mode 100644 esphome/components/copy/button/copy_button.h create mode 100644 esphome/components/copy/cover/__init__.py create mode 100644 esphome/components/copy/cover/copy_cover.cpp create mode 100644 esphome/components/copy/cover/copy_cover.h create mode 100644 esphome/components/copy/fan/__init__.py create mode 100644 esphome/components/copy/fan/copy_fan.cpp create mode 100644 esphome/components/copy/fan/copy_fan.h create mode 100644 esphome/components/copy/lock/__init__.py create mode 100644 esphome/components/copy/lock/copy_lock.cpp create mode 100644 esphome/components/copy/lock/copy_lock.h create mode 100644 esphome/components/copy/number/__init__.py create mode 100644 esphome/components/copy/number/copy_number.cpp create mode 100644 esphome/components/copy/number/copy_number.h create mode 100644 esphome/components/copy/select/__init__.py create mode 100644 esphome/components/copy/select/copy_select.cpp create mode 100644 esphome/components/copy/select/copy_select.h create mode 100644 esphome/components/copy/sensor/__init__.py create mode 100644 esphome/components/copy/sensor/copy_sensor.cpp create mode 100644 esphome/components/copy/sensor/copy_sensor.h create mode 100644 esphome/components/copy/switch/__init__.py create mode 100644 esphome/components/copy/switch/copy_switch.cpp create mode 100644 esphome/components/copy/switch/copy_switch.h create mode 100644 esphome/components/copy/text_sensor/__init__.py create mode 100644 esphome/components/copy/text_sensor/copy_text_sensor.cpp create mode 100644 esphome/components/copy/text_sensor/copy_text_sensor.h diff --git a/CODEOWNERS b/CODEOWNERS index 61469c53fa..4edfde7711 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -44,6 +44,7 @@ esphome/components/climate/* @esphome/core esphome/components/climate_ir/* @glmnet esphome/components/color_temperature/* @jesserockz esphome/components/coolix/* @glmnet +esphome/components/copy/* @OttoWinter esphome/components/cover/* @esphome/core esphome/components/cs5460a/* @balrog-kun esphome/components/cse7761/* @berfenger diff --git a/esphome/components/copy/__init__.py b/esphome/components/copy/__init__.py new file mode 100644 index 0000000000..7594894650 --- /dev/null +++ b/esphome/components/copy/__init__.py @@ -0,0 +1,5 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@OttoWinter"] + +copy_ns = cg.esphome_ns.namespace("copy") diff --git a/esphome/components/copy/binary_sensor/__init__.py b/esphome/components/copy/binary_sensor/__init__.py new file mode 100644 index 0000000000..1b6836fae7 --- /dev/null +++ b/esphome/components/copy/binary_sensor/__init__.py @@ -0,0 +1,41 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyBinarySensor = copy_ns.class_( + "CopyBinarySensor", binary_sensor.BinarySensor, cg.Component +) + + +CONFIG_SCHEMA = ( + binary_sensor.binary_sensor_schema(CopyBinarySensor) + .extend( + { + cv.Required(CONF_SOURCE_ID): cv.use_id(binary_sensor.BinarySensor), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), + inherit_property_from(CONF_DEVICE_CLASS, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/binary_sensor/copy_binary_sensor.cpp b/esphome/components/copy/binary_sensor/copy_binary_sensor.cpp new file mode 100644 index 0000000000..0d96f58750 --- /dev/null +++ b/esphome/components/copy/binary_sensor/copy_binary_sensor.cpp @@ -0,0 +1,18 @@ +#include "copy_binary_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.binary_sensor"; + +void CopyBinarySensor::setup() { + source_->add_on_state_callback([this](bool value) { this->publish_state(value); }); + if (source_->has_state()) + this->publish_state(source_->state); +} + +void CopyBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Copy Binary Sensor", this); } + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/binary_sensor/copy_binary_sensor.h b/esphome/components/copy/binary_sensor/copy_binary_sensor.h new file mode 100644 index 0000000000..d62ed13c76 --- /dev/null +++ b/esphome/components/copy/binary_sensor/copy_binary_sensor.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" + +namespace esphome { +namespace copy { + +class CopyBinarySensor : public binary_sensor::BinarySensor, public Component { + public: + void set_source(binary_sensor::BinarySensor *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + binary_sensor::BinarySensor *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/button/__init__.py b/esphome/components/copy/button/__init__.py new file mode 100644 index 0000000000..65d956601a --- /dev/null +++ b/esphome/components/copy/button/__init__.py @@ -0,0 +1,42 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyButton = copy_ns.class_("CopyButton", button.Button, cg.Component) + + +CONFIG_SCHEMA = ( + button.button_schema() + .extend( + { + cv.GenerateID(): cv.declare_id(CopyButton), + cv.Required(CONF_SOURCE_ID): cv.use_id(button.Button), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), + inherit_property_from(CONF_DEVICE_CLASS, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await button.register_button(var, config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/button/copy_button.cpp b/esphome/components/copy/button/copy_button.cpp new file mode 100644 index 0000000000..595388775c --- /dev/null +++ b/esphome/components/copy/button/copy_button.cpp @@ -0,0 +1,14 @@ +#include "copy_button.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.button"; + +void CopyButton::dump_config() { LOG_BUTTON("", "Copy Button", this); } + +void CopyButton::press_action() { source_->press(); } + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/button/copy_button.h b/esphome/components/copy/button/copy_button.h new file mode 100644 index 0000000000..9996ca0c65 --- /dev/null +++ b/esphome/components/copy/button/copy_button.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/button/button.h" + +namespace esphome { +namespace copy { + +class CopyButton : public button::Button, public Component { + public: + void set_source(button::Button *source) { source_ = source; } + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void press_action() override; + + button::Button *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/cover/__init__.py b/esphome/components/copy/cover/__init__.py new file mode 100644 index 0000000000..155e22883b --- /dev/null +++ b/esphome/components/copy/cover/__init__.py @@ -0,0 +1,38 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import cover +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyCover = copy_ns.class_("CopyCover", cover.Cover, cg.Component) + + +CONFIG_SCHEMA = cover.COVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopyCover), + cv.Required(CONF_SOURCE_ID): cv.use_id(cover.Cover), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), + inherit_property_from(CONF_DEVICE_CLASS, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cover.register_cover(var, config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/cover/copy_cover.cpp b/esphome/components/copy/cover/copy_cover.cpp new file mode 100644 index 0000000000..cf50473018 --- /dev/null +++ b/esphome/components/copy/cover/copy_cover.cpp @@ -0,0 +1,50 @@ +#include "copy_cover.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.cover"; + +void CopyCover::setup() { + source_->add_on_state_callback([this]() { + this->current_operation = this->source_->current_operation; + this->position = this->source_->position; + this->tilt = this->source_->tilt; + this->publish_state(); + }); + + this->current_operation = this->source_->current_operation; + this->position = this->source_->position; + this->tilt = this->source_->tilt; + this->publish_state(); +} + +void CopyCover::dump_config() { LOG_COVER("", "Copy Cover", this); } + +cover::CoverTraits CopyCover::get_traits() { + auto base = source_->get_traits(); + cover::CoverTraits traits{}; + // copy traits manually so it doesn't break when new options are added + // but the control() method hasn't implemented them yet. + traits.set_is_assumed_state(base.get_is_assumed_state()); + traits.set_supports_position(base.get_supports_position()); + traits.set_supports_tilt(base.get_supports_tilt()); + traits.set_supports_toggle(base.get_supports_toggle()); + return traits; +} + +void CopyCover::control(const cover::CoverCall &call) { + auto call2 = source_->make_call(); + call2.set_stop(call.get_stop()); + if (call.get_tilt().has_value()) + call2.set_tilt(*call.get_tilt()); + if (call.get_position().has_value()) + call2.set_position(*call.get_position()); + if (call.get_tilt().has_value()) + call2.set_tilt(*call.get_tilt()); + call2.perform(); +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/cover/copy_cover.h b/esphome/components/copy/cover/copy_cover.h new file mode 100644 index 0000000000..fb278523ff --- /dev/null +++ b/esphome/components/copy/cover/copy_cover.h @@ -0,0 +1,25 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/cover/cover.h" + +namespace esphome { +namespace copy { + +class CopyCover : public cover::Cover, public Component { + public: + void set_source(cover::Cover *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + cover::CoverTraits get_traits() override; + + protected: + void control(const cover::CoverCall &call) override; + + cover::Cover *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/fan/__init__.py b/esphome/components/copy/fan/__init__.py new file mode 100644 index 0000000000..22672c02d8 --- /dev/null +++ b/esphome/components/copy/fan/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import fan +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyFan = copy_ns.class_("CopyFan", fan.Fan, cg.Component) + + +CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopyFan), + cv.Required(CONF_SOURCE_ID): cv.use_id(fan.Fan), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await fan.register_fan(var, config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/fan/copy_fan.cpp b/esphome/components/copy/fan/copy_fan.cpp new file mode 100644 index 0000000000..74d9da279f --- /dev/null +++ b/esphome/components/copy/fan/copy_fan.cpp @@ -0,0 +1,53 @@ +#include "copy_fan.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.fan"; + +void CopyFan::setup() { + source_->add_on_state_callback([this]() { + this->state = source_->state; + this->oscillating = source_->oscillating; + this->speed = source_->speed; + this->direction = source_->direction; + this->publish_state(); + }); + + this->state = source_->state; + this->oscillating = source_->oscillating; + this->speed = source_->speed; + this->direction = source_->direction; + this->publish_state(); +} + +void CopyFan::dump_config() { LOG_FAN("", "Copy Fan", this); } + +fan::FanTraits CopyFan::get_traits() { + fan::FanTraits traits; + auto base = source_->get_traits(); + // copy traits manually so it doesn't break when new options are added + // but the control() method hasn't implemented them yet. + traits.set_oscillation(base.supports_oscillation()); + traits.set_speed(base.supports_speed()); + traits.set_supported_speed_count(base.supported_speed_count()); + traits.set_direction(base.supports_direction()); + return traits; +} + +void CopyFan::control(const fan::FanCall &call) { + auto call2 = source_->make_call(); + if (call.get_state().has_value()) + call2.set_state(*call.get_state()); + if (call.get_oscillating().has_value()) + call2.set_oscillating(*call.get_oscillating()); + if (call.get_speed().has_value()) + call2.set_speed(*call.get_speed()); + if (call.get_direction().has_value()) + call2.set_direction(*call.get_direction()); + call2.perform(); +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/fan/copy_fan.h b/esphome/components/copy/fan/copy_fan.h new file mode 100644 index 0000000000..1a69810510 --- /dev/null +++ b/esphome/components/copy/fan/copy_fan.h @@ -0,0 +1,26 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/fan/fan.h" + +namespace esphome { +namespace copy { + +class CopyFan : public fan::Fan, public Component { + public: + void set_source(fan::Fan *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + fan::FanTraits get_traits() override; + + protected: + void control(const fan::FanCall &call) override; + ; + + fan::Fan *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/lock/__init__.py b/esphome/components/copy/lock/__init__.py new file mode 100644 index 0000000000..d19e4a5807 --- /dev/null +++ b/esphome/components/copy/lock/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import lock +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyLock = copy_ns.class_("CopyLock", lock.Lock, cg.Component) + + +CONFIG_SCHEMA = lock.LOCK_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopyLock), + cv.Required(CONF_SOURCE_ID): cv.use_id(lock.Lock), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await lock.register_lock(var, config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/lock/copy_lock.cpp b/esphome/components/copy/lock/copy_lock.cpp new file mode 100644 index 0000000000..67a8acffec --- /dev/null +++ b/esphome/components/copy/lock/copy_lock.cpp @@ -0,0 +1,29 @@ +#include "copy_lock.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.lock"; + +void CopyLock::setup() { + source_->add_on_state_callback([this]() { this->publish_state(source_->state); }); + + traits.set_assumed_state(source_->traits.get_assumed_state()); + traits.set_requires_code(source_->traits.get_requires_code()); + traits.set_supported_states(source_->traits.get_supported_states()); + traits.set_supports_open(source_->traits.get_supports_open()); + + this->publish_state(source_->state); +} + +void CopyLock::dump_config() { LOG_LOCK("", "Copy Lock", this); } + +void CopyLock::control(const lock::LockCall &call) { + auto call2 = source_->make_call(); + call2.set_state(call.get_state()); + call2.perform(); +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/lock/copy_lock.h b/esphome/components/copy/lock/copy_lock.h new file mode 100644 index 0000000000..0554013674 --- /dev/null +++ b/esphome/components/copy/lock/copy_lock.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/lock/lock.h" + +namespace esphome { +namespace copy { + +class CopyLock : public lock::Lock, public Component { + public: + void set_source(lock::Lock *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void control(const lock::LockCall &call) override; + + lock::Lock *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/number/__init__.py b/esphome/components/copy/number/__init__.py new file mode 100644 index 0000000000..4e78627a1f --- /dev/null +++ b/esphome/components/copy/number/__init__.py @@ -0,0 +1,38 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import number +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_MODE, + CONF_SOURCE_ID, + CONF_UNIT_OF_MEASUREMENT, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyNumber = copy_ns.class_("CopyNumber", number.Number, cg.Component) + + +CONFIG_SCHEMA = number.NUMBER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopyNumber), + cv.Required(CONF_SOURCE_ID): cv.use_id(number.Number), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), + inherit_property_from(CONF_UNIT_OF_MEASUREMENT, CONF_SOURCE_ID), + inherit_property_from(CONF_MODE, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = await number.new_number(config, min_value=0, max_value=0, step=0) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/number/copy_number.cpp b/esphome/components/copy/number/copy_number.cpp new file mode 100644 index 0000000000..46dc200b73 --- /dev/null +++ b/esphome/components/copy/number/copy_number.cpp @@ -0,0 +1,29 @@ +#include "copy_number.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.number"; + +void CopyNumber::setup() { + source_->add_on_state_callback([this](float value) { this->publish_state(value); }); + + traits.set_min_value(source_->traits.get_min_value()); + traits.set_max_value(source_->traits.get_max_value()); + traits.set_step(source_->traits.get_step()); + + if (source_->has_state()) + this->publish_state(source_->state); +} + +void CopyNumber::dump_config() { LOG_NUMBER("", "Copy Number", this); } + +void CopyNumber::control(float value) { + auto call2 = source_->make_call(); + call2.set_value(value); + call2.perform(); +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/number/copy_number.h b/esphome/components/copy/number/copy_number.h new file mode 100644 index 0000000000..1ad956fec4 --- /dev/null +++ b/esphome/components/copy/number/copy_number.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/number/number.h" + +namespace esphome { +namespace copy { + +class CopyNumber : public number::Number, public Component { + public: + void set_source(number::Number *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void control(float value) override; + + number::Number *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/select/__init__.py b/esphome/components/copy/select/__init__.py new file mode 100644 index 0000000000..7d4c1c7705 --- /dev/null +++ b/esphome/components/copy/select/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import select +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopySelect = copy_ns.class_("CopySelect", select.Select, cg.Component) + + +CONFIG_SCHEMA = select.SELECT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopySelect), + cv.Required(CONF_SOURCE_ID): cv.use_id(select.Select), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await select.register_select(var, config, options=[]) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/select/copy_select.cpp b/esphome/components/copy/select/copy_select.cpp new file mode 100644 index 0000000000..0f01c2692c --- /dev/null +++ b/esphome/components/copy/select/copy_select.cpp @@ -0,0 +1,27 @@ +#include "copy_select.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.select"; + +void CopySelect::setup() { + source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); }); + + traits.set_options(source_->traits.get_options()); + + if (source_->has_state()) + this->publish_state(source_->state); +} + +void CopySelect::dump_config() { LOG_SELECT("", "Copy Select", this); } + +void CopySelect::control(const std::string &value) { + auto call = source_->make_call(); + call.set_option(value); + call.perform(); +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/select/copy_select.h b/esphome/components/copy/select/copy_select.h new file mode 100644 index 0000000000..c8666cd394 --- /dev/null +++ b/esphome/components/copy/select/copy_select.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/select/select.h" + +namespace esphome { +namespace copy { + +class CopySelect : public select::Select, public Component { + public: + void set_source(select::Select *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void control(const std::string &value) override; + + select::Select *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/sensor/__init__.py b/esphome/components/copy/sensor/__init__.py new file mode 100644 index 0000000000..8e78cda7c7 --- /dev/null +++ b/esphome/components/copy/sensor/__init__.py @@ -0,0 +1,45 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_SOURCE_ID, + CONF_STATE_CLASS, + CONF_UNIT_OF_MEASUREMENT, + CONF_ACCURACY_DECIMALS, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopySensor = copy_ns.class_("CopySensor", sensor.Sensor, cg.Component) + + +CONFIG_SCHEMA = ( + sensor.sensor_schema(CopySensor) + .extend( + { + cv.Required(CONF_SOURCE_ID): cv.use_id(sensor.Sensor), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_UNIT_OF_MEASUREMENT, CONF_SOURCE_ID), + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ACCURACY_DECIMALS, CONF_SOURCE_ID), + inherit_property_from(CONF_DEVICE_CLASS, CONF_SOURCE_ID), + inherit_property_from(CONF_STATE_CLASS, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/sensor/copy_sensor.cpp b/esphome/components/copy/sensor/copy_sensor.cpp new file mode 100644 index 0000000000..a47a0cf22b --- /dev/null +++ b/esphome/components/copy/sensor/copy_sensor.cpp @@ -0,0 +1,18 @@ +#include "copy_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.sensor"; + +void CopySensor::setup() { + source_->add_on_state_callback([this](float value) { this->publish_state(value); }); + if (source_->has_state()) + this->publish_state(source_->state); +} + +void CopySensor::dump_config() { LOG_SENSOR("", "Copy Sensor", this); } + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/sensor/copy_sensor.h b/esphome/components/copy/sensor/copy_sensor.h new file mode 100644 index 0000000000..1ae790ada3 --- /dev/null +++ b/esphome/components/copy/sensor/copy_sensor.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace copy { + +class CopySensor : public sensor::Sensor, public Component { + public: + void set_source(sensor::Sensor *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + sensor::Sensor *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/switch/__init__.py b/esphome/components/copy/switch/__init__.py new file mode 100644 index 0000000000..6622412123 --- /dev/null +++ b/esphome/components/copy/switch/__init__.py @@ -0,0 +1,38 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopySwitch = copy_ns.class_("CopySwitch", switch.Switch, cg.Component) + + +CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopySwitch), + cv.Required(CONF_SOURCE_ID): cv.use_id(switch.Switch), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), + inherit_property_from(CONF_DEVICE_CLASS, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await switch.register_switch(var, config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/switch/copy_switch.cpp b/esphome/components/copy/switch/copy_switch.cpp new file mode 100644 index 0000000000..8a9fbb03dd --- /dev/null +++ b/esphome/components/copy/switch/copy_switch.cpp @@ -0,0 +1,26 @@ +#include "copy_switch.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.switch"; + +void CopySwitch::setup() { + source_->add_on_state_callback([this](float value) { this->publish_state(value); }); + + this->publish_state(source_->state); +} + +void CopySwitch::dump_config() { LOG_SWITCH("", "Copy Switch", this); } + +void CopySwitch::write_state(bool state) { + if (state) { + source_->turn_on(); + } else { + source_->turn_off(); + } +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/switch/copy_switch.h b/esphome/components/copy/switch/copy_switch.h new file mode 100644 index 0000000000..26cb254ab3 --- /dev/null +++ b/esphome/components/copy/switch/copy_switch.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" + +namespace esphome { +namespace copy { + +class CopySwitch : public switch_::Switch, public Component { + public: + void set_source(switch_::Switch *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void write_state(bool state) override; + + switch_::Switch *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/text_sensor/__init__.py b/esphome/components/copy/text_sensor/__init__.py new file mode 100644 index 0000000000..5b59f21319 --- /dev/null +++ b/esphome/components/copy/text_sensor/__init__.py @@ -0,0 +1,37 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyTextSensor = copy_ns.class_("CopyTextSensor", text_sensor.TextSensor, cg.Component) + + +CONFIG_SCHEMA = ( + text_sensor.text_sensor_schema(CopyTextSensor) + .extend( + { + cv.Required(CONF_SOURCE_ID): cv.use_id(text_sensor.TextSensor), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = await text_sensor.new_text_sensor(config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/text_sensor/copy_text_sensor.cpp b/esphome/components/copy/text_sensor/copy_text_sensor.cpp new file mode 100644 index 0000000000..95fa6d7a1b --- /dev/null +++ b/esphome/components/copy/text_sensor/copy_text_sensor.cpp @@ -0,0 +1,18 @@ +#include "copy_text_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.text_sensor"; + +void CopyTextSensor::setup() { + source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); }); + if (source_->has_state()) + this->publish_state(source_->state); +} + +void CopyTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Copy Sensor", this); } + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/text_sensor/copy_text_sensor.h b/esphome/components/copy/text_sensor/copy_text_sensor.h new file mode 100644 index 0000000000..fe91fe948b --- /dev/null +++ b/esphome/components/copy/text_sensor/copy_text_sensor.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/text_sensor/text_sensor.h" + +namespace esphome { +namespace copy { + +class CopyTextSensor : public text_sensor::TextSensor, public Component { + public: + void set_source(text_sensor::TextSensor *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + text_sensor::TextSensor *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index dec68e0701..00cc90ad6a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -621,6 +621,7 @@ CONF_SLEEP_PIN = "sleep_pin" CONF_SLEEP_WHEN_DONE = "sleep_when_done" CONF_SONY = "sony" CONF_SOURCE = "source" +CONF_SOURCE_ID = "source_id" CONF_SPEED = "speed" CONF_SPEED_COMMAND_TOPIC = "speed_command_topic" CONF_SPEED_COUNT = "speed_count" diff --git a/tests/test1.yaml b/tests/test1.yaml index 2acb420fb5..c4398498c1 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2104,6 +2104,9 @@ fan: on_speed_set: then: - logger.log: "Fan speed was changed!" + - platform: copy + source_id: fan_speed + name: "Fan Speed Copy" interval: - interval: 10s @@ -2671,6 +2674,9 @@ select: - one - two optimistic: true + - platform: copy + source_id: test_select + name: Test Select Copy qr_code: - id: homepage_qr @@ -2700,3 +2706,6 @@ lock: name: "Generic Output Lock" id: test_lock2 output: pca_6 + - platform: copy + source_id: test_lock2 + name: Generic Output Lock Copy diff --git a/tests/test4.yaml b/tests/test4.yaml index 998db8ed2d..54412222b5 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -221,6 +221,10 @@ sensor: - platform: mcp3204 name: "MCP3204 Pin 1" number: 1 + id: mcp_sensor + - platform: copy + source_id: mcp_sensor + name: "MCP binary sensor copy" # # platform sensor.apds9960 requires component apds9960 @@ -364,6 +368,9 @@ switch: name: inverter0_pv_ok_condition_for_parallel pv_power_balance: name: inverter0_pv_power_balance + - platform: copy + source_id: tuya_switch + name: Tuya Switch Copy light: - platform: fastled_clockless @@ -391,6 +398,9 @@ cover: - platform: tuya id: tuya_cover position_datapoint: 2 + - platform: copy + source_id: tuya_cover + name: "Tuya Cover copy" display: - platform: addressable_light @@ -465,6 +475,9 @@ number: min_value: 0 max_value: 17 step: 1 + - platform: copy + source_id: tuya_number + name: Tuya Number Copy text_sensor: - platform: pipsolar @@ -484,6 +497,9 @@ text_sensor: last_qflag: id: inverter0_last_qflag name: inverter0_last_qflag + - platform: copy + source_id: inverter0_device_mode + name: "Inverter Text Sensor Copy" output: - platform: pipsolar @@ -552,6 +568,11 @@ button: name: Safe Mode Button - platform: shutdown name: Shutdown Button + id: shutdown_btn + - platform: copy + source_id: shutdown_btn + name: Shutdown Button Copy + touchscreen: - platform: ektf2232 From d26141151a1cede351895468803b79b897f80d6a Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 20 Feb 2022 21:17:51 +0100 Subject: [PATCH 142/238] Button code cleanup (#3242) --- esphome/components/button/button.cpp | 6 +----- esphome/components/button/button.h | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp index d57b46e9aa..0a5c6567bc 100644 --- a/esphome/components/button/button.cpp +++ b/esphome/components/button/button.cpp @@ -18,11 +18,7 @@ void Button::add_on_press_callback(std::function &&callback) { this->pre 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 ""; -} +std::string Button::get_device_class() { return this->device_class_; } } // namespace button } // namespace esphome diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h index b21a96b8e1..f60c2a2bc5 100644 --- a/esphome/components/button/button.h +++ b/esphome/components/button/button.h @@ -45,12 +45,12 @@ class Button : public EntityBase { protected: /** You should implement this virtual method if you want to create your own button. */ - virtual void press_action(){}; + virtual void press_action() = 0; uint32_t hash_base() override; CallbackManager press_callback_{}; - optional device_class_{}; + std::string device_class_{}; }; } // namespace button From 07c1cf71377821499f730c6af8b13554a31697b0 Mon Sep 17 00:00:00 2001 From: cstaahl <44496349+cstaahl@users.noreply.github.com> Date: Sun, 20 Feb 2022 21:32:35 +0100 Subject: [PATCH 143/238] Pulse meter internal filter mode (#3082) Co-authored-by: Paul Daumlechner Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Otto Winter --- CODEOWNERS | 2 +- .../pulse_meter/pulse_meter_sensor.cpp | 70 +++++++++++++------ .../pulse_meter/pulse_meter_sensor.h | 11 ++- esphome/components/pulse_meter/sensor.py | 14 +++- esphome/const.py | 1 + esphome/cpp_generator.py | 2 +- 6 files changed, 76 insertions(+), 24 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 4edfde7711..52e80a5822 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -144,7 +144,7 @@ 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/pulse_meter/* @cstaahl @stevebaxter esphome/components/pvvx_mithermometer/* @pasiz esphome/components/qr_code/* @wjtje esphome/components/radon_eye_ble/* @jeffeb3 diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.cpp b/esphome/components/pulse_meter/pulse_meter_sensor.cpp index 7d526b241b..f747f9ee40 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.cpp +++ b/esphome/components/pulse_meter/pulse_meter_sensor.cpp @@ -12,18 +12,53 @@ void PulseMeterSensor::setup() { this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); this->last_detected_edge_us_ = 0; - this->last_valid_edge_us_ = 0; + this->last_valid_low_edge_us_ = 0; + this->last_valid_high_edge_us_ = 0; + this->sensor_is_high_ = this->isr_pin_.digital_read(); } void PulseMeterSensor::loop() { const uint32_t now = micros(); + // Check to see if we should filter this edge out + if (this->filter_mode_ == FILTER_EDGE) { + if ((this->last_detected_edge_us_ - this->last_valid_high_edge_us_) >= this->filter_us_) { + // Don't measure the first valid pulse (we need at least two pulses to measure the width) + if (this->last_valid_high_edge_us_ != 0) { + this->pulse_width_us_ = (this->last_detected_edge_us_ - this->last_valid_high_edge_us_); + } + this->total_pulses_++; + this->last_valid_high_edge_us_ = this->last_detected_edge_us_; + } + } else { + // Make sure the signal has been stable long enough + if ((now - this->last_detected_edge_us_) >= this->filter_us_) { + // Only consider HIGH pulses and "new" edges if sensor state is LOW + if (!this->sensor_is_high_ && this->isr_pin_.digital_read() && + (this->last_detected_edge_us_ != this->last_valid_high_edge_us_)) { + // Don't measure the first valid pulse (we need at least two pulses to measure the width) + if (this->last_valid_high_edge_us_ != 0) { + this->pulse_width_us_ = (this->last_detected_edge_us_ - this->last_valid_high_edge_us_); + } + this->sensor_is_high_ = true; + this->total_pulses_++; + this->last_valid_high_edge_us_ = this->last_detected_edge_us_; + } + // Only consider LOW pulses and "new" edges if sensor state is HIGH + else if (this->sensor_is_high_ && !this->isr_pin_.digital_read() && + (this->last_detected_edge_us_ != this->last_valid_low_edge_us_)) { + this->sensor_is_high_ = false; + this->last_valid_low_edge_us_ = this->last_detected_edge_us_; + } + } + } + // If we've exceeded our timeout interval without receiving any pulses, assume 0 pulses/min until // we get at least two valid pulses. - const uint32_t time_since_valid_edge_us = now - this->last_valid_edge_us_; - if ((this->last_valid_edge_us_ != 0) && (time_since_valid_edge_us > this->timeout_us_)) { + const uint32_t time_since_valid_edge_us = now - this->last_valid_high_edge_us_; + if ((this->last_valid_high_edge_us_ != 0) && (time_since_valid_edge_us > this->timeout_us_) && + (this->pulse_width_us_ != 0)) { ESP_LOGD(TAG, "No pulse detected for %us, assuming 0 pulses/min", time_since_valid_edge_us / 1000000); - this->last_valid_edge_us_ = 0; this->pulse_width_us_ = 0; } @@ -52,7 +87,11 @@ void PulseMeterSensor::set_total_pulses(uint32_t pulses) { this->total_pulses_ = void PulseMeterSensor::dump_config() { LOG_SENSOR("", "Pulse Meter", this); LOG_PIN(" Pin: ", this->pin_); - ESP_LOGCONFIG(TAG, " Filtering pulses shorter than %u µs", this->filter_us_); + if (this->filter_mode_ == FILTER_EDGE) { + ESP_LOGCONFIG(TAG, " Filtering rising edges less than %u µs apart", this->filter_us_); + } else { + ESP_LOGCONFIG(TAG, " Filtering pulses shorter than %u µs", this->filter_us_); + } ESP_LOGCONFIG(TAG, " Assuming 0 pulses/min after not receiving a pulse for %us", this->timeout_us_ / 1000000); } @@ -62,23 +101,14 @@ void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) { // Get the current time before we do anything else so the measurements are consistent const uint32_t now = micros(); - // We only look at rising edges - if (!sensor->isr_pin_.digital_read()) { - return; - } - - // Check to see if we should filter this edge out - if ((now - sensor->last_detected_edge_us_) >= sensor->filter_us_) { - // Don't measure the first valid pulse (we need at least two pulses to measure the width) - if (sensor->last_valid_edge_us_ != 0) { - sensor->pulse_width_us_ = (now - sensor->last_valid_edge_us_); + // We only look at rising edges in EDGE mode, and all edges in PULSE mode + if (sensor->filter_mode_ == FILTER_EDGE) { + if (sensor->isr_pin_.digital_read()) { + sensor->last_detected_edge_us_ = now; } - - sensor->total_pulses_++; - sensor->last_valid_edge_us_ = now; + } else { + sensor->last_detected_edge_us_ = now; } - - sensor->last_detected_edge_us_ = now; } } // namespace pulse_meter diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.h b/esphome/components/pulse_meter/pulse_meter_sensor.h index 1cebc1748e..cf08f8c92d 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.h +++ b/esphome/components/pulse_meter/pulse_meter_sensor.h @@ -10,8 +10,14 @@ namespace pulse_meter { class PulseMeterSensor : public sensor::Sensor, public Component { public: + enum InternalFilterMode { + FILTER_EDGE = 0, + FILTER_PULSE, + }; + void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } void set_filter_us(uint32_t filter) { this->filter_us_ = filter; } + void set_filter_mode(InternalFilterMode mode) { this->filter_mode_ = mode; } void set_timeout_us(uint32_t timeout) { this->timeout_us_ = timeout; } void set_total_sensor(sensor::Sensor *sensor) { this->total_sensor_ = sensor; } @@ -30,14 +36,17 @@ class PulseMeterSensor : public sensor::Sensor, public Component { uint32_t filter_us_ = 0; uint32_t timeout_us_ = 1000000UL * 60UL * 5UL; sensor::Sensor *total_sensor_ = nullptr; + InternalFilterMode filter_mode_{FILTER_EDGE}; Deduplicator pulse_width_dedupe_; Deduplicator total_dedupe_; volatile uint32_t last_detected_edge_us_ = 0; - volatile uint32_t last_valid_edge_us_ = 0; + volatile uint32_t last_valid_low_edge_us_ = 0; + volatile uint32_t last_valid_high_edge_us_ = 0; volatile uint32_t pulse_width_us_ = 0; volatile uint32_t total_pulses_ = 0; + volatile bool sensor_is_high_ = false; }; } // namespace pulse_meter diff --git a/esphome/components/pulse_meter/sensor.py b/esphome/components/pulse_meter/sensor.py index fa753b5b05..26bc6b189b 100644 --- a/esphome/components/pulse_meter/sensor.py +++ b/esphome/components/pulse_meter/sensor.py @@ -5,6 +5,7 @@ from esphome.components import sensor from esphome.const import ( CONF_ID, CONF_INTERNAL_FILTER, + CONF_INTERNAL_FILTER_MODE, CONF_PIN, CONF_NUMBER, CONF_TIMEOUT, @@ -18,14 +19,21 @@ from esphome.const import ( ) from esphome.core import CORE -CODEOWNERS = ["@stevebaxter"] +CODEOWNERS = ["@stevebaxter", "@cstaahl"] pulse_meter_ns = cg.esphome_ns.namespace("pulse_meter") + PulseMeterSensor = pulse_meter_ns.class_( "PulseMeterSensor", sensor.Sensor, cg.Component ) +PulseMeterInternalFilterMode = PulseMeterSensor.enum("InternalFilterMode") +FILTER_MODES = { + "EDGE": PulseMeterInternalFilterMode.FILTER_EDGE, + "PULSE": PulseMeterInternalFilterMode.FILTER_PULSE, +} + SetTotalPulsesAction = pulse_meter_ns.class_("SetTotalPulsesAction", automation.Action) @@ -66,6 +74,9 @@ CONFIG_SCHEMA = sensor.sensor_schema( accuracy_decimals=0, state_class=STATE_CLASS_TOTAL_INCREASING, ), + cv.Optional(CONF_INTERNAL_FILTER_MODE, default="EDGE"): cv.enum( + FILTER_MODES, upper=True + ), } ) @@ -78,6 +89,7 @@ async def to_code(config): cg.add(var.set_pin(pin)) cg.add(var.set_filter_us(config[CONF_INTERNAL_FILTER])) cg.add(var.set_timeout_us(config[CONF_TIMEOUT])) + cg.add(var.set_filter_mode(config[CONF_INTERNAL_FILTER_MODE])) if CONF_TOTAL in config: sens = await sensor.new_sensor(config[CONF_TOTAL]) diff --git a/esphome/const.py b/esphome/const.py index 00cc90ad6a..2ec00edf7b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -307,6 +307,7 @@ CONF_INTENSITY = "intensity" CONF_INTERLOCK = "interlock" CONF_INTERNAL = "internal" CONF_INTERNAL_FILTER = "internal_filter" +CONF_INTERNAL_FILTER_MODE = "internal_filter_mode" CONF_INTERRUPT = "interrupt" CONF_INTERVAL = "interval" CONF_INVALID_COOLDOWN = "invalid_cooldown" diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 4ff16ba703..42828450e8 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -954,7 +954,7 @@ class MockObjEnum(MockObj): base = kwargs.pop("base") if self._is_class: base = f"{base}::{self._enum}" - kwargs["op"] = "::" + kwargs["op"] = "::" kwargs["base"] = base MockObj.__init__(self, *args, **kwargs) From 78951c197a10072c8a1846d9adc76612ea495db0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Feb 2022 09:58:53 +1300 Subject: [PATCH 144/238] Fix lilygo touchscreen rotation (#3221) --- .../touchscreen/lilygo_t5_47_touchscreen.cpp | 47 +++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp index b5cf63980b..b92d7d6f10 100644 --- a/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp +++ b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp @@ -113,8 +113,27 @@ void LilygoT547Touchscreen::loop() { if (tp.state == 0x06) tp.state = 0x07; - tp.y = (uint16_t)((buffer[i * 5 + 1 + offset] << 4) | ((buffer[i * 5 + 3 + offset] >> 4) & 0x0F)); - tp.x = (uint16_t)((buffer[i * 5 + 2 + offset] << 4) | (buffer[i * 5 + 3 + offset] & 0x0F)); + uint16_t y = (uint16_t)((buffer[i * 5 + 1 + offset] << 4) | ((buffer[i * 5 + 3 + offset] >> 4) & 0x0F)); + uint16_t x = (uint16_t)((buffer[i * 5 + 2 + offset] << 4) | (buffer[i * 5 + 3 + offset] & 0x0F)); + + switch (this->rotation_) { + case ROTATE_0_DEGREES: + tp.y = this->display_height_ - y; + tp.x = x; + break; + case ROTATE_90_DEGREES: + tp.x = this->display_height_ - y; + tp.y = this->display_width_ - x; + break; + case ROTATE_180_DEGREES: + tp.y = y; + tp.x = this->display_width_ - x; + break; + case ROTATE_270_DEGREES: + tp.x = y; + tp.y = x; + break; + } this->defer([this, tp]() { this->send_touch_(tp); }); } @@ -122,8 +141,28 @@ void LilygoT547Touchscreen::loop() { TouchPoint tp; tp.id = (buffer[0] >> 4) & 0x0F; tp.state = 0x06; - tp.y = (uint16_t)((buffer[0 * 5 + 1] << 4) | ((buffer[0 * 5 + 3] >> 4) & 0x0F)); - tp.x = (uint16_t)((buffer[0 * 5 + 2] << 4) | (buffer[0 * 5 + 3] & 0x0F)); + + uint16_t y = (uint16_t)((buffer[0 * 5 + 1] << 4) | ((buffer[0 * 5 + 3] >> 4) & 0x0F)); + uint16_t x = (uint16_t)((buffer[0 * 5 + 2] << 4) | (buffer[0 * 5 + 3] & 0x0F)); + + switch (this->rotation_) { + case ROTATE_0_DEGREES: + tp.y = this->display_height_ - y; + tp.x = x; + break; + case ROTATE_90_DEGREES: + tp.x = this->display_height_ - y; + tp.y = this->display_width_ - x; + break; + case ROTATE_180_DEGREES: + tp.y = y; + tp.x = this->display_width_ - x; + break; + case ROTATE_270_DEGREES: + tp.x = y; + tp.y = x; + break; + } this->defer([this, tp]() { this->send_touch_(tp); }); } From 2c7b104f4aa71188142c93e7983599575d1c7c3f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Feb 2022 10:01:00 +1300 Subject: [PATCH 145/238] Fix fatal erroring in addon startup script (#3244) --- docker/ha-addon-rootfs/etc/cont-init.d/10-requirements.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/ha-addon-rootfs/etc/cont-init.d/10-requirements.sh b/docker/ha-addon-rootfs/etc/cont-init.d/10-requirements.sh index 9d49c2b4dd..544787d568 100755 --- a/docker/ha-addon-rootfs/etc/cont-init.d/10-requirements.sh +++ b/docker/ha-addon-rootfs/etc/cont-init.d/10-requirements.sh @@ -7,12 +7,12 @@ # Check SSL requirements, if enabled if bashio::config.true 'ssl'; then if ! bashio::config.has_value 'certfile'; then - bashio::fatal 'SSL is enabled, but no certfile was specified.' + bashio::log.fatal 'SSL is enabled, but no certfile was specified.' bashio::exit.nok fi if ! bashio::config.has_value 'keyfile'; then - bashio::fatal 'SSL is enabled, but no keyfile was specified' + bashio::log.fatal 'SSL is enabled, but no keyfile was specified' bashio::exit.nok fi From ba785e29e95c03c78fdab7ee1482eb2ec1841ce6 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 21 Feb 2022 00:23:26 +0100 Subject: [PATCH 146/238] Add support for MPU-6886 (#3183) --- CODEOWNERS | 1 + esphome/components/mpu6886/__init__.py | 1 + esphome/components/mpu6886/mpu6886.cpp | 153 +++++++++++++++++++++++++ esphome/components/mpu6886/mpu6886.h | 39 +++++++ esphome/components/mpu6886/sensor.py | 85 ++++++++++++++ tests/test1.yaml | 17 +++ 6 files changed, 296 insertions(+) create mode 100644 esphome/components/mpu6886/__init__.py create mode 100644 esphome/components/mpu6886/mpu6886.cpp create mode 100644 esphome/components/mpu6886/mpu6886.h create mode 100644 esphome/components/mpu6886/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 52e80a5822..eb8fb873de 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -124,6 +124,7 @@ esphome/components/modbus_controller/select/* @martgras @stegm esphome/components/modbus_controller/sensor/* @martgras esphome/components/modbus_controller/switch/* @martgras esphome/components/modbus_controller/text_sensor/* @martgras +esphome/components/mpu6886/* @fabaff esphome/components/network/* @esphome/core esphome/components/nextion/* @senexcrenshaw esphome/components/nextion/binary_sensor/* @senexcrenshaw diff --git a/esphome/components/mpu6886/__init__.py b/esphome/components/mpu6886/__init__.py new file mode 100644 index 0000000000..933f71ccd7 --- /dev/null +++ b/esphome/components/mpu6886/__init__.py @@ -0,0 +1 @@ +"""Support for MPC-6886.""" diff --git a/esphome/components/mpu6886/mpu6886.cpp b/esphome/components/mpu6886/mpu6886.cpp new file mode 100644 index 0000000000..c296653e6b --- /dev/null +++ b/esphome/components/mpu6886/mpu6886.cpp @@ -0,0 +1,153 @@ +#include "mpu6886.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mpu6886 { + +static const char *const TAG = "mpu6886"; + +const uint8_t MPU6886_REGISTER_WHO_AM_I = 0x75; +const uint8_t MPU6886_REGISTER_POWER_MANAGEMENT_1 = 0x6B; +const uint8_t MPU6886_REGISTER_GYRO_CONFIG = 0x1B; +const uint8_t MPU6886_REGISTER_ACCEL_CONFIG = 0x1C; +const uint8_t MPU6886_REGISTER_ACCEL_XOUT_H = 0x3B; +const uint8_t MPU6886_CLOCK_SOURCE_X_GYRO = 0b001; +const uint8_t MPU6886_SCALE_2000_DPS = 0b11; +const uint8_t MPU6886_WHO_AM_I_IDENTIFIER = 0x19; +const float MPU6886_SCALE_DPS_PER_DIGIT_2000 = 0.060975f; +const uint8_t MPU6886_RANGE_2G = 0b00; +const float MPU6886_RANGE_PER_DIGIT_2G = 0.000061f; +const uint8_t MPU6886_BIT_SLEEP_ENABLED = 6; +const uint8_t MPU6886_BIT_TEMPERATURE_DISABLED = 3; +const float GRAVITY_EARTH = 9.80665f; +// See https://github.com/m5stack/M5-Schematic/blob/master/datasheet/MPU-6886-000193%2Bv1.1_GHIC.PDF.pdf +// p. 43 +const float TEMPERATURE_SENSITIVITY = 326.8; +const float TEMPERATURE_OFFSET = 25.0; + +void MPU6886Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MPU6886..."); + uint8_t who_am_i; + if (!this->read_byte(MPU6886_REGISTER_WHO_AM_I, &who_am_i) || who_am_i != MPU6886_WHO_AM_I_IDENTIFIER) { + this->mark_failed(); + return; + } + + ESP_LOGV(TAG, " Setting up Power Management..."); + // Setup power management + uint8_t power_management; + if (!this->read_byte(MPU6886_REGISTER_POWER_MANAGEMENT_1, &power_management)) { + this->mark_failed(); + return; + } + ESP_LOGV(TAG, " Input power_management: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(power_management)); + // Set clock source - X-Gyro + power_management &= 0b11111000; + power_management |= MPU6886_CLOCK_SOURCE_X_GYRO; + // Disable sleep + power_management &= ~(1 << MPU6886_BIT_SLEEP_ENABLED); + // Enable temperature + power_management &= ~(1 << MPU6886_BIT_TEMPERATURE_DISABLED); + ESP_LOGV(TAG, " Output power_management: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(power_management)); + if (!this->write_byte(MPU6886_REGISTER_POWER_MANAGEMENT_1, power_management)) { + this->mark_failed(); + return; + } + + ESP_LOGV(TAG, " Setting up Gyroscope Config..."); + // Set scale - 2000DPS + uint8_t gyro_config; + if (!this->read_byte(MPU6886_REGISTER_GYRO_CONFIG, &gyro_config)) { + this->mark_failed(); + return; + } + ESP_LOGV(TAG, " Input gyroscope_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_config)); + gyro_config &= 0b11100111; + gyro_config |= MPU6886_SCALE_2000_DPS << 3; + ESP_LOGV(TAG, " Output gyro_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_config)); + if (!this->write_byte(MPU6886_REGISTER_GYRO_CONFIG, gyro_config)) { + this->mark_failed(); + return; + } + + ESP_LOGV(TAG, " Setting up Accelerometer Config..."); + // Set range - 2G + uint8_t accel_config; + if (!this->read_byte(MPU6886_REGISTER_ACCEL_CONFIG, &accel_config)) { + this->mark_failed(); + return; + } + ESP_LOGV(TAG, " Input accelerometer_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config)); + accel_config &= 0b11100111; + accel_config |= (MPU6886_RANGE_2G << 3); + ESP_LOGV(TAG, " Output accel_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config)); + if (!this->write_byte(MPU6886_REGISTER_GYRO_CONFIG, gyro_config)) { + this->mark_failed(); + return; + } +} + +void MPU6886Component::dump_config() { + ESP_LOGCONFIG(TAG, "MPU6886:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with MPU6886 failed!"); + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Acceleration X", this->accel_x_sensor_); + LOG_SENSOR(" ", "Acceleration Y", this->accel_y_sensor_); + LOG_SENSOR(" ", "Acceleration Z", this->accel_z_sensor_); + LOG_SENSOR(" ", "Gyro X", this->gyro_x_sensor_); + LOG_SENSOR(" ", "Gyro Y", this->gyro_y_sensor_); + LOG_SENSOR(" ", "Gyro Z", this->gyro_z_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); +} + +void MPU6886Component::update() { + ESP_LOGV(TAG, " Updating MPU6886..."); + uint16_t raw_data[7]; + if (!this->read_bytes_16(MPU6886_REGISTER_ACCEL_XOUT_H, raw_data, 7)) { + this->status_set_warning(); + return; + } + auto *data = reinterpret_cast(raw_data); + + float accel_x = data[0] * MPU6886_RANGE_PER_DIGIT_2G * GRAVITY_EARTH; + float accel_y = data[1] * MPU6886_RANGE_PER_DIGIT_2G * GRAVITY_EARTH; + float accel_z = data[2] * MPU6886_RANGE_PER_DIGIT_2G * GRAVITY_EARTH; + + float temperature = data[3] / TEMPERATURE_SENSITIVITY + TEMPERATURE_OFFSET; + + float gyro_x = data[4] * MPU6886_SCALE_DPS_PER_DIGIT_2000; + float gyro_y = data[5] * MPU6886_SCALE_DPS_PER_DIGIT_2000; + float gyro_z = data[6] * MPU6886_SCALE_DPS_PER_DIGIT_2000; + + ESP_LOGD(TAG, + "Got accel={x=%.3f m/s², y=%.3f m/s², z=%.3f m/s²}, " + "gyro={x=%.3f °/s, y=%.3f °/s, z=%.3f °/s}, temp=%.3f°C", + accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z, temperature); + + if (this->accel_x_sensor_ != nullptr) + this->accel_x_sensor_->publish_state(accel_x); + if (this->accel_y_sensor_ != nullptr) + this->accel_y_sensor_->publish_state(accel_y); + if (this->accel_z_sensor_ != nullptr) + this->accel_z_sensor_->publish_state(accel_z); + + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(temperature); + + if (this->gyro_x_sensor_ != nullptr) + this->gyro_x_sensor_->publish_state(gyro_x); + if (this->gyro_y_sensor_ != nullptr) + this->gyro_y_sensor_->publish_state(gyro_y); + if (this->gyro_z_sensor_ != nullptr) + this->gyro_z_sensor_->publish_state(gyro_z); + + this->status_clear_warning(); +} + +float MPU6886Component::get_setup_priority() const { return setup_priority::DATA; } + +} // namespace mpu6886 +} // namespace esphome diff --git a/esphome/components/mpu6886/mpu6886.h b/esphome/components/mpu6886/mpu6886.h new file mode 100644 index 0000000000..04551ae56d --- /dev/null +++ b/esphome/components/mpu6886/mpu6886.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 mpu6886 { + +class MPU6886Component : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + + void update() override; + + float get_setup_priority() const override; + + void set_accel_x_sensor(sensor::Sensor *accel_x_sensor) { accel_x_sensor_ = accel_x_sensor; } + void set_accel_y_sensor(sensor::Sensor *accel_y_sensor) { accel_y_sensor_ = accel_y_sensor; } + void set_accel_z_sensor(sensor::Sensor *accel_z_sensor) { accel_z_sensor_ = accel_z_sensor; } + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void set_gyro_x_sensor(sensor::Sensor *gyro_x_sensor) { gyro_x_sensor_ = gyro_x_sensor; } + void set_gyro_y_sensor(sensor::Sensor *gyro_y_sensor) { gyro_y_sensor_ = gyro_y_sensor; } + void set_gyro_z_sensor(sensor::Sensor *gyro_z_sensor) { gyro_z_sensor_ = gyro_z_sensor; } + + protected: + sensor::Sensor *accel_x_sensor_{nullptr}; + sensor::Sensor *accel_y_sensor_{nullptr}; + sensor::Sensor *accel_z_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *gyro_x_sensor_{nullptr}; + sensor::Sensor *gyro_y_sensor_{nullptr}; + sensor::Sensor *gyro_z_sensor_{nullptr}; +}; +; + +} // namespace mpu6886 +} // namespace esphome diff --git a/esphome/components/mpu6886/sensor.py b/esphome/components/mpu6886/sensor.py new file mode 100644 index 0000000000..535007d008 --- /dev/null +++ b/esphome/components/mpu6886/sensor.py @@ -0,0 +1,85 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_TEMPERATURE, + ICON_BRIEFCASE_DOWNLOAD, + STATE_CLASS_MEASUREMENT, + UNIT_METER_PER_SECOND_SQUARED, + ICON_SCREEN_ROTATION, + UNIT_DEGREE_PER_SECOND, + UNIT_CELSIUS, +) + +CODEOWNERS = ["@fabaff"] +DEPENDENCIES = ["i2c"] + +CONF_ACCEL_X = "accel_x" +CONF_ACCEL_Y = "accel_y" +CONF_ACCEL_Z = "accel_z" +CONF_GYRO_X = "gyro_x" +CONF_GYRO_Y = "gyro_y" +CONF_GYRO_Z = "gyro_z" + +mpu6886_ns = cg.esphome_ns.namespace("mpu6886") +MPU6886Component = mpu6886_ns.class_( + "MPU6886Component", cg.PollingComponent, i2c.I2CDevice +) + +accel_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_METER_PER_SECOND_SQUARED, + icon=ICON_BRIEFCASE_DOWNLOAD, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, +) +gyro_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_DEGREE_PER_SECOND, + icon=ICON_SCREEN_ROTATION, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, +) +temperature_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MPU6886Component), + cv.Optional(CONF_ACCEL_X): accel_schema, + cv.Optional(CONF_ACCEL_Y): accel_schema, + cv.Optional(CONF_ACCEL_Z): accel_schema, + cv.Optional(CONF_GYRO_X): gyro_schema, + cv.Optional(CONF_GYRO_Y): gyro_schema, + cv.Optional(CONF_GYRO_Z): gyro_schema, + cv.Optional(CONF_TEMPERATURE): temperature_schema, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x68)) +) + + +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 d in ["x", "y", "z"]: + accel_key = f"accel_{d}" + if accel_key in config: + sens = await sensor.new_sensor(config[accel_key]) + cg.add(getattr(var, f"set_accel_{d}_sensor")(sens)) + accel_key = f"gyro_{d}" + if accel_key in config: + sens = await sensor.new_sensor(config[accel_key]) + cg.add(getattr(var, f"set_gyro_{d}_sensor")(sens)) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature_sensor(sens)) diff --git a/tests/test1.yaml b/tests/test1.yaml index c4398498c1..0d8ba9dfe8 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -724,6 +724,23 @@ sensor: temperature: name: "MPU6050 Temperature" i2c_id: i2c_bus + - platform: mpu6886 + address: 0x68 + accel_x: + name: "MPU6886 Accel X" + accel_y: + name: "MPU6886 Accel Y" + accel_z: + name: "MPU6886 Accel z" + gyro_x: + name: "MPU6886 Gyro X" + gyro_y: + name: "MPU6886 Gyro Y" + gyro_z: + name: "MPU6886 Gyro z" + temperature: + name: "MPU6886 Temperature" + i2c_id: i2c_bus - platform: ms5611 temperature: name: "Outside Temperature" From 771162bfb113ab105ceb5edb545848e530a1fd81 Mon Sep 17 00:00:00 2001 From: Niorix Date: Mon, 21 Feb 2022 06:52:14 +0700 Subject: [PATCH 147/238] light: add RESTORE_AND_OFF/RESTORE_AND_ON LightRestoreMode (#3238) --- esphome/components/light/__init__.py | 2 ++ esphome/components/light/light_state.cpp | 6 ++++++ esphome/components/light/light_state.h | 2 ++ 3 files changed, 10 insertions(+) diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index fe8a90b8db..c397910ec4 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -52,6 +52,8 @@ RESTORE_MODES = { "ALWAYS_ON": LightRestoreMode.LIGHT_ALWAYS_ON, "RESTORE_INVERTED_DEFAULT_OFF": LightRestoreMode.LIGHT_RESTORE_INVERTED_DEFAULT_OFF, "RESTORE_INVERTED_DEFAULT_ON": LightRestoreMode.LIGHT_RESTORE_INVERTED_DEFAULT_ON, + "RESTORE_AND_OFF": LightRestoreMode.LIGHT_RESTORE_AND_OFF, + "RESTORE_AND_ON": LightRestoreMode.LIGHT_RESTORE_AND_ON, } LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 151bc58a1c..dadad38235 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -68,6 +68,12 @@ void LightState::setup() { recovered.state = !recovered.state; } break; + case LIGHT_RESTORE_AND_OFF: + case LIGHT_RESTORE_AND_ON: + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); + this->rtc_.load(&recovered); + recovered.state = (this->restore_mode_ == LIGHT_RESTORE_AND_ON); + break; case LIGHT_ALWAYS_OFF: recovered.state = false; break; diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index ae3711234d..2e523eda5c 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -22,6 +22,8 @@ enum LightRestoreMode { LIGHT_ALWAYS_ON, LIGHT_RESTORE_INVERTED_DEFAULT_OFF, LIGHT_RESTORE_INVERTED_DEFAULT_ON, + LIGHT_RESTORE_AND_OFF, + LIGHT_RESTORE_AND_ON, }; /** This class represents the communication layer between the front-end MQTT layer and the From 69633826bb54355680d24f47b03ed4d985c4ab2f Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Mon, 21 Feb 2022 01:13:06 +0100 Subject: [PATCH 148/238] Implement send_first_at for exponential_moving_average (#3240) --- esphome/components/sensor/__init__.py | 20 ++++++++++++++++---- esphome/components/sensor/filter.cpp | 4 ++-- esphome/components/sensor/filter.h | 2 +- tests/test1.yaml | 1 + 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 577596f6ce..65ae7b2168 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -408,18 +408,30 @@ async def sliding_window_moving_average_filter_to_code(config, filter_id): ) -@FILTER_REGISTRY.register( - "exponential_moving_average", - ExponentialMovingAverageFilter, +EXPONENTIAL_AVERAGE_SCHEMA = cv.All( cv.Schema( { cv.Optional(CONF_ALPHA, default=0.1): cv.positive_float, cv.Optional(CONF_SEND_EVERY, default=15): cv.positive_not_null_int, + cv.Optional(CONF_SEND_FIRST_AT, default=1): cv.positive_not_null_int, } ), + validate_send_first_at, +) + + +@FILTER_REGISTRY.register( + "exponential_moving_average", + ExponentialMovingAverageFilter, + EXPONENTIAL_AVERAGE_SCHEMA, ) async def exponential_moving_average_filter_to_code(config, filter_id): - return cg.new_Pvariable(filter_id, config[CONF_ALPHA], config[CONF_SEND_EVERY]) + return cg.new_Pvariable( + filter_id, + config[CONF_ALPHA], + config[CONF_SEND_EVERY], + config[CONF_SEND_FIRST_AT], + ) @FILTER_REGISTRY.register( diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index 49d2c648b0..d4a7a52fa1 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -200,8 +200,8 @@ optional SlidingWindowMovingAverageFilter::new_value(float value) { } // ExponentialMovingAverageFilter -ExponentialMovingAverageFilter::ExponentialMovingAverageFilter(float alpha, size_t send_every) - : send_every_(send_every), send_at_(send_every - 1), alpha_(alpha) {} +ExponentialMovingAverageFilter::ExponentialMovingAverageFilter(float alpha, size_t send_every, size_t send_first_at) + : send_every_(send_every), send_at_(send_every - send_first_at), alpha_(alpha) {} optional ExponentialMovingAverageFilter::new_value(float value) { if (!std::isnan(value)) { if (this->first_value_) { diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 0ed7ce4801..a39c1ba25a 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -194,7 +194,7 @@ class SlidingWindowMovingAverageFilter : public Filter { */ class ExponentialMovingAverageFilter : public Filter { public: - ExponentialMovingAverageFilter(float alpha, size_t send_every); + ExponentialMovingAverageFilter(float alpha, size_t send_every, size_t send_first_at); optional new_value(float value) override; diff --git a/tests/test1.yaml b/tests/test1.yaml index 0d8ba9dfe8..496226565a 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -358,6 +358,7 @@ sensor: - exponential_moving_average: alpha: 0.1 send_every: 15 + send_first_at: 15 - throttle_average: 60s - throttle: 1s - heartbeat: 5s From 6919930aaa230f0437d0975d902e98cc7dbbf11f Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Mon, 21 Feb 2022 02:05:13 +0100 Subject: [PATCH 149/238] Respect ESPHOME_USE_SUBPROCESS in esp32 post_build script (#3246) --- esphome/components/esp32/post_build.py.script | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32/post_build.py.script b/esphome/components/esp32/post_build.py.script index 2bb1a6c3d6..406516a102 100644 --- a/esphome/components/esp32/post_build.py.script +++ b/esphome/components/esp32/post_build.py.script @@ -1,6 +1,10 @@ # Source https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1005864664 -import esptool +import os +if os.environ.get("ESPHOME_USE_SUBPROCESS") is None: + import esptool +else: + import subprocess from SCons.Script import ARGUMENTS # pylint: disable=E0602 @@ -42,8 +46,11 @@ def esp32_create_combined_bin(source, target, env): print() print(f"Using esptool.py arguments: {' '.join(cmd)}") print() - esptool.main(cmd) + if os.environ.get("ESPHOME_USE_SUBPROCESS") is None: + esptool.main(cmd) + else: + subprocess.run(["esptool.py", *cmd]) # pylint: disable=E0602 env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) # noqa From d1feaa935dd587910a4e3508936e85319f99eacd Mon Sep 17 00:00:00 2001 From: Arturo Casal Date: Mon, 21 Feb 2022 12:47:03 +0100 Subject: [PATCH 150/238] Add device support: MCP4728 (#3174) * Added MCP4728 output component. * Added tests to test1.yaml * Added codeowners * Lint fixes * Implemented code review changes * Lint fixes * Added i2c communication check on setup() * Fixed tests * Lint fix * Update esphome/components/mcp4728/mcp4728_output.cpp Changed log function Co-authored-by: Otto Winter Co-authored-by: Otto Winter --- CODEOWNERS | 1 + esphome/components/mcp4728/__init__.py | 29 +++++ esphome/components/mcp4728/mcp4728_output.cpp | 121 ++++++++++++++++++ esphome/components/mcp4728/mcp4728_output.h | 91 +++++++++++++ esphome/components/mcp4728/output.py | 63 +++++++++ tests/test1.yaml | 31 +++++ 6 files changed, 336 insertions(+) create mode 100644 esphome/components/mcp4728/__init__.py create mode 100644 esphome/components/mcp4728/mcp4728_output.cpp create mode 100644 esphome/components/mcp4728/mcp4728_output.h create mode 100644 esphome/components/mcp4728/output.py diff --git a/CODEOWNERS b/CODEOWNERS index eb8fb873de..a53bf63c69 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -108,6 +108,7 @@ esphome/components/mcp23x17_base/* @jesserockz esphome/components/mcp23xxx_base/* @jesserockz esphome/components/mcp2515/* @danielschramm @mvturnho esphome/components/mcp3204/* @rsumner +esphome/components/mcp4728/* @berfenger esphome/components/mcp47a1/* @jesserockz esphome/components/mcp9808/* @k7hpn esphome/components/md5/* @esphome/core diff --git a/esphome/components/mcp4728/__init__.py b/esphome/components/mcp4728/__init__.py new file mode 100644 index 0000000000..d130ceb738 --- /dev/null +++ b/esphome/components/mcp4728/__init__.py @@ -0,0 +1,29 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c +from esphome.const import CONF_ID + +CODEOWNERS = ["@berfenger"] +DEPENDENCIES = ["i2c"] +MULTI_CONF = True +CONF_STORE_IN_EEPROM = "store_in_eeprom" + +mcp4728_ns = cg.esphome_ns.namespace("mcp4728") +MCP4728Component = mcp4728_ns.class_("MCP4728Component", cg.Component, i2c.I2CDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MCP4728Component), + cv.Optional(CONF_STORE_IN_EEPROM, default=False): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x60)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID], config[CONF_STORE_IN_EEPROM]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/mcp4728/mcp4728_output.cpp b/esphome/components/mcp4728/mcp4728_output.cpp new file mode 100644 index 0000000000..d011967624 --- /dev/null +++ b/esphome/components/mcp4728/mcp4728_output.cpp @@ -0,0 +1,121 @@ +#include "mcp4728_output.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mcp4728 { + +static const char *const TAG = "mcp4728"; + +void MCP4728Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MCP4728 (0x%02X)...", this->address_); + auto err = this->write(nullptr, 0); + if (err != i2c::ERROR_OK) { + this->mark_failed(); + return; + } +} + +void MCP4728Component::dump_config() { + ESP_LOGCONFIG(TAG, "MCP4728:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with MCP4728 failed!"); + } +} + +void MCP4728Component::loop() { + if (this->update_) { + this->update_ = false; + if (this->store_in_eeprom_) { + if (!this->seq_write_()) { + this->status_set_error(); + } else { + this->status_clear_error(); + } + } else { + if (!this->multi_write_()) { + this->status_set_error(); + } else { + this->status_clear_error(); + } + } + } +} + +void MCP4728Component::set_channel_value_(MCP4728ChannelIdx channel, uint16_t value) { + uint8_t cn = 0; + if (channel == MCP4728_CHANNEL_A) { + cn = 'A'; + } else if (channel == MCP4728_CHANNEL_B) { + cn = 'B'; + } else if (channel == MCP4728_CHANNEL_C) { + cn = 'C'; + } else { + cn = 'D'; + } + ESP_LOGV(TAG, "Setting MCP4728 channel %c to %d!", cn, value); + reg_[channel].data = value; + this->update_ = true; +} + +bool MCP4728Component::multi_write_() { + i2c::ErrorCode err[4]; + for (uint8_t i = 0; i < 4; ++i) { + uint8_t wd[3]; + wd[0] = ((uint8_t) CMD::MULTI_WRITE | (i << 1)) & 0xFE; + wd[1] = ((uint8_t) reg_[i].vref << 7) | ((uint8_t) reg_[i].pd << 5) | ((uint8_t) reg_[i].gain << 4) | + (reg_[i].data >> 8); + wd[2] = reg_[i].data & 0xFF; + err[i] = this->write(wd, sizeof(wd)); + } + bool ok = true; + for (auto &e : err) { + if (e != i2c::ERROR_OK) { + ok = false; + break; + } + } + return ok; +} + +bool MCP4728Component::seq_write_() { + uint8_t wd[9]; + wd[0] = (uint8_t) CMD::SEQ_WRITE; + for (uint8_t i = 0; i < 4; i++) { + wd[i * 2 + 1] = ((uint8_t) reg_[i].vref << 7) | ((uint8_t) reg_[i].pd << 5) | ((uint8_t) reg_[i].gain << 4) | + (reg_[i].data >> 8); + wd[i * 2 + 2] = reg_[i].data & 0xFF; + } + auto err = this->write(wd, sizeof(wd)); + return err == i2c::ERROR_OK; +} + +void MCP4728Component::select_vref_(MCP4728ChannelIdx channel, MCP4728Vref vref) { + reg_[channel].vref = vref; + + this->update_ = true; +} + +void MCP4728Component::select_power_down_(MCP4728ChannelIdx channel, MCP4728PwrDown pd) { + reg_[channel].pd = pd; + + this->update_ = true; +} + +void MCP4728Component::select_gain_(MCP4728ChannelIdx channel, MCP4728Gain gain) { + reg_[channel].gain = gain; + + this->update_ = true; +} + +void MCP4728Channel::write_state(float state) { + const uint16_t max_duty = 4095; + const float duty_rounded = roundf(state * max_duty); + auto duty = static_cast(duty_rounded); + this->parent_->set_channel_value_(this->channel_, duty); +} + +} // namespace mcp4728 +} // namespace esphome diff --git a/esphome/components/mcp4728/mcp4728_output.h b/esphome/components/mcp4728/mcp4728_output.h new file mode 100644 index 0000000000..55bcfdccb6 --- /dev/null +++ b/esphome/components/mcp4728/mcp4728_output.h @@ -0,0 +1,91 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/output/float_output.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace mcp4728 { + +enum class CMD { + FAST_WRITE = 0x00, + MULTI_WRITE = 0x40, + SINGLE_WRITE = 0x58, + SEQ_WRITE = 0x50, + SELECT_VREF = 0x80, + SELECT_GAIN = 0xC0, + SELECT_POWER_DOWN = 0xA0 +}; + +enum MCP4728Vref { MCP4728_VREF_VDD = 0, MCP4728_VREF_INTERNAL_2_8V = 1 }; + +enum MCP4728PwrDown { + MCP4728_PD_NORMAL = 0, + MCP4728_PD_GND_1KOHM = 1, + MCP4728_PD_GND_100KOHM = 2, + MCP4728_PD_GND_500KOHM = 3 +}; + +enum MCP4728Gain { MCP4728_GAIN_X1 = 0, MCP4728_GAIN_X2 = 1 }; + +enum MCP4728ChannelIdx { MCP4728_CHANNEL_A = 0, MCP4728_CHANNEL_B = 1, MCP4728_CHANNEL_C = 2, MCP4728_CHANNEL_D = 3 }; + +struct DACInputData { + MCP4728Vref vref; + MCP4728PwrDown pd; + MCP4728Gain gain; + uint16_t data; +}; + +class MCP4728Channel; + +/// MCP4728 float output component. +class MCP4728Component : public Component, public i2c::I2CDevice { + public: + MCP4728Component(bool store_in_eeprom) : store_in_eeprom_(store_in_eeprom) {} + + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + void loop() override; + + protected: + friend MCP4728Channel; + void set_channel_value_(MCP4728ChannelIdx channel, uint16_t value); + bool multi_write_(); + bool seq_write_(); + void select_vref_(MCP4728ChannelIdx channel, MCP4728Vref vref); + void select_power_down_(MCP4728ChannelIdx channel, MCP4728PwrDown pd); + void select_gain_(MCP4728ChannelIdx channel, MCP4728Gain gain); + + private: + DACInputData reg_[4]; + bool store_in_eeprom_ = false; + bool update_ = false; +}; + +class MCP4728Channel : public output::FloatOutput { + public: + MCP4728Channel(MCP4728Component *parent, MCP4728ChannelIdx channel, MCP4728Vref vref, MCP4728Gain gain, + MCP4728PwrDown pwrdown) + : parent_(parent), channel_(channel), vref_(vref), gain_(gain), pwrdown_(pwrdown) { + // update VREF + parent->select_vref_(channel, vref_); + // update PD + parent->select_power_down_(channel, pwrdown_); + // update GAIN + parent->select_gain_(channel, gain_); + } + + protected: + void write_state(float state) override; + + MCP4728Component *parent_; + MCP4728ChannelIdx channel_; + MCP4728Vref vref_; + MCP4728Gain gain_; + MCP4728PwrDown pwrdown_; +}; + +} // namespace mcp4728 +} // namespace esphome diff --git a/esphome/components/mcp4728/output.py b/esphome/components/mcp4728/output.py new file mode 100644 index 0000000000..e0913ab98a --- /dev/null +++ b/esphome/components/mcp4728/output.py @@ -0,0 +1,63 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output +from esphome.const import CONF_CHANNEL, CONF_ID, CONF_GAIN +from . import MCP4728Component, mcp4728_ns + +DEPENDENCIES = ["mcp4728"] + +MCP4728Channel = mcp4728_ns.class_("MCP4728Channel", output.FloatOutput) +CONF_MCP4728_ID = "mcp4728_id" +CONF_VREF = "vref" +CONF_POWER_DOWN = "power_down" + +MCP4728Vref = mcp4728_ns.enum("MCP4728Vref") +VREF_OPTIONS = { + "vdd": MCP4728Vref.MCP4728_VREF_VDD, + "internal": MCP4728Vref.MCP4728_VREF_INTERNAL_2_8V, +} + +MCP4728Gain = mcp4728_ns.enum("MCP4728Gain") +GAIN_OPTIONS = {"X1": MCP4728Gain.MCP4728_GAIN_X1, "X2": MCP4728Gain.MCP4728_GAIN_X2} + +MCP4728PwrDown = mcp4728_ns.enum("MCP4728PwrDown") +PWRDOWN_OPTIONS = { + "normal": MCP4728PwrDown.MCP4728_PD_NORMAL, + "gnd_1k": MCP4728PwrDown.MCP4728_PD_GND_1KOHM, + "gnd_100k": MCP4728PwrDown.MCP4728_PD_GND_100KOHM, + "gnd_500k": MCP4728PwrDown.MCP4728_PD_GND_500KOHM, +} + +MCP4728ChannelIdx = mcp4728_ns.enum("MCP4728ChannelIdx") +CHANNEL_OPTIONS = { + "A": MCP4728ChannelIdx.MCP4728_CHANNEL_A, + "B": MCP4728ChannelIdx.MCP4728_CHANNEL_B, + "C": MCP4728ChannelIdx.MCP4728_CHANNEL_C, + "D": MCP4728ChannelIdx.MCP4728_CHANNEL_D, +} + +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(MCP4728Channel), + cv.GenerateID(CONF_MCP4728_ID): cv.use_id(MCP4728Component), + cv.Required(CONF_CHANNEL): cv.enum(CHANNEL_OPTIONS, upper=True), + cv.Optional(CONF_VREF, default="vdd"): cv.enum(VREF_OPTIONS, upper=False), + cv.Optional(CONF_POWER_DOWN, default="normal"): cv.enum( + PWRDOWN_OPTIONS, upper=False + ), + cv.Optional(CONF_GAIN, default="X1"): cv.enum(GAIN_OPTIONS, upper=True), + } +) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_MCP4728_ID]) + var = cg.new_Pvariable( + config[CONF_ID], + paren, + config[CONF_CHANNEL], + config[CONF_VREF], + config[CONF_GAIN], + config[CONF_POWER_DOWN], + ) + await output.register_output(var, config) diff --git a/tests/test1.yaml b/tests/test1.yaml index 496226565a..65169e2fd9 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1501,6 +1501,28 @@ output: - platform: mcp4725 id: mcp4725_dac_output i2c_id: i2c_bus + - platform: mcp4728 + id: mcp4728_dac_output_a + channel: A + vref: vdd + power_down: normal + - platform: mcp4728 + id: mcp4728_dac_output_b + channel: B + vref: internal + gain: X1 + power_down: gnd_1k + - platform: mcp4728 + id: mcp4728_dac_output_c + channel: C + vref: vdd + power_down: gnd_100k + - platform: mcp4728 + id: mcp4728_dac_output_d + channel: D + vref: internal + gain: X2 + power_down: gnd_500k e131: @@ -2013,6 +2035,9 @@ switch: - output.set_level: id: mcp4725_dac_output level: !lambda "return 0.5;" + - output.set_level: + id: mcp4728_dac_output_a + level: !lambda "return 0.5;" turn_off_action: - switch.turn_on: living_room_lights_off restore_state: False @@ -2393,6 +2418,12 @@ rc522_i2c: ESP_LOGD("main", "Found tag %s", x.c_str()); i2c_id: i2c_bus +mcp4728: + - id: mcp4728_dac + store_in_eeprom: False + address: 0x60 + i2c_id: i2c_bus + gps: uart_id: uart0 From a5b4105971a6978dd842b8638d4c6abab3ccbe32 Mon Sep 17 00:00:00 2001 From: Micha Nordmann Date: Mon, 21 Feb 2022 19:35:04 +0100 Subject: [PATCH 151/238] support for waveshare 7.50in-hd-b (#3239) --- .../components/waveshare_epaper/display.py | 4 + .../waveshare_epaper/waveshare_epaper.cpp | 101 ++++++++++++++++++ .../waveshare_epaper/waveshare_epaper.h | 20 ++++ 3 files changed, 125 insertions(+) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 44120ebbc5..fe5b51290e 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -50,6 +50,9 @@ WaveshareEPaper7P5InBV2 = waveshare_epaper_ns.class_( WaveshareEPaper7P5InV2 = waveshare_epaper_ns.class_( "WaveshareEPaper7P5InV2", WaveshareEPaper ) +WaveshareEPaper7P5InHDB = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InHDB", WaveshareEPaper +) WaveshareEPaper2P13InDKE = waveshare_epaper_ns.class_( "WaveshareEPaper2P13InDKE", WaveshareEPaper ) @@ -76,6 +79,7 @@ MODELS = { "7.50in-bv2": ("b", WaveshareEPaper7P5InBV2), "7.50in-bc": ("b", WaveshareEPaper7P5InBC), "7.50inv2": ("b", WaveshareEPaper7P5InV2), + "7.50in-hd-b": ("b", WaveshareEPaper7P5InHDB), "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 71e3b22e7d..59b3e90b03 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -1241,6 +1241,107 @@ void WaveshareEPaper7P5InBC::dump_config() { LOG_UPDATE_INTERVAL(this); } +void WaveshareEPaper7P5InHDB::initialize() { + this->command(0x12); // SWRESET + + this->wait_until_idle_(); // waiting for the electronic paper IC to release the idle signal + + this->command(0x46); // Auto Write RAM + this->data(0xF7); + + this->wait_until_idle_(); // waiting for the electronic paper IC to release the idle signal + + this->command(0x47); // Auto Write RAM + this->data(0xF7); + + this->wait_until_idle_(); // waiting for the electronic paper IC to release the idle signal + + this->command(0x0C); // Soft start setting + this->data(0xAE); + this->data(0xC7); + this->data(0xC3); + this->data(0xC0); + this->data(0x40); + + this->command(0x01); // Set MUX as 527 + this->data(0xAF); + this->data(0x02); + this->data(0x01); + + this->command(0x11); // Data entry mode + this->data(0x01); + + this->command(0x44); + this->data(0x00); // RAM x address start at 0 + this->data(0x00); + this->data(0x6F); // RAM x address end at 36Fh -> 879 + this->data(0x03); + + this->command(0x45); + this->data(0xAF); // RAM y address start at 20Fh; + this->data(0x02); + this->data(0x00); // RAM y address end at 00h; + this->data(0x00); + + this->command(0x3C); // VBD + this->data(0x01); // LUT1, for white + + this->command(0x18); + this->data(0X80); + + this->command(0x22); + this->data(0XB1); // Load Temperature and waveform setting. + + this->command(0x20); + + this->wait_until_idle_(); // waiting for the electronic paper IC to release the idle signal + + this->command(0x4E); + this->data(0x00); + this->data(0x00); + + this->command(0x4F); + this->data(0xAF); + this->data(0x02); +} + +void HOT WaveshareEPaper7P5InHDB::display() { + this->command(0x4F); + this->data(0xAf); + this->data(0x02); + + // BLACK + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // RED + this->command(0x26); + this->start_data_(); + for (size_t i = 0; i < this->get_buffer_length_(); i++) + this->write_byte(0x00); + this->end_data_(); + + this->command(0x22); + this->data(0xC7); + this->command(0x20); + delay(100); // NOLINT +} + +int WaveshareEPaper7P5InHDB::get_width_internal() { return 880; } + +int WaveshareEPaper7P5InHDB::get_height_internal() { return 528; } + +void WaveshareEPaper7P5InHDB::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5in-HD-b"); + 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 96bd2fc782..41b93978ab 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -350,6 +350,26 @@ class WaveshareEPaper7P5InV2 : public WaveshareEPaper { int get_height_internal() override; }; +class WaveshareEPaper7P5InHDB : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // deep sleep + this->command(0x10); + this->data(0x01); + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + class WaveshareEPaper2P13InDKE : public WaveshareEPaper { public: void initialize() override; From b55e9329d98a2b63a12134108c2236db68129d62 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 22 Feb 2022 13:47:16 +1300 Subject: [PATCH 152/238] Fix template button after abstract press_action (#3250) --- esphome/components/template/button/__init__.py | 5 ++++- .../components/template/button/template_button.h | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 esphome/components/template/button/template_button.h diff --git a/esphome/components/template/button/__init__.py b/esphome/components/template/button/__init__.py index aa192d118e..a8bf595942 100644 --- a/esphome/components/template/button/__init__.py +++ b/esphome/components/template/button/__init__.py @@ -1,10 +1,13 @@ import esphome.config_validation as cv from esphome.components import button +from .. import template_ns + +TemplateButton = template_ns.class_("TemplateButton", button.Button) CONFIG_SCHEMA = button.BUTTON_SCHEMA.extend( { - cv.GenerateID(): cv.declare_id(button.Button), + cv.GenerateID(): cv.declare_id(TemplateButton), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/template/button/template_button.h b/esphome/components/template/button/template_button.h new file mode 100644 index 0000000000..68e976f64b --- /dev/null +++ b/esphome/components/template/button/template_button.h @@ -0,0 +1,15 @@ +#pragma once + +#include "esphome/components/button/button.h" + +namespace esphome { +namespace template_ { + +class TemplateButton : public button::Button { + public: + // Implements the abstract `press_action` but the `on_press` trigger already handles the press. + void press_action() override{}; +}; + +} // namespace template_ +} // namespace esphome From 9323b3a248e151cf51478b7c3628aa5b87f9a864 Mon Sep 17 00:00:00 2001 From: Nicholas Peters Date: Mon, 21 Feb 2022 19:53:24 -0500 Subject: [PATCH 153/238] Fix regression caused by TSL2591 auto gain (#3249) --- esphome/components/tsl2591/tsl2591.cpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/esphome/components/tsl2591/tsl2591.cpp b/esphome/components/tsl2591/tsl2591.cpp index 8a540c5f13..f8c59a53c6 100644 --- a/esphome/components/tsl2591/tsl2591.cpp +++ b/esphome/components/tsl2591/tsl2591.cpp @@ -43,16 +43,34 @@ void TSL2591Component::disable_if_power_saving_() { } void TSL2591Component::setup() { - if (this->component_gain_ == TSL2591_CGAIN_AUTO) - this->gain_ = TSL2591_GAIN_MED; + switch (this->component_gain_) { + case TSL2591_CGAIN_LOW: + this->gain_ = TSL2591_GAIN_LOW; + break; + case TSL2591_CGAIN_MED: + this->gain_ = TSL2591_GAIN_MED; + break; + case TSL2591_CGAIN_HIGH: + this->gain_ = TSL2591_GAIN_HIGH; + break; + case TSL2591_CGAIN_MAX: + this->gain_ = TSL2591_GAIN_MAX; + break; + case TSL2591_CGAIN_AUTO: + this->gain_ = TSL2591_GAIN_MED; + break; + } + uint8_t address = this->address_; ESP_LOGI(TAG, "Setting up TSL2591 sensor at I2C address 0x%02X", address); + uint8_t id; if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_DEVICE_ID, &id)) { ESP_LOGE(TAG, "Failed I2C read during setup()"); this->mark_failed(); return; } + if (id != 0x50) { ESP_LOGE(TAG, "Could not find the TSL2591 sensor. The ID register of the device at address 0x%02X reported 0x%02X " @@ -61,6 +79,7 @@ void TSL2591Component::setup() { this->mark_failed(); return; } + this->set_integration_time_and_gain(this->integration_time_, this->gain_); this->disable_if_power_saving_(); } From 68b3fd6b8f1f306391248ba1aec4f2f809b210c9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 22 Feb 2022 13:54:23 +1300 Subject: [PATCH 154/238] Store platform as uppercase (#3251) --- esphome/storage_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/storage_json.py b/esphome/storage_json.py index d561de4ce6..a941fca0af 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -99,7 +99,7 @@ class StorageJSON: def from_esphome_core( esph, old ): # type: (CoreType, Optional[StorageJSON]) -> StorageJSON - hardware = esph.target_platform + hardware = esph.target_platform.upper() if esph.is_esp32: from esphome.components import esp32 From c9094ca537c5d14c9929eadb2c61ee8b19b5a57b Mon Sep 17 00:00:00 2001 From: RubyBailey <60991881+RubyBailey@users.noreply.github.com> Date: Tue, 22 Feb 2022 02:22:30 -0800 Subject: [PATCH 155/238] Add sensor support: Honeywell ABP (SPI version) (#3164) Co-authored-by: RubyBailey --- CODEOWNERS | 1 + esphome/components/honeywellabp/__init__.py | 1 + .../components/honeywellabp/honeywellabp.cpp | 102 ++++++++++++++++++ .../components/honeywellabp/honeywellabp.h | 45 ++++++++ esphome/components/honeywellabp/sensor.py | 70 ++++++++++++ tests/test1.yaml | 8 ++ 6 files changed, 227 insertions(+) create mode 100644 esphome/components/honeywellabp/__init__.py create mode 100644 esphome/components/honeywellabp/honeywellabp.cpp create mode 100644 esphome/components/honeywellabp/honeywellabp.h create mode 100644 esphome/components/honeywellabp/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index a53bf63c69..6e3801d1f3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -80,6 +80,7 @@ esphome/components/hbridge/light/* @DotNetDann esphome/components/heatpumpir/* @rob-deutsch esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/homeassistant/* @OttoWinter +esphome/components/honeywellabp/* @RubyBailey esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/i2c/* @esphome/core esphome/components/improv_serial/* @esphome/core diff --git a/esphome/components/honeywellabp/__init__.py b/esphome/components/honeywellabp/__init__.py new file mode 100644 index 0000000000..03440d675d --- /dev/null +++ b/esphome/components/honeywellabp/__init__.py @@ -0,0 +1 @@ +"""Support for Honeywell ABP""" diff --git a/esphome/components/honeywellabp/honeywellabp.cpp b/esphome/components/honeywellabp/honeywellabp.cpp new file mode 100644 index 0000000000..910c39e8c8 --- /dev/null +++ b/esphome/components/honeywellabp/honeywellabp.cpp @@ -0,0 +1,102 @@ +#include "honeywellabp.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace honeywellabp { + +static const char *const TAG = "honeywellabp"; + +const float MIN_COUNT = 1638.4; // 1638 counts (10% of 2^14 counts or 0x0666) +const float MAX_COUNT = 14745.6; // 14745 counts (90% of 2^14 counts or 0x3999) + +void HONEYWELLABPSensor::setup() { + ESP_LOGD(TAG, "Setting up Honeywell ABP Sensor "); + this->spi_setup(); +} + +uint8_t HONEYWELLABPSensor::readsensor_() { + // Polls the sensor for new data. + // transfer 4 bytes (the last two are temperature only used by some sensors) + this->enable(); + buf_[0] = this->read_byte(); + buf_[1] = this->read_byte(); + buf_[2] = this->read_byte(); + buf_[3] = this->read_byte(); + this->disable(); + + // Check the status codes: + // status = 0 : normal operation + // status = 1 : device in command mode + // status = 2 : stale data + // status = 3 : diagnostic condition + status_ = buf_[0] >> 6 & 0x3; + ESP_LOGV(TAG, "Sensor status %d", status_); + + // if device is normal and there is new data, bitmask and save the raw data + if (status_ == 0) { + // 14 - bit pressure is the last 6 bits of byte 0 (high bits) & all of byte 1 (lowest 8 bits) + pressure_count_ = ((uint16_t)(buf_[0]) << 8 & 0x3F00) | ((uint16_t)(buf_[1]) & 0xFF); + // 11 - bit temperature is all of byte 2 (lowest 8 bits) and the first three bits of byte 3 + temperature_count_ = (((uint16_t)(buf_[2]) << 3) & 0x7F8) | (((uint16_t)(buf_[3]) >> 5) & 0x7); + ESP_LOGV(TAG, "Sensor pressure_count_ %d", pressure_count_); + ESP_LOGV(TAG, "Sensor temperature_count_ %d", temperature_count_); + } + return status_; +} + +// returns status +uint8_t HONEYWELLABPSensor::readstatus_() { return status_; } + +// The pressure value from the most recent reading in raw counts +int HONEYWELLABPSensor::rawpressure_() { return pressure_count_; } + +// The temperature value from the most recent reading in raw counts +int HONEYWELLABPSensor::rawtemperature_() { return temperature_count_; } + +// Converts a digital pressure measurement in counts to pressure measured +float HONEYWELLABPSensor::countstopressure_(const int counts, const float min_pressure, const float max_pressure) { + return ((((float) counts - MIN_COUNT) * (max_pressure - min_pressure)) / (MAX_COUNT - MIN_COUNT)) + min_pressure; +} + +// Converts a digital temperature measurement in counts to temperature in C +// This will be invalid if sensore daoes not have temperature measurement capability +float HONEYWELLABPSensor::countstotemperatures_(const int counts) { return (((float) counts / 2047.0) * 200.0) - 50.0; } + +// Pressure value from the most recent reading in units +float HONEYWELLABPSensor::read_pressure_() { + return countstopressure_(pressure_count_, honeywellabp_min_pressure_, honeywellabp_max_pressure_); +} + +// Temperature value from the most recent reading in degrees C +float HONEYWELLABPSensor::read_temperature_() { return countstotemperatures_(temperature_count_); } + +void HONEYWELLABPSensor::update() { + ESP_LOGV(TAG, "Update Honeywell ABP Sensor"); + if (readsensor_() == 0) { + if (this->pressure_sensor_ != nullptr) + this->pressure_sensor_->publish_state(read_pressure_() * 1.0); + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(read_temperature_() * 1.0); + } +} + +float HONEYWELLABPSensor::get_setup_priority() const { return setup_priority::LATE; } + +void HONEYWELLABPSensor::dump_config() { + // LOG_SENSOR("", "HONEYWELLABP", this); + LOG_PIN(" CS Pin: ", this->cs_); + ESP_LOGCONFIG(TAG, " Min Pressure Range: %0.1f", honeywellabp_min_pressure_); + ESP_LOGCONFIG(TAG, " Max Pressure Range: %0.1f", honeywellabp_max_pressure_); + LOG_UPDATE_INTERVAL(this); +} + +void HONEYWELLABPSensor::set_honeywellabp_min_pressure(float min_pressure) { + this->honeywellabp_min_pressure_ = min_pressure; +} + +void HONEYWELLABPSensor::set_honeywellabp_max_pressure(float max_pressure) { + this->honeywellabp_max_pressure_ = max_pressure; +} + +} // namespace honeywellabp +} // namespace esphome diff --git a/esphome/components/honeywellabp/honeywellabp.h b/esphome/components/honeywellabp/honeywellabp.h new file mode 100644 index 0000000000..44d5952ca6 --- /dev/null +++ b/esphome/components/honeywellabp/honeywellabp.h @@ -0,0 +1,45 @@ +// for Honeywell ABP sensor +// adapting code from https://github.com/vwls/Honeywell_pressure_sensors +#pragma once + +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/spi/spi.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace honeywellabp { + +class HONEYWELLABPSensor : public PollingComponent, + public spi::SPIDevice { + public: + void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void setup() override; + void update() override; + float get_setup_priority() const override; + void dump_config() override; + void set_honeywellabp_min_pressure(float min_pressure); + void set_honeywellabp_max_pressure(float max_pressure); + + protected: + float honeywellabp_min_pressure_ = 0.0; + float honeywellabp_max_pressure_ = 0.0; + uint8_t buf_[4]; // buffer to hold sensor data + uint8_t status_ = 0; // byte to hold status information. + int pressure_count_ = 0; // hold raw pressure data (14 - bit, 0 - 16384) + int temperature_count_ = 0; // hold raw temperature data (11 - bit, 0 - 2048) + sensor::Sensor *pressure_sensor_; + sensor::Sensor *temperature_sensor_; + uint8_t readsensor_(); + uint8_t readstatus_(); + int rawpressure_(); + int rawtemperature_(); + float countstopressure_(int counts, float min_pressure, float max_pressure); + float countstotemperatures_(int counts); + float read_pressure_(); + float read_temperature_(); +}; + +} // namespace honeywellabp +} // namespace esphome diff --git a/esphome/components/honeywellabp/sensor.py b/esphome/components/honeywellabp/sensor.py new file mode 100644 index 0000000000..720a96b93c --- /dev/null +++ b/esphome/components/honeywellabp/sensor.py @@ -0,0 +1,70 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.components import spi +from esphome.const import ( + CONF_ID, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, +) + +DEPENDENCIES = ["spi"] +CODEOWNERS = ["@RubyBailey"] + +CONF_MIN_PRESSURE = "min_pressure" +CONF_MAX_PRESSURE = "max_pressure" + +honeywellabp_ns = cg.esphome_ns.namespace("honeywellabp") +HONEYWELLABPSensor = honeywellabp_ns.class_( + "HONEYWELLABPSensor", sensor.Sensor, cg.PollingComponent, spi.SPIDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HONEYWELLABPSensor), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement="psi", + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Required(CONF_MIN_PRESSURE): cv.float_, + cv.Required(CONF_MAX_PRESSURE): cv.float_, + } + ), + 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.polling_component_schema("60s")) + .extend(spi.spi_device_schema(cs_pin_required=True)) +) + + +async def to_code(config): + + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await spi.register_spi_device(var, config) + + if CONF_PRESSURE in config: + conf = config[CONF_PRESSURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_pressure_sensor(sens)) + cg.add(var.set_honeywellabp_min_pressure(conf[CONF_MIN_PRESSURE])) + cg.add(var.set_honeywellabp_max_pressure(conf[CONF_MAX_PRESSURE])) + + if CONF_TEMPERATURE in config: + conf = config[CONF_TEMPERATURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_temperature_sensor(sens)) diff --git a/tests/test1.yaml b/tests/test1.yaml index 65169e2fd9..8cd01f1d6f 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -600,6 +600,14 @@ sensor: oversampling: 8x update_interval: 15s i2c_id: i2c_bus + - platform: honeywellabp + pressure: + name: "Honeywell pressure" + min_pressure: 0 + max_pressure: 15 + temperature: + name: "Honeywell temperature" + cs_pin: GPIO5 - platform: qmc5883l address: 0x0D field_strength_x: From bf60e40d0bda314bccf1dd7c870ef9af6af87e56 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 23 Feb 2022 11:05:53 +1300 Subject: [PATCH 156/238] Add optional display page for touchscreen binary sensors (#3247) --- esphome/components/touchscreen/__init__.py | 2 ++ esphome/components/touchscreen/binary_sensor/__init__.py | 8 +++++++- .../binary_sensor/touchscreen_binary_sensor.cpp | 4 ++++ .../touchscreen/binary_sensor/touchscreen_binary_sensor.h | 4 ++++ esphome/components/touchscreen/touchscreen.h | 1 + 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/esphome/components/touchscreen/__init__.py b/esphome/components/touchscreen/__init__.py index 8246b95187..125103e2b8 100644 --- a/esphome/components/touchscreen/__init__.py +++ b/esphome/components/touchscreen/__init__.py @@ -6,6 +6,8 @@ from esphome import automation from esphome.const import CONF_ON_TOUCH CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["display"] + IS_PLATFORM_COMPONENT = True touchscreen_ns = cg.esphome_ns.namespace("touchscreen") diff --git a/esphome/components/touchscreen/binary_sensor/__init__.py b/esphome/components/touchscreen/binary_sensor/__init__.py index 73cbf1df7e..800bc4c2a9 100644 --- a/esphome/components/touchscreen/binary_sensor/__init__.py +++ b/esphome/components/touchscreen/binary_sensor/__init__.py @@ -1,7 +1,8 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import binary_sensor +from esphome.components import binary_sensor, display +from esphome.const import CONF_PAGE_ID from .. import touchscreen_ns, CONF_TOUCHSCREEN_ID, Touchscreen, TouchListener @@ -41,6 +42,7 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_X_MAX): cv.int_range(min=0, max=2000), cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=2000), cv.Required(CONF_Y_MAX): cv.int_range(min=0, max=2000), + cv.Optional(CONF_PAGE_ID): cv.use_id(display.DisplayPage), } ) .extend(cv.COMPONENT_SCHEMA), @@ -61,3 +63,7 @@ async def to_code(config): config[CONF_Y_MAX], ) ) + + if CONF_PAGE_ID in config: + page = await cg.get_variable(config[CONF_PAGE_ID]) + cg.add(var.set_page(page)) diff --git a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp index ba12aeeae0..583392cce3 100644 --- a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp +++ b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp @@ -6,6 +6,10 @@ namespace touchscreen { void TouchscreenBinarySensor::touch(TouchPoint tp) { bool touched = (tp.x >= this->x_min_ && tp.x <= this->x_max_ && tp.y >= this->y_min_ && tp.y <= this->y_max_); + if (this->page_ != nullptr) { + touched &= this->page_ == this->parent_->get_display()->get_active_page(); + } + if (touched) { this->publish_state(true); } else { diff --git a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h index 7b8cac5c4c..d7e53962e2 100644 --- a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h +++ b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/display/display_buffer.h" #include "esphome/components/touchscreen/touchscreen.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" @@ -23,11 +24,14 @@ class TouchscreenBinarySensor : public binary_sensor::BinarySensor, this->y_max_ = y_max; } + void set_page(display::DisplayPage *page) { this->page_ = page; } + void touch(TouchPoint tp) override; void release() override; protected: int16_t x_min_, x_max_, y_min_, y_max_; + display::DisplayPage *page_{nullptr}; }; } // namespace touchscreen diff --git a/esphome/components/touchscreen/touchscreen.h b/esphome/components/touchscreen/touchscreen.h index 2c0ec9e268..0597759894 100644 --- a/esphome/components/touchscreen/touchscreen.h +++ b/esphome/components/touchscreen/touchscreen.h @@ -37,6 +37,7 @@ class Touchscreen { this->display_height_ = display->get_height_internal(); this->rotation_ = static_cast(display->get_rotation()); } + display::DisplayBuffer *get_display() const { return this->display_; } Trigger *get_touch_trigger() { return &this->touch_trigger_; } From 1d2e0f74ead0aadd9654d73039ee5ef4dd4af708 Mon Sep 17 00:00:00 2001 From: Sean Brogan Date: Mon, 28 Feb 2022 14:30:33 -0800 Subject: [PATCH 157/238] Add Mopeka BLE and Mopeka Pro Check BLE Sensor (#2618) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 2 + esphome/components/mopeka_ble/__init__.py | 23 +++ esphome/components/mopeka_ble/mopeka_ble.cpp | 50 +++++++ esphome/components/mopeka_ble/mopeka_ble.h | 22 +++ .../components/mopeka_pro_check/__init__.py | 1 + .../mopeka_pro_check/mopeka_pro_check.cpp | 136 ++++++++++++++++++ .../mopeka_pro_check/mopeka_pro_check.h | 58 ++++++++ esphome/components/mopeka_pro_check/sensor.py | 131 +++++++++++++++++ tests/test2.yaml | 14 ++ 9 files changed, 437 insertions(+) create mode 100644 esphome/components/mopeka_ble/__init__.py create mode 100644 esphome/components/mopeka_ble/mopeka_ble.cpp create mode 100644 esphome/components/mopeka_ble/mopeka_ble.h create mode 100644 esphome/components/mopeka_pro_check/__init__.py create mode 100644 esphome/components/mopeka_pro_check/mopeka_pro_check.cpp create mode 100644 esphome/components/mopeka_pro_check/mopeka_pro_check.h create mode 100644 esphome/components/mopeka_pro_check/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 6e3801d1f3..c111fa7816 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -126,6 +126,8 @@ esphome/components/modbus_controller/select/* @martgras @stegm esphome/components/modbus_controller/sensor/* @martgras esphome/components/modbus_controller/switch/* @martgras esphome/components/modbus_controller/text_sensor/* @martgras +esphome/components/mopeka_ble/* @spbrogan +esphome/components/mopeka_pro_check/* @spbrogan esphome/components/mpu6886/* @fabaff esphome/components/network/* @esphome/core esphome/components/nextion/* @senexcrenshaw diff --git a/esphome/components/mopeka_ble/__init__.py b/esphome/components/mopeka_ble/__init__.py new file mode 100644 index 0000000000..47396435a8 --- /dev/null +++ b/esphome/components/mopeka_ble/__init__.py @@ -0,0 +1,23 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import esp32_ble_tracker +from esphome.const import CONF_ID + +CODEOWNERS = ["@spbrogan"] +DEPENDENCIES = ["esp32_ble_tracker"] + +mopeka_ble_ns = cg.esphome_ns.namespace("mopeka_ble") +MopekaListener = mopeka_ble_ns.class_( + "MopekaListener", esp32_ble_tracker.ESPBTDeviceListener +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(MopekaListener), + } +).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await esp32_ble_tracker.register_ble_device(var, config) diff --git a/esphome/components/mopeka_ble/mopeka_ble.cpp b/esphome/components/mopeka_ble/mopeka_ble.cpp new file mode 100644 index 0000000000..844d3a7dfd --- /dev/null +++ b/esphome/components/mopeka_ble/mopeka_ble.cpp @@ -0,0 +1,50 @@ +#include "mopeka_ble.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace mopeka_ble { + +static const char *const TAG = "mopeka_ble"; +static const uint8_t MANUFACTURER_DATA_LENGTH = 10; +static const uint16_t MANUFACTURER_ID = 0x0059; + +/** + * Parse all incoming BLE payloads to see if it is a Mopeka BLE advertisement. + * Currently this supports the following products: + * + * Mopeka Pro Check. + * If the sync button is pressed, report the MAC so a user can add this as a sensor. + */ + +bool MopekaListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + const auto &manu_datas = device.get_manufacturer_datas(); + + if (manu_datas.size() != 1) { + return false; + } + + const auto &manu_data = manu_datas[0]; + + if (manu_data.data.size() != MANUFACTURER_DATA_LENGTH) { + return false; + } + + if (manu_data.uuid != esp32_ble_tracker::ESPBTUUID::from_uint16(MANUFACTURER_ID)) { + return false; + } + + if (this->parse_sync_button_(manu_data.data)) { + // button pressed + ESP_LOGI(TAG, "SENSOR FOUND: %s", device.address_str().c_str()); + } + return false; +} + +bool MopekaListener::parse_sync_button_(const std::vector &message) { return (message[2] & 0x80) != 0; } + +} // namespace mopeka_ble +} // namespace esphome + +#endif diff --git a/esphome/components/mopeka_ble/mopeka_ble.h b/esphome/components/mopeka_ble/mopeka_ble.h new file mode 100644 index 0000000000..7b797a3bbe --- /dev/null +++ b/esphome/components/mopeka_ble/mopeka_ble.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace mopeka_ble { + +class MopekaListener : public esp32_ble_tracker::ESPBTDeviceListener { + public: + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + + protected: + bool parse_sync_button_(const std::vector &message); +}; + +} // namespace mopeka_ble +} // namespace esphome + +#endif diff --git a/esphome/components/mopeka_pro_check/__init__.py b/esphome/components/mopeka_pro_check/__init__.py new file mode 100644 index 0000000000..c57f60f521 --- /dev/null +++ b/esphome/components/mopeka_pro_check/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@spbrogan"] diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp new file mode 100644 index 0000000000..bcfe0a80ce --- /dev/null +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp @@ -0,0 +1,136 @@ +#include "mopeka_pro_check.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace mopeka_pro_check { + +static const char *const TAG = "mopeka_pro_check"; +static const uint8_t MANUFACTURER_DATA_LENGTH = 10; +static const uint16_t MANUFACTURER_ID = 0x0059; +static const double MOPEKA_LPG_COEF[] = {0.573045, -0.002822, -0.00000535}; // Magic numbers provided by Mopeka + +void MopekaProCheck::dump_config() { + ESP_LOGCONFIG(TAG, "Mopeka Pro Check"); + LOG_SENSOR(" ", "Level", this->level_); + LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "Battery Level", this->battery_level_); + LOG_SENSOR(" ", "Reading Distance", this->distance_); +} + +/** + * Main parse function that gets called for all ble advertisements. + * Check if advertisement is for our sensor and if so decode it and + * update the sensor state data. + */ +bool MopekaProCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + if (device.address_uint64() != this->address_) { + return false; + } + + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + + const auto &manu_datas = device.get_manufacturer_datas(); + + if (manu_datas.size() != 1) { + ESP_LOGE(TAG, "Unexpected manu_datas size (%d)", manu_datas.size()); + return false; + } + + const auto &manu_data = manu_datas[0]; + + ESP_LOGVV(TAG, "Manufacturer data:"); + for (const uint8_t byte : manu_data.data) { + ESP_LOGVV(TAG, "0x%02x", byte); + } + + if (manu_data.data.size() != MANUFACTURER_DATA_LENGTH) { + ESP_LOGE(TAG, "Unexpected manu_data size (%d)", manu_data.data.size()); + return false; + } + + // Now parse the data - See Datasheet for definition + + if (static_cast(manu_data.data[0]) != STANDARD_BOTTOM_UP) { + ESP_LOGE(TAG, "Unsupported Sensor Type (0x%X)", manu_data.data[0]); + return false; + } + + // Get battery level first + if (this->battery_level_ != nullptr) { + uint8_t level = this->parse_battery_level_(manu_data.data); + this->battery_level_->publish_state(level); + } + + // Get distance and level if either are sensors + if ((this->distance_ != nullptr) || (this->level_ != nullptr)) { + uint32_t distance_value = this->parse_distance_(manu_data.data); + SensorReadQuality quality_value = this->parse_read_quality_(manu_data.data); + ESP_LOGD(TAG, "Distance Sensor: Quality (0x%X) Distance (%dmm)", quality_value, distance_value); + if (quality_value < QUALITY_HIGH) { + ESP_LOGW(TAG, "Poor read quality."); + } + if (quality_value < QUALITY_MED) { + // if really bad reading set to 0 + ESP_LOGW(TAG, "Setting distance to 0"); + distance_value = 0; + } + + // update distance sensor + if (this->distance_ != nullptr) { + this->distance_->publish_state(distance_value); + } + + // update level sensor + if (this->level_ != nullptr) { + uint8_t tank_level = 0; + if (distance_value >= this->full_mm_) { + tank_level = 100; // cap at 100% + } else if (distance_value > this->empty_mm_) { + tank_level = ((100.0f / (this->full_mm_ - this->empty_mm_)) * (distance_value - this->empty_mm_)); + } + this->level_->publish_state(tank_level); + } + } + + // Get temperature of sensor + if (this->temperature_ != nullptr) { + uint8_t temp_in_c = this->parse_temperature_(manu_data.data); + this->temperature_->publish_state(temp_in_c); + } + + return true; +} + +uint8_t MopekaProCheck::parse_battery_level_(const std::vector &message) { + float v = (float) ((message[1] & 0x7F) / 32.0f); + // convert voltage and scale for CR2032 + float percent = (v - 2.2f) / 0.65f * 100.0f; + if (percent < 0.0f) { + return 0; + } + if (percent > 100.0f) { + return 100; + } + return (uint8_t) percent; +} + +uint32_t MopekaProCheck::parse_distance_(const std::vector &message) { + uint16_t raw = (message[4] * 256) + message[3]; + double raw_level = raw & 0x3FFF; + double raw_t = (message[2] & 0x7F); + + return (uint32_t)(raw_level * (MOPEKA_LPG_COEF[0] + MOPEKA_LPG_COEF[1] * raw_t + MOPEKA_LPG_COEF[2] * raw_t * raw_t)); +} + +uint8_t MopekaProCheck::parse_temperature_(const std::vector &message) { return (message[2] & 0x7F) - 40; } + +SensorReadQuality MopekaProCheck::parse_read_quality_(const std::vector &message) { + return static_cast(message[4] >> 6); +} + +} // namespace mopeka_pro_check +} // namespace esphome + +#endif diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.h b/esphome/components/mopeka_pro_check/mopeka_pro_check.h new file mode 100644 index 0000000000..59d33f7763 --- /dev/null +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.h @@ -0,0 +1,58 @@ +#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 mopeka_pro_check { + +enum SensorType { + STANDARD_BOTTOM_UP = 0x03, + TOP_DOWN_AIR_ABOVE = 0x04, + BOTTOM_UP_WATER = 0x05 + // all other values are reserved +}; + +// Sensor read quality. If sensor is poorly placed or tank level +// gets too low the read quality will show and the distanace +// measurement may be inaccurate. +enum SensorReadQuality { QUALITY_HIGH = 0x3, QUALITY_MED = 0x2, QUALITY_LOW = 0x1, QUALITY_NONE = 0x0 }; + +class MopekaProCheck : 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_level(sensor::Sensor *level) { level_ = level; }; + void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }; + void set_battery_level(sensor::Sensor *bat) { battery_level_ = bat; }; + void set_distance(sensor::Sensor *distance) { distance_ = distance; }; + void set_tank_full(float full) { full_mm_ = full; }; + void set_tank_empty(float empty) { empty_mm_ = empty; }; + + protected: + uint64_t address_; + sensor::Sensor *level_{nullptr}; + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *distance_{nullptr}; + sensor::Sensor *battery_level_{nullptr}; + + uint32_t full_mm_; + uint32_t empty_mm_; + + uint8_t parse_battery_level_(const std::vector &message); + uint32_t parse_distance_(const std::vector &message); + uint8_t parse_temperature_(const std::vector &message); + SensorReadQuality parse_read_quality_(const std::vector &message); +}; + +} // namespace mopeka_pro_check +} // namespace esphome + +#endif diff --git a/esphome/components/mopeka_pro_check/sensor.py b/esphome/components/mopeka_pro_check/sensor.py new file mode 100644 index 0000000000..4cd90227ab --- /dev/null +++ b/esphome/components/mopeka_pro_check/sensor.py @@ -0,0 +1,131 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, esp32_ble_tracker +from esphome.const import ( + CONF_DISTANCE, + CONF_MAC_ADDRESS, + CONF_ID, + ICON_THERMOMETER, + ICON_RULER, + UNIT_PERCENT, + CONF_LEVEL, + CONF_TEMPERATURE, + DEVICE_CLASS_TEMPERATURE, + UNIT_CELSIUS, + STATE_CLASS_MEASUREMENT, + CONF_BATTERY_LEVEL, + DEVICE_CLASS_BATTERY, +) + +CONF_TANK_TYPE = "tank_type" +CONF_CUSTOM_DISTANCE_FULL = "custom_distance_full" +CONF_CUSTOM_DISTANCE_EMPTY = "custom_distance_empty" + +ICON_PROPANE_TANK = "mdi:propane-tank" + +TANK_TYPE_CUSTOM = "CUSTOM" + +UNIT_MILLIMETER = "mm" + + +def small_distance(value): + """small_distance is stored in mm""" + meters = cv.distance(value) + return meters * 1000 + + +# +# Map of standard tank types to their +# empty and full distance values. +# Format is - tank name: (empty distance in mm, full distance in mm) +# +CONF_SUPPORTED_TANKS_MAP = { + TANK_TYPE_CUSTOM: (0, 100), + "20LB_V": (38, 254), # empty/full readings for 20lb US tank + "30LB_V": (38, 381), + "40LB_V": (38, 508), +} + +CODEOWNERS = ["@spbrogan"] +DEPENDENCIES = ["esp32_ble_tracker"] + +mopeka_pro_check_ns = cg.esphome_ns.namespace("mopeka_pro_check") +MopekaProCheck = mopeka_pro_check_ns.class_( + "MopekaProCheck", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MopekaProCheck), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_CUSTOM_DISTANCE_FULL): small_distance, + cv.Optional(CONF_CUSTOM_DISTANCE_EMPTY): small_distance, + cv.Required(CONF_TANK_TYPE): cv.enum(CONF_SUPPORTED_TANKS_MAP, upper=True), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_LEVEL): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_PROPANE_TANK, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DISTANCE): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETER, + icon=ICON_RULER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + 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 config[CONF_TANK_TYPE] == TANK_TYPE_CUSTOM: + # Support custom tank min/max + if CONF_CUSTOM_DISTANCE_EMPTY in config: + cg.add(var.set_tank_empty(config[CONF_CUSTOM_DISTANCE_EMPTY])) + else: + cg.add(var.set_tank_empty(CONF_SUPPORTED_TANKS_MAP[TANK_TYPE_CUSTOM][0])) + if CONF_CUSTOM_DISTANCE_FULL in config: + cg.add(var.set_tank_full(config[CONF_CUSTOM_DISTANCE_FULL])) + else: + cg.add(var.set_tank_full(CONF_SUPPORTED_TANKS_MAP[TANK_TYPE_CUSTOM][1])) + else: + # Set the Tank empty and full based on map - User is requesting standard tank + t = config[CONF_TANK_TYPE] + cg.add(var.set_tank_empty(CONF_SUPPORTED_TANKS_MAP[t][0])) + cg.add(var.set_tank_full(CONF_SUPPORTED_TANKS_MAP[t][1])) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature(sens)) + if CONF_LEVEL in config: + sens = await sensor.new_sensor(config[CONF_LEVEL]) + cg.add(var.set_level(sens)) + if CONF_DISTANCE in config: + sens = await sensor.new_sensor(config[CONF_DISTANCE]) + cg.add(var.set_distance(sens)) + if CONF_BATTERY_LEVEL in config: + sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) + cg.add(var.set_battery_level(sens)) diff --git a/tests/test2.yaml b/tests/test2.yaml index 76b9775c54..ec3ccff70c 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -331,6 +331,19 @@ sensor: name: "RD200 Radon" radon_long_term: name: "RD200 Radon Long Term" + - platform: mopeka_pro_check + mac_address: D3:75:F2:DC:16:91 + tank_type: CUSTOM + custom_distance_full: 40cm + custom_distance_empty: 10mm + temperature: + name: "Propane test temp" + level: + name: "Propane test level" + distance: + name: "Propane test distance" + battery_level: + name: "Propane test battery level" time: - platform: homeassistant @@ -442,6 +455,7 @@ ruuvi_ble: xiaomi_ble: +mopeka_ble: #esp32_ble_beacon: # type: iBeacon From 38ff66debd37a6d41862d93d3176752bae129abf Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 2 Mar 2022 16:32:45 +1300 Subject: [PATCH 158/238] Remove stray define (#3260) --- esphome/components/mqtt/mqtt_client.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 148316672a..1fea0c80cc 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -1,5 +1,4 @@ #include "mqtt_client.h" -#define USE_MQTT #ifdef USE_MQTT From dc6eff83ea51e64c3c1cfc943288473718769ba0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 2 Mar 2022 16:44:35 +1300 Subject: [PATCH 159/238] Only get free memory size from internal (#3259) --- esphome/components/json/json_util.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 7e88fb6e59..9acba76597 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -22,7 +22,7 @@ std::string build_json(const json_build_t &f) { #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; + const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) - 2048; #endif DynamicJsonDocument json_document(free_heap); @@ -42,7 +42,7 @@ void parse_json(const std::string &data, const json_parse_t &f) { #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; + const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) - 2048; #endif DynamicJsonDocument json_document(free_heap); From 3b8ca809003a9e0249c1c92ae6a9158f7ed430a9 Mon Sep 17 00:00:00 2001 From: wilberforce Date: Tue, 8 Mar 2022 15:02:24 +1300 Subject: [PATCH 160/238] Webserver v2 (#2688) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/climate/climate_traits.h | 2 +- esphome/components/web_server/__init__.py | 45 +- esphome/components/web_server/server_index.h | 573 +++++++++++++++++++ esphome/components/web_server/web_server.cpp | 439 ++++++++++---- esphome/components/web_server/web_server.h | 38 +- tests/test1.yaml | 4 +- 6 files changed, 975 insertions(+), 126 deletions(-) create mode 100644 esphome/components/web_server/server_index.h diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index d113510eeb..3ec51bc3c2 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -141,7 +141,7 @@ class ClimateTraits { } bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); } bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); } - std::set get_supported_swing_modes() { return supported_swing_modes_; } + std::set get_supported_swing_modes() const { return supported_swing_modes_; } float get_visual_min_temperature() const { return visual_min_temperature_; } void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; } diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index eaa20ccbb4..42683c8d77 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -14,6 +14,8 @@ from esphome.const import ( CONF_PASSWORD, CONF_INCLUDE_INTERNAL, CONF_OTA, + CONF_VERSION, + CONF_LOCAL, ) from esphome.core import CORE, coroutine_with_priority @@ -22,18 +24,37 @@ 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) + +def default_url(config): + config = config.copy() + if config[CONF_VERSION] == 1: + if not (CONF_CSS_URL in config): + config[CONF_CSS_URL] = "https://esphome.io/_static/webserver-v1.min.css" + if not (CONF_JS_URL in config): + config[CONF_JS_URL] = "https://esphome.io/_static/webserver-v1.min.js" + if config[CONF_VERSION] == 2: + if not (CONF_CSS_URL in config): + config[CONF_CSS_URL] = "" + if not (CONF_JS_URL in config): + config[CONF_JS_URL] = "https://oi.esphome.io/v2/www.js" + return config + + +def validate_local(config): + if CONF_LOCAL in config and config[CONF_VERSION] == 1: + raise cv.Invalid("'local' is not supported in version 1") + return config + + 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_VERSION, default=2): cv.one_of(1, 2), + cv.Optional(CONF_CSS_URL): 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_URL): cv.string, cv.Optional(CONF_JS_INCLUDE): cv.file_, cv.Optional(CONF_AUTH): cv.Schema( { @@ -50,9 +71,12 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, cv.Optional(CONF_OTA, default=True): cv.boolean, - }, + cv.Optional(CONF_LOCAL): cv.boolean, + } ).extend(cv.COMPONENT_SCHEMA), cv.only_with_arduino, + default_url, + validate_local, ) @@ -68,6 +92,7 @@ async def to_code(config): cg.add(paren.set_port(config[CONF_PORT])) cg.add_define("USE_WEBSERVER") cg.add_define("USE_WEBSERVER_PORT", config[CONF_PORT]) + cg.add_define("USE_WEBSERVER_VERSION", config[CONF_VERSION]) 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])) @@ -75,13 +100,15 @@ async def to_code(config): 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") + cg.add_define("USE_WEBSERVER_CSS_INCLUDE") path = CORE.relative_config_path(config[CONF_CSS_INCLUDE]) with open(file=path, encoding="utf-8") as myfile: cg.add(var.set_css_include(myfile.read())) if CONF_JS_INCLUDE in config: - cg.add_define("WEBSERVER_JS_INCLUDE") + cg.add_define("USE_WEBSERVER_JS_INCLUDE") path = CORE.relative_config_path(config[CONF_JS_INCLUDE]) with open(file=path, encoding="utf-8") as myfile: cg.add(var.set_js_include(myfile.read())) cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL])) + if CONF_LOCAL in config and config[CONF_LOCAL]: + cg.add_define("USE_WEBSERVER_LOCAL") diff --git a/esphome/components/web_server/server_index.h b/esphome/components/web_server/server_index.h new file mode 100644 index 0000000000..719a804d0c --- /dev/null +++ b/esphome/components/web_server/server_index.h @@ -0,0 +1,573 @@ +#pragma once +// Generated from https://github.com/esphome/esphome-webserver +#include "esphome/core/hal.h" +namespace esphome { + +namespace web_server { + +const uint8_t INDEX_GZ[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xbd, 0x7d, 0xd9, 0x76, 0xe3, 0xc6, 0x92, 0xe0, 0xf3, + 0x9c, 0x33, 0x7f, 0x30, 0x2f, 0x10, 0xac, 0x56, 0x01, 0x16, 0x08, 0x91, 0x54, 0x6d, 0x06, 0x05, 0xf2, 0xca, 0x55, + 0xe5, 0x5b, 0x65, 0xd7, 0xe6, 0x92, 0xaa, 0xbc, 0xc8, 0x74, 0x09, 0x22, 0x93, 0x22, 0x5c, 0x20, 0x40, 0x03, 0x49, + 0x2d, 0xa6, 0xd0, 0xa7, 0x9f, 0xfa, 0x69, 0xce, 0x99, 0xf5, 0xa1, 0x5f, 0xe6, 0xf4, 0xcb, 0x7c, 0xc4, 0x7c, 0xcf, + 0xfd, 0x81, 0xe9, 0x4f, 0x98, 0x88, 0xc8, 0x05, 0x09, 0x90, 0x5a, 0xec, 0xf6, 0xdc, 0x53, 0x8b, 0x80, 0x5c, 0x23, + 0x23, 0x23, 0x63, 0x4f, 0x68, 0x6f, 0x63, 0x9c, 0x8d, 0xf8, 0xe5, 0x9c, 0x59, 0x53, 0x3e, 0x4b, 0xfa, 0x7b, 0xf2, + 0x7f, 0x16, 0x8d, 0xfb, 0x7b, 0x49, 0x9c, 0x7e, 0xb2, 0x72, 0x96, 0x84, 0xf1, 0x28, 0x4b, 0xad, 0x69, 0xce, 0x26, + 0xe1, 0x38, 0xe2, 0x51, 0x10, 0xcf, 0xa2, 0x53, 0x66, 0xed, 0xf4, 0xf7, 0x66, 0x8c, 0x47, 0xd6, 0x68, 0x1a, 0xe5, + 0x05, 0xe3, 0xe1, 0xfb, 0xc3, 0xaf, 0x5a, 0x8f, 0xfb, 0x7b, 0xc5, 0x28, 0x8f, 0xe7, 0xdc, 0xc2, 0x21, 0xc3, 0x59, + 0x36, 0x5e, 0x24, 0xac, 0x7f, 0x16, 0xe5, 0xd6, 0x3e, 0x0b, 0xdf, 0x9c, 0xfc, 0xc2, 0x46, 0xdc, 0x1f, 0xb3, 0x49, + 0x9c, 0xb2, 0xb7, 0x79, 0x36, 0x67, 0x39, 0xbf, 0xf4, 0x2e, 0xd6, 0x57, 0xc4, 0xac, 0xf0, 0x9e, 0xe9, 0xaa, 0x53, + 0xc6, 0xdf, 0x9c, 0xa7, 0xaa, 0xcf, 0x53, 0x26, 0x26, 0xc9, 0xf2, 0xc2, 0xe3, 0xd7, 0xb4, 0x39, 0xb8, 0x9c, 0x9d, + 0x64, 0x49, 0xe1, 0x1d, 0xe8, 0xfa, 0x79, 0x9e, 0xf1, 0x0c, 0xc1, 0xf2, 0xa7, 0x51, 0x61, 0xb4, 0xf4, 0x3e, 0xad, + 0x69, 0x32, 0x97, 0x95, 0x2f, 0x8a, 0x67, 0xe9, 0x62, 0xc6, 0xf2, 0xe8, 0x24, 0x61, 0x5e, 0xc1, 0x42, 0x87, 0x79, + 0xdc, 0x8b, 0xdd, 0xb0, 0xcf, 0xad, 0x38, 0xb5, 0xd8, 0x60, 0x9f, 0x51, 0xc9, 0x92, 0xe9, 0x56, 0xc1, 0x46, 0xdb, + 0x03, 0x74, 0x4d, 0xe2, 0xd3, 0x85, 0x7e, 0x3f, 0xcf, 0x63, 0xae, 0x9e, 0xcf, 0xa2, 0x64, 0xc1, 0x82, 0xb8, 0x74, + 0x03, 0x76, 0xc4, 0x87, 0x61, 0xec, 0x3d, 0xa1, 0x41, 0x61, 0xc8, 0xe5, 0x24, 0xcb, 0x1d, 0xc4, 0x55, 0x8c, 0x63, + 0xf3, 0xab, 0x2b, 0x87, 0x87, 0xcb, 0xd2, 0x75, 0x0f, 0x98, 0x3f, 0x8a, 0x92, 0xc4, 0xc1, 0x89, 0xb7, 0xb6, 0x0a, + 0x9c, 0x31, 0xf6, 0xf8, 0x51, 0x3c, 0x74, 0x7b, 0xf1, 0xc4, 0xe1, 0xcc, 0xad, 0xfa, 0x65, 0x13, 0x8b, 0x33, 0x87, + 0xbb, 0xee, 0xa7, 0xeb, 0xfb, 0xe4, 0x8c, 0x2f, 0x72, 0x80, 0xbd, 0xf4, 0xde, 0xa8, 0x99, 0x2f, 0xb0, 0xfe, 0x19, + 0x75, 0xec, 0x01, 0xec, 0x05, 0xb7, 0x3e, 0x84, 0xe7, 0x71, 0x3a, 0xce, 0xce, 0xfd, 0x83, 0x69, 0x04, 0x3f, 0xde, + 0x65, 0x19, 0xdf, 0xda, 0x72, 0xce, 0xb2, 0x78, 0x6c, 0xb5, 0xc3, 0xd0, 0xac, 0xbc, 0x7c, 0x72, 0x70, 0x70, 0x75, + 0xd5, 0x28, 0xf0, 0xd3, 0x88, 0xc7, 0x67, 0x4c, 0x74, 0x06, 0x00, 0x6c, 0xf8, 0x39, 0xe7, 0x6c, 0x7c, 0xc0, 0x2f, + 0x13, 0x28, 0x65, 0x8c, 0x17, 0x36, 0xac, 0xf1, 0x69, 0x36, 0x02, 0xb4, 0xa5, 0x06, 0xe2, 0xa1, 0x69, 0xce, 0xe6, + 0x49, 0x34, 0x62, 0x58, 0x0f, 0x23, 0x55, 0x3d, 0xaa, 0x46, 0xde, 0x57, 0xa1, 0xd8, 0x5e, 0xc7, 0xf5, 0x62, 0x16, + 0xa6, 0xec, 0xdc, 0x7a, 0x15, 0xcd, 0x7b, 0xa3, 0x24, 0x2a, 0x0a, 0xa0, 0xd7, 0x25, 0x2d, 0x21, 0x5f, 0x8c, 0x80, + 0x40, 0x68, 0x81, 0x4b, 0x44, 0xd3, 0x34, 0x2e, 0xfc, 0x8f, 0x9b, 0xa3, 0xa2, 0x78, 0xc7, 0x8a, 0x45, 0xc2, 0x37, + 0x43, 0xd8, 0x0b, 0xbe, 0x11, 0x86, 0x5f, 0xb9, 0x7c, 0x9a, 0x67, 0xe7, 0xd6, 0xb3, 0x3c, 0x87, 0xe6, 0x36, 0x4c, + 0x29, 0x1a, 0x58, 0x71, 0x61, 0xa5, 0x19, 0xb7, 0xf4, 0x60, 0xb8, 0x81, 0xbe, 0xf5, 0xbe, 0x60, 0xd6, 0xf1, 0x22, + 0x2d, 0xa2, 0x09, 0x83, 0xa6, 0xc7, 0x56, 0x96, 0x5b, 0xc7, 0x30, 0xe8, 0x31, 0x6c, 0x59, 0xc1, 0xe1, 0xd4, 0xf8, + 0xb6, 0xdb, 0xa3, 0xb9, 0xa0, 0xf0, 0x90, 0x5d, 0xf0, 0x90, 0x95, 0x40, 0x98, 0x56, 0xa1, 0x97, 0xe1, 0xb8, 0xcb, + 0x04, 0x0a, 0x58, 0x18, 0x33, 0x24, 0x59, 0xc7, 0x6c, 0xac, 0x37, 0xe7, 0xc3, 0xd6, 0x96, 0xc6, 0x35, 0xe0, 0xc4, + 0x81, 0xb6, 0x45, 0xa3, 0xad, 0x27, 0x16, 0x5e, 0x43, 0x91, 0xeb, 0x31, 0x5f, 0xa2, 0xef, 0xe0, 0x32, 0x1d, 0xd5, + 0xc7, 0x86, 0xca, 0x92, 0x67, 0x07, 0x3c, 0x8f, 0xd3, 0x53, 0x00, 0x42, 0xce, 0x64, 0x36, 0x29, 0x4b, 0xb1, 0xf9, + 0x4f, 0x58, 0xc8, 0xc2, 0x3e, 0x8e, 0x9e, 0x33, 0xc7, 0x2e, 0xa8, 0x87, 0x1d, 0x86, 0x88, 0x7a, 0x20, 0x30, 0x36, + 0x60, 0x01, 0xdb, 0xb6, 0x6d, 0xef, 0x2b, 0xd7, 0x3b, 0x47, 0x0a, 0xf2, 0x7d, 0x9f, 0xc8, 0x57, 0x74, 0x8e, 0xc3, + 0x0e, 0x02, 0xed, 0x27, 0x2c, 0x3d, 0xe5, 0xd3, 0x01, 0x3b, 0x6a, 0x0f, 0x03, 0x0e, 0x50, 0x8d, 0x17, 0x23, 0xe6, + 0x20, 0x3d, 0x7a, 0x05, 0x1e, 0x9f, 0x6d, 0x07, 0xa6, 0xc0, 0x8d, 0xd9, 0xa0, 0x35, 0xd6, 0xb6, 0xc6, 0x55, 0x24, + 0xaa, 0x00, 0x43, 0x3a, 0xb7, 0xe1, 0x84, 0x9d, 0xb0, 0xdc, 0x80, 0x43, 0x37, 0xeb, 0xd5, 0x76, 0x70, 0x01, 0x3b, + 0x04, 0xfd, 0xac, 0xc9, 0x22, 0x1d, 0xf1, 0x18, 0x18, 0x97, 0xbd, 0x0d, 0xe0, 0x8a, 0x9d, 0xd3, 0x1b, 0x67, 0xbb, + 0xa5, 0xeb, 0xc4, 0xee, 0x36, 0x3b, 0x2a, 0xb6, 0x3b, 0x43, 0x0f, 0xa1, 0xd4, 0xc8, 0x97, 0x0b, 0x8f, 0x61, 0x81, + 0x70, 0x46, 0x98, 0x3e, 0x9e, 0x1f, 0x06, 0xcc, 0x5f, 0xa5, 0xe3, 0x90, 0xfb, 0xb3, 0x68, 0x8e, 0xab, 0x61, 0x44, + 0x03, 0x51, 0x3a, 0x42, 0xe8, 0x6a, 0xfb, 0x82, 0x18, 0xf3, 0x2b, 0x12, 0x70, 0x01, 0x21, 0x70, 0x66, 0x9f, 0x45, + 0xa3, 0x29, 0x1c, 0xf1, 0x0a, 0x71, 0x63, 0x75, 0x1c, 0x46, 0x39, 0x8b, 0x38, 0x7b, 0x96, 0x30, 0x7c, 0xc3, 0x1d, + 0x80, 0x9e, 0xb6, 0xeb, 0x15, 0xea, 0xdc, 0x25, 0x31, 0x7f, 0x9d, 0xc1, 0x3c, 0x3d, 0x41, 0x24, 0x40, 0xc5, 0xc5, + 0xd6, 0x56, 0x8c, 0x24, 0xb2, 0xcf, 0x61, 0xb7, 0x4e, 0x16, 0xc0, 0x04, 0xec, 0x14, 0x5b, 0xd8, 0x80, 0x6d, 0x2f, + 0xf6, 0x39, 0x20, 0xf1, 0x49, 0x96, 0x72, 0x18, 0x0e, 0xe0, 0xd5, 0x14, 0xe4, 0x47, 0xf3, 0x39, 0x4b, 0xc7, 0x4f, + 0xa6, 0x71, 0x32, 0x06, 0x6c, 0x94, 0xb0, 0xde, 0x8c, 0x85, 0xb0, 0x4e, 0x58, 0x4c, 0x70, 0xf3, 0x8a, 0x68, 0xfb, + 0x90, 0x90, 0x79, 0x68, 0xdb, 0x3d, 0xe4, 0x40, 0x72, 0x15, 0xc8, 0x83, 0x68, 0xe3, 0xde, 0x01, 0xeb, 0x2f, 0x5c, + 0xbe, 0x1d, 0xc6, 0x7a, 0x1b, 0x25, 0x82, 0x9f, 0x20, 0xa7, 0x01, 0xfc, 0x33, 0xe0, 0x81, 0x3d, 0x64, 0x5c, 0xdf, + 0x49, 0xae, 0x93, 0x32, 0xb5, 0x42, 0x40, 0xc0, 0x08, 0x39, 0x88, 0xc4, 0xc1, 0xdb, 0x2c, 0xb9, 0x9c, 0xc4, 0x49, + 0x72, 0xb0, 0x98, 0xcf, 0xb3, 0x9c, 0x7b, 0x5f, 0x87, 0x4b, 0x9e, 0x55, 0x6b, 0xa5, 0x43, 0x5e, 0x9c, 0xc7, 0x1c, + 0x11, 0xea, 0x2e, 0x47, 0x11, 0x6c, 0xf5, 0x97, 0x59, 0x96, 0xb0, 0x28, 0x85, 0x65, 0xb0, 0x81, 0x6d, 0x07, 0xe9, + 0x22, 0x49, 0x7a, 0x27, 0x30, 0xec, 0xa7, 0x1e, 0x55, 0x0b, 0x8e, 0x1f, 0xd0, 0xf3, 0x7e, 0x9e, 0x47, 0x97, 0xd0, + 0x10, 0xdb, 0x00, 0x2d, 0xc2, 0x6e, 0x7d, 0x7d, 0xf0, 0xe6, 0xb5, 0x2f, 0x08, 0x3f, 0x9e, 0x5c, 0x02, 0xa0, 0x65, + 0xc5, 0x35, 0x27, 0x79, 0x36, 0x6b, 0x4c, 0x8d, 0x78, 0x88, 0x43, 0xd6, 0xbb, 0x06, 0x84, 0x98, 0x46, 0x86, 0x5d, + 0x62, 0x26, 0x04, 0xaf, 0x89, 0x9e, 0x65, 0x25, 0x9e, 0x81, 0x01, 0x3e, 0x04, 0xa2, 0x18, 0xa6, 0xbc, 0x19, 0x5a, + 0x9e, 0x5f, 0x2e, 0xe3, 0x90, 0xe0, 0x9c, 0xa3, 0xfc, 0x45, 0x18, 0x47, 0x11, 0xcc, 0xbe, 0x14, 0x03, 0x96, 0x0a, + 0xe2, 0xb8, 0x2c, 0xbd, 0x44, 0x13, 0x31, 0x72, 0x3c, 0x64, 0x28, 0x1c, 0x8e, 0xd1, 0xd5, 0x15, 0x83, 0x17, 0xd7, + 0xfb, 0x26, 0x5c, 0x46, 0x6a, 0x3d, 0x28, 0xa1, 0xf0, 0x7c, 0x05, 0x82, 0x4f, 0xa0, 0x24, 0x3b, 0x03, 0x39, 0x08, + 0x70, 0x7e, 0xed, 0x81, 0xfc, 0x4f, 0x10, 0x8a, 0x8d, 0x8e, 0x07, 0x12, 0xf4, 0xc9, 0x34, 0x4a, 0x4f, 0xd9, 0x38, + 0x48, 0x58, 0x29, 0x39, 0xef, 0xbe, 0x05, 0x7b, 0x0c, 0xe4, 0x54, 0x58, 0xcf, 0x0f, 0x5f, 0xbd, 0x94, 0x3b, 0x57, + 0x63, 0xc6, 0xb0, 0x49, 0x0b, 0x10, 0xab, 0xc0, 0xb6, 0x25, 0x3b, 0x7e, 0xc6, 0x15, 0xf7, 0x16, 0x25, 0x71, 0xf1, + 0x7e, 0x0e, 0x2a, 0x06, 0x7b, 0x0b, 0xc3, 0xc0, 0xf4, 0x21, 0x4c, 0x45, 0xe5, 0x30, 0x9f, 0xa8, 0x18, 0xeb, 0x22, + 0xe8, 0x2c, 0x56, 0x2a, 0x5e, 0x33, 0xc7, 0x2d, 0x81, 0x54, 0x79, 0x3c, 0xb2, 0xa2, 0xf1, 0xf8, 0x45, 0x1a, 0xf3, + 0x38, 0x4a, 0xe2, 0xdf, 0x08, 0x93, 0x4b, 0xa4, 0x31, 0xde, 0x93, 0x9b, 0x00, 0x6b, 0xa7, 0x1e, 0x89, 0xab, 0x98, + 0xec, 0x06, 0x21, 0x43, 0x70, 0xcb, 0x24, 0x3c, 0x1a, 0x4a, 0xf0, 0x12, 0x7f, 0xbe, 0x28, 0xa6, 0x88, 0x58, 0x39, + 0x30, 0x32, 0xf2, 0xec, 0xa4, 0x60, 0xf9, 0x19, 0x1b, 0x6b, 0x0a, 0x28, 0x60, 0x55, 0xd4, 0x1c, 0x94, 0x17, 0x9a, + 0xd1, 0x51, 0x32, 0x94, 0xc1, 0x50, 0x3d, 0x93, 0xcd, 0x32, 0x49, 0xcc, 0x5a, 0xc3, 0xd1, 0x5c, 0xc0, 0x11, 0x4a, + 0x85, 0xe4, 0x04, 0x45, 0xa8, 0x56, 0x38, 0x05, 0x2e, 0x04, 0x52, 0xc1, 0x3c, 0xe6, 0x4a, 0x92, 0x3d, 0x5b, 0x90, + 0x48, 0x28, 0xa0, 0x23, 0x1c, 0x64, 0x82, 0xb4, 0x70, 0xe1, 0x54, 0x01, 0x97, 0x97, 0xe0, 0x0a, 0x2e, 0xa2, 0xd4, + 0x1c, 0x24, 0x80, 0xf0, 0x1b, 0x21, 0x0b, 0x7d, 0x6c, 0x41, 0x64, 0xe0, 0xeb, 0x9d, 0x07, 0xc4, 0xca, 0x75, 0x57, + 0x0b, 0xf1, 0xae, 0x01, 0x1b, 0x27, 0x46, 0x7a, 0xf2, 0x36, 0xb8, 0x9f, 0x66, 0xfb, 0xa3, 0x11, 0x2b, 0x8a, 0x2c, + 0xdf, 0xda, 0xda, 0xa0, 0xf6, 0xd7, 0x29, 0x5a, 0x80, 0x49, 0x57, 0xf3, 0x3a, 0xbb, 0x20, 0x09, 0x6e, 0x8a, 0x15, + 0x25, 0xd3, 0x03, 0xfb, 0xe3, 0x47, 0xe0, 0xd9, 0x9e, 0x44, 0x03, 0x60, 0x7d, 0x55, 0xf1, 0x13, 0xfa, 0x4c, 0x1d, + 0x33, 0x6b, 0xf5, 0x4b, 0xa7, 0x0e, 0x92, 0x07, 0xc3, 0xba, 0xa5, 0xb1, 0xa1, 0x6b, 0x87, 0xc6, 0xdd, 0x90, 0x02, + 0x72, 0x79, 0x4a, 0x22, 0xdb, 0xd8, 0x46, 0xd0, 0xda, 0x4a, 0x8f, 0x50, 0xaf, 0x56, 0x93, 0x13, 0xa0, 0x47, 0x6c, + 0xd8, 0x93, 0xf5, 0x61, 0x21, 0x30, 0x97, 0xb3, 0x5f, 0x17, 0xac, 0xe0, 0x82, 0x74, 0x61, 0xdc, 0x1c, 0xc6, 0x2d, + 0x57, 0xb4, 0xc3, 0x9a, 0xee, 0xb8, 0x0e, 0xb6, 0x37, 0x73, 0x94, 0x63, 0x05, 0x52, 0xf2, 0xcd, 0xe4, 0x84, 0xb0, + 0x32, 0xf7, 0xea, 0xea, 0x1b, 0x35, 0x48, 0xb5, 0x95, 0x5a, 0x07, 0x6a, 0xec, 0x89, 0xad, 0x9a, 0x8c, 0x6d, 0x57, + 0x0a, 0xd4, 0x8d, 0x4e, 0xaf, 0x46, 0x07, 0x70, 0xe6, 0xda, 0x9a, 0xa4, 0x2b, 0x65, 0xfb, 0xad, 0xc2, 0xe9, 0x1b, + 0x31, 0x32, 0x69, 0xa3, 0xec, 0x76, 0xea, 0x51, 0x27, 0x1e, 0xda, 0xae, 0xd4, 0x55, 0x8c, 0x61, 0x51, 0x67, 0x0c, + 0x4d, 0xa8, 0xe7, 0xba, 0x8b, 0xad, 0x89, 0x8a, 0x85, 0x6a, 0xaf, 0x95, 0x01, 0xc1, 0xc3, 0x23, 0x50, 0x4e, 0xd6, + 0xda, 0x07, 0xaf, 0xa3, 0x19, 0x43, 0x8c, 0x7a, 0xd7, 0x35, 0x90, 0x06, 0x04, 0x34, 0x19, 0x36, 0xc5, 0x1b, 0x77, + 0x85, 0xd6, 0x54, 0x3f, 0x5f, 0x31, 0x68, 0x11, 0xa0, 0x5f, 0x97, 0x6b, 0xb6, 0x88, 0xe4, 0xa6, 0x24, 0x67, 0x85, + 0x1f, 0x51, 0x26, 0xf6, 0x84, 0x04, 0x3c, 0x2c, 0x1e, 0xb6, 0xbf, 0xb1, 0x71, 0xb2, 0x15, 0x53, 0x6b, 0xe4, 0xc8, + 0x53, 0x00, 0xcf, 0x24, 0x04, 0x80, 0x5d, 0xd2, 0xcf, 0xda, 0xc1, 0x42, 0xb4, 0x1d, 0x20, 0x1d, 0xf8, 0x93, 0x24, + 0xe2, 0x4e, 0x67, 0xa7, 0xed, 0x02, 0x1d, 0x02, 0x13, 0x07, 0x19, 0x01, 0xea, 0x7d, 0xb5, 0x14, 0x86, 0x4b, 0x89, + 0x5d, 0xee, 0x83, 0x52, 0x34, 0x8d, 0x27, 0xdc, 0xc9, 0x50, 0x88, 0xb8, 0x25, 0x4b, 0x40, 0xc8, 0xe8, 0x73, 0x05, + 0x5c, 0x82, 0x0b, 0xee, 0x22, 0xaa, 0x35, 0x43, 0x53, 0x90, 0x12, 0x97, 0x22, 0x29, 0xa8, 0x20, 0x30, 0x98, 0x4a, + 0x4f, 0x51, 0x14, 0xc8, 0xb7, 0x78, 0x20, 0x06, 0x0d, 0x56, 0x34, 0xca, 0x78, 0x10, 0xaf, 0x16, 0x82, 0x18, 0xf6, + 0x79, 0xf6, 0x32, 0x3b, 0x67, 0xf9, 0x93, 0x08, 0x61, 0x0f, 0x44, 0xf7, 0x12, 0x38, 0x3d, 0x31, 0x74, 0xd6, 0x53, + 0xb4, 0x72, 0x46, 0x8b, 0x86, 0x8d, 0x98, 0xc5, 0x28, 0x08, 0x41, 0xca, 0x11, 0xee, 0x53, 0x3c, 0x52, 0x74, 0xf6, + 0x50, 0x94, 0x30, 0x4d, 0x5b, 0xfb, 0x2f, 0xeb, 0xb4, 0x05, 0x23, 0xcc, 0x15, 0xb5, 0xd6, 0x4f, 0xac, 0xeb, 0x49, + 0xd9, 0xec, 0x48, 0xda, 0x32, 0x84, 0x19, 0xc8, 0x8f, 0xab, 0xab, 0x4a, 0x49, 0x07, 0x61, 0xaa, 0xb9, 0x39, 0x6a, + 0x4e, 0xe2, 0x48, 0xb8, 0x25, 0x08, 0x23, 0x54, 0xbc, 0xf2, 0x2c, 0x49, 0x0c, 0x59, 0xe4, 0xc5, 0x3d, 0xa7, 0x21, + 0x8e, 0x00, 0x8a, 0x59, 0x4d, 0x22, 0x0d, 0x78, 0xa0, 0x2b, 0x50, 0x28, 0x29, 0x69, 0xe4, 0x55, 0x4d, 0x04, 0xc4, + 0xe9, 0x98, 0xe5, 0xc2, 0x40, 0x93, 0x32, 0x14, 0x26, 0x4c, 0x81, 0xa0, 0xd9, 0x18, 0x38, 0xbc, 0x5a, 0x00, 0xa8, + 0x27, 0xfe, 0x34, 0x2b, 0xb8, 0xae, 0x33, 0xa1, 0x8f, 0xaf, 0xae, 0x62, 0x61, 0x2f, 0x22, 0x01, 0xe4, 0x6c, 0x96, + 0x9d, 0xb1, 0x35, 0x50, 0xf7, 0xd4, 0x60, 0x26, 0xc8, 0xc6, 0x30, 0x20, 0x44, 0x41, 0xb4, 0xcc, 0x93, 0x78, 0xc4, + 0xb4, 0x94, 0x9a, 0xf9, 0xa0, 0xd0, 0xb1, 0x0b, 0xe0, 0x11, 0xcc, 0xed, 0xf7, 0xfb, 0x6d, 0xaf, 0xe3, 0x96, 0x02, + 0xe1, 0xcb, 0x15, 0x8c, 0xde, 0x20, 0x1f, 0xa5, 0x0a, 0xbe, 0x8e, 0x17, 0x70, 0xd7, 0x10, 0x8a, 0x5c, 0xd8, 0x49, + 0x9e, 0x64, 0xc4, 0xae, 0x37, 0x86, 0x41, 0x39, 0x53, 0x8c, 0x1b, 0x55, 0x5c, 0x71, 0x6c, 0xdf, 0x69, 0xb4, 0x69, + 0x72, 0x52, 0x27, 0x4c, 0x6d, 0x8c, 0xdc, 0xf3, 0x42, 0x5b, 0xc0, 0xe6, 0xf6, 0xa0, 0x96, 0x48, 0xd5, 0x40, 0xeb, + 0x00, 0xa1, 0xb0, 0x74, 0x9d, 0x95, 0x25, 0x55, 0x9d, 0x25, 0x13, 0xd7, 0x07, 0xe8, 0x0d, 0x93, 0x60, 0xae, 0x43, + 0xc1, 0x81, 0x64, 0x08, 0x1c, 0x2d, 0x32, 0xb1, 0x5f, 0x4f, 0x60, 0x7b, 0x4e, 0xa2, 0xd1, 0x27, 0x0d, 0x6e, 0x85, + 0xf6, 0x26, 0x19, 0x38, 0x8d, 0x92, 0xd0, 0x60, 0x57, 0xe6, 0xba, 0x15, 0x87, 0xae, 0x1d, 0x14, 0x30, 0xc8, 0x56, + 0xc8, 0xbe, 0xb9, 0xd1, 0x4d, 0x6a, 0x97, 0xe4, 0xa1, 0xec, 0x27, 0x4d, 0x25, 0x37, 0x90, 0x1c, 0x57, 0xdc, 0x80, + 0x2b, 0xc2, 0x83, 0xad, 0x69, 0x40, 0x02, 0x74, 0x57, 0x8e, 0xe3, 0xe2, 0x7a, 0x14, 0xfc, 0xa9, 0x60, 0x3e, 0x35, + 0x66, 0xba, 0x15, 0x52, 0xcd, 0xe1, 0xa4, 0x1a, 0xac, 0x41, 0x93, 0xca, 0x83, 0x62, 0x35, 0xdf, 0xa0, 0xa2, 0x42, + 0x14, 0x7f, 0x2a, 0xaa, 0x50, 0x05, 0x43, 0x30, 0x0a, 0x2f, 0x97, 0x04, 0x97, 0xad, 0xb2, 0x16, 0xc9, 0x53, 0x63, + 0x12, 0xa9, 0x9a, 0xe4, 0x32, 0x50, 0xb0, 0xe8, 0xb4, 0xfa, 0x52, 0x13, 0x57, 0x2c, 0x37, 0x0d, 0x35, 0x33, 0xc9, + 0x95, 0x35, 0xe1, 0x14, 0x68, 0x77, 0x29, 0xed, 0xdd, 0x5c, 0x4f, 0xa1, 0xd6, 0x53, 0xf8, 0x86, 0x0d, 0x65, 0xd2, + 0x76, 0x3e, 0x00, 0x75, 0xbf, 0x56, 0x89, 0xfa, 0xa9, 0x8f, 0x8c, 0xd9, 0xd5, 0x4c, 0x17, 0x18, 0x8a, 0x24, 0x93, + 0x74, 0x20, 0xe9, 0x0d, 0xd9, 0x46, 0x65, 0x19, 0x65, 0xae, 0x38, 0x20, 0x35, 0xab, 0x34, 0xf3, 0x52, 0xb7, 0xa1, + 0xbf, 0x97, 0xa5, 0xc4, 0x13, 0x17, 0x98, 0x89, 0xbd, 0x9b, 0x70, 0xe3, 0xa5, 0x61, 0x26, 0xb4, 0x5f, 0xa1, 0xec, + 0xd4, 0x30, 0x94, 0x4a, 0x16, 0x88, 0x63, 0xe3, 0x6b, 0xa5, 0x19, 0x64, 0xfe, 0x1a, 0x7d, 0x0a, 0x40, 0x49, 0x60, + 0xf3, 0x35, 0x96, 0xbc, 0x28, 0xac, 0xe3, 0x71, 0x83, 0xf0, 0x58, 0xb1, 0xd0, 0x1a, 0xcb, 0xd7, 0xf2, 0x2c, 0xf6, + 0x6b, 0x26, 0xa1, 0x89, 0xc9, 0x62, 0x50, 0x04, 0xb6, 0x72, 0x44, 0x54, 0xb2, 0x2d, 0x19, 0x24, 0x64, 0x90, 0xae, + 0x22, 0xbd, 0x36, 0x92, 0x81, 0xeb, 0x54, 0x70, 0xb4, 0x74, 0x18, 0x46, 0x0e, 0x1a, 0xee, 0xb4, 0x17, 0x2b, 0x88, + 0x6c, 0xea, 0x9b, 0x44, 0x8a, 0x68, 0x9c, 0x16, 0xa8, 0xc2, 0x99, 0x32, 0xdd, 0x71, 0x60, 0x39, 0xc0, 0xf6, 0x57, + 0x48, 0x6f, 0xad, 0xda, 0xe9, 0xfa, 0x95, 0xc1, 0x77, 0x75, 0x95, 0x20, 0x3d, 0x08, 0x85, 0x17, 0xf6, 0x6c, 0xa0, + 0x78, 0xef, 0xfe, 0x4b, 0x6c, 0x45, 0xfa, 0x67, 0x55, 0x52, 0x59, 0x0a, 0x35, 0xca, 0xad, 0xef, 0x13, 0x33, 0x5d, + 0x8b, 0xaa, 0xe2, 0xc0, 0xe0, 0xea, 0x07, 0x4a, 0x60, 0x57, 0x4b, 0x3e, 0x90, 0x43, 0xc7, 0xae, 0xeb, 0x06, 0x05, + 0x19, 0x2f, 0x1b, 0xeb, 0x4c, 0xc8, 0xad, 0x2d, 0xd3, 0x66, 0x3a, 0xd3, 0xc3, 0x3f, 0x71, 0x50, 0x38, 0x17, 0x97, + 0x29, 0x69, 0x30, 0x4f, 0x94, 0x38, 0x5a, 0x31, 0x40, 0xdb, 0x3d, 0xb4, 0xb4, 0xa3, 0xf3, 0x28, 0xe6, 0x96, 0x1e, + 0x45, 0x58, 0xda, 0xc8, 0x9f, 0xa4, 0xd2, 0x01, 0xeb, 0x42, 0x15, 0x92, 0x8c, 0x70, 0x53, 0x17, 0x2d, 0x46, 0x53, + 0x86, 0x2e, 0x70, 0xa5, 0x4f, 0x98, 0xbc, 0x67, 0x03, 0xd7, 0x2d, 0x06, 0x66, 0xeb, 0x61, 0x2f, 0x9b, 0xdd, 0x6b, + 0xea, 0x3f, 0xec, 0x11, 0xf0, 0xb6, 0x99, 0xaa, 0x2b, 0x1b, 0xef, 0x92, 0x45, 0xa2, 0x87, 0x6d, 0xdd, 0xd8, 0x52, + 0xd7, 0xef, 0x35, 0xcc, 0xeb, 0xca, 0x30, 0xaf, 0x09, 0xd5, 0x86, 0x1c, 0x56, 0x66, 0x0e, 0x33, 0x0d, 0x79, 0xb1, + 0x83, 0x6e, 0x4f, 0x38, 0x85, 0xc0, 0x88, 0xd0, 0xfa, 0xa0, 0xa2, 0x06, 0x42, 0x25, 0x57, 0x52, 0x35, 0x5b, 0x24, + 0x63, 0x09, 0x2c, 0x98, 0xb0, 0x5c, 0xd2, 0xd1, 0x79, 0x9c, 0x24, 0x55, 0xe9, 0x9f, 0xca, 0xe0, 0xc5, 0xb0, 0xb7, + 0xb1, 0x76, 0xb1, 0xa2, 0x85, 0x02, 0xc1, 0xd5, 0x4a, 0xd8, 0x7b, 0xc7, 0xad, 0xf6, 0x5d, 0x78, 0x1c, 0xb9, 0xe9, + 0x8d, 0x80, 0x7a, 0xf4, 0xb0, 0x6a, 0xd2, 0xde, 0x7f, 0x86, 0x2e, 0x35, 0x63, 0x3d, 0x28, 0xce, 0xa8, 0xf8, 0x77, + 0xe9, 0x53, 0xbf, 0x73, 0x79, 0xb7, 0x8a, 0xae, 0xa6, 0x43, 0x45, 0x39, 0x3e, 0x4c, 0x17, 0x4b, 0x5b, 0x39, 0x02, + 0x72, 0x3d, 0x2c, 0x72, 0x01, 0x13, 0x35, 0x58, 0x50, 0x8a, 0x55, 0x6b, 0x61, 0xf7, 0xf2, 0x36, 0x67, 0x0e, 0xb9, + 0xc2, 0x45, 0xff, 0x27, 0xd9, 0x6c, 0x8e, 0x9a, 0x59, 0x83, 0xa8, 0xa1, 0xc1, 0xfb, 0x46, 0x7d, 0xb9, 0xa6, 0xac, + 0xd6, 0x87, 0x4e, 0x64, 0x8d, 0x9e, 0xb4, 0xa1, 0x0c, 0x06, 0xd5, 0x42, 0x17, 0xd5, 0xf5, 0xe6, 0x26, 0x8b, 0x59, + 0x47, 0xe3, 0x3e, 0xc9, 0x6d, 0xad, 0x4d, 0x7a, 0x1a, 0x07, 0xc4, 0x93, 0x24, 0xc1, 0x9b, 0x04, 0x50, 0x56, 0xc8, + 0x59, 0x96, 0x0d, 0xf4, 0x2d, 0xcb, 0x12, 0xf7, 0xef, 0xdb, 0xde, 0x7e, 0xcd, 0xb2, 0xf6, 0xf6, 0xaf, 0x37, 0x91, + 0xab, 0x3a, 0x69, 0x41, 0x1e, 0x0d, 0xa1, 0x68, 0x45, 0xa7, 0x0c, 0x97, 0xb3, 0x6c, 0xcc, 0x02, 0x1b, 0xba, 0xa7, + 0x76, 0xa9, 0xa4, 0x32, 0x1c, 0x8e, 0x94, 0x39, 0xcb, 0x77, 0x75, 0x4f, 0x6a, 0xb0, 0x0f, 0x24, 0xa0, 0xd5, 0x85, + 0xef, 0xc2, 0xd3, 0x24, 0x3b, 0x89, 0x92, 0x43, 0x21, 0xc0, 0x6b, 0x2d, 0x3f, 0x80, 0xc9, 0x48, 0x1a, 0xab, 0x21, + 0xa4, 0xbe, 0x1b, 0x7c, 0x17, 0xdc, 0xde, 0xa3, 0xb2, 0x56, 0xec, 0x8e, 0xdf, 0xf6, 0x3b, 0xb6, 0xf2, 0x88, 0xbd, + 0x34, 0xa7, 0x03, 0x89, 0x53, 0x00, 0x66, 0x0e, 0x41, 0x92, 0x15, 0x5e, 0xc4, 0xc2, 0x97, 0x83, 0x97, 0xca, 0xa4, + 0xce, 0xc0, 0x84, 0x00, 0x23, 0x3f, 0x89, 0x79, 0x0b, 0xe3, 0x91, 0xb6, 0xb7, 0x14, 0x15, 0xe8, 0x57, 0x24, 0xbf, + 0x74, 0xa9, 0xac, 0x41, 0xef, 0x63, 0x78, 0x0c, 0xcd, 0x36, 0x37, 0x97, 0xce, 0xab, 0x88, 0x4f, 0xfd, 0x3c, 0x4a, + 0xc7, 0xd9, 0xcc, 0x71, 0xb7, 0x6d, 0xdb, 0xf5, 0x0b, 0xb2, 0x44, 0xbe, 0x70, 0xcb, 0xcd, 0x63, 0x6f, 0xca, 0x42, + 0x7b, 0x60, 0x6f, 0x7f, 0xf4, 0xde, 0xb2, 0xf0, 0x78, 0x6f, 0x73, 0x39, 0x65, 0x65, 0xff, 0xd8, 0xbb, 0xd0, 0x3e, + 0x77, 0xef, 0x2d, 0x72, 0x19, 0xe8, 0x15, 0xf6, 0x2f, 0x24, 0x18, 0x40, 0x6e, 0xe4, 0x7f, 0x07, 0x2e, 0xf7, 0x9e, + 0x02, 0x22, 0xd2, 0x4f, 0x7b, 0x75, 0x65, 0x67, 0xe4, 0x31, 0xb0, 0x37, 0xb4, 0xb1, 0xba, 0xb5, 0x55, 0x89, 0xf9, + 0xaa, 0xd4, 0x1b, 0xb1, 0xb0, 0x66, 0xa9, 0x7b, 0xef, 0x29, 0xb4, 0x52, 0x3f, 0xc8, 0x23, 0x46, 0x42, 0x73, 0x55, + 0x4f, 0x70, 0x8c, 0x23, 0xbe, 0xfe, 0x58, 0x1f, 0x09, 0x2f, 0x85, 0x1f, 0x83, 0xf6, 0x12, 0x81, 0xf8, 0x06, 0x03, + 0xc7, 0x3b, 0x0c, 0x77, 0xf6, 0x9c, 0x41, 0xe0, 0x6c, 0xb4, 0x5a, 0x57, 0x3f, 0xed, 0x1c, 0xfd, 0x1c, 0xb5, 0x7e, + 0xdb, 0x6f, 0xfd, 0x38, 0x74, 0xaf, 0x9c, 0x9f, 0x76, 0x06, 0x47, 0xf2, 0xed, 0xe8, 0xe7, 0xfe, 0x4f, 0xc5, 0xf0, + 0x73, 0x51, 0xb8, 0xe9, 0xba, 0x3b, 0xa7, 0x60, 0x29, 0x85, 0x3b, 0xad, 0x56, 0x1f, 0x9e, 0x16, 0xf0, 0x84, 0x3f, + 0x2f, 0xe1, 0xc7, 0xd5, 0x91, 0xf5, 0x1f, 0x7e, 0x4a, 0xff, 0xe3, 0x4f, 0xf9, 0x10, 0xc7, 0x3c, 0xfa, 0xf9, 0xa7, + 0xc2, 0xbe, 0xd7, 0x0f, 0x77, 0x86, 0xdb, 0xae, 0xa3, 0x6b, 0x3e, 0x0f, 0xab, 0x47, 0x68, 0x75, 0xf4, 0xb3, 0x7c, + 0xb3, 0xef, 0x1d, 0xef, 0xf5, 0xc3, 0xe1, 0x95, 0x63, 0x5f, 0xdd, 0x73, 0xaf, 0x5c, 0xf7, 0x6a, 0x13, 0xe7, 0x99, + 0xc3, 0xe8, 0xf7, 0xe0, 0xe7, 0x19, 0xfc, 0xb4, 0xe1, 0xe7, 0x29, 0xfc, 0xfc, 0x19, 0xba, 0x09, 0xff, 0xdb, 0x15, + 0xf9, 0x42, 0xae, 0x30, 0x60, 0x11, 0xc1, 0x2e, 0xb8, 0x9b, 0x3b, 0xb1, 0x37, 0x21, 0xa4, 0xc1, 0x39, 0xf4, 0x7d, + 0x1f, 0xdd, 0xa4, 0xce, 0xf2, 0xe3, 0x26, 0x6c, 0x3a, 0x52, 0xce, 0x66, 0xc0, 0x3c, 0xe1, 0x39, 0x28, 0x02, 0x2e, + 0x62, 0xab, 0x05, 0x06, 0x57, 0xbd, 0x45, 0x38, 0x61, 0x0e, 0x28, 0x05, 0x87, 0x0c, 0x1f, 0xba, 0xae, 0xf7, 0x4c, + 0xc6, 0x0c, 0xf1, 0x9c, 0x0b, 0xd2, 0x4a, 0x33, 0xa1, 0xd2, 0xd8, 0xae, 0x37, 0x5f, 0x53, 0x09, 0xc7, 0x3a, 0x3d, + 0x85, 0xba, 0x4d, 0x11, 0x68, 0xfb, 0x8e, 0x45, 0x9f, 0xf0, 0x48, 0x3e, 0x37, 0x82, 0xc0, 0x2b, 0x9a, 0x7c, 0x53, + 0x69, 0x34, 0x74, 0x44, 0x61, 0x8e, 0x7d, 0xc9, 0x60, 0x86, 0x15, 0x15, 0x91, 0x93, 0xd0, 0x14, 0x9a, 0x2d, 0x4c, + 0xfe, 0x36, 0xca, 0xf9, 0x66, 0xa5, 0xd8, 0x86, 0x35, 0x4d, 0xb6, 0xa9, 0xe9, 0xdf, 0x61, 0x0a, 0x54, 0x2d, 0x29, + 0xfe, 0x61, 0x8e, 0x1f, 0xa6, 0xb4, 0xac, 0xd7, 0x0e, 0x07, 0x0b, 0xbd, 0x00, 0xbe, 0x23, 0xfa, 0x39, 0x6f, 0x51, + 0x8c, 0xc1, 0x5f, 0xe9, 0x66, 0xf0, 0xc4, 0x7c, 0xe8, 0xa2, 0x59, 0x96, 0xda, 0xb9, 0x95, 0x22, 0xbb, 0x7f, 0x81, + 0x27, 0x23, 0x2d, 0xbd, 0x83, 0x50, 0x9d, 0x98, 0xc3, 0x9c, 0xb1, 0xef, 0xa2, 0xe4, 0x13, 0xcb, 0x9d, 0x0b, 0xaf, + 0xd3, 0xfd, 0x82, 0x3a, 0x7b, 0xa8, 0x9b, 0xbd, 0xae, 0xc2, 0x68, 0x4a, 0x2d, 0x50, 0x21, 0xc2, 0x56, 0xc7, 0x43, + 0x8e, 0x41, 0x28, 0xc8, 0xbd, 0x2c, 0xec, 0x12, 0x85, 0xdb, 0x7b, 0xc5, 0xd9, 0x69, 0xdf, 0x0e, 0x6c, 0x1b, 0x34, + 0xfe, 0x43, 0x72, 0x5b, 0x09, 0xc5, 0x02, 0x14, 0xb2, 0xbd, 0xb8, 0xc7, 0xb7, 0xb7, 0x2b, 0x87, 0x13, 0x06, 0xd2, + 0xa9, 0x7b, 0xe2, 0x45, 0xde, 0x34, 0x84, 0x01, 0x47, 0xd0, 0x0c, 0xbb, 0xf4, 0x46, 0x7b, 0xb1, 0x9c, 0x06, 0x7d, + 0x21, 0x7e, 0x12, 0x15, 0xfc, 0x05, 0xfa, 0x23, 0xc2, 0x11, 0x2a, 0xfb, 0x3e, 0xbb, 0x60, 0x23, 0xa5, 0x67, 0x00, + 0xa2, 0x22, 0xb7, 0xe7, 0x8e, 0x42, 0xa3, 0x19, 0xcc, 0x1d, 0x86, 0x87, 0x03, 0x1b, 0xce, 0x12, 0x9c, 0xca, 0x30, + 0x3a, 0xea, 0x0c, 0x07, 0x69, 0x08, 0xbc, 0x56, 0xe3, 0x56, 0x16, 0x2d, 0x6a, 0x45, 0xdd, 0xe1, 0xc0, 0x39, 0x05, + 0x25, 0x1d, 0x74, 0x71, 0x07, 0xdf, 0xd0, 0x43, 0x91, 0x87, 0xef, 0xd8, 0xe9, 0xb3, 0x8b, 0xb9, 0x63, 0xef, 0xed, + 0xd8, 0xdb, 0x58, 0xea, 0xd9, 0x40, 0x5e, 0x30, 0x77, 0x78, 0xe9, 0x9a, 0x9d, 0x77, 0x87, 0x08, 0x2a, 0x16, 0xe2, + 0xe4, 0x97, 0x03, 0xbb, 0x2f, 0xa6, 0x6e, 0xc3, 0xa0, 0xa9, 0xdc, 0x7e, 0xdc, 0xd1, 0x43, 0x5a, 0xaa, 0xea, 0xaa, + 0xa0, 0x83, 0xb2, 0x6e, 0xe0, 0x4c, 0xcd, 0x45, 0xb4, 0x70, 0x32, 0x89, 0x05, 0x30, 0x78, 0xb0, 0x19, 0x4c, 0x6a, + 0x74, 0xdb, 0x1d, 0x0e, 0x2e, 0x83, 0x7b, 0xf6, 0x3d, 0xf5, 0x72, 0xc6, 0x02, 0xb0, 0x2e, 0x68, 0xfa, 0x33, 0x94, + 0x22, 0xf0, 0x73, 0xce, 0x60, 0x91, 0x97, 0x54, 0x34, 0x96, 0x45, 0x0b, 0x2c, 0x3a, 0x0c, 0x10, 0x54, 0x2f, 0xd7, + 0xda, 0x9f, 0xd8, 0x93, 0x71, 0x48, 0xb0, 0x6f, 0x6d, 0xc1, 0xd6, 0x6c, 0x77, 0x86, 0x18, 0x6f, 0xc8, 0x79, 0xf1, + 0x5d, 0xcc, 0x41, 0x24, 0xec, 0xf4, 0x6d, 0x77, 0x60, 0x5b, 0xb8, 0xb5, 0xbd, 0x6c, 0x3b, 0x14, 0x18, 0x8e, 0xb7, + 0xdf, 0xb2, 0x60, 0xda, 0x0f, 0xdb, 0x03, 0xa7, 0x10, 0xa2, 0x23, 0xc1, 0xb8, 0xa5, 0xe0, 0xe0, 0x6d, 0x6f, 0x0a, + 0x0c, 0x1d, 0x29, 0x77, 0xd3, 0xde, 0x56, 0x85, 0x50, 0xf4, 0x71, 0x7b, 0xec, 0x06, 0x31, 0xfc, 0x70, 0x5a, 0x48, + 0x34, 0x53, 0xdd, 0x57, 0x4b, 0x66, 0x37, 0x18, 0x2b, 0x8d, 0x3c, 0x09, 0xb3, 0x6d, 0x07, 0x3d, 0xb4, 0xc0, 0x69, + 0xf7, 0x06, 0x00, 0xc3, 0xb6, 0xa3, 0x28, 0x6d, 0x47, 0x91, 0x9a, 0xd2, 0xcf, 0x8f, 0xaa, 0xed, 0x60, 0x83, 0x88, + 0xf9, 0x95, 0xf4, 0x01, 0xb0, 0x82, 0xc4, 0x2b, 0x86, 0x2a, 0xe6, 0xf5, 0xbc, 0x16, 0xdf, 0x5a, 0x2a, 0x56, 0xc4, + 0x3c, 0x83, 0x43, 0xf1, 0x52, 0x9b, 0x61, 0x42, 0xdd, 0x9e, 0x23, 0x32, 0x34, 0xc9, 0x87, 0x6d, 0x20, 0x7a, 0xe5, + 0x60, 0x4f, 0xcd, 0x63, 0x91, 0x84, 0x55, 0x73, 0xef, 0x08, 0x48, 0x7b, 0x18, 0xbe, 0x16, 0x11, 0xc7, 0x9e, 0xf2, + 0xe6, 0xb3, 0x24, 0x7c, 0xde, 0x08, 0x17, 0x47, 0x18, 0x11, 0x3a, 0xf0, 0x47, 0x8b, 0x1c, 0xf8, 0x01, 0x7f, 0x0d, + 0x9a, 0x41, 0x28, 0x9b, 0xa2, 0xa1, 0x87, 0x21, 0x60, 0x8f, 0x16, 0xde, 0x70, 0x9b, 0x1b, 0xd5, 0xa8, 0x51, 0x92, + 0xf2, 0x42, 0x81, 0xe1, 0x1e, 0x97, 0xa6, 0x3d, 0x32, 0x06, 0x19, 0x31, 0x76, 0x30, 0xe6, 0xef, 0x8f, 0xb0, 0x1a, + 0x27, 0x28, 0xdc, 0x92, 0x4e, 0x5b, 0xc5, 0xfe, 0x0e, 0xfc, 0x14, 0x38, 0x38, 0xd6, 0x81, 0x9d, 0xb5, 0xb5, 0x95, + 0xc8, 0x45, 0xed, 0xa5, 0x3d, 0x8a, 0x44, 0xa0, 0x3f, 0xb8, 0xf0, 0x53, 0xa8, 0x46, 0x14, 0x51, 0x11, 0x69, 0xa0, + 0x66, 0x54, 0xad, 0x82, 0xef, 0xc8, 0xf4, 0xc0, 0x73, 0x74, 0x5b, 0x93, 0xa2, 0xa8, 0x1b, 0x0b, 0x5f, 0xbe, 0xeb, + 0x52, 0x68, 0x0b, 0x03, 0x90, 0x82, 0xd0, 0x04, 0xc1, 0xb8, 0xe4, 0x94, 0xac, 0xe8, 0xef, 0xa3, 0xe1, 0x2b, 0x9f, + 0x1e, 0x65, 0xdb, 0xdb, 0x43, 0x11, 0xb7, 0x20, 0xc2, 0xe1, 0x86, 0x77, 0x35, 0xae, 0x00, 0xa8, 0x4f, 0xe7, 0xc4, + 0x75, 0xc7, 0xb4, 0x22, 0x4d, 0x97, 0x7c, 0x9f, 0x1c, 0x66, 0x00, 0x0c, 0xee, 0x38, 0x47, 0xfe, 0xe0, 0x2f, 0x43, + 0x30, 0x8f, 0xfd, 0xcf, 0xdd, 0x1d, 0xc5, 0x68, 0x7a, 0x32, 0xa6, 0xb8, 0xa4, 0x18, 0x6b, 0xc7, 0x23, 0xdf, 0x68, + 0x90, 0x7b, 0x29, 0xac, 0x00, 0xa4, 0x39, 0xf0, 0x84, 0x8a, 0x82, 0x90, 0xa2, 0x02, 0xdb, 0xc7, 0xc3, 0xcf, 0xf1, + 0x64, 0xbf, 0x03, 0x0d, 0x6f, 0xa0, 0xdf, 0x9e, 0xc2, 0xdb, 0x5f, 0xf4, 0xdb, 0x97, 0x2c, 0xf8, 0xa5, 0x94, 0xae, + 0xfb, 0xda, 0x14, 0x0f, 0xd5, 0x14, 0xa5, 0xd8, 0x22, 0x03, 0x87, 0xcc, 0x5d, 0xf5, 0xd9, 0x70, 0xb7, 0x04, 0x64, + 0x28, 0xd6, 0x05, 0x3a, 0x5a, 0x74, 0x8a, 0xc8, 0x75, 0x4d, 0x54, 0x18, 0xb9, 0x04, 0xe6, 0x82, 0x2b, 0xba, 0x25, + 0xe2, 0xec, 0xb7, 0xdd, 0x65, 0xad, 0x2d, 0xe9, 0x77, 0x6c, 0x36, 0xe7, 0x97, 0x07, 0x24, 0xe8, 0x03, 0x99, 0x36, + 0x20, 0x62, 0xe7, 0xed, 0x5e, 0xbc, 0xc7, 0x7b, 0x31, 0x70, 0xf5, 0x42, 0x91, 0x18, 0x9e, 0x55, 0xef, 0x2d, 0x7a, + 0x29, 0x4d, 0x62, 0xf2, 0x6a, 0xcb, 0xeb, 0xca, 0xe5, 0x6d, 0x6f, 0xc3, 0x02, 0x7b, 0x46, 0x57, 0x2e, 0xba, 0x96, + 0xa5, 0xc0, 0x09, 0x40, 0xf4, 0xb8, 0x4e, 0x72, 0x44, 0x71, 0x98, 0xcd, 0x86, 0x8c, 0x83, 0xb9, 0x6b, 0x47, 0xc5, + 0x31, 0xb1, 0xbb, 0x4c, 0xd8, 0x81, 0x95, 0x11, 0x95, 0xb7, 0x3a, 0xc2, 0x3b, 0x2c, 0xfa, 0x6b, 0xff, 0xf6, 0x47, + 0x8f, 0x6d, 0x77, 0x5c, 0x90, 0x20, 0xb5, 0xb1, 0x1e, 0x55, 0x63, 0x41, 0x7d, 0xf8, 0x51, 0x63, 0xa9, 0xcc, 0xb7, + 0xb7, 0xcb, 0x7a, 0xa8, 0x56, 0x9d, 0xe0, 0x5a, 0x34, 0xe5, 0xa2, 0x99, 0x0d, 0xc2, 0x01, 0x89, 0x09, 0x14, 0x68, + 0x6e, 0x65, 0xc5, 0x00, 0x43, 0xca, 0x72, 0xe4, 0x4f, 0x21, 0xf3, 0xe2, 0xb2, 0xd4, 0xa9, 0x2f, 0xd2, 0x1f, 0x19, + 0x62, 0xd4, 0x93, 0x94, 0x15, 0x10, 0xb0, 0x5e, 0xea, 0x25, 0xb4, 0x45, 0xb0, 0xf2, 0x67, 0x2a, 0x87, 0x46, 0x68, + 0x20, 0x51, 0x68, 0xa8, 0x25, 0x4a, 0xf9, 0xcc, 0xc3, 0x18, 0xa4, 0xfd, 0x93, 0x9a, 0xef, 0x2b, 0x57, 0x4a, 0x47, + 0x7e, 0x54, 0x0c, 0x03, 0xaa, 0x5f, 0x48, 0x0e, 0x36, 0x0d, 0xdf, 0x03, 0x19, 0x55, 0x86, 0x27, 0x31, 0xc2, 0xa7, + 0x71, 0xce, 0xc8, 0x52, 0xd8, 0x94, 0x30, 0x4b, 0xd5, 0x36, 0x52, 0xed, 0x22, 0xd3, 0x09, 0xe5, 0xc2, 0xfc, 0x53, + 0x23, 0x76, 0x91, 0x85, 0x2b, 0xad, 0x41, 0xfd, 0x78, 0x63, 0x02, 0x94, 0x5d, 0x5d, 0x65, 0xc2, 0xc6, 0x8d, 0x48, + 0xdf, 0xd0, 0x15, 0xd3, 0x81, 0x5a, 0x54, 0xe0, 0x44, 0xa4, 0xf1, 0x50, 0x0c, 0x85, 0x46, 0x38, 0xa4, 0x28, 0x72, + 0xe1, 0x1a, 0x87, 0xbe, 0x18, 0x68, 0xdb, 0x28, 0x0d, 0x9d, 0x04, 0x98, 0x80, 0x58, 0xbb, 0xa1, 0x4d, 0xa5, 0x83, + 0x34, 0x48, 0xa8, 0x14, 0xed, 0x1c, 0x58, 0x7f, 0x18, 0x49, 0x0c, 0x80, 0xfe, 0x50, 0x8d, 0x14, 0x51, 0x96, 0x05, + 0x6e, 0x00, 0xcd, 0x75, 0x80, 0x3b, 0xe1, 0x0b, 0x05, 0x15, 0xa6, 0xa7, 0x59, 0x79, 0x29, 0x84, 0xc8, 0xab, 0x35, + 0x29, 0x6b, 0xc4, 0x93, 0xcf, 0xd0, 0xe0, 0x53, 0xd6, 0xf5, 0x6b, 0xb9, 0x0e, 0x5d, 0xf0, 0x14, 0xb6, 0x55, 0x3d, + 0xbf, 0x0a, 0x39, 0x19, 0xd7, 0x20, 0x2b, 0x24, 0xd3, 0x5f, 0x31, 0x92, 0xf7, 0x5f, 0xf9, 0x55, 0x2d, 0x35, 0x86, + 0xb2, 0xf7, 0xeb, 0x9a, 0x61, 0x79, 0x39, 0xaf, 0xdc, 0x14, 0x04, 0xdc, 0x92, 0x25, 0xc1, 0x52, 0x4a, 0x08, 0xd0, + 0xb0, 0x3d, 0x92, 0x4a, 0x41, 0x51, 0x6a, 0xf7, 0xce, 0x53, 0xd0, 0x02, 0x8c, 0xa0, 0x96, 0x4a, 0xa6, 0x91, 0xc8, + 0x97, 0x42, 0x14, 0x88, 0xf2, 0x60, 0x04, 0x76, 0x6a, 0x33, 0xd2, 0x75, 0xe1, 0xfa, 0xf1, 0x0c, 0x53, 0x7b, 0x08, + 0xf4, 0xd8, 0xdb, 0x00, 0x55, 0xa2, 0x2e, 0xc3, 0x72, 0xa2, 0xd0, 0xac, 0x26, 0x59, 0x40, 0x8d, 0x69, 0x83, 0x94, + 0x6c, 0x83, 0x2e, 0x57, 0x80, 0x7e, 0x24, 0x8e, 0x67, 0xb5, 0x03, 0x42, 0xd6, 0xa0, 0x82, 0x21, 0x4f, 0xa9, 0x90, + 0xc2, 0xbc, 0xd7, 0xa5, 0x22, 0x3c, 0x9f, 0x03, 0x2e, 0xb5, 0xe0, 0xcc, 0xcb, 0x68, 0xe0, 0x83, 0xf8, 0x24, 0xc1, + 0xc4, 0x17, 0x5c, 0x15, 0xe8, 0xc1, 0x41, 0xa7, 0xd9, 0x14, 0x28, 0x15, 0x37, 0x29, 0x83, 0x6d, 0x45, 0xae, 0x0d, + 0x3f, 0x24, 0xcb, 0xd6, 0x5d, 0x1e, 0xea, 0x2e, 0x44, 0x02, 0xd8, 0xe9, 0x25, 0x7a, 0xbe, 0x65, 0xbd, 0x74, 0x18, + 0x9c, 0x69, 0x89, 0x83, 0xc0, 0x6f, 0x6f, 0x27, 0xc3, 0x32, 0x25, 0xb2, 0x6b, 0x92, 0xba, 0x80, 0x1c, 0x86, 0x6a, + 0xae, 0x1d, 0x98, 0xa5, 0xd2, 0xc7, 0xf3, 0x72, 0x86, 0xdb, 0xa5, 0x34, 0xe4, 0x66, 0xbc, 0x9a, 0xe6, 0x73, 0x2b, + 0xc9, 0xa6, 0xfd, 0xad, 0xf8, 0xa2, 0xe0, 0x1f, 0x38, 0xb1, 0xd4, 0xea, 0x29, 0xb5, 0xc2, 0xa3, 0xcc, 0x2d, 0x59, + 0xa7, 0xb8, 0x56, 0xd7, 0x0d, 0x54, 0x23, 0x8c, 0xa6, 0x61, 0x23, 0x60, 0x62, 0x82, 0x8a, 0x5f, 0x37, 0x89, 0x98, + 0xce, 0x96, 0xe0, 0x3a, 0x42, 0xef, 0xa1, 0x9c, 0xe0, 0xae, 0xa6, 0xd9, 0xe7, 0xe1, 0xfc, 0x7a, 0xe2, 0xde, 0x37, + 0x88, 0xfb, 0xcb, 0x90, 0x1b, 0x84, 0x1e, 0xcb, 0x84, 0x1f, 0xe9, 0xfb, 0x28, 0x54, 0xd5, 0x93, 0xd3, 0xb0, 0x62, + 0x59, 0xe2, 0xc9, 0x08, 0x75, 0x18, 0x51, 0xd1, 0x1a, 0x23, 0xbb, 0xba, 0xca, 0xcd, 0xb3, 0x40, 0x4e, 0x53, 0x8f, + 0xd7, 0xfd, 0xb4, 0x15, 0x39, 0x1b, 0x9e, 0xc8, 0xfd, 0x57, 0x35, 0x4f, 0x64, 0x45, 0xe7, 0x38, 0xd2, 0x35, 0x81, + 0xdc, 0x27, 0xa7, 0xab, 0x87, 0x54, 0xc8, 0x16, 0xbd, 0x6c, 0xe3, 0x8c, 0xea, 0x80, 0xa4, 0x9e, 0x51, 0x81, 0x55, + 0x8d, 0xbd, 0xb5, 0xd5, 0x11, 0xe9, 0x96, 0x4a, 0xb0, 0xc1, 0xd6, 0xc2, 0x68, 0xc6, 0x28, 0xe8, 0x94, 0x14, 0x19, + 0xa8, 0x51, 0x7e, 0x0d, 0x63, 0xd8, 0xa7, 0x06, 0x20, 0x38, 0xd7, 0x57, 0x7f, 0x59, 0x4a, 0xb2, 0x10, 0x90, 0xb8, + 0x4b, 0x06, 0x6c, 0x4d, 0x10, 0x33, 0xd2, 0xc9, 0x7b, 0xa0, 0xbc, 0x01, 0x43, 0x1b, 0x01, 0xec, 0x02, 0x71, 0xe8, + 0x41, 0xc5, 0xb6, 0x09, 0x29, 0x3a, 0x36, 0xf0, 0x1c, 0x80, 0x9d, 0x57, 0xae, 0xd1, 0x77, 0x55, 0x0a, 0x18, 0x92, + 0x81, 0x1b, 0xb0, 0xca, 0x2d, 0xb7, 0xff, 0x1c, 0xcc, 0x06, 0x78, 0x7d, 0x26, 0x9b, 0x6f, 0x62, 0x9e, 0x60, 0x15, + 0xbb, 0xf0, 0x2b, 0xcd, 0x5a, 0xc4, 0x9d, 0x0e, 0x1b, 0xf5, 0x0a, 0x13, 0xa2, 0xf6, 0x00, 0x6b, 0xdf, 0xa3, 0x87, + 0x45, 0xbc, 0xbf, 0xc2, 0x77, 0x3d, 0x6e, 0xb9, 0xaf, 0x97, 0x45, 0x2b, 0x5d, 0x45, 0x8d, 0x81, 0xc9, 0xba, 0x9d, + 0x8c, 0x6b, 0x2f, 0x0f, 0x84, 0x2f, 0xb8, 0x5a, 0x23, 0xab, 0x5c, 0x8a, 0x8d, 0x45, 0xd2, 0xd3, 0x3e, 0x05, 0xd8, + 0x37, 0x9b, 0xbd, 0x00, 0x33, 0xef, 0x2b, 0x54, 0x49, 0x48, 0x69, 0x76, 0x83, 0x25, 0x09, 0x6d, 0x45, 0x46, 0x9d, + 0x0f, 0x1c, 0x6d, 0x73, 0x2b, 0x8e, 0x60, 0x38, 0x27, 0x61, 0x3a, 0x56, 0x1e, 0x36, 0x19, 0xb8, 0xf2, 0x8e, 0x98, + 0xb6, 0x09, 0xf0, 0x6f, 0x06, 0x7c, 0x7b, 0x25, 0xb9, 0xb6, 0xd0, 0x30, 0x3c, 0x41, 0x84, 0x55, 0x9e, 0x08, 0x34, + 0x14, 0x60, 0x8d, 0x6b, 0x2d, 0x0f, 0x50, 0xe1, 0x6b, 0x67, 0x13, 0x00, 0x12, 0x59, 0x41, 0xce, 0x8a, 0xa3, 0x1b, + 0x56, 0xb9, 0xde, 0x4f, 0x8d, 0x82, 0xc4, 0xc5, 0x83, 0xe9, 0xea, 0x96, 0xfe, 0x0c, 0x35, 0x67, 0x52, 0xc4, 0xb4, + 0x13, 0x04, 0xfd, 0xa3, 0xcc, 0xc9, 0x69, 0x3a, 0xa1, 0x7d, 0xce, 0x9d, 0xda, 0xd4, 0x3d, 0x46, 0xdd, 0x3c, 0x89, + 0x2d, 0x5e, 0xc7, 0x4d, 0x29, 0x17, 0x26, 0x39, 0xe6, 0xa6, 0x48, 0xc5, 0x66, 0x8a, 0xdd, 0xb9, 0xf5, 0x83, 0x16, + 0xd2, 0x41, 0xdb, 0x14, 0x39, 0xd8, 0xac, 0xe2, 0xf7, 0x04, 0xc6, 0x73, 0x81, 0xf8, 0xf2, 0x15, 0x25, 0xe9, 0x30, + 0xc7, 0x5c, 0x60, 0xf5, 0x62, 0x0a, 0xf2, 0x77, 0x8e, 0x4e, 0xb3, 0x37, 0xf0, 0x41, 0xe2, 0x0d, 0x38, 0x66, 0x8d, + 0x7d, 0xe7, 0x52, 0x51, 0x47, 0x08, 0x54, 0x46, 0xb5, 0x4c, 0xc7, 0x89, 0x95, 0xfb, 0x46, 0xd0, 0xd5, 0x5b, 0x1d, + 0xce, 0x37, 0x9e, 0x1b, 0xbb, 0x11, 0xc4, 0x60, 0x2d, 0x14, 0x43, 0x4f, 0xb2, 0xf0, 0x1c, 0xb6, 0x67, 0x7b, 0xbb, + 0x57, 0xec, 0xf1, 0xca, 0x45, 0x52, 0xc1, 0x18, 0x63, 0x46, 0x31, 0x9e, 0x89, 0x9a, 0x58, 0x44, 0x64, 0xcb, 0xd6, + 0x61, 0x81, 0x01, 0x00, 0x68, 0x69, 0x72, 0xaf, 0x9a, 0x08, 0x95, 0xf1, 0x5c, 0x5a, 0x4f, 0x15, 0x44, 0x55, 0x8d, + 0xdf, 0xae, 0xcf, 0x40, 0x21, 0xb8, 0x37, 0x3a, 0x1e, 0x06, 0x21, 0x60, 0x17, 0x05, 0x2f, 0xd0, 0x07, 0xb4, 0x57, + 0x25, 0x42, 0x31, 0x73, 0xb2, 0x1e, 0x33, 0x8c, 0x54, 0xd0, 0x85, 0x4a, 0xd8, 0x2a, 0xcd, 0xf0, 0xab, 0x83, 0xd0, + 0x8c, 0x32, 0xee, 0xbf, 0xaa, 0xd6, 0x0c, 0xf2, 0x83, 0x79, 0xab, 0x84, 0xfa, 0x76, 0x25, 0x22, 0x53, 0x81, 0x89, + 0x87, 0x59, 0x4a, 0xbf, 0x5f, 0xd6, 0x49, 0x3f, 0x2f, 0x97, 0xe7, 0x9c, 0x24, 0x5f, 0xe7, 0x0e, 0x92, 0x4f, 0xba, + 0xfb, 0x95, 0xf0, 0x43, 0x0d, 0xa3, 0x26, 0xfc, 0xea, 0x5b, 0x1a, 0xe6, 0x9e, 0x72, 0x6f, 0xf5, 0xbb, 0xc8, 0x74, + 0x51, 0x9e, 0x83, 0x22, 0xa4, 0x1f, 0xc1, 0x34, 0x34, 0x68, 0x50, 0x24, 0x8b, 0xc5, 0xda, 0x04, 0x71, 0x7d, 0xcc, + 0xa9, 0x76, 0x28, 0x63, 0x8c, 0x68, 0x5a, 0x52, 0x90, 0x24, 0x70, 0x50, 0x7e, 0x03, 0x03, 0x62, 0x12, 0x12, 0xd2, + 0x20, 0x74, 0xd6, 0x66, 0x22, 0x2a, 0x73, 0xf1, 0x76, 0xe5, 0xb2, 0x26, 0x50, 0x84, 0x9e, 0x60, 0xa6, 0x52, 0x2a, + 0x08, 0xa4, 0xca, 0xb7, 0xd1, 0xa9, 0x39, 0x43, 0x73, 0xd7, 0x14, 0x40, 0x5e, 0xdb, 0xf5, 0xa0, 0xc9, 0x7b, 0xf2, + 0xa1, 0xaf, 0x13, 0x23, 0x5e, 0x66, 0xd0, 0x35, 0x1c, 0xfe, 0x1a, 0x2b, 0x29, 0x42, 0x26, 0x7c, 0xaf, 0x60, 0x13, + 0x21, 0x99, 0x82, 0x9e, 0x09, 0xf8, 0x43, 0xbd, 0xb2, 0x97, 0xee, 0xe5, 0x95, 0x49, 0x8b, 0xca, 0x56, 0xa2, 0x66, + 0x2d, 0x8e, 0xe2, 0xed, 0x14, 0xce, 0xb3, 0x47, 0x09, 0x04, 0x24, 0xa9, 0x9c, 0xa4, 0x9a, 0xf7, 0x28, 0x1d, 0x02, + 0x48, 0x70, 0xfa, 0x09, 0x2c, 0xb4, 0x9b, 0x12, 0x13, 0x2c, 0xaa, 0xc6, 0x6e, 0x73, 0x90, 0x9a, 0x73, 0x92, 0x7c, + 0x73, 0x94, 0xda, 0xdb, 0x4a, 0x7b, 0xc6, 0xec, 0x00, 0xdb, 0x76, 0xb7, 0xf3, 0xa3, 0x74, 0xbb, 0x33, 0x34, 0x18, + 0x17, 0x86, 0xff, 0x93, 0x12, 0xd3, 0x40, 0x0a, 0x29, 0x1b, 0x3f, 0xa1, 0x0c, 0xc3, 0xff, 0x96, 0x24, 0x80, 0x07, + 0xb5, 0xdd, 0x58, 0x31, 0xee, 0x15, 0x45, 0xc9, 0x6d, 0x55, 0xed, 0x6a, 0x09, 0x1a, 0xba, 0x91, 0xf4, 0x89, 0x62, + 0x9e, 0x13, 0x00, 0xa3, 0xc8, 0xfc, 0x1d, 0xd3, 0x49, 0xde, 0xbf, 0xac, 0x4d, 0xed, 0xf6, 0x7d, 0x3f, 0xca, 0x4f, + 0x29, 0xa4, 0xa2, 0xb2, 0x39, 0x89, 0xf8, 0x77, 0x05, 0x98, 0xe6, 0xc4, 0x47, 0x7a, 0xae, 0x61, 0x28, 0xc0, 0x57, + 0x3a, 0x94, 0x9a, 0xed, 0xe9, 0x1f, 0x9d, 0xed, 0xbe, 0x44, 0x8a, 0x20, 0x81, 0x06, 0x5e, 0xae, 0x59, 0x2f, 0xac, + 0x32, 0xb8, 0x23, 0xfe, 0x14, 0x7c, 0x5f, 0x5e, 0x07, 0x9f, 0x71, 0xfe, 0x05, 0xa0, 0x55, 0x81, 0x01, 0xe5, 0x83, + 0xa6, 0x62, 0x25, 0xd8, 0x25, 0x0a, 0xcc, 0xca, 0xcf, 0x1f, 0xd7, 0x69, 0xdd, 0xd4, 0x2c, 0xd1, 0x29, 0x3f, 0x77, + 0x0d, 0x33, 0xbe, 0xd7, 0xc8, 0x1f, 0xdf, 0x7f, 0x0e, 0xb2, 0x9d, 0x50, 0xbb, 0xb5, 0x55, 0x6c, 0x90, 0x86, 0x86, + 0xf7, 0xc2, 0xe6, 0xd0, 0x16, 0xf1, 0x52, 0xa8, 0x67, 0x2c, 0xc6, 0xdb, 0x22, 0x54, 0x86, 0x0f, 0x58, 0x30, 0x87, + 0x15, 0x82, 0xc5, 0x4e, 0x65, 0xf2, 0x19, 0x0e, 0x9a, 0x22, 0xd7, 0x42, 0x28, 0x7c, 0x39, 0x88, 0x4a, 0x49, 0x8b, + 0x75, 0xb4, 0x3d, 0x3b, 0x83, 0xe7, 0x97, 0x71, 0x01, 0xd8, 0x81, 0xe5, 0x57, 0x58, 0x16, 0x07, 0xc8, 0xc5, 0x43, + 0x59, 0xeb, 0x15, 0x8d, 0xc7, 0x37, 0x76, 0x61, 0x75, 0x01, 0x3e, 0x8d, 0xd2, 0x71, 0x22, 0x26, 0x31, 0x93, 0x2a, + 0xd7, 0xe4, 0xda, 0xe8, 0x5e, 0x5a, 0xa3, 0x79, 0x2e, 0x38, 0x78, 0x85, 0xe0, 0x06, 0xd3, 0x57, 0xf2, 0x72, 0xbd, + 0x82, 0x82, 0xa1, 0xf6, 0xe6, 0x26, 0x98, 0x2b, 0xf1, 0x98, 0xc1, 0x35, 0xfd, 0x3a, 0x9c, 0x8a, 0x6e, 0x5e, 0xae, + 0x18, 0xfc, 0x3a, 0x67, 0xac, 0x21, 0x00, 0x88, 0x4e, 0x1e, 0x5e, 0x6f, 0x26, 0xbd, 0x52, 0xd2, 0x41, 0x49, 0x84, + 0xf8, 0xae, 0xcc, 0xd7, 0x5d, 0x2a, 0xba, 0x72, 0xd5, 0xbd, 0xaf, 0x19, 0x33, 0x2e, 0x18, 0x3d, 0xe7, 0xb3, 0xa4, + 0x71, 0xed, 0x86, 0xee, 0xea, 0xfc, 0xe8, 0xfd, 0x20, 0xf3, 0x16, 0x66, 0x40, 0x26, 0x20, 0x0a, 0x9e, 0x7b, 0xaf, + 0x8d, 0x88, 0xf2, 0xb7, 0x66, 0x88, 0x57, 0x0e, 0xb3, 0x2e, 0x92, 0xfc, 0xed, 0xe0, 0xdb, 0xe0, 0xfa, 0x96, 0x46, + 0x04, 0xb9, 0xab, 0x22, 0xc8, 0x84, 0xb9, 0x99, 0x3e, 0x70, 0xfb, 0x77, 0x65, 0x08, 0x22, 0x2a, 0xa6, 0x43, 0xe5, + 0xb8, 0x7f, 0xb4, 0x41, 0xa5, 0x42, 0xe2, 0x53, 0x95, 0xbb, 0x72, 0x6d, 0x6a, 0xa8, 0xc7, 0x75, 0x32, 0x0b, 0x4d, + 0xb3, 0x26, 0x97, 0xb2, 0x69, 0x31, 0x32, 0x4d, 0x4e, 0xb5, 0xf9, 0xdd, 0x6b, 0x83, 0x74, 0x0c, 0xd5, 0xc5, 0x5a, + 0x2d, 0x98, 0xdf, 0x95, 0x17, 0xde, 0xf5, 0x62, 0x23, 0x95, 0xa1, 0xa6, 0x3d, 0x8a, 0x3e, 0x8e, 0xdb, 0xcc, 0xe5, + 0x51, 0xfa, 0x67, 0x0d, 0x00, 0xd3, 0x10, 0x16, 0xdd, 0x4d, 0xcb, 0xd8, 0x13, 0xcb, 0xd3, 0x13, 0x19, 0x28, 0x7a, + 0xae, 0xf3, 0x55, 0xab, 0xc4, 0xd2, 0x35, 0x08, 0x76, 0x6f, 0xc8, 0x58, 0x95, 0xb8, 0x5b, 0xad, 0x5f, 0xcd, 0xf3, + 0x79, 0xca, 0x57, 0xf2, 0x7c, 0x6a, 0x1a, 0xdd, 0x46, 0xdb, 0xbd, 0x39, 0x35, 0x54, 0xcc, 0xb5, 0xbe, 0xc9, 0x1f, + 0x98, 0xae, 0x83, 0xae, 0x16, 0x81, 0x66, 0x75, 0xaa, 0x9e, 0x95, 0xe5, 0xac, 0x9e, 0xc9, 0x31, 0x13, 0xb6, 0xa9, + 0x34, 0x87, 0xe8, 0x86, 0xa9, 0x9a, 0xe9, 0xc7, 0xc6, 0xb1, 0x90, 0x6d, 0x9e, 0x5f, 0x8e, 0x73, 0xc0, 0xb4, 0x3c, + 0x5f, 0x26, 0x0c, 0x3f, 0x5e, 0x5d, 0xfd, 0x28, 0xf8, 0x54, 0xd5, 0xd1, 0x5b, 0xbe, 0xd4, 0x3d, 0x83, 0x59, 0xa9, + 0x8c, 0x88, 0x13, 0xb6, 0x7e, 0xf0, 0xe6, 0xe9, 0x15, 0xb0, 0x9c, 0xc0, 0xea, 0x4e, 0x98, 0xd3, 0x18, 0xaa, 0x3a, + 0xc0, 0x3f, 0xac, 0x1f, 0x6c, 0xdd, 0x19, 0xfe, 0x61, 0xf0, 0x43, 0x70, 0x63, 0x63, 0xe3, 0x18, 0xef, 0xd6, 0x12, + 0x41, 0x5e, 0x61, 0x40, 0x1f, 0xaf, 0x3e, 0x0a, 0x5c, 0xae, 0x63, 0xdb, 0x03, 0x87, 0xdc, 0xd6, 0xc0, 0xdf, 0x24, + 0x4f, 0x1a, 0x2d, 0x0a, 0x9e, 0xcd, 0xe4, 0x0c, 0x85, 0xbc, 0xe6, 0xe3, 0xa0, 0xee, 0x08, 0x7f, 0x03, 0xa7, 0x16, + 0x5e, 0x5e, 0x7e, 0x82, 0x3e, 0x60, 0xe9, 0x4a, 0x6e, 0x2a, 0xfc, 0x94, 0xf2, 0x88, 0xae, 0xd6, 0x79, 0x30, 0x52, + 0x5c, 0x4c, 0x51, 0xe8, 0xb8, 0xcb, 0x1b, 0x67, 0x23, 0xa3, 0xbf, 0xc4, 0xab, 0x8b, 0x74, 0xf9, 0x48, 0x64, 0xab, + 0x96, 0xde, 0x2f, 0x3a, 0xba, 0x6d, 0xcf, 0x18, 0x9f, 0x66, 0x63, 0x0a, 0xcc, 0xf8, 0x38, 0x11, 0x5e, 0x9f, 0x18, + 0xeb, 0xbb, 0x45, 0xa0, 0xba, 0x39, 0x36, 0xd9, 0xe1, 0x78, 0xbd, 0xd9, 0xac, 0x71, 0x07, 0x6f, 0x9c, 0x27, 0xce, + 0xb2, 0x44, 0x8f, 0xca, 0x52, 0xc3, 0x03, 0x52, 0x21, 0x6e, 0xde, 0x33, 0x81, 0x71, 0xd9, 0x25, 0x71, 0x6d, 0x37, + 0x10, 0x6b, 0xb1, 0x27, 0x31, 0x4b, 0xc6, 0xb6, 0x07, 0xe5, 0x81, 0xbe, 0x18, 0x4d, 0xb7, 0x80, 0x69, 0x7b, 0xed, + 0xec, 0x3c, 0xb5, 0xbd, 0x6a, 0xaa, 0x00, 0x66, 0xc9, 0xf2, 0xf8, 0x14, 0x49, 0xf7, 0x1b, 0xe8, 0x22, 0x06, 0x8c, + 0x8d, 0x2b, 0x73, 0xee, 0x72, 0xdd, 0x8e, 0xf8, 0x46, 0x13, 0xa9, 0x52, 0x1f, 0x51, 0xdf, 0x61, 0x58, 0xab, 0xab, + 0x0c, 0x24, 0x30, 0x8f, 0xbc, 0x3b, 0xae, 0xa5, 0xa7, 0x63, 0x16, 0x93, 0x2a, 0x7d, 0x4b, 0x5d, 0x8b, 0x6b, 0xba, + 0xbd, 0xe2, 0x01, 0xe8, 0x1f, 0xe8, 0xb7, 0x88, 0x85, 0xbf, 0x9d, 0xd7, 0x52, 0x58, 0x1b, 0x73, 0xe4, 0xe8, 0x6b, + 0x0f, 0x7e, 0x61, 0xd5, 0x9e, 0x81, 0x1a, 0x66, 0xc4, 0x48, 0x7e, 0x33, 0xee, 0x55, 0x4d, 0x1c, 0xb9, 0x0b, 0xc0, + 0xfa, 0x96, 0x74, 0x49, 0x0e, 0xaf, 0x64, 0xb9, 0x2a, 0x86, 0xfc, 0x1b, 0xec, 0xb3, 0xde, 0x9c, 0x80, 0x99, 0x38, + 0xe5, 0x25, 0x26, 0xa6, 0x88, 0xcb, 0xcd, 0xd2, 0xe7, 0x69, 0xda, 0x2c, 0xda, 0xc0, 0x29, 0x8c, 0x04, 0x8e, 0xd8, + 0x37, 0xb6, 0xa1, 0x99, 0xb0, 0x11, 0x13, 0x6a, 0x54, 0x4a, 0x09, 0x1f, 0xc8, 0xad, 0x96, 0xf4, 0x65, 0x6e, 0xaf, + 0xbe, 0xdc, 0x26, 0x28, 0xa0, 0xa8, 0x81, 0xe5, 0xd0, 0x38, 0x6e, 0x19, 0xc8, 0x85, 0xc5, 0xb0, 0x30, 0x6a, 0x55, + 0xae, 0x26, 0xa3, 0x3a, 0x99, 0xaf, 0x16, 0x17, 0x2a, 0xf4, 0xe0, 0x91, 0x40, 0xce, 0x5f, 0x60, 0xea, 0x60, 0x56, + 0x6a, 0x33, 0x2d, 0x36, 0x51, 0xde, 0x33, 0x1d, 0x92, 0xeb, 0xaf, 0xe1, 0xa1, 0xf2, 0x8b, 0x57, 0xe6, 0x14, 0xf3, + 0x45, 0x1e, 0x4b, 0x5b, 0x63, 0x6e, 0xfd, 0xaf, 0xf2, 0x3e, 0xad, 0x04, 0xec, 0x37, 0x60, 0x53, 0xc6, 0x5a, 0x62, + 0xe3, 0x82, 0xa4, 0xbc, 0x96, 0xa7, 0xf4, 0xbe, 0x86, 0xf0, 0x5d, 0x51, 0xe9, 0x2a, 0x91, 0x75, 0x8d, 0x56, 0xf7, + 0xeb, 0x82, 0xe5, 0x97, 0x07, 0x0c, 0x73, 0x93, 0x51, 0x21, 0x5b, 0x51, 0xb3, 0x29, 0xbf, 0xda, 0xbb, 0xf1, 0x2b, + 0x0f, 0x25, 0x05, 0xd5, 0x2a, 0xd9, 0xbc, 0x72, 0xc3, 0x31, 0x6e, 0xdc, 0x70, 0x8c, 0x7b, 0x14, 0x57, 0xae, 0x50, + 0xad, 0xf3, 0xdf, 0x57, 0xdd, 0x4f, 0x74, 0xd6, 0x86, 0xfa, 0xd4, 0x0d, 0xd7, 0xa6, 0xa7, 0xdf, 0xb0, 0x54, 0x23, + 0x4b, 0xe8, 0xa6, 0xa5, 0x62, 0x32, 0x12, 0x25, 0xa6, 0xab, 0x94, 0x47, 0x7d, 0x8d, 0xb8, 0x00, 0x76, 0x43, 0xf9, + 0x8b, 0x7f, 0x0d, 0xcf, 0x8f, 0x03, 0x54, 0xa2, 0x96, 0x93, 0x2c, 0xe5, 0xad, 0x49, 0x34, 0x8b, 0x93, 0xcb, 0x60, + 0x11, 0xb7, 0x66, 0x59, 0x9a, 0x15, 0x73, 0xa0, 0x4a, 0xaf, 0xb8, 0x04, 0x1d, 0x7e, 0xd6, 0x5a, 0xc4, 0xde, 0x73, + 0x96, 0x9c, 0x31, 0x1e, 0x8f, 0x22, 0xcf, 0xde, 0xcf, 0x81, 0x3d, 0x58, 0xaf, 0xa3, 0x3c, 0xcf, 0xce, 0x6d, 0xef, + 0x5d, 0x76, 0x02, 0x44, 0xeb, 0xbd, 0xb9, 0xb8, 0x3c, 0x65, 0xa9, 0xf7, 0xfe, 0x64, 0x91, 0xf2, 0x85, 0x57, 0x44, + 0x69, 0xd1, 0x2a, 0x58, 0x1e, 0x4f, 0x40, 0x4c, 0x24, 0x59, 0xde, 0xc2, 0xfc, 0xe7, 0x19, 0x0b, 0x92, 0xf8, 0x74, + 0xca, 0xad, 0x71, 0x94, 0x7f, 0xea, 0xb5, 0x5a, 0xf3, 0x3c, 0x9e, 0x45, 0xf9, 0x65, 0x8b, 0x5a, 0x04, 0x9f, 0xb5, + 0x77, 0xa3, 0x2f, 0x26, 0xf7, 0x7b, 0x3c, 0x87, 0xbe, 0x31, 0x62, 0x31, 0x00, 0xe6, 0x63, 0xed, 0x3e, 0x68, 0xcf, + 0x8a, 0x0d, 0x11, 0x51, 0x8a, 0x52, 0x5e, 0x1e, 0x7b, 0x1f, 0x41, 0xb7, 0x3d, 0xf6, 0x4f, 0x78, 0xea, 0x81, 0x2d, + 0xc7, 0xb3, 0x74, 0x39, 0x5a, 0xe4, 0x05, 0x0c, 0x30, 0xcf, 0xe2, 0x94, 0xb3, 0xbc, 0x77, 0x92, 0xe5, 0x80, 0xb6, + 0x56, 0x1e, 0x8d, 0xe3, 0x45, 0x11, 0xdc, 0x9f, 0x5f, 0xf4, 0x50, 0x57, 0x38, 0xcd, 0xb3, 0x45, 0x3a, 0x96, 0x73, + 0xc5, 0x29, 0x1c, 0x8c, 0x98, 0x9b, 0x15, 0xf4, 0x25, 0x14, 0x80, 0x2f, 0x65, 0x51, 0xde, 0x3a, 0xc5, 0xce, 0xa8, + 0xe8, 0xb7, 0xc7, 0xec, 0xd4, 0xcb, 0x4f, 0x4f, 0x22, 0xa7, 0xd3, 0x7d, 0xe4, 0xa9, 0x7f, 0xfe, 0x03, 0x17, 0x14, + 0xf7, 0xb5, 0xc5, 0x9d, 0x76, 0xfb, 0x1f, 0xdc, 0x5e, 0x63, 0x16, 0x02, 0x28, 0xe8, 0xcc, 0x2f, 0xac, 0x22, 0x4b, + 0x60, 0x7f, 0xd6, 0xf5, 0xec, 0xcd, 0xc1, 0x6e, 0x8a, 0xd3, 0xd3, 0xa0, 0x3b, 0xbf, 0x28, 0x71, 0x75, 0x81, 0x48, + 0xc8, 0x94, 0x8b, 0x94, 0x6f, 0xcb, 0x3f, 0x0a, 0xf1, 0xe3, 0xf5, 0x10, 0x77, 0x15, 0xc4, 0x15, 0xd6, 0x5b, 0x63, + 0x38, 0x07, 0x84, 0xfe, 0x4e, 0x21, 0x00, 0x99, 0x82, 0x11, 0x98, 0x2b, 0x38, 0xe8, 0xe5, 0x0f, 0x83, 0xd1, 0x5d, + 0x0f, 0xc6, 0xe3, 0xdb, 0xc0, 0xc8, 0xd3, 0xf1, 0xb2, 0xbe, 0xaf, 0x1d, 0x30, 0x4e, 0x7b, 0x53, 0x86, 0xf4, 0x14, + 0x74, 0xf1, 0xf9, 0x3c, 0x1e, 0xf3, 0xa9, 0x78, 0x24, 0x72, 0x3e, 0x17, 0x75, 0x0f, 0xda, 0x6d, 0xf1, 0x5e, 0x80, + 0x40, 0x0b, 0x3a, 0x3e, 0x36, 0x00, 0x22, 0x7a, 0x71, 0xdd, 0x47, 0x6c, 0x3e, 0xdc, 0xfa, 0xa5, 0x1a, 0xef, 0x52, + 0xe5, 0x0d, 0x0a, 0x11, 0xa1, 0xbe, 0xd9, 0x82, 0x19, 0x6f, 0x45, 0xbf, 0xa3, 0x03, 0x55, 0x83, 0x0f, 0x8c, 0xa4, + 0x5e, 0xc0, 0x3d, 0x33, 0x17, 0xa8, 0x97, 0xf6, 0xd1, 0x25, 0xd5, 0x6a, 0xb9, 0x20, 0x37, 0x18, 0xba, 0x90, 0x28, + 0x20, 0xe8, 0x14, 0x83, 0x9c, 0xbe, 0xa9, 0x91, 0xb9, 0x41, 0xee, 0x64, 0x2e, 0x1c, 0xf9, 0x4c, 0xf3, 0xf5, 0x62, + 0x6b, 0x0b, 0xac, 0xec, 0x17, 0x4c, 0x36, 0x00, 0xee, 0x4d, 0xae, 0xae, 0xef, 0x43, 0x61, 0x4a, 0x29, 0x43, 0x6a, + 0x76, 0xd3, 0x15, 0x7d, 0xd8, 0x95, 0x98, 0x32, 0x92, 0x8f, 0x86, 0xff, 0x0e, 0xc5, 0xde, 0xd1, 0x86, 0x65, 0x91, + 0x2d, 0xf2, 0x11, 0x79, 0xea, 0x56, 0x2d, 0x7e, 0x9b, 0x04, 0xae, 0xed, 0x31, 0xcd, 0xe7, 0xd1, 0x0c, 0xae, 0x7d, + 0xe4, 0x80, 0x53, 0x10, 0x44, 0xdc, 0x31, 0x90, 0x5e, 0x0e, 0x05, 0x21, 0x8a, 0xae, 0x31, 0xe5, 0xbb, 0xd1, 0xfd, + 0x4b, 0x7f, 0x91, 0xc6, 0xc0, 0xe9, 0x3e, 0xc6, 0x63, 0xba, 0x77, 0x12, 0x8f, 0x29, 0x10, 0xd1, 0xa2, 0xc4, 0x23, + 0xf4, 0x6c, 0x43, 0x81, 0xfa, 0x0e, 0x0b, 0x3c, 0xcb, 0x44, 0x16, 0xbb, 0x65, 0x63, 0x30, 0xc1, 0x10, 0x95, 0xe3, + 0x6c, 0x16, 0xc5, 0x69, 0x80, 0xdf, 0x07, 0xf1, 0xf4, 0x88, 0x01, 0x76, 0xf1, 0xe0, 0x27, 0x93, 0xb9, 0x68, 0x1d, + 0xd7, 0xff, 0x05, 0xf8, 0x08, 0xf5, 0x2f, 0xa5, 0x1d, 0xa6, 0xe1, 0x52, 0x61, 0xde, 0x7a, 0x29, 0xf0, 0x1e, 0xae, + 0x74, 0x56, 0x46, 0x7e, 0x8e, 0x3d, 0x4e, 0x3f, 0x06, 0xad, 0x4e, 0xd0, 0xd1, 0xa6, 0x6b, 0xed, 0x36, 0xaa, 0xc8, + 0x65, 0x91, 0x37, 0x1a, 0x09, 0x06, 0xfd, 0x2c, 0xe0, 0xac, 0xde, 0x35, 0xac, 0x9e, 0xa4, 0x4b, 0x74, 0xe0, 0x9c, + 0xa6, 0x4e, 0x0d, 0x08, 0x8a, 0x05, 0x5c, 0x33, 0x95, 0x5b, 0x46, 0x24, 0x94, 0xbe, 0xa4, 0x03, 0x5c, 0xbf, 0x4b, + 0x84, 0xf7, 0x86, 0xea, 0x29, 0x50, 0x8a, 0xe4, 0x16, 0xc7, 0x7b, 0xe2, 0xc4, 0x5b, 0x44, 0x63, 0xa1, 0x0d, 0x47, + 0xd0, 0xb6, 0xfe, 0x32, 0x02, 0x2c, 0x7d, 0x0a, 0xed, 0xcd, 0xa5, 0xa3, 0x12, 0xeb, 0x73, 0x98, 0x6b, 0x5f, 0x48, + 0x3d, 0xba, 0x91, 0x6f, 0xf7, 0x37, 0x97, 0xbc, 0xdc, 0xdb, 0x11, 0xbd, 0xfb, 0xc7, 0x65, 0x41, 0x02, 0xca, 0x74, + 0xa4, 0x55, 0x53, 0x88, 0x3a, 0x18, 0x96, 0xd2, 0x77, 0x71, 0xdc, 0x42, 0x2b, 0x5d, 0xc2, 0x63, 0x2c, 0xc9, 0x2e, + 0xc7, 0x74, 0xa5, 0x28, 0x87, 0x33, 0xa9, 0x13, 0x52, 0x72, 0x91, 0x83, 0xd1, 0x5b, 0x85, 0xe2, 0x18, 0x21, 0x18, + 0x6c, 0x2e, 0xe3, 0x32, 0xdc, 0x5c, 0x66, 0xe5, 0x31, 0x68, 0x26, 0x08, 0x55, 0xa1, 0x3e, 0xef, 0x02, 0x13, 0x0b, + 0x27, 0x8b, 0x45, 0x23, 0xe0, 0xb4, 0xac, 0xb4, 0xad, 0x81, 0x80, 0x06, 0x2c, 0x40, 0x2c, 0x00, 0xdd, 0x8d, 0x7a, + 0x31, 0x58, 0x8b, 0x68, 0xdd, 0x87, 0x81, 0xf6, 0x76, 0x44, 0x23, 0x58, 0x57, 0x8e, 0x20, 0x57, 0xcb, 0xc2, 0x74, + 0x1c, 0x73, 0x69, 0x49, 0x74, 0xc2, 0x12, 0xe8, 0x9f, 0x5f, 0x5d, 0xb5, 0xa1, 0x9b, 0x78, 0xb5, 0xf6, 0xe2, 0x74, + 0xbe, 0x90, 0xdf, 0xd4, 0x82, 0x59, 0x3a, 0x18, 0xe6, 0xc4, 0x94, 0xff, 0x81, 0x8a, 0xdb, 0x05, 0x36, 0x8d, 0x6b, + 0x03, 0x3c, 0x14, 0x32, 0x40, 0x50, 0x2a, 0x1a, 0x80, 0xd2, 0x78, 0xbc, 0x5a, 0xa6, 0x97, 0x51, 0xc0, 0x0b, 0x9c, + 0xc1, 0x39, 0x3e, 0xa7, 0xf0, 0x3c, 0x8b, 0x53, 0x7c, 0xcc, 0xf1, 0x31, 0xba, 0xc0, 0xc7, 0xac, 0xb4, 0xff, 0x2e, + 0xe8, 0xb6, 0x34, 0x02, 0xb2, 0xab, 0x2b, 0x60, 0xee, 0x1a, 0x05, 0x40, 0x10, 0xe2, 0xdb, 0x2a, 0xcc, 0xc4, 0x16, + 0x2b, 0xe6, 0x2d, 0x51, 0x6e, 0x91, 0xf0, 0x0c, 0xc1, 0xb6, 0xca, 0x9d, 0x86, 0x8e, 0xe0, 0xc9, 0x2c, 0x92, 0x27, + 0xf8, 0xe2, 0xda, 0x96, 0xf8, 0xf8, 0x85, 0x40, 0x07, 0x3d, 0xe2, 0xda, 0x74, 0x19, 0x97, 0x9f, 0xb5, 0x89, 0x43, + 0x1b, 0x67, 0x01, 0x35, 0x0d, 0x99, 0x3d, 0x8f, 0xe2, 0x44, 0x34, 0x5e, 0xb3, 0x92, 0x46, 0x3a, 0x20, 0x2d, 0x64, + 0x6f, 0xa7, 0x82, 0x0d, 0x80, 0x1f, 0x89, 0xcb, 0xd4, 0x15, 0xf4, 0xb6, 0xa8, 0xa2, 0x28, 0xb9, 0x3c, 0xbc, 0x03, + 0xe1, 0x0f, 0xd7, 0xeb, 0x1c, 0x82, 0x5d, 0x17, 0xa5, 0xf5, 0x16, 0x00, 0xf1, 0x9c, 0xb1, 0xb1, 0x67, 0x5b, 0xc0, + 0x26, 0xc5, 0xf3, 0xc7, 0x84, 0x9d, 0x31, 0xf9, 0x11, 0x14, 0xdd, 0x57, 0x57, 0x8e, 0x40, 0xda, 0x72, 0x79, 0x3f, + 0x53, 0x52, 0x9e, 0x5a, 0x97, 0x5c, 0x7d, 0x1d, 0x78, 0xcf, 0x36, 0x06, 0x6d, 0xce, 0xd1, 0xae, 0x0f, 0xeb, 0x75, + 0x40, 0x91, 0xb5, 0x01, 0x4c, 0xd2, 0xcf, 0x6e, 0x5a, 0x0a, 0xf4, 0x63, 0x93, 0x09, 0x1c, 0x00, 0x15, 0xb7, 0xd0, + 0xa7, 0x5b, 0x00, 0x03, 0x66, 0xa6, 0x67, 0x8b, 0x16, 0x76, 0xd5, 0x56, 0x3f, 0x21, 0x2a, 0x92, 0x6c, 0xf4, 0xa9, + 0x36, 0xc5, 0x02, 0x09, 0x08, 0xc7, 0x6a, 0xf0, 0x29, 0xfb, 0xdf, 0xfe, 0xf5, 0x7f, 0xfe, 0x57, 0x18, 0x8e, 0x3a, + 0xb8, 0xa5, 0x75, 0x7d, 0xab, 0xff, 0x01, 0xad, 0x16, 0xe9, 0x2d, 0xed, 0xfe, 0xf6, 0xcf, 0xff, 0x0d, 0x9a, 0xd1, + 0xcd, 0x1a, 0xb7, 0x3c, 0x0e, 0xec, 0x11, 0x6a, 0x32, 0x77, 0x03, 0xa4, 0xd6, 0xf5, 0xda, 0xf1, 0xff, 0x05, 0x81, + 0x2d, 0x78, 0x36, 0xbf, 0x11, 0x08, 0x84, 0x75, 0x94, 0x64, 0x05, 0x13, 0x50, 0x08, 0x36, 0x79, 0x47, 0x30, 0x68, + 0x86, 0x39, 0x90, 0x6c, 0x61, 0x89, 0xde, 0x02, 0xfb, 0xb5, 0xde, 0x8d, 0x5d, 0x29, 0x18, 0x27, 0xd0, 0xc9, 0x03, + 0x00, 0xfb, 0x20, 0x9e, 0xe0, 0x81, 0x4e, 0x33, 0x6c, 0xbb, 0xce, 0x17, 0x68, 0x0c, 0xa1, 0x89, 0x4c, 0x8c, 0x20, + 0x5c, 0x1d, 0xaa, 0x1f, 0xfc, 0x04, 0xd6, 0xf2, 0x51, 0x3f, 0x47, 0x17, 0xfa, 0x19, 0xd9, 0x0f, 0x0c, 0x0b, 0x82, + 0x62, 0x86, 0x3a, 0x40, 0x73, 0x61, 0xea, 0xa4, 0x56, 0xfc, 0x81, 0xa9, 0xe4, 0xb0, 0x8f, 0x98, 0x0f, 0x89, 0xb7, + 0x5f, 0x16, 0x39, 0xab, 0x38, 0x26, 0x36, 0x10, 0xac, 0xc8, 0xac, 0xff, 0x98, 0x64, 0xe7, 0xe5, 0x75, 0x75, 0x53, + 0xa0, 0xe2, 0x72, 0x6f, 0x1c, 0x9f, 0xf5, 0x25, 0x22, 0x1b, 0x6b, 0x59, 0xed, 0xd2, 0x5c, 0x18, 0x56, 0xc9, 0x75, + 0xc9, 0x47, 0x5c, 0x96, 0xd7, 0x46, 0x01, 0x80, 0xe3, 0xee, 0x9d, 0xe4, 0x7d, 0xb9, 0x80, 0x57, 0x78, 0x61, 0x8b, + 0x20, 0x41, 0x3e, 0x2e, 0x64, 0x0c, 0x27, 0x19, 0x63, 0xb2, 0x7a, 0xd4, 0x5a, 0x33, 0xc5, 0xd2, 0xb1, 0x61, 0x8d, + 0x0b, 0x73, 0xc9, 0x85, 0x63, 0xa9, 0x0e, 0x49, 0x2e, 0x8c, 0x1f, 0xe0, 0x68, 0x70, 0xe1, 0xf8, 0x5a, 0x2e, 0x8c, + 0x6b, 0x1b, 0xe0, 0xc8, 0xa1, 0xbd, 0x8d, 0xb6, 0xb8, 0x21, 0x15, 0x38, 0x0a, 0x37, 0x30, 0xc0, 0x46, 0x9f, 0xa4, + 0x6c, 0x23, 0x50, 0xd1, 0x18, 0x95, 0xd2, 0x9a, 0x04, 0x9b, 0x64, 0xcf, 0xc1, 0xe2, 0x18, 0x64, 0x9b, 0x39, 0x32, + 0x58, 0xc2, 0x13, 0x86, 0xc7, 0xff, 0x78, 0x07, 0xfb, 0x8a, 0xcd, 0x2c, 0xe9, 0x19, 0xa4, 0xcf, 0x0e, 0x0d, 0xe0, + 0x2d, 0x85, 0x3b, 0x23, 0xb0, 0xdf, 0xbe, 0x39, 0x38, 0xb4, 0xbd, 0x93, 0x6c, 0x7c, 0x19, 0xd8, 0xa0, 0x8a, 0x82, + 0x24, 0x73, 0x7d, 0x3e, 0x65, 0xa9, 0xa3, 0x94, 0xc1, 0x2c, 0x01, 0x65, 0x38, 0x3b, 0x15, 0xb7, 0xef, 0x9b, 0xae, + 0x58, 0x40, 0x1b, 0x7d, 0x9e, 0xaf, 0xbf, 0xc7, 0xc5, 0x97, 0x2b, 0x79, 0x8e, 0x8f, 0x7d, 0x0c, 0x46, 0xef, 0xed, + 0xc0, 0x03, 0xbe, 0x1c, 0x20, 0x05, 0xe9, 0x37, 0x01, 0x67, 0x21, 0xde, 0x77, 0xb0, 0xfd, 0x8e, 0xea, 0x8b, 0x50, + 0x28, 0x1a, 0xd0, 0xfa, 0x5a, 0xa5, 0x04, 0xd0, 0xd8, 0x63, 0x22, 0x41, 0xdc, 0x18, 0xc0, 0x01, 0x1f, 0xeb, 0x12, + 0x41, 0xa6, 0x46, 0x11, 0x8d, 0x52, 0xb1, 0x7f, 0x59, 0x85, 0x13, 0x12, 0xfa, 0xc4, 0x64, 0xf0, 0x93, 0xc0, 0x3f, + 0x36, 0xbf, 0x34, 0x25, 0x3e, 0x0a, 0xa3, 0x17, 0x79, 0xf4, 0x57, 0xb0, 0x61, 0xbd, 0xf3, 0x63, 0x6a, 0xa9, 0xcc, + 0x1a, 0xb4, 0xb7, 0xd1, 0xfc, 0x6b, 0x2b, 0xfb, 0x15, 0x24, 0x5e, 0x12, 0xcd, 0x0b, 0x16, 0xa8, 0x07, 0x69, 0xe1, + 0xa0, 0xa1, 0xb4, 0x6a, 0x52, 0x9a, 0x92, 0xb1, 0xe4, 0xd3, 0xa5, 0x69, 0x02, 0x3d, 0x04, 0x13, 0x08, 0xd3, 0xb7, + 0x5b, 0x11, 0xb0, 0xf7, 0x34, 0x48, 0xd8, 0x84, 0x97, 0x1c, 0xef, 0x07, 0x2f, 0x95, 0xcd, 0xe9, 0x77, 0x1f, 0x80, + 0x59, 0x64, 0xf9, 0xf8, 0xff, 0x6d, 0x63, 0x8f, 0x83, 0x14, 0xcc, 0x18, 0xba, 0x30, 0x80, 0x97, 0xb1, 0x00, 0x22, + 0xf3, 0x7d, 0x69, 0x4c, 0x34, 0x62, 0x68, 0x8f, 0x97, 0x3c, 0xb7, 0xf8, 0xd4, 0xe3, 0xb9, 0xd9, 0x0e, 0x34, 0xa5, + 0x15, 0xa3, 0x7c, 0xd5, 0x2c, 0xdc, 0x75, 0xa5, 0xf2, 0xb8, 0xda, 0x58, 0xd9, 0xd6, 0xf5, 0xb7, 0x15, 0x0c, 0x19, + 0x5e, 0x80, 0x52, 0x70, 0xbe, 0xa5, 0xe8, 0x61, 0xae, 0x69, 0xd5, 0x3f, 0x70, 0xab, 0xee, 0x51, 0xd2, 0xd9, 0x3e, + 0xa2, 0xb3, 0x4d, 0xcc, 0x65, 0xb8, 0x14, 0x73, 0x8f, 0xa2, 0x64, 0xe4, 0x20, 0x00, 0x56, 0xcb, 0xba, 0x0f, 0xd8, + 0x04, 0x2e, 0x3d, 0x2c, 0xcb, 0xde, 0x25, 0x73, 0x8e, 0x7e, 0x93, 0x79, 0xe4, 0xe2, 0xfa, 0xa0, 0xfe, 0x04, 0x5b, + 0xbb, 0x74, 0x87, 0xde, 0xf7, 0xc6, 0x77, 0xad, 0x6c, 0x45, 0xa9, 0xb6, 0x07, 0xf8, 0xfd, 0x3e, 0xc4, 0xbe, 0xaf, + 0x1c, 0x1b, 0xb5, 0x10, 0xaa, 0xb9, 0x6c, 0x11, 0xe1, 0xd8, 0xd8, 0x4d, 0x78, 0x41, 0xbf, 0xba, 0xce, 0x98, 0xfd, + 0xee, 0x76, 0x63, 0x96, 0xdd, 0xd1, 0x98, 0xfd, 0xee, 0x4f, 0x36, 0x66, 0xbf, 0x6a, 0x1a, 0xb3, 0xbf, 0xfe, 0x1e, + 0x63, 0x36, 0xcf, 0xce, 0x8b, 0xb0, 0x23, 0x83, 0xa7, 0xc0, 0x4c, 0xfe, 0x3e, 0x56, 0x2d, 0x4c, 0xd4, 0xb0, 0x69, + 0xc9, 0x88, 0x15, 0xf9, 0x5e, 0xc0, 0xab, 0xa5, 0x09, 0xd9, 0xd6, 0x89, 0x55, 0xad, 0xfb, 0xea, 0x26, 0x09, 0xe8, + 0xf5, 0xae, 0xbe, 0x03, 0xd5, 0x55, 0x46, 0x66, 0x40, 0x9f, 0x82, 0xd4, 0x1d, 0xbb, 0xdb, 0x2a, 0xa3, 0xc7, 0x1c, + 0xa1, 0xa7, 0x1c, 0xb5, 0x82, 0x7c, 0x96, 0xf6, 0x7f, 0x3a, 0xea, 0xf4, 0x76, 0x3b, 0x33, 0xe8, 0x0d, 0x72, 0x0b, + 0xde, 0xda, 0xbd, 0xdd, 0x5d, 0x7c, 0x3b, 0x57, 0x6f, 0x5d, 0x7c, 0x8b, 0xd5, 0xdb, 0x03, 0x7c, 0x1b, 0xa9, 0xb7, + 0x87, 0xf8, 0x36, 0x56, 0x6f, 0x8f, 0xf0, 0xed, 0xcc, 0x2e, 0x8f, 0xb8, 0x06, 0xee, 0x11, 0xd0, 0x15, 0x29, 0x89, + 0x81, 0x2a, 0x83, 0xd3, 0x88, 0x37, 0xb0, 0xa2, 0xd3, 0x20, 0xf6, 0x84, 0x02, 0x1d, 0x14, 0xde, 0x39, 0xb0, 0xf4, + 0x80, 0x12, 0x8e, 0x9e, 0xe2, 0x55, 0x7c, 0xd0, 0x3d, 0x0f, 0xe3, 0x19, 0x53, 0xdf, 0x24, 0x55, 0xab, 0x06, 0x35, + 0x05, 0xec, 0xed, 0xb2, 0xa7, 0xf7, 0x49, 0xd8, 0xd0, 0x2a, 0x77, 0x82, 0x76, 0xae, 0xaa, 0x13, 0xd3, 0xb5, 0xf4, + 0x0e, 0x5f, 0x23, 0x20, 0x40, 0x00, 0x2b, 0xa3, 0x74, 0x02, 0x6a, 0x40, 0xeb, 0x02, 0x94, 0xf4, 0xb5, 0x42, 0x03, + 0x21, 0xd2, 0x62, 0x82, 0xd6, 0xa4, 0xdf, 0x0e, 0xa3, 0x53, 0xfd, 0xfc, 0x0a, 0xf4, 0xa9, 0xe8, 0x94, 0xdd, 0x26, + 0x40, 0x08, 0x44, 0x53, 0x78, 0x28, 0x20, 0x48, 0x0b, 0x81, 0xad, 0x41, 0x63, 0x41, 0x0a, 0x0f, 0xc4, 0x4e, 0x5d, + 0x9c, 0xd0, 0xf4, 0xf5, 0x22, 0xc0, 0x68, 0x55, 0xb0, 0x07, 0x6a, 0x1d, 0x95, 0x0a, 0x0c, 0x43, 0x05, 0x16, 0xdc, + 0x28, 0x63, 0x84, 0x2a, 0x72, 0x93, 0xa4, 0xb1, 0x94, 0x90, 0x31, 0x1d, 0xbc, 0xda, 0xbb, 0xbb, 0xca, 0xf7, 0x3e, + 0xeb, 0x8c, 0xf0, 0x8f, 0xe4, 0xaa, 0x9f, 0x4d, 0x26, 0x93, 0x1b, 0x85, 0xce, 0x67, 0xe3, 0x09, 0xeb, 0xb2, 0x07, + 0x3d, 0x74, 0xfe, 0xb5, 0xa4, 0x2f, 0xae, 0x53, 0x12, 0xee, 0x96, 0x77, 0x6b, 0x8c, 0xce, 0x38, 0x90, 0x43, 0x77, + 0x97, 0x4e, 0x25, 0x60, 0x65, 0x09, 0x5c, 0xf9, 0x34, 0x4e, 0x83, 0x76, 0xe9, 0x9f, 0x49, 0x76, 0xfe, 0xd9, 0xe3, + 0xc7, 0x8f, 0x4b, 0x7f, 0xac, 0xde, 0xda, 0xe3, 0x71, 0xe9, 0x8f, 0x96, 0x7a, 0x19, 0xed, 0xf6, 0x64, 0x52, 0xfa, + 0xb1, 0x2a, 0xd8, 0xed, 0x8e, 0xc6, 0xbb, 0xdd, 0xd2, 0x3f, 0x37, 0x5a, 0x94, 0x3e, 0x93, 0x6f, 0x39, 0x1b, 0xd7, + 0x3c, 0x88, 0x8f, 0xc0, 0x78, 0xf5, 0x05, 0xa1, 0x2d, 0xd1, 0x64, 0x10, 0x8f, 0x41, 0xb4, 0xe0, 0x60, 0xeb, 0x02, + 0x6f, 0x67, 0xc0, 0x9f, 0x27, 0x92, 0xb7, 0x8b, 0x4f, 0x7e, 0x22, 0x47, 0xff, 0xd5, 0xe4, 0xe8, 0x48, 0xcc, 0xc4, + 0xcd, 0x19, 0xc9, 0x81, 0x66, 0x35, 0x52, 0x16, 0x55, 0xff, 0x1a, 0xb2, 0x8a, 0xd9, 0x23, 0xb7, 0xc1, 0x96, 0x82, + 0xc7, 0x7f, 0x7d, 0x1d, 0x8f, 0xff, 0xe6, 0x76, 0x1e, 0x7f, 0x72, 0x37, 0x16, 0xff, 0xcd, 0x9f, 0xcc, 0xe2, 0xbf, + 0x6e, 0xb2, 0xf8, 0xcd, 0x3b, 0xb1, 0xf8, 0x35, 0x89, 0x1f, 0xa4, 0x9a, 0xbe, 0x49, 0x43, 0xfb, 0x0d, 0xd8, 0x30, + 0x46, 0xc9, 0x64, 0x02, 0x45, 0x93, 0x89, 0xad, 0x92, 0x1d, 0x81, 0x13, 0x51, 0xab, 0xd7, 0xb5, 0x12, 0x6a, 0xf5, + 0xd5, 0x57, 0x66, 0x99, 0x59, 0x20, 0xfd, 0x0d, 0xa6, 0x7c, 0x57, 0x35, 0x52, 0x65, 0x56, 0x9f, 0x06, 0x19, 0xc7, + 0x05, 0x9e, 0x26, 0x2c, 0x28, 0x79, 0x76, 0x7a, 0x9a, 0x30, 0xfd, 0xed, 0x33, 0xd5, 0xd2, 0x7c, 0x33, 0xe7, 0x33, + 0xcb, 0x07, 0x26, 0xb4, 0x41, 0x0d, 0xd0, 0x9e, 0x70, 0x64, 0xd2, 0xe7, 0xa0, 0x45, 0xd8, 0xfa, 0x4c, 0x7e, 0x37, + 0x98, 0xfc, 0xa9, 0x4b, 0xc9, 0x7e, 0x65, 0x40, 0xb3, 0xea, 0x8a, 0x2e, 0x4c, 0x91, 0x02, 0x32, 0x2e, 0x95, 0xdb, + 0x12, 0xa0, 0x9d, 0xe3, 0x47, 0x4e, 0x74, 0xca, 0xd2, 0xca, 0x37, 0x85, 0x34, 0x9b, 0xc0, 0x8f, 0x1e, 0x88, 0x29, + 0xc4, 0x67, 0x02, 0xf5, 0xb8, 0x22, 0x0e, 0xe8, 0xd4, 0xd6, 0x68, 0xac, 0x2a, 0x0c, 0xcd, 0xa5, 0xa8, 0x9c, 0x93, + 0xd5, 0x79, 0xd6, 0x8a, 0xe6, 0xeb, 0x85, 0xf2, 0xdd, 0xa6, 0xbb, 0x45, 0x34, 0x14, 0xe7, 0x76, 0x5f, 0xdb, 0x98, + 0x35, 0x9a, 0x29, 0xeb, 0x5e, 0x38, 0x9a, 0xe8, 0x24, 0xbb, 0xa8, 0xdb, 0x48, 0x26, 0x0c, 0x68, 0x3e, 0xe9, 0xbd, + 0x57, 0x75, 0xaa, 0xa0, 0x34, 0xbd, 0xa2, 0x22, 0xd3, 0x8b, 0x48, 0x83, 0x7c, 0x60, 0xb0, 0x03, 0xa9, 0x60, 0xca, + 0x30, 0x0f, 0x71, 0x17, 0x6d, 0x47, 0xa0, 0x32, 0x6d, 0x2b, 0x60, 0x51, 0x3a, 0xe4, 0xe8, 0x6b, 0xc2, 0x0e, 0x7d, + 0xab, 0x06, 0x70, 0xaa, 0x6d, 0xb3, 0xdb, 0x19, 0x3e, 0x98, 0x16, 0xe7, 0xc7, 0x7e, 0x71, 0xee, 0xc1, 0x3f, 0xeb, + 0xf3, 0x25, 0xb0, 0xb0, 0x93, 0x4f, 0x31, 0x07, 0x85, 0x71, 0xde, 0x42, 0xa3, 0x98, 0xdc, 0x3b, 0x92, 0xd7, 0x53, + 0xa8, 0x45, 0x5c, 0x89, 0xe8, 0x2d, 0x0a, 0xb4, 0x40, 0x48, 0xd5, 0x0e, 0xd2, 0x2c, 0x65, 0xbd, 0x7a, 0x48, 0xcd, + 0xd4, 0x76, 0x15, 0xb6, 0x86, 0xcb, 0x0c, 0x2d, 0x16, 0x7e, 0x09, 0x16, 0x8b, 0x90, 0x11, 0x6d, 0x15, 0x8e, 0x69, + 0xaf, 0x6d, 0x1f, 0x48, 0x64, 0x6e, 0x93, 0x28, 0xcc, 0x57, 0x55, 0xfa, 0xeb, 0x54, 0xf2, 0xdb, 0x02, 0x4c, 0xdd, + 0x07, 0x0f, 0x3c, 0xf5, 0xcf, 0x88, 0xcc, 0x35, 0x8b, 0x29, 0xc0, 0x74, 0x17, 0xc8, 0x82, 0x68, 0x82, 0x5f, 0x10, + 0xbb, 0x4b, 0xcb, 0x13, 0xca, 0xee, 0x5a, 0xa2, 0xcc, 0x0a, 0x3a, 0x8f, 0xc1, 0xc6, 0xb8, 0xf3, 0xf0, 0x37, 0x2f, + 0xbf, 0x94, 0x38, 0x52, 0x97, 0xf4, 0x6c, 0xbb, 0x87, 0xa7, 0x39, 0x89, 0x2e, 0xc1, 0xd4, 0x21, 0x01, 0x7a, 0x82, + 0xce, 0xae, 0xde, 0x3c, 0x93, 0x91, 0xd2, 0x9c, 0x25, 0xf4, 0x99, 0x7e, 0xb9, 0x15, 0xbb, 0x0f, 0xe7, 0x17, 0x6a, + 0x37, 0x3a, 0x8d, 0x08, 0xe8, 0x9f, 0x1a, 0xe8, 0xbc, 0x3e, 0xb2, 0x5a, 0x0f, 0xd6, 0x3d, 0x00, 0x18, 0x84, 0xd4, + 0x6e, 0xe5, 0x02, 0xaa, 0x36, 0x94, 0x18, 0xa1, 0xde, 0x6a, 0x20, 0xcb, 0xdf, 0x05, 0x09, 0x11, 0x81, 0xbd, 0x8b, + 0x9f, 0x72, 0x8b, 0xc1, 0xa0, 0x92, 0x9a, 0xc1, 0x2c, 0x1e, 0x8f, 0x13, 0xd6, 0x53, 0xc2, 0xdf, 0xea, 0x3c, 0xc4, + 0x48, 0xa9, 0xb9, 0x65, 0xf5, 0x5d, 0x31, 0x90, 0xa7, 0xf1, 0x14, 0x9d, 0x80, 0x32, 0x82, 0xdf, 0x63, 0x5b, 0x8b, + 0x4e, 0x19, 0x42, 0x6c, 0x57, 0xc8, 0xa3, 0xe7, 0xfa, 0x5a, 0x1e, 0x80, 0x26, 0x44, 0x1b, 0x0e, 0x46, 0x75, 0x36, + 0x0f, 0x5a, 0xbb, 0xf5, 0x85, 0x60, 0x95, 0x5e, 0x82, 0xb7, 0x66, 0x59, 0x1e, 0xd0, 0x44, 0x4b, 0x7c, 0xf8, 0xc7, + 0xf2, 0x3b, 0xb2, 0x8c, 0x06, 0xc0, 0x6f, 0x7e, 0xe9, 0xa2, 0xb2, 0xbe, 0x98, 0xff, 0x3f, 0xa7, 0xe5, 0x8b, 0xf5, + 0xa7, 0xe5, 0x0b, 0x75, 0x5a, 0x6e, 0xa6, 0xd8, 0xcf, 0x26, 0x1d, 0xfc, 0xd3, 0xab, 0x16, 0x04, 0xbb, 0x02, 0xe8, + 0xb0, 0x50, 0xe9, 0x6b, 0x75, 0xe1, 0x3f, 0x1a, 0xba, 0xed, 0xe1, 0x1f, 0x1f, 0xd4, 0x9b, 0xb6, 0x85, 0x85, 0xf8, + 0xaf, 0x5d, 0xab, 0xea, 0xdc, 0xc7, 0x3a, 0xec, 0xf5, 0x60, 0xb5, 0xae, 0x7b, 0xf3, 0xa1, 0x05, 0x7e, 0xc5, 0x9d, + 0x40, 0x31, 0x63, 0xb0, 0x43, 0xa2, 0x93, 0x13, 0x28, 0x9d, 0x64, 0xa3, 0x45, 0xf1, 0x8f, 0x12, 0x7e, 0x89, 0xc4, + 0x1b, 0x8f, 0x74, 0x63, 0x1c, 0xd5, 0x55, 0x84, 0xdd, 0xd5, 0x08, 0x4b, 0xbd, 0x4f, 0x41, 0x01, 0x84, 0xc9, 0x9c, + 0xae, 0x7f, 0x7f, 0xcd, 0x21, 0xf8, 0xbb, 0xec, 0xcd, 0xda, 0xc5, 0xfc, 0x7b, 0x91, 0x71, 0x23, 0x12, 0x7e, 0x17, + 0x0e, 0xcc, 0x3d, 0x6c, 0x3f, 0x5e, 0x0f, 0xee, 0x91, 0x9a, 0x69, 0xa8, 0x84, 0x82, 0x94, 0x3b, 0xa0, 0xe2, 0x46, + 0x8b, 0x84, 0xdf, 0x3c, 0xea, 0x75, 0x94, 0xb1, 0x32, 0xea, 0x0d, 0x0c, 0xbd, 0x6a, 0x7b, 0x47, 0x2e, 0xfd, 0xd9, + 0x17, 0xf7, 0xf1, 0x8f, 0xf0, 0xea, 0x9c, 0x54, 0x8a, 0xbf, 0x30, 0x7c, 0x51, 0xf1, 0xdf, 0xac, 0x69, 0xf6, 0x42, + 0x82, 0x93, 0x72, 0x7f, 0xd7, 0xd6, 0xa8, 0xcf, 0xde, 0xa9, 0xb9, 0xd4, 0x83, 0x7e, 0x57, 0xeb, 0xdf, 0x37, 0xf8, + 0x1d, 0xdb, 0x8e, 0x84, 0xce, 0x5c, 0x6f, 0x2b, 0x7f, 0x65, 0xc2, 0x6a, 0x63, 0x81, 0xe7, 0xbb, 0x36, 0x57, 0x1b, + 0x44, 0xed, 0x37, 0xc3, 0x13, 0x6d, 0x1e, 0xc9, 0xb0, 0x1b, 0xb6, 0x17, 0x16, 0xd2, 0xb7, 0x2c, 0xbc, 0x87, 0x9f, + 0x1a, 0xb2, 0x2e, 0x66, 0x49, 0x0a, 0x3a, 0xd5, 0x94, 0xf3, 0x79, 0xb0, 0xb3, 0x73, 0x7e, 0x7e, 0xee, 0x9f, 0xef, + 0xfa, 0x59, 0x7e, 0xba, 0xd3, 0x6d, 0xb7, 0xdb, 0xf8, 0x85, 0x18, 0xdb, 0x3a, 0x8b, 0xd9, 0xf9, 0x97, 0xd9, 0x45, + 0x68, 0x3f, 0xb2, 0x1e, 0x5b, 0x8f, 0x76, 0xad, 0x07, 0x0f, 0x6d, 0x8b, 0xb8, 0x3f, 0x94, 0xec, 0xda, 0x96, 0xe0, + 0xfe, 0xa1, 0x0d, 0xc5, 0xfd, 0xbd, 0x53, 0xa5, 0xc0, 0x61, 0x06, 0xae, 0x50, 0x8f, 0xc0, 0x66, 0xc9, 0x3e, 0xb1, + 0xfa, 0x39, 0x17, 0x65, 0x2d, 0x29, 0x43, 0xd4, 0x2b, 0x1e, 0xf6, 0x51, 0x34, 0x0f, 0x88, 0x86, 0xcc, 0x42, 0x74, + 0x00, 0x89, 0x52, 0x9a, 0x02, 0xa3, 0xba, 0x27, 0xf0, 0x04, 0x1a, 0xfb, 0xd4, 0x82, 0xe7, 0x57, 0xdd, 0x47, 0x20, + 0xe0, 0xce, 0x5a, 0xf7, 0x47, 0xed, 0x56, 0xc7, 0xea, 0xb4, 0xba, 0xfe, 0x23, 0xab, 0x2b, 0xfe, 0x07, 0x06, 0xb9, + 0x6b, 0x75, 0xe0, 0x69, 0xd7, 0x82, 0xf7, 0xb3, 0xfb, 0x22, 0x24, 0x1c, 0xd9, 0x3b, 0xfd, 0x3d, 0xfc, 0x85, 0x29, + 0xb0, 0xa8, 0x2f, 0x6c, 0xf1, 0x2b, 0x9e, 0xec, 0xcf, 0xcc, 0xd2, 0xce, 0xe3, 0xb5, 0xc5, 0xdd, 0x47, 0x6b, 0x8b, + 0x77, 0x1f, 0xae, 0x2d, 0xbe, 0xff, 0xa0, 0x5e, 0xbc, 0x73, 0x2a, 0xaa, 0x34, 0x53, 0x08, 0xed, 0x59, 0x04, 0x54, + 0x72, 0xe1, 0x74, 0x00, 0xce, 0xb6, 0xd5, 0xc2, 0x1f, 0x8f, 0xba, 0xae, 0xee, 0x75, 0x82, 0xbd, 0xf4, 0x2a, 0x1f, + 0x3d, 0x86, 0x55, 0x3e, 0xef, 0x3e, 0x1c, 0x61, 0x3b, 0x5a, 0x28, 0xfc, 0x3b, 0xdb, 0x7d, 0x3c, 0x02, 0x71, 0x60, + 0xe1, 0x3f, 0xf8, 0x33, 0x7d, 0xd0, 0x1d, 0x89, 0x97, 0x36, 0xd6, 0x7f, 0xe8, 0x3c, 0x2a, 0xa0, 0x29, 0xfe, 0xf9, + 0x4d, 0xeb, 0xcf, 0xa8, 0xbe, 0x9b, 0xe3, 0xde, 0x07, 0x1c, 0x3d, 0x9e, 0x76, 0xfd, 0x2f, 0xce, 0x1e, 0xf9, 0x8f, + 0xa7, 0x9d, 0x47, 0x1f, 0xc4, 0x5b, 0x02, 0x18, 0xfc, 0x02, 0xff, 0x7d, 0xd8, 0x6d, 0x83, 0x69, 0xeb, 0x3f, 0x3e, + 0xdb, 0xf5, 0x77, 0x93, 0xd6, 0x43, 0xff, 0x31, 0xfe, 0xab, 0x86, 0x9b, 0x66, 0x33, 0x66, 0x5b, 0xb8, 0xdf, 0x0d, + 0xbb, 0xd0, 0x9c, 0xa3, 0x7b, 0xdf, 0x7a, 0x70, 0xff, 0xf9, 0x63, 0xd8, 0xa3, 0x69, 0xa7, 0x0b, 0xff, 0x5f, 0xf7, + 0xf8, 0x01, 0x11, 0x2f, 0x07, 0x8e, 0x18, 0xe6, 0xce, 0x29, 0xc4, 0xd1, 0xd7, 0x8a, 0xee, 0x79, 0x3f, 0x5e, 0x67, + 0xda, 0xff, 0x70, 0xbb, 0x69, 0xff, 0xd7, 0x3b, 0xba, 0x6f, 0x7f, 0xf8, 0x93, 0x6d, 0xfb, 0x1f, 0x9b, 0xb6, 0xfd, + 0x39, 0x5b, 0x31, 0xee, 0x9b, 0xf6, 0xd9, 0x21, 0x73, 0x8e, 0xbe, 0x65, 0x43, 0xcc, 0x13, 0x85, 0xd6, 0x7f, 0xad, + 0x79, 0x3a, 0x32, 0x3c, 0xc8, 0xe7, 0x4c, 0x9c, 0xe4, 0xef, 0xaf, 0x43, 0x08, 0xe3, 0xb7, 0x22, 0xe4, 0xc5, 0xdd, + 0xf0, 0x41, 0x9f, 0x16, 0xff, 0x13, 0xf1, 0xf1, 0xbd, 0x89, 0x8f, 0x9a, 0x2f, 0x99, 0x8c, 0x79, 0xb2, 0xc1, 0x0f, + 0xe8, 0xdd, 0xb1, 0x77, 0x18, 0xbe, 0x15, 0xb6, 0x48, 0x7e, 0x7a, 0xf7, 0x7b, 0xfc, 0xde, 0x22, 0x8d, 0x32, 0xb4, + 0xa5, 0x83, 0x62, 0x8e, 0x1f, 0xe3, 0x54, 0x2f, 0x67, 0x22, 0x55, 0x3f, 0xa4, 0x7b, 0x36, 0xf7, 0xb5, 0x73, 0x03, + 0x33, 0x5b, 0xc3, 0x65, 0xc6, 0x23, 0xfc, 0x6d, 0x2f, 0x3c, 0xe6, 0x09, 0xde, 0x02, 0x94, 0x37, 0x66, 0x30, 0x11, + 0xf3, 0x5b, 0x4c, 0x22, 0x55, 0xee, 0xef, 0x19, 0x3a, 0x0c, 0x5e, 0xb1, 0x71, 0x1c, 0x39, 0xb6, 0x33, 0x87, 0x13, + 0x0b, 0x63, 0xb6, 0x6a, 0x19, 0x9c, 0x94, 0xbc, 0xe9, 0xda, 0xea, 0x17, 0x8c, 0xe4, 0xf8, 0xc1, 0xa6, 0xf0, 0x48, + 0xba, 0xce, 0x6c, 0xa9, 0xfe, 0xc3, 0xf8, 0xaa, 0x24, 0x47, 0xd6, 0x5d, 0xa9, 0x0c, 0xb6, 0xd0, 0x19, 0x3a, 0x7e, + 0x17, 0x6c, 0x08, 0x2a, 0xc6, 0x0f, 0xe0, 0xfc, 0xe0, 0xb4, 0x76, 0x41, 0xa7, 0x31, 0xba, 0xe9, 0x81, 0x86, 0x2b, + 0x1f, 0xdf, 0x14, 0x7e, 0x83, 0x46, 0xa9, 0xa7, 0x7f, 0xe3, 0x12, 0x50, 0x86, 0xca, 0xf5, 0xff, 0xf2, 0xf2, 0x50, + 0x5e, 0x72, 0xb5, 0xd1, 0x27, 0x49, 0xbe, 0xe8, 0xea, 0x03, 0x3b, 0xdb, 0x20, 0x2e, 0xe8, 0xd7, 0xde, 0x51, 0x50, + 0x16, 0x25, 0x02, 0xe6, 0x98, 0x5a, 0xd2, 0x6c, 0x08, 0x6d, 0x21, 0x0f, 0xc6, 0xec, 0x2c, 0x1e, 0x49, 0xb6, 0xee, + 0x59, 0x32, 0x37, 0xbe, 0x45, 0xab, 0x08, 0x3b, 0x9e, 0x30, 0x9c, 0xe1, 0x05, 0x65, 0x54, 0x98, 0x66, 0x76, 0xff, + 0x5e, 0x4f, 0x43, 0x52, 0x4f, 0xcf, 0xb5, 0xf1, 0x77, 0xf0, 0x1d, 0x81, 0xa1, 0xf6, 0x8f, 0xe1, 0x3d, 0xfc, 0x2d, + 0x7c, 0xf7, 0x86, 0xb6, 0xeb, 0x13, 0x53, 0xbc, 0x57, 0xfd, 0x2a, 0x3e, 0xe4, 0x08, 0xdb, 0x20, 0xbf, 0xbc, 0xbb, + 0x0a, 0x32, 0x29, 0xb4, 0xba, 0x0f, 0x2a, 0xa1, 0x05, 0xcf, 0x06, 0x97, 0x02, 0x06, 0xda, 0xf5, 0x1f, 0x18, 0xac, + 0xf0, 0xac, 0x85, 0x3f, 0x6b, 0xcc, 0xf0, 0x3e, 0x34, 0x50, 0xdc, 0xf0, 0x25, 0x34, 0xdf, 0x15, 0x8c, 0x17, 0xfa, + 0xfd, 0x48, 0xac, 0x4a, 0xb0, 0xa9, 0x3a, 0xc5, 0xac, 0x09, 0x8f, 0x88, 0x78, 0xb6, 0xed, 0x39, 0xfa, 0xfb, 0xfe, + 0x92, 0x5c, 0xe5, 0xe5, 0xa4, 0xa7, 0xd0, 0xd7, 0xd1, 0xdf, 0xad, 0x5d, 0x57, 0xe7, 0xd5, 0x4e, 0xce, 0x9a, 0x29, + 0x90, 0xe0, 0x1b, 0x21, 0x18, 0xca, 0xd5, 0x16, 0xdf, 0x6f, 0x12, 0xc7, 0xb8, 0xfa, 0xc2, 0xd5, 0x9a, 0x74, 0x43, + 0xf3, 0x50, 0xb0, 0x8a, 0x68, 0xe8, 0x5c, 0x00, 0x23, 0xa0, 0x9f, 0x55, 0xb1, 0x7a, 0x90, 0x04, 0xe5, 0x27, 0x11, + 0xfe, 0xfa, 0x09, 0xfa, 0x51, 0x56, 0x07, 0x90, 0xd3, 0x07, 0xfa, 0x08, 0xd2, 0x17, 0xe3, 0xb2, 0xb9, 0x08, 0xd0, + 0x17, 0xf0, 0xb7, 0x99, 0x55, 0xb9, 0xe1, 0xf2, 0xd2, 0x17, 0x86, 0xc1, 0xc7, 0x71, 0x4e, 0x77, 0x09, 0xd5, 0xfa, + 0x6b, 0xd7, 0xfc, 0x2a, 0x54, 0xd3, 0xa9, 0x64, 0xc5, 0xc0, 0xc6, 0x22, 0x5b, 0x65, 0xe9, 0x98, 0x5f, 0xa8, 0x35, + 0x2f, 0x7b, 0x8d, 0x45, 0x9a, 0x0e, 0x7e, 0xc1, 0xdb, 0x16, 0x48, 0xb6, 0x81, 0x8d, 0x5d, 0xbb, 0x26, 0x52, 0x6e, + 0xf0, 0x8e, 0x54, 0xf5, 0x2b, 0x59, 0xcc, 0x03, 0x6f, 0x9b, 0xbb, 0xa5, 0xc7, 0xa5, 0x7d, 0x70, 0xa5, 0xa7, 0xf0, + 0x84, 0x45, 0xdc, 0x8f, 0x52, 0xca, 0xf7, 0x70, 0x0c, 0xb6, 0xe0, 0x75, 0xd8, 0xae, 0x5b, 0x02, 0xe7, 0x31, 0x7e, + 0x67, 0x8d, 0x40, 0xbd, 0x0f, 0x85, 0x6e, 0xe5, 0xb5, 0x9b, 0x76, 0xfb, 0x6f, 0x0e, 0xf7, 0x2d, 0x71, 0x9a, 0xf7, + 0x76, 0xe0, 0x75, 0x8f, 0x6c, 0x61, 0x91, 0x52, 0x10, 0x8a, 0x94, 0x02, 0x4b, 0x64, 0xc3, 0x84, 0xf6, 0x8e, 0x58, + 0xa6, 0x6d, 0xb1, 0x74, 0x24, 0x3c, 0x78, 0x33, 0xb0, 0x15, 0x62, 0xfc, 0x8a, 0xd1, 0x0e, 0x76, 0x6b, 0xe1, 0x4e, + 0xc3, 0x11, 0x10, 0x3e, 0x3e, 0xa5, 0x20, 0xf0, 0xd4, 0x96, 0xfe, 0x3e, 0x10, 0xeb, 0x4c, 0x65, 0x62, 0xc8, 0xa1, + 0x74, 0x5e, 0xde, 0x6a, 0xeb, 0x62, 0x71, 0x32, 0x03, 0x3e, 0xa4, 0x92, 0x29, 0xde, 0xcb, 0x0e, 0x7b, 0x34, 0x15, + 0x66, 0x01, 0xae, 0x3a, 0x21, 0xa7, 0x9d, 0xfe, 0x5e, 0x24, 0xf5, 0x1d, 0x3c, 0xbb, 0x05, 0x1c, 0x5e, 0x10, 0x73, + 0xa8, 0x54, 0xf8, 0x71, 0xb6, 0x73, 0xce, 0x4e, 0x5a, 0xd1, 0x3c, 0xae, 0x7c, 0x7f, 0x28, 0xfd, 0xfa, 0x7b, 0x4a, + 0x10, 0xca, 0x84, 0x33, 0xf9, 0x18, 0x19, 0x89, 0x07, 0x88, 0x38, 0x22, 0xd0, 0x52, 0x3a, 0x16, 0x49, 0x69, 0x04, + 0xe4, 0x03, 0xac, 0x44, 0xbf, 0xca, 0x01, 0x29, 0x25, 0x41, 0x69, 0xf7, 0xff, 0xf6, 0xbf, 0xfe, 0xb7, 0xf4, 0x29, + 0x02, 0x5a, 0x01, 0x2c, 0xcc, 0xdc, 0xa8, 0x62, 0x67, 0xec, 0x02, 0xac, 0xd0, 0x78, 0xdc, 0x9a, 0x46, 0xc9, 0x04, + 0x20, 0x28, 0x98, 0xb8, 0xbb, 0x21, 0xeb, 0x81, 0x0a, 0x24, 0x58, 0x66, 0xd8, 0x59, 0x82, 0x57, 0x2f, 0xc2, 0x1d, + 0xfb, 0x43, 0x19, 0x7c, 0x2a, 0xb7, 0x94, 0x08, 0xda, 0xc8, 0xe7, 0x33, 0x68, 0xae, 0x96, 0xd3, 0xa7, 0x7e, 0x23, + 0x8c, 0x64, 0x1e, 0xac, 0x96, 0xd0, 0x07, 0x2d, 0x75, 0xa0, 0xe0, 0xdf, 0xfe, 0xf5, 0x3f, 0xff, 0x77, 0xf5, 0x8a, + 0xfe, 0xff, 0xbf, 0xfd, 0xcb, 0x3f, 0xfd, 0xdf, 0xff, 0xf3, 0x5f, 0x30, 0x39, 0x52, 0xc6, 0x08, 0xe8, 0x28, 0x59, + 0x55, 0x80, 0x40, 0x9c, 0xa9, 0x7a, 0xb6, 0xdf, 0x01, 0xcd, 0x42, 0x04, 0x29, 0x41, 0x22, 0x62, 0xa6, 0x24, 0x50, + 0x42, 0xd5, 0x0d, 0x38, 0x83, 0xfd, 0xb3, 0x28, 0x4a, 0x6d, 0x3f, 0x68, 0xdb, 0xd5, 0x9e, 0xf6, 0x8d, 0xbe, 0x3b, + 0xb8, 0x1b, 0x77, 0xca, 0x14, 0xf1, 0xf5, 0x5e, 0x2d, 0x95, 0xe3, 0x0a, 0x4b, 0xca, 0xaa, 0xdc, 0x42, 0x8f, 0xf2, + 0x12, 0x5f, 0x83, 0xae, 0x51, 0x4c, 0x5b, 0x5b, 0xeb, 0xd3, 0xfb, 0x65, 0x51, 0xf0, 0x78, 0x82, 0xfb, 0x21, 0xdc, + 0x63, 0x14, 0x0a, 0x6c, 0xa1, 0x4a, 0x92, 0x5c, 0x96, 0x34, 0x8a, 0x30, 0x61, 0xee, 0x3f, 0xfe, 0x87, 0xf2, 0x2f, + 0x33, 0x54, 0x05, 0x2c, 0x67, 0x16, 0x5d, 0x48, 0xc3, 0xe6, 0x61, 0xbb, 0x3d, 0xbf, 0x70, 0x97, 0xd5, 0x0c, 0xde, + 0x75, 0x93, 0x91, 0x4b, 0xcd, 0x1c, 0x90, 0x62, 0x88, 0xda, 0x7b, 0x07, 0xba, 0x7c, 0x1b, 0x9d, 0x3d, 0x65, 0xf9, + 0xf9, 0x92, 0x1c, 0x48, 0xf1, 0x6f, 0x18, 0xeb, 0x93, 0xbe, 0x36, 0x28, 0x31, 0x56, 0xb1, 0x34, 0x7a, 0x75, 0x45, + 0xaf, 0x69, 0x67, 0x35, 0xd3, 0xc4, 0x8c, 0x55, 0x9a, 0x51, 0x46, 0xcc, 0xc3, 0x80, 0x0e, 0xde, 0xb4, 0xbb, 0xd4, + 0xc3, 0x73, 0x9e, 0xcd, 0xcc, 0xe0, 0x24, 0x8b, 0xd8, 0x88, 0x4d, 0x94, 0x8f, 0x52, 0xd6, 0x8b, 0xc0, 0x63, 0xf9, + 0x19, 0x9e, 0x31, 0xc0, 0x6d, 0x16, 0xf1, 0x80, 0x28, 0xb5, 0x67, 0x86, 0x2f, 0x23, 0x0c, 0x0c, 0x67, 0x4b, 0x63, + 0xae, 0x9e, 0x68, 0x8a, 0x9e, 0xc0, 0x7a, 0x7e, 0x4a, 0xe9, 0x53, 0x77, 0x73, 0x28, 0xe1, 0x48, 0x78, 0x51, 0x65, + 0x87, 0x54, 0x26, 0xf6, 0xbb, 0x9a, 0x39, 0x2e, 0x99, 0x31, 0x18, 0xc1, 0xb7, 0x37, 0x16, 0x52, 0x52, 0x34, 0xfd, + 0x15, 0x94, 0x1f, 0x5a, 0x80, 0xdd, 0x6c, 0x45, 0x85, 0xd8, 0xea, 0x5d, 0xf8, 0x42, 0xab, 0xe2, 0xd1, 0x7c, 0x4e, + 0x0d, 0x5d, 0xa0, 0x53, 0x52, 0xa9, 0x91, 0x71, 0x50, 0x2c, 0x5c, 0x84, 0x9e, 0x65, 0x1b, 0x49, 0xd0, 0xe2, 0x49, + 0x06, 0xa5, 0xe9, 0xf7, 0x0d, 0xff, 0x3f, 0xdf, 0x8d, 0x21, 0x2b, 0x85, 0x78, 0x00, 0x00}; + +} // namespace web_server +} // namespace esphome diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 44d044750e..af7273fbde 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -24,11 +24,20 @@ #include "esphome/components/fan/fan_helpers.h" #endif +#ifdef USE_CLIMATE +#include "esphome/components/climate/climate.h" +#endif + +#ifdef USE_WEBSERVER_LOCAL +#include "server_index.h" +#endif + namespace esphome { namespace web_server { static const char *const TAG = "web_server"; +#if USE_WEBSERVER_VERSION == 1 void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action, const std::function &action_func = nullptr) { stream->print("print(""); stream->print(""); } +#endif UrlMatch match_url(const std::string &url, bool only_domain = false) { UrlMatch match; @@ -87,76 +97,94 @@ void WebServer::setup() { this->base_->init(); this->events_.onConnect([this](AsyncEventSourceClient *client) { - // Configure reconnect timeout - client->send("", "ping", millis(), 30000); + // Configure reconnect timeout and send config + + client->send(json::build_json([this](JsonObject root) { + root["title"] = App.get_name(); + root["ota"] = this->allow_ota_; + root["lang"] = "en"; + }).c_str(), + "ping", millis(), 30000); #ifdef USE_SENSOR for (auto *obj : App.get_sensors()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->sensor_json(obj, obj->state).c_str(), "state"); + client->send(this->sensor_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); } #endif #ifdef USE_SWITCH for (auto *obj : App.get_switches()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->switch_json(obj, obj->state).c_str(), "state"); + client->send(this->switch_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); } #endif +#ifdef USE_BUTTON + for (auto *obj : App.get_buttons()) + client->send(this->button_json(obj, DETAIL_ALL).c_str(), "state"); +#endif + #ifdef USE_BINARY_SENSOR for (auto *obj : App.get_binary_sensors()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->binary_sensor_json(obj, obj->state).c_str(), "state"); + client->send(this->binary_sensor_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); } #endif #ifdef USE_FAN for (auto *obj : App.get_fans()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->fan_json(obj).c_str(), "state"); + client->send(this->fan_json(obj, DETAIL_ALL).c_str(), "state"); } #endif #ifdef USE_LIGHT for (auto *obj : App.get_lights()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->light_json(obj).c_str(), "state"); + client->send(this->light_json(obj, DETAIL_ALL).c_str(), "state"); } #endif #ifdef USE_TEXT_SENSOR for (auto *obj : App.get_text_sensors()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->text_sensor_json(obj, obj->state).c_str(), "state"); + client->send(this->text_sensor_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); } #endif #ifdef USE_COVER for (auto *obj : App.get_covers()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->cover_json(obj).c_str(), "state"); + client->send(this->cover_json(obj, DETAIL_ALL).c_str(), "state"); } #endif #ifdef USE_NUMBER for (auto *obj : App.get_numbers()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->number_json(obj, obj->state).c_str(), "state"); + client->send(this->number_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); } #endif #ifdef USE_SELECT for (auto *obj : App.get_selects()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->select_json(obj, obj->state).c_str(), "state"); + client->send(this->select_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); + } +#endif + +#ifdef USE_CLIMATE + for (auto *obj : App.get_climates()) { + if (this->include_internal_ || !obj->is_internal()) + client->send(this->climate_json(obj, DETAIL_ALL).c_str(), "state"); } #endif #ifdef USE_LOCK for (auto *obj : App.get_locks()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->lock_json(obj, obj->state).c_str(), "state"); + client->send(this->lock_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); } #endif }); @@ -181,15 +209,27 @@ void WebServer::dump_config() { } float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; } +#ifdef USE_WEBSERVER_LOCAL +void WebServer::handle_index_request(AsyncWebServerRequest *request) { + AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ)); + response->addHeader("Content-Encoding", "gzip"); + request->send(response); +} +#else void WebServer::handle_index_request(AsyncWebServerRequest *request) { AsyncResponseStream *stream = request->beginResponseStream("text/html"); - std::string title = App.get_name() + " Web Server"; - stream->print(F("" - "")); + // All content is controlled and created by user - so allowing all origins is fine here. + stream->addHeader("Access-Control-Allow-Origin", "*"); +#if USE_WEBSERVER_VERSION == 1 + const std::string &title = App.get_name(); + stream->print(F("<!DOCTYPE html><html lang=\"en\"><head><meta charset=UTF-8><meta " + "name=viewport content=\"width=device-width, initial-scale=1,user-scalable=no\"><title>")); stream->print(title.c_str()); stream->print(F("")); -#ifdef WEBSERVER_CSS_INCLUDE +#else + stream->print(F("")); +#endif +#ifdef USE_WEBSERVER_CSS_INCLUDE stream->print(F("")); #endif if (strlen(this->css_url_) > 0) { @@ -197,11 +237,12 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { stream->print(this->css_url_); stream->print(F("\">")); } - stream->print(F("

")); + stream->print(F("")); +#if USE_WEBSERVER_VERSION == 1 + stream->print(F("

")); stream->print(title.c_str()); - stream->print(F("

States

")); - // All content is controlled and created by user - so allowing all origins is fine here. - stream->addHeader("Access-Control-Allow-Origin", "*"); + stream->print(F("")); + stream->print(F("

States

NameStateActions
")); #ifdef USE_SENSOR for (auto *obj : App.get_sensors()) { @@ -308,6 +349,13 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { } #endif +#ifdef USE_CLIMATE + for (auto *obj : App.get_climates()) { + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "climate", ""); + } +#endif + stream->print(F("
NameStateActions

See ESPHome Web API for " "REST API documentation.

")); if (this->allow_ota_) { @@ -316,23 +364,30 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { "type=\"file\" name=\"update\">")); } stream->print(F("

Debug Log

"));
-
-#ifdef WEBSERVER_JS_INCLUDE
+#endif
+#ifdef USE_WEBSERVER_JS_INCLUDE
   if (this->js_include_ != nullptr) {
-    stream->print(F(""));
+    stream->print(F(""));
   }
+#endif
+#if USE_WEBSERVER_VERSION == 2
+  stream->print(F(""));
 #endif
   if (strlen(this->js_url_) > 0) {
     stream->print(F(""));
   }
+#if USE_WEBSERVER_VERSION == 1
   stream->print(F("
")); +#else + stream->print(F("")); +#endif request->send(stream); } - -#ifdef WEBSERVER_CSS_INCLUDE +#endif +#ifdef USE_WEBSERVER_CSS_INCLUDE void WebServer::handle_css_request(AsyncWebServerRequest *request) { AsyncResponseStream *stream = request->beginResponseStream("text/css"); if (this->css_include_ != nullptr) { @@ -343,10 +398,11 @@ void WebServer::handle_css_request(AsyncWebServerRequest *request) { } #endif -#ifdef WEBSERVER_JS_INCLUDE +#ifdef USE_WEBSERVER_JS_INCLUDE void WebServer::handle_js_request(AsyncWebServerRequest *request) { AsyncResponseStream *stream = request->beginResponseStream("text/javascript"); if (this->js_include_ != nullptr) { + stream->addHeader("Access-Control-Allow-Origin", "*"); stream->print(this->js_include_); } @@ -354,64 +410,75 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) { } #endif +#define set_json_id(root, obj, sensor, start_config) \ + (root)["id"] = sensor; \ + if (((start_config) == DETAIL_ALL)) \ + (root)["name"] = (obj)->get_name(); + +#define set_json_value(root, obj, sensor, value, start_config) \ + set_json_id((root), (obj), sensor, start_config)(root)["value"] = value; + +#define set_json_state_value(root, obj, sensor, state, value, start_config) \ + set_json_value(root, obj, sensor, value, start_config)(root)["state"] = state; + +#define set_json_icon_state_value(root, obj, sensor, state, value, start_config) \ + set_json_value(root, obj, sensor, value, start_config)(root)["state"] = state; \ + if (((start_config) == DETAIL_ALL)) \ + (root)["icon"] = (obj)->get_icon(); + #ifdef USE_SENSOR void WebServer::on_sensor_update(sensor::Sensor *obj, float state) { - this->events_.send(this->sensor_json(obj, state).c_str(), "state"); + this->events_.send(this->sensor_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (sensor::Sensor *obj : App.get_sensors()) { if (obj->get_object_id() != match.id) continue; - std::string data = this->sensor_json(obj, obj->state); + std::string data = this->sensor_json(obj, obj->state, DETAIL_STATE); request->send(200, "text/json", data.c_str()); return; } request->send(404); } -std::string WebServer::sensor_json(sensor::Sensor *obj, float value) { - return json::build_json([obj, value](JsonObject root) { - root["id"] = "sensor-" + obj->get_object_id(); +std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config) { + return json::build_json([obj, value, start_config](JsonObject root) { std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals()); if (!obj->get_unit_of_measurement().empty()) state += " " + obj->get_unit_of_measurement(); - root["state"] = state; - root["value"] = value; + set_json_icon_state_value(root, obj, "sensor-" + obj->get_object_id(), state, value, start_config); }); } #endif #ifdef USE_TEXT_SENSOR void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) { - this->events_.send(this->text_sensor_json(obj, state).c_str(), "state"); + this->events_.send(this->text_sensor_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (text_sensor::TextSensor *obj : App.get_text_sensors()) { if (obj->get_object_id() != match.id) continue; - std::string data = this->text_sensor_json(obj, obj->state); + std::string data = this->text_sensor_json(obj, obj->state, DETAIL_STATE); request->send(200, "text/json", data.c_str()); return; } 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) { - root["id"] = "text_sensor-" + obj->get_object_id(); - root["state"] = value; - root["value"] = value; +std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value, + JsonDetail start_config) { + return json::build_json([obj, value, start_config](JsonObject root) { + set_json_icon_state_value(root, obj, "text_sensor-" + obj->get_object_id(), value, value, start_config); }); } #endif #ifdef USE_SWITCH void WebServer::on_switch_update(switch_::Switch *obj, bool state) { - this->events_.send(this->switch_json(obj, state).c_str(), "state"); + this->events_.send(this->switch_json(obj, state, DETAIL_STATE).c_str(), "state"); } -std::string WebServer::switch_json(switch_::Switch *obj, bool value) { - return json::build_json([obj, value](JsonObject root) { - root["id"] = "switch-" + obj->get_object_id(); - root["state"] = value ? "ON" : "OFF"; - root["value"] = value; +std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) { + return json::build_json([obj, value, start_config](JsonObject root) { + set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config); }); } void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -420,7 +487,7 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM continue; if (request->method() == HTTP_GET) { - std::string data = this->switch_json(obj, obj->state); + std::string data = this->switch_json(obj, obj->state, DETAIL_STATE); request->send(200, "text/json", data.c_str()); } else if (match.method == "toggle") { this->defer([obj]() { obj->toggle(); }); @@ -441,14 +508,19 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM #endif #ifdef USE_BUTTON +std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) { + return json::build_json( + [obj, start_config](JsonObject root) { set_json_id(root, obj, "button-" + obj->get_object_id(), start_config); }); +} + void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (button::Button *obj : App.get_buttons()) { if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_POST && match.method == "press") { this->defer([obj]() { obj->press(); }); request->send(200); + return; } else { request->send(404); } @@ -460,20 +532,18 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM #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"); + this->events_.send(this->binary_sensor_json(obj, state, DETAIL_STATE).c_str(), "state"); } -std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value) { - return json::build_json([obj, value](JsonObject root) { - root["id"] = "binary_sensor-" + obj->get_object_id(); - root["state"] = value ? "ON" : "OFF"; - root["value"] = value; +std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) { + return json::build_json([obj, value, start_config](JsonObject root) { + set_json_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config); }); } void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) { if (obj->get_object_id() != match.id) continue; - std::string data = this->binary_sensor_json(obj, obj->state); + std::string data = this->binary_sensor_json(obj, obj->state, DETAIL_STATE); request->send(200, "text/json", data.c_str()); return; } @@ -482,15 +552,15 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con #endif #ifdef USE_FAN -void WebServer::on_fan_update(fan::Fan *obj) { this->events_.send(this->fan_json(obj).c_str(), "state"); } -std::string WebServer::fan_json(fan::Fan *obj) { - return json::build_json([obj](JsonObject root) { - root["id"] = "fan-" + obj->get_object_id(); - root["state"] = obj->state ? "ON" : "OFF"; - root["value"] = obj->state; +void WebServer::on_fan_update(fan::Fan *obj) { this->events_.send(this->fan_json(obj, DETAIL_STATE).c_str(), "state"); } +std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { + return json::build_json([obj, start_config](JsonObject root) { + set_json_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state, start_config); const auto traits = obj->get_traits(); if (traits.supports_speed()) { root["speed_level"] = obj->speed; + root["speed_count"] = traits.supported_speed_count(); + #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) @@ -517,7 +587,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc continue; if (request->method() == HTTP_GET) { - std::string data = this->fan_json(obj); + std::string data = this->fan_json(obj, DETAIL_STATE); request->send(200, "text/json", data.c_str()); } else if (match.method == "toggle") { this->defer([obj]() { obj->toggle().perform(); }); @@ -573,14 +643,16 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc #endif #ifdef USE_LIGHT -void WebServer::on_light_update(light::LightState *obj) { 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, DETAIL_STATE).c_str(), "state"); +} void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (light::LightState *obj : App.get_lights()) { if (obj->get_object_id() != match.id) continue; if (request->method() == HTTP_GET) { - std::string data = this->light_json(obj); + std::string data = this->light_json(obj, DETAIL_STATE); request->send(200, "text/json", data.c_str()); } else if (match.method == "toggle") { this->defer([obj]() { obj->toggle().perform(); }); @@ -632,24 +704,34 @@ 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) { - root["id"] = "light-" + obj->get_object_id(); +std::string WebServer::light_json(light::LightState *obj, JsonDetail start_config) { + return json::build_json([obj, start_config](JsonObject root) { + set_json_id(root, obj, "light-" + obj->get_object_id(), start_config); root["state"] = obj->remote_values.is_on() ? "ON" : "OFF"; + light::LightJSONSchema::dump_json(*obj, root); + if (start_config == DETAIL_ALL) { + JsonArray opt = root.createNestedArray("effects"); + opt.add("None"); + for (auto const &option : obj->get_effects()) { + opt.add(option->get_name()); + } + } }); } #endif #ifdef USE_COVER -void WebServer::on_cover_update(cover::Cover *obj) { 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, DETAIL_STATE).c_str(), "state"); +} void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (cover::Cover *obj : App.get_covers()) { if (obj->get_object_id() != match.id) continue; if (request->method() == HTTP_GET) { - std::string data = this->cover_json(obj); + std::string data = this->cover_json(obj, DETAIL_STATE); request->send(200, "text/json", data.c_str()); continue; } @@ -684,11 +766,10 @@ 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) { - root["id"] = "cover-" + obj->get_object_id(); - root["state"] = obj->is_fully_closed() ? "CLOSED" : "OPEN"; - root["value"] = obj->position; +std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { + return json::build_json([obj, start_config](JsonObject root) { + set_json_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN", + obj->position, start_config); root["current_operation"] = cover::cover_operation_to_str(obj->current_operation); if (obj->get_traits().get_supports_tilt()) @@ -699,7 +780,7 @@ std::string WebServer::cover_json(cover::Cover *obj) { #ifdef USE_NUMBER void WebServer::on_number_update(number::Number *obj, float state) { - this->events_.send(this->number_json(obj, state).c_str(), "state"); + this->events_.send(this->number_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_numbers()) { @@ -707,18 +788,16 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM continue; if (request->method() == HTTP_GET) { - std::string data = this->number_json(obj, obj->state); + std::string data = this->number_json(obj, obj->state, DETAIL_STATE); request->send(200, "text/json", data.c_str()); return; } - if (match.method != "set") { request->send(404); return; } auto call = obj->make_call(); - if (request->hasParam("value")) { String value = request->getParam("value")->value(); optional value_f = parse_number(value.c_str()); @@ -732,19 +811,30 @@ 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) { - root["id"] = "number-" + obj->get_object_id(); + +std::string WebServer::number_json(number::Number *obj, float value, JsonDetail start_config) { + return json::build_json([obj, value, start_config](JsonObject root) { + set_json_id(root, obj, "number-" + obj->get_object_id(), start_config); + if (start_config == DETAIL_ALL) { + root["min_value"] = obj->traits.get_min_value(); + root["max_value"] = obj->traits.get_max_value(); + root["step"] = obj->traits.get_step(); + root["mode"] = (int) obj->traits.get_mode(); + } std::string state = str_sprintf("%f", value); root["state"] = state; - root["value"] = value; + if (isnan(value)) { + root["value"] = "\"NaN\""; + } else { + root["value"] = value; + } }); } #endif #ifdef USE_SELECT void WebServer::on_select_update(select::Select *obj, const std::string &state) { - this->events_.send(this->select_json(obj, state).c_str(), "state"); + this->events_.send(this->select_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_selects()) { @@ -752,7 +842,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM continue; if (request->method() == HTTP_GET) { - std::string data = this->select_json(obj, obj->state); + std::string data = this->select_json(obj, obj->state, DETAIL_STATE); request->send(200, "text/json", data.c_str()); return; } @@ -775,24 +865,158 @@ 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) { - root["id"] = "select-" + obj->get_object_id(); - root["state"] = value; - root["value"] = value; +std::string WebServer::select_json(select::Select *obj, const std::string &value, JsonDetail start_config) { + return json::build_json([obj, value, start_config](JsonObject root) { + set_json_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config); + if (start_config == DETAIL_ALL) { + JsonArray opt = root.createNestedArray("option"); + for (auto &option : obj->traits.get_options()) { + opt.add(option); + } + } + }); +} +#endif + +#ifdef USE_CLIMATE +void WebServer::on_climate_update(climate::Climate *obj) { + this->events_.send(this->climate_json(obj, DETAIL_STATE).c_str(), "state"); +} + +void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) { + for (auto *obj : App.get_climates()) { + if (obj->get_object_id() != match.id) + continue; + + if (request->method() == HTTP_GET) { + std::string data = this->climate_json(obj, DETAIL_STATE); + request->send(200, "text/json", data.c_str()); + return; + } + + if (match.method != "set") { + request->send(404); + return; + } + + auto call = obj->make_call(); + + if (request->hasParam("mode")) { + String mode = request->getParam("mode")->value(); + call.set_mode(mode.c_str()); + } + + if (request->hasParam("target_temperature_high")) { + String value = request->getParam("target_temperature_high")->value(); + optional value_f = parse_number(value.c_str()); + if (value_f.has_value()) + call.set_target_temperature_high(*value_f); + } + + if (request->hasParam("target_temperature_low")) { + String value = request->getParam("target_temperature_low")->value(); + optional value_f = parse_number(value.c_str()); + if (value_f.has_value()) + call.set_target_temperature_low(*value_f); + } + + if (request->hasParam("target_temperature")) { + String value = request->getParam("target_temperature")->value(); + optional value_f = parse_number(value.c_str()); + if (value_f.has_value()) + call.set_target_temperature(*value_f); + } + + this->defer([call]() mutable { call.perform(); }); + request->send(200); + return; + } + request->send(404); +} + +// Longest: HORIZONTAL +#define PSTR_LOCAL(mode_s) strncpy_P(__buf, (PGM_P)((mode_s)), 15) + +std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) { + return json::build_json([obj, start_config](JsonObject root) { + set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config); + const auto traits = obj->get_traits(); + char __buf[16]; + + if (start_config == DETAIL_ALL) { + JsonArray opt = root.createNestedArray("modes"); + for (climate::ClimateMode m : traits.get_supported_modes()) + opt.add(PSTR_LOCAL(climate::climate_mode_to_string(m))); + if (!traits.get_supported_custom_fan_modes().empty()) { + JsonArray opt = root.createNestedArray("fan_modes"); + for (climate::ClimateFanMode m : traits.get_supported_fan_modes()) + opt.add(PSTR_LOCAL(climate::climate_fan_mode_to_string(m))); + } + + if (!traits.get_supported_custom_fan_modes().empty()) { + JsonArray opt = root.createNestedArray("custom_fan_modes"); + for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) + opt.add(custom_fan_mode); + } + if (traits.get_supports_swing_modes()) { + JsonArray opt = root.createNestedArray("swing_modes"); + for (auto swing_mode : traits.get_supported_swing_modes()) + opt.add(PSTR_LOCAL(climate::climate_swing_mode_to_string(swing_mode))); + } + if (traits.get_supports_presets() && obj->preset.has_value()) { + JsonArray opt = root.createNestedArray("presets"); + for (climate::ClimatePreset m : traits.get_supported_presets()) + opt.add(PSTR_LOCAL(climate::climate_preset_to_string(m))); + } + if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) { + JsonArray opt = root.createNestedArray("custom_presets"); + for (auto const &custom_preset : traits.get_supported_custom_presets()) + opt.add(custom_preset); + } + } + + root["mode"] = PSTR_LOCAL(climate_mode_to_string(obj->mode)); + + if (traits.get_supports_action()) { + root["action"] = PSTR_LOCAL(climate_action_to_string(obj->action)); + } + if (traits.get_supports_fan_modes() && obj->fan_mode.has_value()) { + root["fan_mode"] = PSTR_LOCAL(climate_fan_mode_to_string(obj->fan_mode.value())); + } + if (!traits.get_supported_custom_fan_modes().empty() && obj->custom_fan_mode.has_value()) { + root["custom_fan_mode"] = obj->custom_fan_mode.value().c_str(); + } + if (traits.get_supports_presets() && obj->preset.has_value()) { + root["preset"] = PSTR_LOCAL(climate_preset_to_string(obj->preset.value())); + } + if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) { + root["custom_preset"] = obj->custom_preset.value().c_str(); + } + if (traits.get_supports_swing_modes()) { + root["swing_mode"] = PSTR_LOCAL(climate_swing_mode_to_string(obj->swing_mode)); + } + if (traits.get_supports_current_temperature()) { + root["current_temperature"] = obj->current_temperature; + } + if (traits.get_supports_two_point_target_temperature()) { + root["current_temperature_low"] = obj->target_temperature_low; + root["current_temperature_high"] = obj->target_temperature_low; + } else { + root["target_temperature"] = obj->target_temperature; + root["state"] = obj->target_temperature; + } }); } #endif #ifdef USE_LOCK void WebServer::on_lock_update(lock::Lock *obj) { - this->events_.send(this->lock_json(obj, obj->state).c_str(), "state"); + this->events_.send(this->lock_json(obj, obj->state, DETAIL_STATE).c_str(), "state"); } -std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value) { - return json::build_json([obj, value](JsonObject root) { - root["id"] = "lock-" + obj->get_object_id(); - root["state"] = lock::lock_state_to_string(value); - root["value"] = value; +std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) { + return json::build_json([obj, value, start_config](JsonObject root) { + set_json_icon_state_value(root, obj, "lock-" + obj->get_object_id(), lock::lock_state_to_string(value), value, + start_config); }); } void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -801,7 +1025,7 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat continue; if (request->method() == HTTP_GET) { - std::string data = this->lock_json(obj, obj->state); + std::string data = this->lock_json(obj, obj->state, DETAIL_STATE); request->send(200, "text/json", data.c_str()); } else if (match.method == "lock") { this->defer([obj]() { obj->lock(); }); @@ -825,12 +1049,12 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { if (request->url() == "/") return true; -#ifdef WEBSERVER_CSS_INCLUDE +#ifdef USE_WEBSERVER_CSS_INCLUDE if (request->url() == "/0.css") return true; #endif -#ifdef WEBSERVER_JS_INCLUDE +#ifdef USE_WEBSERVER_JS_INCLUDE if (request->url() == "/0.js") return true; #endif @@ -888,6 +1112,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return true; #endif +#ifdef USE_CLIMATE + if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "climate") + return true; +#endif + #ifdef USE_LOCK if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "lock") return true; @@ -901,14 +1130,14 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { return; } -#ifdef WEBSERVER_CSS_INCLUDE +#ifdef USE_WEBSERVER_CSS_INCLUDE if (request->url() == "/0.css") { this->handle_css_request(request); return; } #endif -#ifdef WEBSERVER_JS_INCLUDE +#ifdef USE_WEBSERVER_JS_INCLUDE if (request->url() == "/0.js") { this->handle_js_request(request); return; @@ -986,9 +1215,17 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { } #endif +#ifdef USE_CLIMATE + if (match.domain == "climate") { + this->handle_climate_request(request, match); + return; + } +#endif + #ifdef USE_LOCK if (match.domain == "lock") { this->handle_lock_request(request, match); + return; } #endif diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 3dd5c93f59..2717997f60 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -2,9 +2,9 @@ #ifdef USE_ARDUINO +#include "esphome/components/web_server_base/web_server_base.h" #include "esphome/core/component.h" #include "esphome/core/controller.h" -#include "esphome/components/web_server_base/web_server_base.h" #include @@ -19,6 +19,8 @@ struct UrlMatch { bool valid; ///< Whether this match is valid }; +enum JsonDetail { DETAIL_ALL, DETAIL_STATE }; + /** This class allows users to create a web server with their ESP nodes. * * Behind the scenes it's using AsyncWebServer to set up the server. It exposes 3 things: @@ -99,7 +101,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the sensor state with its value as a JSON string. - std::string sensor_json(sensor::Sensor *obj, float value); + std::string sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config); #endif #ifdef USE_SWITCH @@ -109,12 +111,15 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the switch state with its value as a JSON string. - std::string switch_json(switch_::Switch *obj, bool value); + std::string switch_json(switch_::Switch *obj, bool value, JsonDetail start_config); #endif #ifdef USE_BUTTON /// Handle a button request under '/button//press'. void handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match); + + /// Dump the button details with its value as a JSON string. + std::string button_json(button::Button *obj, JsonDetail start_config); #endif #ifdef USE_BINARY_SENSOR @@ -124,7 +129,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the binary sensor state with its value as a JSON string. - std::string binary_sensor_json(binary_sensor::BinarySensor *obj, bool value); + std::string binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config); #endif #ifdef USE_FAN @@ -134,7 +139,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the fan state as a JSON string. - std::string fan_json(fan::Fan *obj); + std::string fan_json(fan::Fan *obj, JsonDetail start_config); #endif #ifdef USE_LIGHT @@ -144,7 +149,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the light state as a JSON string. - std::string light_json(light::LightState *obj); + std::string light_json(light::LightState *obj, JsonDetail start_config); #endif #ifdef USE_TEXT_SENSOR @@ -154,7 +159,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the text sensor state with its value as a JSON string. - std::string text_sensor_json(text_sensor::TextSensor *obj, const std::string &value); + std::string text_sensor_json(text_sensor::TextSensor *obj, const std::string &value, JsonDetail start_config); #endif #ifdef USE_COVER @@ -164,7 +169,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the cover state as a JSON string. - std::string cover_json(cover::Cover *obj); + std::string cover_json(cover::Cover *obj, JsonDetail start_config); #endif #ifdef USE_NUMBER @@ -173,7 +178,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the number state with its value as a JSON string. - std::string number_json(number::Number *obj, float value); + std::string number_json(number::Number *obj, float value, JsonDetail start_config); #endif #ifdef USE_SELECT @@ -181,8 +186,17 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a select request under '/select/'. void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match); - /// Dump the number state with its value as a JSON string. - std::string select_json(select::Select *obj, const std::string &value); + /// Dump the select state with its value as a JSON string. + std::string select_json(select::Select *obj, const std::string &value, JsonDetail start_config); +#endif + +#ifdef USE_CLIMATE + void on_climate_update(climate::Climate *obj) override; + /// Handle a climate request under '/climate/'. + void handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match); + + /// Dump the climate details + std::string climate_json(climate::Climate *obj, JsonDetail start_config); #endif #ifdef USE_LOCK @@ -192,7 +206,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the lock state with its value as a JSON string. - std::string lock_json(lock::Lock *obj, lock::LockState value); + std::string lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config); #endif /// Override the web handler's canHandle method. diff --git a/tests/test1.yaml b/tests/test1.yaml index 8cd01f1d6f..3763fd3fa5 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -247,9 +247,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 + version: 2 power_supply: id: "atx_power_supply" From 4c22a98b0b20c94911c840b97fe54c5938baab37 Mon Sep 17 00:00:00 2001 From: JasperPlant <78851352+JasperPlant@users.noreply.github.com> Date: Tue, 8 Mar 2022 03:21:13 +0100 Subject: [PATCH 161/238] Add entity_category_diagnostics to SGP30 baseline sensors (#3272) --- esphome/components/sgp30/sensor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/sgp30/sensor.py b/esphome/components/sgp30/sensor.py index 2596e0065d..14a078b501 100644 --- a/esphome/components/sgp30/sensor.py +++ b/esphome/components/sgp30/sensor.py @@ -13,6 +13,7 @@ from esphome.const import ( UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_BILLION, ICON_MOLECULE_CO2, + ENTITY_CATEGORY_DIAGNOSTIC, ) DEPENDENCIES = ["i2c"] @@ -49,10 +50,12 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_ECO2_BASELINE): sensor.sensor_schema( icon=ICON_MOLECULE_CO2, accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_TVOC_BASELINE): sensor.sensor_schema( icon=ICON_RADIATOR, accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean, cv.Optional(CONF_BASELINE): cv.Schema( From 900b4f1af96221181f5a21b9193e2a2266a57c1b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 9 Mar 2022 10:27:54 +1300 Subject: [PATCH 162/238] Bump esphome-dashboard to 20220309.0 (#3277) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 427045af02..739ad79098 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.5 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20220219.0 +esphome-dashboard==20220309.0 aioesphomeapi==10.8.2 zeroconf==0.38.3 From 5b2457af0b26be0f93b7f9cf51fced6e7cb61cf4 Mon Sep 17 00:00:00 2001 From: wilberforce Date: Wed, 9 Mar 2022 10:28:16 +1300 Subject: [PATCH 163/238] Add visual step/min/max for webserver climate (#3275) --- 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 af7273fbde..278aeab937 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -976,7 +976,9 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf } root["mode"] = PSTR_LOCAL(climate_mode_to_string(obj->mode)); - + root["max_temp"] = traits.get_visual_max_temperature(); + root["min_temp"] = traits.get_visual_min_temperature(); + root["step"] = traits.get_visual_temperature_step(); if (traits.get_supports_action()) { root["action"] = PSTR_LOCAL(climate_action_to_string(obj->action)); } From 0af1edefffbd283f1a40425a11e3d4c6e3a94000 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 9 Mar 2022 20:07:50 +1300 Subject: [PATCH 164/238] Bump version to 2022.4.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 2ec00edf7b..46c9a659be 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.3.0-dev" +__version__ = "2022.4.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From a29d65d47ca83c025ac05625d16417c8a7da1387 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Mar 2022 13:27:15 +0100 Subject: [PATCH 165/238] Bump pytest-asyncio from 0.18.1 to 0.18.2 (#3262) Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.18.1 to 0.18.2. - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.18.1...v0.18.2) --- updated-dependencies: - dependency-name: pytest-asyncio dependency-type: direct:production update-type: version-update:semver-patch ... 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 afc4fd9d2a..8da8945c8b 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -8,6 +8,6 @@ pre-commit pytest==7.0.1 pytest-cov==3.0.0 pytest-mock==3.7.0 -pytest-asyncio==0.18.1 +pytest-asyncio==0.18.2 asyncmock==0.4.2 hypothesis==5.49.0 From 65d3e8fbfcda4b408897f3591865aa1ff3738da3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Mar 2022 13:27:33 +0100 Subject: [PATCH 166/238] Bump zeroconf from 0.38.3 to 0.38.4 (#3257) Bumps [zeroconf](https://github.com/jstasiak/python-zeroconf) from 0.38.3 to 0.38.4. - [Release notes](https://github.com/jstasiak/python-zeroconf/releases) - [Commits](https://github.com/jstasiak/python-zeroconf/compare/0.38.3...0.38.4) --- updated-dependencies: - dependency-name: zeroconf dependency-type: direct:production update-type: version-update:semver-patch ... 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 739ad79098..bcc7c2a911 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ esptool==3.2 click==8.0.3 esphome-dashboard==20220309.0 aioesphomeapi==10.8.2 -zeroconf==0.38.3 +zeroconf==0.38.4 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 6bf733e24e4e2dbe5e3c01aa91ba8154517cad57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Mar 2022 13:27:56 +0100 Subject: [PATCH 167/238] Bump click from 8.0.3 to 8.0.4 (#3248) Bumps [click](https://github.com/pallets/click) from 8.0.3 to 8.0.4. - [Release notes](https://github.com/pallets/click/releases) - [Changelog](https://github.com/pallets/click/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/click/compare/8.0.3...8.0.4) --- updated-dependencies: - dependency-name: click dependency-type: direct:production update-type: version-update:semver-patch ... 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 bcc7c2a911..c23cdcd33f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.5 # When updating platformio, also update Dockerfile esptool==3.2 -click==8.0.3 +click==8.0.4 esphome-dashboard==20220309.0 aioesphomeapi==10.8.2 zeroconf==0.38.4 From 3208c8ed1e488c10f28a3fffdd6f5f5d627848b3 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 9 Mar 2022 13:48:02 +0100 Subject: [PATCH 168/238] Bump docker dependencies (#3281) --- docker/Dockerfile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 65e831f89b..ad4891d62e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,13 +6,13 @@ ARG BASEIMGTYPE=docker # https://github.com/hassio-addons/addon-debian-base/releases -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 ghcr.io/hassio-addons/debian-base/amd64:5.3.0 AS base-hassio-amd64 +FROM ghcr.io/hassio-addons/debian-base/aarch64:5.3.0 AS base-hassio-arm64 +FROM ghcr.io/hassio-addons/debian-base/armv7:5.3.0 AS base-hassio-armv7 # https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye -FROM debian:bullseye-20220125-slim AS base-docker-amd64 -FROM debian:bullseye-20220125-slim AS base-docker-arm64 -FROM debian:bullseye-20220125-slim AS base-docker-armv7 +FROM debian:bullseye-20220228-slim AS base-docker-amd64 +FROM debian:bullseye-20220228-slim AS base-docker-arm64 +FROM debian:bullseye-20220228-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 aafdfa933e82706e2ddad140217e39cd084c6fe3 Mon Sep 17 00:00:00 2001 From: stegm Date: Wed, 9 Mar 2022 20:40:43 +0100 Subject: [PATCH 169/238] Add optimistic config flag to modbus select. (#3267) --- esphome/components/modbus_controller/select/__init__.py | 4 +++- esphome/components/modbus_controller/select/modbus_select.cpp | 3 +++ esphome/components/modbus_controller/select/modbus_select.h | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/modbus_controller/select/__init__.py b/esphome/components/modbus_controller/select/__init__.py index 7d03064fa5..6f194ef2a3 100644 --- a/esphome/components/modbus_controller/select/__init__.py +++ b/esphome/components/modbus_controller/select/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import select -from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA +from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC from esphome.jsonschema import jschema_composite from .. import ( @@ -79,6 +79,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, cv.Required(CONF_OPTIONSMAP): ensure_option_map(), cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, cv.Optional(CONF_LAMBDA): cv.returning_lambda, cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, }, @@ -112,6 +113,7 @@ async def to_code(config): cg.add(parent.add_sensor_item(var)) cg.add(var.set_parent(parent)) cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE])) + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) if CONF_LAMBDA in config: template_ = await cg.process_lambda( diff --git a/esphome/components/modbus_controller/select/modbus_select.cpp b/esphome/components/modbus_controller/select/modbus_select.cpp index 2c6b32f545..33cef39a18 100644 --- a/esphome/components/modbus_controller/select/modbus_select.cpp +++ b/esphome/components/modbus_controller/select/modbus_select.cpp @@ -80,6 +80,9 @@ void ModbusSelect::control(const std::string &value) { } parent_->queue_command(write_cmd); + + if (this->optimistic_) + this->publish_state(value); } } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/select/modbus_select.h b/esphome/components/modbus_controller/select/modbus_select.h index 0875194768..2a31dfd7cc 100644 --- a/esphome/components/modbus_controller/select/modbus_select.h +++ b/esphome/components/modbus_controller/select/modbus_select.h @@ -32,6 +32,7 @@ class ModbusSelect : public Component, public select::Select, public SensorItem void set_parent(ModbusController *const parent) { this->parent_ = parent; } void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } + void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } 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; } @@ -43,6 +44,7 @@ class ModbusSelect : public Component, public select::Select, public SensorItem std::vector mapping_; ModbusController *parent_; bool use_write_multiple_{false}; + bool optimistic_{false}; optional transform_func_; optional write_transform_func_; }; From 59f67796dcf94a05d21bdcee5450bec68f890106 Mon Sep 17 00:00:00 2001 From: Rai-Rai Date: Sun, 13 Mar 2022 20:00:00 +0100 Subject: [PATCH 170/238] Fixed wrong comment (#3286) --- esphome/components/climate/climate_mode.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/climate/climate_mode.h b/esphome/components/climate/climate_mode.h index 3e5626919c..139400a08a 100644 --- a/esphome/components/climate/climate_mode.h +++ b/esphome/components/climate/climate_mode.h @@ -76,7 +76,7 @@ enum ClimateSwingMode : uint8_t { CLIMATE_SWING_HORIZONTAL = 3, }; -/// Enum for all modes a climate swing can be in +/// Enum for all preset modes enum ClimatePreset : uint8_t { /// No preset is active CLIMATE_PRESET_NONE = 0, @@ -108,7 +108,7 @@ const LogString *climate_fan_mode_to_string(ClimateFanMode mode); /// Convert the given ClimateSwingMode to a human-readable string. const LogString *climate_swing_mode_to_string(ClimateSwingMode mode); -/// Convert the given ClimateSwingMode to a human-readable string. +/// Convert the given PresetMode to a human-readable string. const LogString *climate_preset_to_string(ClimatePreset preset); } // namespace climate From 99f5ed14614ae792174bb85f6b0b9579d15e7461 Mon Sep 17 00:00:00 2001 From: andrewpc Date: Tue, 15 Mar 2022 06:09:17 +1100 Subject: [PATCH 171/238] Add support for QMP6988 Pressure sensor (#3192) --- CODEOWNERS | 1 + esphome/components/qmp6988/__init__.py | 1 + esphome/components/qmp6988/qmp6988.cpp | 397 +++++++++++++++++++++++++ esphome/components/qmp6988/qmp6988.h | 116 ++++++++ esphome/components/qmp6988/sensor.py | 101 +++++++ tests/test1.yaml | 11 + 6 files changed, 627 insertions(+) create mode 100644 esphome/components/qmp6988/__init__.py create mode 100644 esphome/components/qmp6988/qmp6988.cpp create mode 100644 esphome/components/qmp6988/qmp6988.h create mode 100644 esphome/components/qmp6988/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index c111fa7816..8958aa2928 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -151,6 +151,7 @@ esphome/components/preferences/* @esphome/core esphome/components/psram/* @esphome/core esphome/components/pulse_meter/* @cstaahl @stevebaxter esphome/components/pvvx_mithermometer/* @pasiz +esphome/components/qmp6988/* @andrewpc esphome/components/qr_code/* @wjtje esphome/components/radon_eye_ble/* @jeffeb3 esphome/components/radon_eye_rd200/* @jeffeb3 diff --git a/esphome/components/qmp6988/__init__.py b/esphome/components/qmp6988/__init__.py new file mode 100644 index 0000000000..09bcf51589 --- /dev/null +++ b/esphome/components/qmp6988/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@andrewpc"] diff --git a/esphome/components/qmp6988/qmp6988.cpp b/esphome/components/qmp6988/qmp6988.cpp new file mode 100644 index 0000000000..5bad1e4a47 --- /dev/null +++ b/esphome/components/qmp6988/qmp6988.cpp @@ -0,0 +1,397 @@ +#include "qmp6988.h" +#include + +namespace esphome { +namespace qmp6988 { + +static const uint8_t QMP6988_CHIP_ID = 0x5C; + +static const uint8_t QMP6988_CHIP_ID_REG = 0xD1; /* Chip ID confirmation Register */ +static const uint8_t QMP6988_RESET_REG = 0xE0; /* Device reset register */ +static const uint8_t QMP6988_DEVICE_STAT_REG = 0xF3; /* Device state register */ +static const uint8_t QMP6988_CTRLMEAS_REG = 0xF4; /* Measurement Condition Control Register */ +/* data */ +static const uint8_t QMP6988_PRESSURE_MSB_REG = 0xF7; /* Pressure MSB Register */ +static const uint8_t QMP6988_TEMPERATURE_MSB_REG = 0xFA; /* Temperature MSB Reg */ + +/* compensation calculation */ +static const uint8_t QMP6988_CALIBRATION_DATA_START = 0xA0; /* QMP6988 compensation coefficients */ +static const uint8_t QMP6988_CALIBRATION_DATA_LENGTH = 25; + +static const uint8_t SHIFT_RIGHT_4_POSITION = 4; +static const uint8_t SHIFT_LEFT_2_POSITION = 2; +static const uint8_t SHIFT_LEFT_4_POSITION = 4; +static const uint8_t SHIFT_LEFT_5_POSITION = 5; +static const uint8_t SHIFT_LEFT_8_POSITION = 8; +static const uint8_t SHIFT_LEFT_12_POSITION = 12; +static const uint8_t SHIFT_LEFT_16_POSITION = 16; + +/* power mode */ +static const uint8_t QMP6988_SLEEP_MODE = 0x00; +static const uint8_t QMP6988_FORCED_MODE = 0x01; +static const uint8_t QMP6988_NORMAL_MODE = 0x03; + +static const uint8_t QMP6988_CTRLMEAS_REG_MODE_POS = 0; +static const uint8_t QMP6988_CTRLMEAS_REG_MODE_MSK = 0x03; +static const uint8_t QMP6988_CTRLMEAS_REG_MODE_LEN = 2; + +static const uint8_t QMP6988_CTRLMEAS_REG_OSRST_POS = 5; +static const uint8_t QMP6988_CTRLMEAS_REG_OSRST_MSK = 0xE0; +static const uint8_t QMP6988_CTRLMEAS_REG_OSRST_LEN = 3; + +static const uint8_t QMP6988_CTRLMEAS_REG_OSRSP_POS = 2; +static const uint8_t QMP6988_CTRLMEAS_REG_OSRSP_MSK = 0x1C; +static const uint8_t QMP6988_CTRLMEAS_REG_OSRSP_LEN = 3; + +static const uint8_t QMP6988_CONFIG_REG = 0xF1; /*IIR filter co-efficient setting Register*/ +static const uint8_t QMP6988_CONFIG_REG_FILTER_POS = 0; +static const uint8_t QMP6988_CONFIG_REG_FILTER_MSK = 0x07; +static const uint8_t QMP6988_CONFIG_REG_FILTER_LEN = 3; + +static const uint32_t SUBTRACTOR = 8388608; + +static const char *const TAG = "qmp6988"; + +static const char *oversampling_to_str(QMP6988Oversampling oversampling) { + switch (oversampling) { + case QMP6988_OVERSAMPLING_SKIPPED: + return "None"; + case QMP6988_OVERSAMPLING_1X: + return "1x"; + case QMP6988_OVERSAMPLING_2X: + return "2x"; + case QMP6988_OVERSAMPLING_4X: + return "4x"; + case QMP6988_OVERSAMPLING_8X: + return "8x"; + case QMP6988_OVERSAMPLING_16X: + return "16x"; + case QMP6988_OVERSAMPLING_32X: + return "32x"; + case QMP6988_OVERSAMPLING_64X: + return "64x"; + default: + return "UNKNOWN"; + } +} + +static const char *iir_filter_to_str(QMP6988IIRFilter filter) { + switch (filter) { + case QMP6988_IIR_FILTER_OFF: + return "OFF"; + case QMP6988_IIR_FILTER_2X: + return "2x"; + case QMP6988_IIR_FILTER_4X: + return "4x"; + case QMP6988_IIR_FILTER_8X: + return "8x"; + case QMP6988_IIR_FILTER_16X: + return "16x"; + case QMP6988_IIR_FILTER_32X: + return "32x"; + default: + return "UNKNOWN"; + } +} + +bool QMP6988Component::device_check_() { + uint8_t ret = 0; + + ret = this->read_register(QMP6988_CHIP_ID_REG, &(qmp6988_data_.chip_id), 1); + if (ret != i2c::ERROR_OK) { + ESP_LOGE(TAG, "%s: read chip ID (0xD1) failed", __func__); + } + ESP_LOGD(TAG, "qmp6988 read chip id = 0x%x", qmp6988_data_.chip_id); + + return qmp6988_data_.chip_id == QMP6988_CHIP_ID; +} + +bool QMP6988Component::get_calibration_data_() { + uint8_t status = 0; + // BITFIELDS temp_COE; + uint8_t a_data_uint8_tr[QMP6988_CALIBRATION_DATA_LENGTH] = {0}; + int len; + + for (len = 0; len < QMP6988_CALIBRATION_DATA_LENGTH; len += 1) { + status = this->read_register(QMP6988_CALIBRATION_DATA_START + len, &a_data_uint8_tr[len], 1); + if (status != i2c::ERROR_OK) { + ESP_LOGE(TAG, "qmp6988 read calibration data (0xA0) error!"); + return false; + } + } + + qmp6988_data_.qmp6988_cali.COE_a0 = + (QMP6988_S32_t)(((a_data_uint8_tr[18] << SHIFT_LEFT_12_POSITION) | + (a_data_uint8_tr[19] << SHIFT_LEFT_4_POSITION) | (a_data_uint8_tr[24] & 0x0f)) + << 12); + qmp6988_data_.qmp6988_cali.COE_a0 = qmp6988_data_.qmp6988_cali.COE_a0 >> 12; + + qmp6988_data_.qmp6988_cali.COE_a1 = + (QMP6988_S16_t)(((a_data_uint8_tr[20]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[21]); + qmp6988_data_.qmp6988_cali.COE_a2 = + (QMP6988_S16_t)(((a_data_uint8_tr[22]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[23]); + + qmp6988_data_.qmp6988_cali.COE_b00 = + (QMP6988_S32_t)(((a_data_uint8_tr[0] << SHIFT_LEFT_12_POSITION) | (a_data_uint8_tr[1] << SHIFT_LEFT_4_POSITION) | + ((a_data_uint8_tr[24] & 0xf0) >> SHIFT_RIGHT_4_POSITION)) + << 12); + qmp6988_data_.qmp6988_cali.COE_b00 = qmp6988_data_.qmp6988_cali.COE_b00 >> 12; + + qmp6988_data_.qmp6988_cali.COE_bt1 = + (QMP6988_S16_t)(((a_data_uint8_tr[2]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[3]); + qmp6988_data_.qmp6988_cali.COE_bt2 = + (QMP6988_S16_t)(((a_data_uint8_tr[4]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[5]); + qmp6988_data_.qmp6988_cali.COE_bp1 = + (QMP6988_S16_t)(((a_data_uint8_tr[6]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[7]); + qmp6988_data_.qmp6988_cali.COE_b11 = + (QMP6988_S16_t)(((a_data_uint8_tr[8]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[9]); + qmp6988_data_.qmp6988_cali.COE_bp2 = + (QMP6988_S16_t)(((a_data_uint8_tr[10]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[11]); + qmp6988_data_.qmp6988_cali.COE_b12 = + (QMP6988_S16_t)(((a_data_uint8_tr[12]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[13]); + qmp6988_data_.qmp6988_cali.COE_b21 = + (QMP6988_S16_t)(((a_data_uint8_tr[14]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[15]); + qmp6988_data_.qmp6988_cali.COE_bp3 = + (QMP6988_S16_t)(((a_data_uint8_tr[16]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[17]); + + ESP_LOGV(TAG, "<-----------calibration data-------------->\r\n"); + ESP_LOGV(TAG, "COE_a0[%d] COE_a1[%d] COE_a2[%d] COE_b00[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_a0, + qmp6988_data_.qmp6988_cali.COE_a1, qmp6988_data_.qmp6988_cali.COE_a2, qmp6988_data_.qmp6988_cali.COE_b00); + ESP_LOGV(TAG, "COE_bt1[%d] COE_bt2[%d] COE_bp1[%d] COE_b11[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_bt1, + qmp6988_data_.qmp6988_cali.COE_bt2, qmp6988_data_.qmp6988_cali.COE_bp1, qmp6988_data_.qmp6988_cali.COE_b11); + ESP_LOGV(TAG, "COE_bp2[%d] COE_b12[%d] COE_b21[%d] COE_bp3[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_bp2, + qmp6988_data_.qmp6988_cali.COE_b12, qmp6988_data_.qmp6988_cali.COE_b21, qmp6988_data_.qmp6988_cali.COE_bp3); + ESP_LOGV(TAG, "<-----------calibration data-------------->\r\n"); + + qmp6988_data_.ik.a0 = qmp6988_data_.qmp6988_cali.COE_a0; // 20Q4 + qmp6988_data_.ik.b00 = qmp6988_data_.qmp6988_cali.COE_b00; // 20Q4 + + qmp6988_data_.ik.a1 = 3608L * (QMP6988_S32_t) qmp6988_data_.qmp6988_cali.COE_a1 - 1731677965L; // 31Q23 + qmp6988_data_.ik.a2 = 16889L * (QMP6988_S32_t) qmp6988_data_.qmp6988_cali.COE_a2 - 87619360L; // 30Q47 + + qmp6988_data_.ik.bt1 = 2982L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bt1 + 107370906L; // 28Q15 + qmp6988_data_.ik.bt2 = 329854L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bt2 + 108083093L; // 34Q38 + qmp6988_data_.ik.bp1 = 19923L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bp1 + 1133836764L; // 31Q20 + qmp6988_data_.ik.b11 = 2406L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_b11 + 118215883L; // 28Q34 + qmp6988_data_.ik.bp2 = 3079L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bp2 - 181579595L; // 29Q43 + qmp6988_data_.ik.b12 = 6846L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_b12 + 85590281L; // 29Q53 + qmp6988_data_.ik.b21 = 13836L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_b21 + 79333336L; // 29Q60 + qmp6988_data_.ik.bp3 = 2915L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bp3 + 157155561L; // 28Q65 + ESP_LOGV(TAG, "<----------- int calibration data -------------->\r\n"); + ESP_LOGV(TAG, "a0[%d] a1[%d] a2[%d] b00[%d]\r\n", qmp6988_data_.ik.a0, qmp6988_data_.ik.a1, qmp6988_data_.ik.a2, + qmp6988_data_.ik.b00); + ESP_LOGV(TAG, "bt1[%lld] bt2[%lld] bp1[%lld] b11[%lld]\r\n", qmp6988_data_.ik.bt1, qmp6988_data_.ik.bt2, + qmp6988_data_.ik.bp1, qmp6988_data_.ik.b11); + ESP_LOGV(TAG, "bp2[%lld] b12[%lld] b21[%lld] bp3[%lld]\r\n", qmp6988_data_.ik.bp2, qmp6988_data_.ik.b12, + qmp6988_data_.ik.b21, qmp6988_data_.ik.bp3); + ESP_LOGV(TAG, "<----------- int calibration data -------------->\r\n"); + return true; +} + +QMP6988_S16_t QMP6988Component::get_compensated_temperature_(qmp6988_ik_data_t *ik, QMP6988_S32_t dt) { + QMP6988_S16_t ret; + QMP6988_S64_t wk1, wk2; + + // wk1: 60Q4 // bit size + wk1 = ((QMP6988_S64_t) ik->a1 * (QMP6988_S64_t) dt); // 31Q23+24-1=54 (54Q23) + wk2 = ((QMP6988_S64_t) ik->a2 * (QMP6988_S64_t) dt) >> 14; // 30Q47+24-1=53 (39Q33) + wk2 = (wk2 * (QMP6988_S64_t) dt) >> 10; // 39Q33+24-1=62 (52Q23) + wk2 = ((wk1 + wk2) / 32767) >> 19; // 54,52->55Q23 (20Q04) + ret = (QMP6988_S16_t)((ik->a0 + wk2) >> 4); // 21Q4 -> 17Q0 + return ret; +} + +QMP6988_S32_t QMP6988Component::get_compensated_pressure_(qmp6988_ik_data_t *ik, QMP6988_S32_t dp, QMP6988_S16_t tx) { + QMP6988_S32_t ret; + QMP6988_S64_t wk1, wk2, wk3; + + // wk1 = 48Q16 // bit size + wk1 = ((QMP6988_S64_t) ik->bt1 * (QMP6988_S64_t) tx); // 28Q15+16-1=43 (43Q15) + wk2 = ((QMP6988_S64_t) ik->bp1 * (QMP6988_S64_t) dp) >> 5; // 31Q20+24-1=54 (49Q15) + wk1 += wk2; // 43,49->50Q15 + wk2 = ((QMP6988_S64_t) ik->bt2 * (QMP6988_S64_t) tx) >> 1; // 34Q38+16-1=49 (48Q37) + wk2 = (wk2 * (QMP6988_S64_t) tx) >> 8; // 48Q37+16-1=63 (55Q29) + wk3 = wk2; // 55Q29 + wk2 = ((QMP6988_S64_t) ik->b11 * (QMP6988_S64_t) tx) >> 4; // 28Q34+16-1=43 (39Q30) + wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1; // 39Q30+24-1=62 (61Q29) + wk3 += wk2; // 55,61->62Q29 + wk2 = ((QMP6988_S64_t) ik->bp2 * (QMP6988_S64_t) dp) >> 13; // 29Q43+24-1=52 (39Q30) + wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1; // 39Q30+24-1=62 (61Q29) + wk3 += wk2; // 62,61->63Q29 + wk1 += wk3 >> 14; // Q29 >> 14 -> Q15 + wk2 = ((QMP6988_S64_t) ik->b12 * (QMP6988_S64_t) tx); // 29Q53+16-1=45 (45Q53) + wk2 = (wk2 * (QMP6988_S64_t) tx) >> 22; // 45Q53+16-1=61 (39Q31) + wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1; // 39Q31+24-1=62 (61Q30) + wk3 = wk2; // 61Q30 + wk2 = ((QMP6988_S64_t) ik->b21 * (QMP6988_S64_t) tx) >> 6; // 29Q60+16-1=45 (39Q54) + wk2 = (wk2 * (QMP6988_S64_t) dp) >> 23; // 39Q54+24-1=62 (39Q31) + wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1; // 39Q31+24-1=62 (61Q20) + wk3 += wk2; // 61,61->62Q30 + wk2 = ((QMP6988_S64_t) ik->bp3 * (QMP6988_S64_t) dp) >> 12; // 28Q65+24-1=51 (39Q53) + wk2 = (wk2 * (QMP6988_S64_t) dp) >> 23; // 39Q53+24-1=62 (39Q30) + wk2 = (wk2 * (QMP6988_S64_t) dp); // 39Q30+24-1=62 (62Q30) + wk3 += wk2; // 62,62->63Q30 + wk1 += wk3 >> 15; // Q30 >> 15 = Q15 + wk1 /= 32767L; + wk1 >>= 11; // Q15 >> 7 = Q4 + wk1 += ik->b00; // Q4 + 20Q4 + // wk1 >>= 4; // 28Q4 -> 24Q0 + ret = (QMP6988_S32_t) wk1; + return ret; +} + +void QMP6988Component::software_reset_() { + uint8_t ret = 0; + + ret = this->write_byte(QMP6988_RESET_REG, 0xe6); + if (ret != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Software Reset (0xe6) failed"); + } + delay(10); + + this->write_byte(QMP6988_RESET_REG, 0x00); +} + +void QMP6988Component::set_power_mode_(uint8_t power_mode) { + uint8_t data; + + ESP_LOGD(TAG, "Setting Power mode to: %d", power_mode); + + qmp6988_data_.power_mode = power_mode; + this->read_register(QMP6988_CTRLMEAS_REG, &data, 1); + data = data & 0xfc; + if (power_mode == QMP6988_SLEEP_MODE) { + data |= 0x00; + } else if (power_mode == QMP6988_FORCED_MODE) { + data |= 0x01; + } else if (power_mode == QMP6988_NORMAL_MODE) { + data |= 0x03; + } + this->write_byte(QMP6988_CTRLMEAS_REG, data); + + ESP_LOGD(TAG, "Set Power mode 0xf4=0x%x \r\n", data); + + delay(10); +} + +void QMP6988Component::write_filter_(unsigned char filter) { + uint8_t data; + + data = (filter & 0x03); + this->write_byte(QMP6988_CONFIG_REG, data); + delay(10); +} + +void QMP6988Component::write_oversampling_pressure_(unsigned char oversampling_p) { + uint8_t data; + + this->read_register(QMP6988_CTRLMEAS_REG, &data, 1); + data &= 0xe3; + data |= (oversampling_p << 2); + this->write_byte(QMP6988_CTRLMEAS_REG, data); + delay(10); +} + +void QMP6988Component::write_oversampling_temperature_(unsigned char oversampling_t) { + uint8_t data; + + this->read_register(QMP6988_CTRLMEAS_REG, &data, 1); + data &= 0x1f; + data |= (oversampling_t << 5); + this->write_byte(QMP6988_CTRLMEAS_REG, data); + delay(10); +} + +void QMP6988Component::set_temperature_oversampling(QMP6988Oversampling oversampling_t) { + this->temperature_oversampling_ = oversampling_t; +} + +void QMP6988Component::set_pressure_oversampling(QMP6988Oversampling oversampling_p) { + this->pressure_oversampling_ = oversampling_p; +} + +void QMP6988Component::set_iir_filter(QMP6988IIRFilter iirfilter) { this->iir_filter_ = iirfilter; } + +void QMP6988Component::calculate_altitude_(float pressure, float temp) { + float altitude; + altitude = (pow((101325 / pressure), 1 / 5.257) - 1) * (temp + 273.15) / 0.0065; + this->qmp6988_data_.altitude = altitude; +} + +void QMP6988Component::calculate_pressure_() { + uint8_t err = 0; + QMP6988_U32_t p_read, t_read; + QMP6988_S32_t p_raw, t_raw; + uint8_t a_data_uint8_tr[6] = {0}; + QMP6988_S32_t t_int, p_int; + this->qmp6988_data_.temperature = 0; + this->qmp6988_data_.pressure = 0; + + err = this->read_register(QMP6988_PRESSURE_MSB_REG, a_data_uint8_tr, 6); + if (err != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Error reading raw pressure/temp values"); + return; + } + p_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[0])) << SHIFT_LEFT_16_POSITION) | + (((QMP6988_U16_t)(a_data_uint8_tr[1])) << SHIFT_LEFT_8_POSITION) | (a_data_uint8_tr[2])); + p_raw = (QMP6988_S32_t)(p_read - SUBTRACTOR); + + t_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[3])) << SHIFT_LEFT_16_POSITION) | + (((QMP6988_U16_t)(a_data_uint8_tr[4])) << SHIFT_LEFT_8_POSITION) | (a_data_uint8_tr[5])); + t_raw = (QMP6988_S32_t)(t_read - SUBTRACTOR); + + t_int = this->get_compensated_temperature_(&(qmp6988_data_.ik), t_raw); + p_int = this->get_compensated_pressure_(&(qmp6988_data_.ik), p_raw, t_int); + + this->qmp6988_data_.temperature = (float) t_int / 256.0f; + this->qmp6988_data_.pressure = (float) p_int / 16.0f; +} + +void QMP6988Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up QMP6988"); + + bool ret; + ret = this->device_check_(); + if (!ret) { + ESP_LOGCONFIG(TAG, "Setup failed - device not found"); + } + + this->software_reset_(); + this->get_calibration_data_(); + this->set_power_mode_(QMP6988_NORMAL_MODE); + this->write_filter_(iir_filter_); + this->write_oversampling_pressure_(this->pressure_oversampling_); + this->write_oversampling_temperature_(this->temperature_oversampling_); +} + +void QMP6988Component::dump_config() { + ESP_LOGCONFIG(TAG, "QMP6988:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with QMP6988 failed!"); + } + LOG_UPDATE_INTERVAL(this); + + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + ESP_LOGCONFIG(TAG, " Temperature Oversampling: %s", oversampling_to_str(this->temperature_oversampling_)); + LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); + ESP_LOGCONFIG(TAG, " Pressure Oversampling: %s", oversampling_to_str(this->pressure_oversampling_)); + ESP_LOGCONFIG(TAG, " IIR Filter: %s", iir_filter_to_str(this->iir_filter_)); +} + +float QMP6988Component::get_setup_priority() const { return setup_priority::DATA; } + +void QMP6988Component::update() { + this->calculate_pressure_(); + float pressurehectopascals = this->qmp6988_data_.pressure / 100; + float temperature = this->qmp6988_data_.temperature; + + ESP_LOGD(TAG, "Temperature=%.2f°C, Pressure=%.2fhPa", temperature, pressurehectopascals); + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(temperature); + if (this->pressure_sensor_ != nullptr) + this->pressure_sensor_->publish_state(pressurehectopascals); +} + +} // namespace qmp6988 +} // namespace esphome diff --git a/esphome/components/qmp6988/qmp6988.h b/esphome/components/qmp6988/qmp6988.h new file mode 100644 index 0000000000..ef944ba4ff --- /dev/null +++ b/esphome/components/qmp6988/qmp6988.h @@ -0,0 +1,116 @@ +#pragma once + +#include "esphome/core/log.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace qmp6988 { + +#define QMP6988_U16_t unsigned short +#define QMP6988_S16_t short +#define QMP6988_U32_t unsigned int +#define QMP6988_S32_t int +#define QMP6988_U64_t unsigned long long +#define QMP6988_S64_t long long + +/* oversampling */ +enum QMP6988Oversampling { + QMP6988_OVERSAMPLING_SKIPPED = 0x00, + QMP6988_OVERSAMPLING_1X = 0x01, + QMP6988_OVERSAMPLING_2X = 0x02, + QMP6988_OVERSAMPLING_4X = 0x03, + QMP6988_OVERSAMPLING_8X = 0x04, + QMP6988_OVERSAMPLING_16X = 0x05, + QMP6988_OVERSAMPLING_32X = 0x06, + QMP6988_OVERSAMPLING_64X = 0x07, +}; + +/* filter */ +enum QMP6988IIRFilter { + QMP6988_IIR_FILTER_OFF = 0x00, + QMP6988_IIR_FILTER_2X = 0x01, + QMP6988_IIR_FILTER_4X = 0x02, + QMP6988_IIR_FILTER_8X = 0x03, + QMP6988_IIR_FILTER_16X = 0x04, + QMP6988_IIR_FILTER_32X = 0x05, +}; + +using qmp6988_cali_data_t = struct Qmp6988CaliData { + QMP6988_S32_t COE_a0; + QMP6988_S16_t COE_a1; + QMP6988_S16_t COE_a2; + QMP6988_S32_t COE_b00; + QMP6988_S16_t COE_bt1; + QMP6988_S16_t COE_bt2; + QMP6988_S16_t COE_bp1; + QMP6988_S16_t COE_b11; + QMP6988_S16_t COE_bp2; + QMP6988_S16_t COE_b12; + QMP6988_S16_t COE_b21; + QMP6988_S16_t COE_bp3; +}; + +using qmp6988_fk_data_t = struct Qmp6988FkData { + float a0, b00; + float a1, a2, bt1, bt2, bp1, b11, bp2, b12, b21, bp3; +}; + +using qmp6988_ik_data_t = struct Qmp6988IkData { + QMP6988_S32_t a0, b00; + QMP6988_S32_t a1, a2; + QMP6988_S64_t bt1, bt2, bp1, b11, bp2, b12, b21, bp3; +}; + +using qmp6988_data_t = struct Qmp6988Data { + uint8_t chip_id; + uint8_t power_mode; + float temperature; + float pressure; + float altitude; + qmp6988_cali_data_t qmp6988_cali; + qmp6988_ik_data_t ik; +}; + +class QMP6988Component : public PollingComponent, public i2c::I2CDevice { + public: + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } + + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + + void set_iir_filter(QMP6988IIRFilter iirfilter); + void set_temperature_oversampling(QMP6988Oversampling oversampling_t); + void set_pressure_oversampling(QMP6988Oversampling oversampling_p); + + protected: + qmp6988_data_t qmp6988_data_; + sensor::Sensor *temperature_sensor_; + sensor::Sensor *pressure_sensor_; + + QMP6988Oversampling temperature_oversampling_{QMP6988_OVERSAMPLING_16X}; + QMP6988Oversampling pressure_oversampling_{QMP6988_OVERSAMPLING_16X}; + QMP6988IIRFilter iir_filter_{QMP6988_IIR_FILTER_OFF}; + + void software_reset_(); + bool get_calibration_data_(); + bool device_check_(); + void set_power_mode_(uint8_t power_mode); + void write_oversampling_temperature_(unsigned char oversampling_t); + void write_oversampling_pressure_(unsigned char oversampling_p); + void write_filter_(unsigned char filter); + void calculate_pressure_(); + void calculate_altitude_(float pressure, float temp); + + QMP6988_S32_t get_compensated_pressure_(qmp6988_ik_data_t *ik, QMP6988_S32_t dp, QMP6988_S16_t tx); + QMP6988_S16_t get_compensated_temperature_(qmp6988_ik_data_t *ik, QMP6988_S32_t dt); +}; + +} // namespace qmp6988 +} // namespace esphome diff --git a/esphome/components/qmp6988/sensor.py b/esphome/components/qmp6988/sensor.py new file mode 100644 index 0000000000..fdcfd4e66b --- /dev/null +++ b/esphome/components/qmp6988/sensor.py @@ -0,0 +1,101 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, + CONF_IIR_FILTER, + CONF_OVERSAMPLING, +) + +DEPENDENCIES = ["i2c"] + +qmp6988_ns = cg.esphome_ns.namespace("qmp6988") +QMP6988Component = qmp6988_ns.class_( + "QMP6988Component", cg.PollingComponent, i2c.I2CDevice +) + +QMP6988Oversampling = qmp6988_ns.enum("QMP6988Oversampling") +OVERSAMPLING_OPTIONS = { + "NONE": QMP6988Oversampling.QMP6988_OVERSAMPLING_SKIPPED, + "1X": QMP6988Oversampling.QMP6988_OVERSAMPLING_1X, + "2X": QMP6988Oversampling.QMP6988_OVERSAMPLING_2X, + "4X": QMP6988Oversampling.QMP6988_OVERSAMPLING_4X, + "8X": QMP6988Oversampling.QMP6988_OVERSAMPLING_8X, + "16X": QMP6988Oversampling.QMP6988_OVERSAMPLING_16X, + "32X": QMP6988Oversampling.QMP6988_OVERSAMPLING_32X, + "64X": QMP6988Oversampling.QMP6988_OVERSAMPLING_64X, +} + +QMP6988IIRFilter = qmp6988_ns.enum("QMP6988IIRFilter") +IIR_FILTER_OPTIONS = { + "OFF": QMP6988IIRFilter.QMP6988_IIR_FILTER_OFF, + "2X": QMP6988IIRFilter.QMP6988_IIR_FILTER_2X, + "4X": QMP6988IIRFilter.QMP6988_IIR_FILTER_4X, + "8X": QMP6988IIRFilter.QMP6988_IIR_FILTER_8X, + "16X": QMP6988IIRFilter.QMP6988_IIR_FILTER_16X, + "32X": QMP6988IIRFilter.QMP6988_IIR_FILTER_32X, +} + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(QMP6988Component), + 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="8X"): 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="8X"): 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(0x70)) +) + + +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_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(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(conf[CONF_OVERSAMPLING])) + + cg.add(var.set_iir_filter(config[CONF_IIR_FILTER])) diff --git a/tests/test1.yaml b/tests/test1.yaml index 3763fd3fa5..bd4c919e67 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -798,6 +798,17 @@ sensor: value: 12345 total: name: "Pulse Meter Total" + - platform: qmp6988 + temperature: + name: "Living Temperature QMP" + oversampling: 32x + pressure: + name: "Living Pressure QMP" + oversampling: 2x + address: 0x70 + update_interval: 30s + iir_filter: 16x + i2c_id: i2c_bus - platform: rotary_encoder name: "Rotary Encoder" id: rotary_encoder1 From 68e957c14792ea6874b284db1f52f3d0731165f5 Mon Sep 17 00:00:00 2001 From: rbaron Date: Tue, 15 Mar 2022 23:05:29 +0100 Subject: [PATCH 172/238] Adds support for b-parasite's v2 BLE protocol (#3290) --- esphome/components/b_parasite/b_parasite.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/esphome/components/b_parasite/b_parasite.cpp b/esphome/components/b_parasite/b_parasite.cpp index ee12226977..2e548a8072 100644 --- a/esphome/components/b_parasite/b_parasite.cpp +++ b/esphome/components/b_parasite/b_parasite.cpp @@ -38,7 +38,7 @@ 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) { + if (protocol_version != 1 && protocol_version != 2) { ESP_LOGE(TAG, "Unsupported protocol version: %u", protocol_version); return false; } @@ -57,9 +57,15 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { uint16_t battery_millivolt = data[2] << 8 | data[3]; float battery_voltage = battery_millivolt / 1000.0f; - // Temperature in 1000 * Celsius. - uint16_t temp_millicelcius = data[4] << 8 | data[5]; - float temp_celcius = temp_millicelcius / 1000.0f; + // Temperature in 1000 * Celsius (protocol v1) or 100 * Celsius (protocol v2). + float temp_celsius; + if (protocol_version == 1) { + uint16_t temp_millicelsius = data[4] << 8 | data[5]; + temp_celsius = temp_millicelsius / 1000.0f; + } else { + int16_t temp_centicelsius = data[4] << 8 | data[5]; + temp_celsius = temp_centicelsius / 100.0f; + } // Relative air humidity in the range [0, 2^16). uint16_t humidity = data[6] << 8 | data[7]; @@ -76,7 +82,7 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { battery_voltage_->publish_state(battery_voltage); } if (temperature_ != nullptr) { - temperature_->publish_state(temp_celcius); + temperature_->publish_state(temp_celsius); } if (humidity_ != nullptr) { humidity_->publish_state(humidity_percent); From 4525588116492bca019758f64dddc1a14bf52eb1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Mar 2022 13:35:37 +1300 Subject: [PATCH 173/238] Add helper overloads for hex print 16-bit (#3297) --- esphome/core/helpers.cpp | 19 +++++++++++++++++++ esphome/core/helpers.h | 4 ++++ 2 files changed, 23 insertions(+) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index a346cd7e0b..b03d890ad8 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -213,6 +213,25 @@ std::string format_hex_pretty(const uint8_t *data, size_t length) { } std::string format_hex_pretty(const std::vector &data) { return format_hex_pretty(data.data(), data.size()); } +std::string format_hex_pretty(const uint16_t *data, size_t length) { + if (length == 0) + return ""; + std::string ret; + ret.resize(5 * length - 1); + for (size_t i = 0; i < length; i++) { + ret[5 * i] = format_hex_pretty_char((data[i] & 0xF000) >> 12); + ret[5 * i + 1] = format_hex_pretty_char((data[i] & 0x0F00) >> 8); + ret[5 * i + 2] = format_hex_pretty_char((data[i] & 0x00F0) >> 4); + ret[5 * i + 3] = format_hex_pretty_char(data[i] & 0x000F); + if (i != length - 1) + ret[5 * i + 2] = '.'; + } + if (length > 4) + return ret + " (" + to_string(length) + ")"; + return ret; +} +std::string format_hex_pretty(const std::vector &data) { return format_hex_pretty(data.data(), data.size()); } + ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { if (on == nullptr && strcasecmp(str, "on") == 0) return PARSE_ON; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index e0763d2c71..074bea6fd1 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -386,8 +386,12 @@ 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 word array \p data of length \p len in pretty-printed, human-readable hex. +std::string format_hex_pretty(const uint16_t *data, size_t length); /// Format the vector \p data in pretty-printed, human-readable hex. std::string format_hex_pretty(const std::vector &data); +/// Format the vector \p data in pretty-printed, human-readable hex. +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); From 0372d17a116a17b3da0048f927ee67cdb35f3c55 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Mar 2022 13:43:05 +1300 Subject: [PATCH 174/238] Allow custom register type for modbus number (#3202) --- esphome/components/modbus_controller/number/__init__.py | 6 ++++++ esphome/components/modbus_controller/number/modbus_number.h | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py index 56ec734315..37a39ff334 100644 --- a/esphome/components/modbus_controller/number/__init__.py +++ b/esphome/components/modbus_controller/number/__init__.py @@ -11,6 +11,7 @@ from esphome.const import ( ) from .. import ( + MODBUS_WRITE_REGISTER_TYPE, add_modbus_base_properties, modbus_controller_ns, modbus_calc_properties, @@ -24,6 +25,7 @@ from ..const import ( CONF_CUSTOM_COMMAND, CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_TYPE, CONF_SKIP_UPDATES, CONF_USE_WRITE_MULTIPLE, CONF_VALUE_TYPE, @@ -61,6 +63,9 @@ CONFIG_SCHEMA = cv.All( number.NUMBER_SCHEMA.extend(ModbusItemBaseSchema).extend( { cv.GenerateID(): cv.declare_id(ModbusNumber), + cv.Optional(CONF_REGISTER_TYPE, default="holding"): cv.enum( + MODBUS_WRITE_REGISTER_TYPE + ), cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, # 24 bits are the maximum value for fp32 before precison is lost @@ -81,6 +86,7 @@ async def to_code(config): byte_offset, reg_count = modbus_calc_properties(config) var = cg.new_Pvariable( config[CONF_ID], + config[CONF_REGISTER_TYPE], config[CONF_ADDRESS], byte_offset, config[CONF_BITMASK], diff --git a/esphome/components/modbus_controller/number/modbus_number.h b/esphome/components/modbus_controller/number/modbus_number.h index 0c525d9c89..aa5c8d1500 100644 --- a/esphome/components/modbus_controller/number/modbus_number.h +++ b/esphome/components/modbus_controller/number/modbus_number.h @@ -11,9 +11,9 @@ 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) { - this->register_type = ModbusRegisterType::HOLDING; + ModbusNumber(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) { + this->register_type = register_type; this->start_address = start_address; this->offset = offset; this->bitmask = bitmask; From e621b938e36b0b6ede82d14419a4830529789659 Mon Sep 17 00:00:00 2001 From: wysiwyng <4764286+wysiwyng@users.noreply.github.com> Date: Wed, 16 Mar 2022 20:33:05 +0100 Subject: [PATCH 175/238] Fix WDT reset during dallas search algorithm (#3293) --- esphome/components/dallas/esp_one_wire.cpp | 4 ---- esphome/components/dallas/esp_one_wire.h | 1 - 2 files changed, 5 deletions(-) diff --git a/esphome/components/dallas/esp_one_wire.cpp b/esphome/components/dallas/esp_one_wire.cpp index 885846e5e5..5bd0f42855 100644 --- a/esphome/components/dallas/esp_one_wire.cpp +++ b/esphome/components/dallas/esp_one_wire.cpp @@ -142,7 +142,6 @@ void IRAM_ATTR ESPOneWire::select(uint64_t address) { void IRAM_ATTR ESPOneWire::reset_search() { this->last_discrepancy_ = 0; this->last_device_flag_ = false; - this->last_family_discrepancy_ = 0; this->rom_number_ = 0; } uint64_t IRAM_ATTR ESPOneWire::search() { @@ -195,9 +194,6 @@ uint64_t IRAM_ATTR ESPOneWire::search() { if (!branch) { last_zero = id_bit_number; - if (last_zero < 9) { - this->last_discrepancy_ = last_zero; - } } } diff --git a/esphome/components/dallas/esp_one_wire.h b/esphome/components/dallas/esp_one_wire.h index ef6f079f02..7544a6fe98 100644 --- a/esphome/components/dallas/esp_one_wire.h +++ b/esphome/components/dallas/esp_one_wire.h @@ -60,7 +60,6 @@ class ESPOneWire { ISRInternalGPIOPin pin_; uint8_t last_discrepancy_{0}; - uint8_t last_family_discrepancy_{0}; bool last_device_flag_{false}; uint64_t rom_number_{0}; }; From bfbf88b2ea0d521564f3937111dcc6fc2a6cfbbb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 23 Mar 2022 09:45:05 +1300 Subject: [PATCH 176/238] Webserver utilize Component Iterator to not overload eventstream (#3310) --- esphome/components/api/api_connection.cpp | 2 +- esphome/components/api/api_server.h | 1 - esphome/components/api/list_entities.cpp | 3 +- esphome/components/api/list_entities.h | 6 +- esphome/components/api/proto.cpp | 1 - esphome/components/api/subscribe_state.cpp | 3 +- esphome/components/api/subscribe_state.h | 6 +- .../components/web_server/list_entities.cpp | 97 +++++++++++++++++++ esphome/components/web_server/list_entities.h | 60 ++++++++++++ esphome/components/web_server/web_server.cpp | 86 +--------------- esphome/components/web_server/web_server.h | 7 +- .../util.cpp => core/component_iterator.cpp} | 56 ++++++----- .../api/util.h => core/component_iterator.h} | 23 +++-- 13 files changed, 217 insertions(+), 134 deletions(-) create mode 100644 esphome/components/web_server/list_entities.cpp create mode 100644 esphome/components/web_server/list_entities.h rename esphome/{components/api/util.cpp => core/component_iterator.cpp} (80%) rename esphome/{components/api/util.h => core/component_iterator.h} (91%) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index b998ef5929..81f2465b74 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -23,7 +23,7 @@ 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) { + : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) { this->proto_write_buffer_.reserve(64); #if defined(USE_API_PLAINTEXT) diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 3214da5b3d..fdc46922ad 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -7,7 +7,6 @@ #include "esphome/components/socket/socket.h" #include "api_pb2.h" #include "api_pb2_service.h" -#include "util.h" #include "list_entities.h" #include "subscribe_state.h" #include "user_services.h" diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index fb0dfa3d05..9f55fda617 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -40,8 +40,7 @@ bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->s #endif bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); } -ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client) - : ComponentIterator(server), client_(client) {} +ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {} bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { auto resp = service->encode_list_service_response(); return this->client_->send_list_entities_services_response(resp); diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index bfceb39ebf..51c343eb03 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -1,8 +1,8 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/component_iterator.h" #include "esphome/core/defines.h" -#include "util.h" namespace esphome { namespace api { @@ -11,7 +11,7 @@ class APIConnection; class ListEntitiesIterator : public ComponentIterator { public: - ListEntitiesIterator(APIServer *server, APIConnection *client); + ListEntitiesIterator(APIConnection *client); #ifdef USE_BINARY_SENSOR bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; #endif @@ -60,5 +60,3 @@ class ListEntitiesIterator : public ComponentIterator { } // namespace api } // namespace esphome - -#include "api_server.h" diff --git a/esphome/components/api/proto.cpp b/esphome/components/api/proto.cpp index 0ba277d90a..ca7a4c0887 100644 --- a/esphome/components/api/proto.cpp +++ b/esphome/components/api/proto.cpp @@ -1,5 +1,4 @@ #include "proto.h" -#include "util.h" #include "esphome/core/log.h" namespace esphome { diff --git a/esphome/components/api/subscribe_state.cpp b/esphome/components/api/subscribe_state.cpp index 10416ecc5c..ba277502c8 100644 --- a/esphome/components/api/subscribe_state.cpp +++ b/esphome/components/api/subscribe_state.cpp @@ -50,8 +50,7 @@ bool InitialStateIterator::on_select(select::Select *select) { #ifdef USE_LOCK bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); } #endif -InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client) - : ComponentIterator(server), client_(client) {} +InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {} } // namespace api } // namespace esphome diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index caea013f84..515e1a2d07 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -1,9 +1,9 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/component_iterator.h" #include "esphome/core/controller.h" #include "esphome/core/defines.h" -#include "util.h" namespace esphome { namespace api { @@ -12,7 +12,7 @@ class APIConnection; class InitialStateIterator : public ComponentIterator { public: - InitialStateIterator(APIServer *server, APIConnection *client); + InitialStateIterator(APIConnection *client); #ifdef USE_BINARY_SENSOR bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; #endif @@ -55,5 +55,3 @@ class InitialStateIterator : public ComponentIterator { } // namespace api } // namespace esphome - -#include "api_server.h" diff --git a/esphome/components/web_server/list_entities.cpp b/esphome/components/web_server/list_entities.cpp new file mode 100644 index 0000000000..6f833a5c83 --- /dev/null +++ b/esphome/components/web_server/list_entities.cpp @@ -0,0 +1,97 @@ +#ifdef USE_ARDUINO + +#include "list_entities.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" +#include "esphome/core/util.h" + +#include "web_server.h" + +namespace esphome { +namespace web_server { + +ListEntitiesIterator::ListEntitiesIterator(WebServer *web_server) : web_server_(web_server) {} + +#ifdef USE_BINARY_SENSOR +bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->web_server_->events_.send( + this->web_server_->binary_sensor_json(binary_sensor, binary_sensor->state, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif +#ifdef USE_COVER +bool ListEntitiesIterator::on_cover(cover::Cover *cover) { + this->web_server_->events_.send(this->web_server_->cover_json(cover, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif +#ifdef USE_FAN +bool ListEntitiesIterator::on_fan(fan::Fan *fan) { + this->web_server_->events_.send(this->web_server_->fan_json(fan, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif +#ifdef USE_LIGHT +bool ListEntitiesIterator::on_light(light::LightState *light) { + this->web_server_->events_.send(this->web_server_->light_json(light, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif +#ifdef USE_SENSOR +bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { + this->web_server_->events_.send(this->web_server_->sensor_json(sensor, sensor->state, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif +#ifdef USE_SWITCH +bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { + this->web_server_->events_.send(this->web_server_->switch_json(a_switch, a_switch->state, DETAIL_ALL).c_str(), + "state"); + return true; +} +#endif +#ifdef USE_BUTTON +bool ListEntitiesIterator::on_button(button::Button *button) { + this->web_server_->events_.send(this->web_server_->button_json(button, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif +#ifdef USE_TEXT_SENSOR +bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { + this->web_server_->events_.send( + this->web_server_->text_sensor_json(text_sensor, text_sensor->state, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif +#ifdef USE_LOCK +bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { + this->web_server_->events_.send(this->web_server_->lock_json(a_lock, a_lock->state, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif + +#ifdef USE_CLIMATE +bool ListEntitiesIterator::on_climate(climate::Climate *climate) { + this->web_server_->events_.send(this->web_server_->climate_json(climate, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif + +#ifdef USE_NUMBER +bool ListEntitiesIterator::on_number(number::Number *number) { + this->web_server_->events_.send(this->web_server_->number_json(number, number->state, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif + +#ifdef USE_SELECT +bool ListEntitiesIterator::on_select(select::Select *select) { + this->web_server_->events_.send(this->web_server_->select_json(select, select->state, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif + +} // namespace web_server +} // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/web_server/list_entities.h b/esphome/components/web_server/list_entities.h new file mode 100644 index 0000000000..85868caff8 --- /dev/null +++ b/esphome/components/web_server/list_entities.h @@ -0,0 +1,60 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "esphome/core/component.h" +#include "esphome/core/component_iterator.h" +#include "esphome/core/defines.h" +namespace esphome { +namespace web_server { + +class WebServer; + +class ListEntitiesIterator : public ComponentIterator { + public: + ListEntitiesIterator(WebServer *web_server); +#ifdef USE_BINARY_SENSOR + bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; +#endif +#ifdef USE_COVER + bool on_cover(cover::Cover *cover) override; +#endif +#ifdef USE_FAN + bool on_fan(fan::Fan *fan) override; +#endif +#ifdef USE_LIGHT + bool on_light(light::LightState *light) override; +#endif +#ifdef USE_SENSOR + bool on_sensor(sensor::Sensor *sensor) override; +#endif +#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 +#ifdef USE_CLIMATE + bool on_climate(climate::Climate *climate) override; +#endif +#ifdef USE_NUMBER + bool on_number(number::Number *number) override; +#endif +#ifdef USE_SELECT + bool on_select(select::Select *select) override; +#endif +#ifdef USE_LOCK + bool on_lock(lock::Lock *a_lock) override; +#endif + + protected: + WebServer *web_server_; +}; + +} // namespace web_server +} // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 278aeab937..0dfd608661 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1,6 +1,7 @@ #ifdef USE_ARDUINO #include "web_server.h" + #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/core/entity_base.h" @@ -17,7 +18,7 @@ #endif #ifdef USE_LOGGER -#include +#include "esphome/components/logger/logger.h" #endif #ifdef USE_FAN @@ -106,87 +107,7 @@ void WebServer::setup() { }).c_str(), "ping", millis(), 30000); -#ifdef USE_SENSOR - for (auto *obj : App.get_sensors()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->sensor_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_SWITCH - for (auto *obj : App.get_switches()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->switch_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_BUTTON - for (auto *obj : App.get_buttons()) - client->send(this->button_json(obj, DETAIL_ALL).c_str(), "state"); -#endif - -#ifdef USE_BINARY_SENSOR - for (auto *obj : App.get_binary_sensors()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->binary_sensor_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_FAN - for (auto *obj : App.get_fans()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->fan_json(obj, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_LIGHT - for (auto *obj : App.get_lights()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->light_json(obj, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_TEXT_SENSOR - for (auto *obj : App.get_text_sensors()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->text_sensor_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_COVER - for (auto *obj : App.get_covers()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->cover_json(obj, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_NUMBER - for (auto *obj : App.get_numbers()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->number_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_SELECT - for (auto *obj : App.get_selects()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->select_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_CLIMATE - for (auto *obj : App.get_climates()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->climate_json(obj, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_LOCK - for (auto *obj : App.get_locks()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->lock_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); - } -#endif + this->entities_iterator_.begin(this->include_internal_); }); #ifdef USE_LOGGER @@ -203,6 +124,7 @@ void WebServer::setup() { this->set_interval(10000, [this]() { this->events_.send("", "ping", millis(), 30000); }); } +void WebServer::loop() { this->entities_iterator_.advance(); } 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()); diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 2717997f60..73813ecfa1 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -2,6 +2,8 @@ #ifdef USE_ARDUINO +#include "list_entities.h" + #include "esphome/components/web_server_base/web_server_base.h" #include "esphome/core/component.h" #include "esphome/core/controller.h" @@ -32,7 +34,7 @@ enum JsonDetail { DETAIL_ALL, DETAIL_STATE }; */ class WebServer : public Controller, public Component, public AsyncWebHandler { public: - WebServer(web_server_base::WebServerBase *base) : base_(base) {} + WebServer(web_server_base::WebServerBase *base) : base_(base), entities_iterator_(ListEntitiesIterator(this)) {} /** Set the URL to the CSS that's sent to each client. Defaults to * https://esphome.io/_static/webserver-v1.min.css @@ -76,6 +78,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { // (In most use cases you won't need these) /// Setup the internal web server and register handlers. void setup() override; + void loop() override; void dump_config() override; @@ -217,8 +220,10 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { bool isRequestHandlerTrivial() override; protected: + friend ListEntitiesIterator; web_server_base::WebServerBase *base_; AsyncEventSource events_{"/events"}; + ListEntitiesIterator entities_iterator_; const char *css_url_{nullptr}; const char *css_include_{nullptr}; const char *js_url_{nullptr}; diff --git a/esphome/components/api/util.cpp b/esphome/core/component_iterator.cpp similarity index 80% rename from esphome/components/api/util.cpp rename to esphome/core/component_iterator.cpp index fd55f89f9b..4781607a2d 100644 --- a/esphome/components/api/util.cpp +++ b/esphome/core/component_iterator.cpp @@ -1,16 +1,18 @@ -#include "util.h" -#include "api_server.h" -#include "user_services.h" -#include "esphome/core/log.h" +#include "component_iterator.h" + #include "esphome/core/application.h" -namespace esphome { -namespace api { +#ifdef USE_API +#include "esphome/components/api/api_server.h" +#include "esphome/components/api/user_services.h" +#endif -ComponentIterator::ComponentIterator(APIServer *server) : server_(server) {} -void ComponentIterator::begin() { +namespace esphome { + +void ComponentIterator::begin(bool include_internal) { this->state_ = IteratorState::BEGIN; this->at_ = 0; + this->include_internal_ = include_internal; } void ComponentIterator::advance() { bool advance_platform = false; @@ -32,7 +34,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *binary_sensor = App.get_binary_sensors()[this->at_]; - if (binary_sensor->is_internal()) { + if (binary_sensor->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -47,7 +49,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *cover = App.get_covers()[this->at_]; - if (cover->is_internal()) { + if (cover->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -62,7 +64,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *fan = App.get_fans()[this->at_]; - if (fan->is_internal()) { + if (fan->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -77,7 +79,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *light = App.get_lights()[this->at_]; - if (light->is_internal()) { + if (light->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -92,7 +94,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *sensor = App.get_sensors()[this->at_]; - if (sensor->is_internal()) { + if (sensor->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -107,7 +109,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *a_switch = App.get_switches()[this->at_]; - if (a_switch->is_internal()) { + if (a_switch->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -122,7 +124,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *button = App.get_buttons()[this->at_]; - if (button->is_internal()) { + if (button->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -137,7 +139,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *text_sensor = App.get_text_sensors()[this->at_]; - if (text_sensor->is_internal()) { + if (text_sensor->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -146,20 +148,22 @@ void ComponentIterator::advance() { } break; #endif +#ifdef USE_API case IteratorState ::SERVICE: - if (this->at_ >= this->server_->get_user_services().size()) { + if (this->at_ >= api::global_api_server->get_user_services().size()) { advance_platform = true; } else { - auto *service = this->server_->get_user_services()[this->at_]; + auto *service = api::global_api_server->get_user_services()[this->at_]; success = this->on_service(service); } break; +#endif #ifdef USE_ESP32_CAMERA case IteratorState::CAMERA: if (esp32_camera::global_esp32_camera == nullptr) { advance_platform = true; } else { - if (esp32_camera::global_esp32_camera->is_internal()) { + if (esp32_camera::global_esp32_camera->is_internal() && !this->include_internal_) { advance_platform = success = true; break; } else { @@ -174,7 +178,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *climate = App.get_climates()[this->at_]; - if (climate->is_internal()) { + if (climate->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -189,7 +193,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *number = App.get_numbers()[this->at_]; - if (number->is_internal()) { + if (number->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -204,7 +208,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *select = App.get_selects()[this->at_]; - if (select->is_internal()) { + if (select->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -219,7 +223,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *a_lock = App.get_locks()[this->at_]; - if (a_lock->is_internal()) { + if (a_lock->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -244,10 +248,10 @@ void ComponentIterator::advance() { } bool ComponentIterator::on_end() { return true; } bool ComponentIterator::on_begin() { return true; } -bool ComponentIterator::on_service(UserServiceDescriptor *service) { return true; } +#ifdef USE_API +bool ComponentIterator::on_service(api::UserServiceDescriptor *service) { return true; } +#endif #ifdef USE_ESP32_CAMERA bool ComponentIterator::on_camera(esp32_camera::ESP32Camera *camera) { return true; } #endif - -} // namespace api } // namespace esphome diff --git a/esphome/components/api/util.h b/esphome/core/component_iterator.h similarity index 91% rename from esphome/components/api/util.h rename to esphome/core/component_iterator.h index 9204b0829e..bd95fe95e1 100644 --- a/esphome/components/api/util.h +++ b/esphome/core/component_iterator.h @@ -1,23 +1,24 @@ #pragma once -#include "esphome/core/helpers.h" #include "esphome/core/component.h" #include "esphome/core/controller.h" +#include "esphome/core/helpers.h" + #ifdef USE_ESP32_CAMERA #include "esphome/components/esp32_camera/esp32_camera.h" #endif namespace esphome { -namespace api { -class APIServer; +#ifdef USE_API +namespace api { class UserServiceDescriptor; +} // namespace api +#endif class ComponentIterator { public: - ComponentIterator(APIServer *server); - - void begin(); + void begin(bool include_internal = false); void advance(); virtual bool on_begin(); #ifdef USE_BINARY_SENSOR @@ -44,7 +45,9 @@ class ComponentIterator { #ifdef USE_TEXT_SENSOR virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0; #endif - virtual bool on_service(UserServiceDescriptor *service); +#ifdef USE_API + virtual bool on_service(api::UserServiceDescriptor *service); +#endif #ifdef USE_ESP32_CAMERA virtual bool on_camera(esp32_camera::ESP32Camera *camera); #endif @@ -90,7 +93,9 @@ class ComponentIterator { #ifdef USE_TEXT_SENSOR TEXT_SENSOR, #endif +#ifdef USE_API SERVICE, +#endif #ifdef USE_ESP32_CAMERA CAMERA, #endif @@ -109,9 +114,7 @@ class ComponentIterator { MAX, } state_{IteratorState::NONE}; size_t at_{0}; - - APIServer *server_; + bool include_internal_{false}; }; -} // namespace api } // namespace esphome From 1496bc1b0729cfaef625df8b2d0d5e732b20c2a1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 23 Mar 2022 09:46:25 +1300 Subject: [PATCH 177/238] Reserve less memory for json (#3289) --- esphome/components/json/json_util.cpp | 60 +++++++++++++++++++-------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 9acba76597..2070b312e8 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -16,16 +16,24 @@ static const char *const TAG = "json"; static std::vector global_json_build_buffer; // NOLINT std::string build_json(const json_build_t &f) { - // Here we are allocating as much heap memory as available minus 2kb to be safe + // Here we are allocating up to 5kb of memory, + // with the heap size minus 2kb to be safe if less than 5kb // 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) + const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance) #elif defined(USE_ESP32) - const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) - 2048; + const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL); #endif - DynamicJsonDocument json_document(free_heap); + const size_t request_size = std::min(free_heap - 2048, (size_t) 5120); + + DynamicJsonDocument json_document(request_size); + if (json_document.memoryPool().buffer() == nullptr) { + ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, largest free heap block: %u bytes", + request_size, free_heap); + return "{}"; + } JsonObject root = json_document.to(); f(root); json_document.shrinkToFit(); @@ -36,27 +44,45 @@ std::string build_json(const json_build_t &f) { } 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 + // Here we are allocating 1.5 times the data size, + // with the heap size minus 2kb to be safe if less than that // 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) + const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance) #elif defined(USE_ESP32) - const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) - 2048; + const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL); #endif + bool pass = false; + do { + const size_t request_size = std::min(free_heap - 2048, (size_t)(data.size() * 1.5)); - DynamicJsonDocument json_document(free_heap); - DeserializationError err = deserializeJson(json_document, data); - json_document.shrinkToFit(); + DynamicJsonDocument json_document(request_size); + if (json_document.memoryPool().buffer() == nullptr) { + ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size, + free_heap); + return; + } + DeserializationError err = deserializeJson(json_document, data); + json_document.shrinkToFit(); - JsonObject root = json_document.as(); + JsonObject root = json_document.as(); - if (err) { - ESP_LOGW(TAG, "Parsing JSON failed."); - return; - } - - f(root); + if (err == DeserializationError::Ok) { + pass = true; + f(root); + } else if (err == DeserializationError::NoMemory) { + if (request_size * 2 >= free_heap) { + ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller"); + return; + } + ESP_LOGW(TAG, "Increasing memory allocation."); + continue; + } else { + ESP_LOGE(TAG, "JSON parse error: %s", err.c_str()); + return; + } + } while (!pass); } } // namespace json From 58b70b42dd5fc91e7e0eca7db20ea114b5b12672 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 23 Mar 2022 11:12:22 +1300 Subject: [PATCH 178/238] Add small delay before setting up app in safe mode (#3323) --- esphome/components/ota/ota_component.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 37da3bdc44..3138c495da 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -473,6 +473,8 @@ bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_ App.reboot(); }); + // Delay here to allow power to stabilise before Wi-Fi/Ethernet is initialised. + delay(100); // NOLINT App.setup(); ESP_LOGI(TAG, "Waiting for OTA attempt."); From 2034ab4f6c88dba9e14615dc4ced56a86ecc2372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=2E=20=C3=81rkosi=20R=C3=B3bert?= Date: Thu, 24 Mar 2022 02:28:21 +0100 Subject: [PATCH 179/238] increase delay for Ethernet module warm up (#3326) --- esphome/components/ota/ota_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 3138c495da..fa2605d589 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -474,7 +474,7 @@ bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_ }); // Delay here to allow power to stabilise before Wi-Fi/Ethernet is initialised. - delay(100); // NOLINT + delay(300); // NOLINT App.setup(); ESP_LOGI(TAG, "Waiting for OTA attempt."); From d8024a59288d0db176dc18eed0ba58b74eafda2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Mar 2022 14:29:43 +1300 Subject: [PATCH 180/238] Bump esptool from 3.2 to 3.3 (#3327) Bumps [esptool](https://github.com/espressif/esptool) from 3.2 to 3.3. - [Release notes](https://github.com/espressif/esptool/releases) - [Commits](https://github.com/espressif/esptool/compare/v3.2...v3.3) --- updated-dependencies: - dependency-name: esptool 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 c23cdcd33f..e934eb2c6f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ tzlocal==4.1 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.5 # When updating platformio, also update Dockerfile -esptool==3.2 +esptool==3.3 click==8.0.4 esphome-dashboard==20220309.0 aioesphomeapi==10.8.2 From 48584e94c4cc14d878bfac1fa53cd05cb821f540 Mon Sep 17 00:00:00 2001 From: Stanislav Meduna Date: Thu, 24 Mar 2022 07:37:48 +0100 Subject: [PATCH 181/238] Allow to set user defined characters on LCD (#3322) --- esphome/components/lcd_base/__init__.py | 33 ++++++++++++++++++++- esphome/components/lcd_base/lcd_display.cpp | 7 +++++ esphome/components/lcd_base/lcd_display.h | 5 ++++ tests/test1.yaml | 11 +++++++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/esphome/components/lcd_base/__init__.py b/esphome/components/lcd_base/__init__.py index 0ed2036c55..92fd0b5563 100644 --- a/esphome/components/lcd_base/__init__.py +++ b/esphome/components/lcd_base/__init__.py @@ -1,7 +1,9 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import display -from esphome.const import CONF_DIMENSIONS +from esphome.const import CONF_DIMENSIONS, CONF_POSITION, CONF_DATA + +CONF_USER_CHARACTERS = "user_characters" lcd_base_ns = cg.esphome_ns.namespace("lcd_base") LCDDisplay = lcd_base_ns.class_("LCDDisplay", cg.PollingComponent) @@ -16,9 +18,35 @@ def validate_lcd_dimensions(value): return value +def validate_user_characters(value): + positions = set() + for conf in value: + if conf[CONF_POSITION] in positions: + raise cv.Invalid( + f"Duplicate user defined character at position {conf[CONF_POSITION]}" + ) + positions.add(conf[CONF_POSITION]) + return value + + LCD_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend( { cv.Required(CONF_DIMENSIONS): validate_lcd_dimensions, + cv.Optional(CONF_USER_CHARACTERS): cv.All( + cv.ensure_list( + cv.Schema( + { + cv.Required(CONF_POSITION): cv.int_range(min=0, max=7), + cv.Required(CONF_DATA): cv.All( + cv.ensure_list(cv.int_range(min=0, max=31)), + cv.Length(min=8, max=8), + ), + } + ), + ), + cv.Length(max=8), + validate_user_characters, + ), } ).extend(cv.polling_component_schema("1s")) @@ -27,3 +55,6 @@ async def setup_lcd_display(var, config): await cg.register_component(var, config) await display.register_display(var, config) cg.add(var.set_dimensions(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1])) + if CONF_USER_CHARACTERS in config: + for usr in config[CONF_USER_CHARACTERS]: + cg.add(var.set_user_defined_char(usr[CONF_POSITION], usr[CONF_DATA])) diff --git a/esphome/components/lcd_base/lcd_display.cpp b/esphome/components/lcd_base/lcd_display.cpp index ddd7d6a6b3..b937e36c6c 100644 --- a/esphome/components/lcd_base/lcd_display.cpp +++ b/esphome/components/lcd_base/lcd_display.cpp @@ -65,6 +65,13 @@ void LCDDisplay::setup() { this->command_(LCD_DISPLAY_COMMAND_FUNCTION_SET | display_function); } + // store user defined characters + for (auto &user_defined_char : this->user_defined_chars_) { + this->command_(LCD_DISPLAY_COMMAND_SET_CGRAM_ADDR | (user_defined_char.first << 3)); + for (auto data : user_defined_char.second) + this->send(data, true); + } + this->command_(LCD_DISPLAY_COMMAND_FUNCTION_SET | display_function); uint8_t display_control = LCD_DISPLAY_DISPLAY_ON; this->command_(LCD_DISPLAY_COMMAND_DISPLAY_CONTROL | display_control); diff --git a/esphome/components/lcd_base/lcd_display.h b/esphome/components/lcd_base/lcd_display.h index ee150059c6..0c9e59758c 100644 --- a/esphome/components/lcd_base/lcd_display.h +++ b/esphome/components/lcd_base/lcd_display.h @@ -7,6 +7,8 @@ #include "esphome/components/time/real_time_clock.h" #endif +#include + namespace esphome { namespace lcd_base { @@ -19,6 +21,8 @@ class LCDDisplay : public PollingComponent { this->rows_ = rows; } + void set_user_defined_char(uint8_t pos, const std::vector &data) { this->user_defined_chars_[pos] = data; } + void setup() override; float get_setup_priority() const override; void update() override; @@ -58,6 +62,7 @@ class LCDDisplay : public PollingComponent { uint8_t columns_; uint8_t rows_; uint8_t *buffer_{nullptr}; + std::map > user_defined_chars_; }; } // namespace lcd_base diff --git a/tests/test1.yaml b/tests/test1.yaml index bd4c919e67..181f62d3f4 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2218,6 +2218,17 @@ display: - platform: lcd_pcf8574 dimensions: 18x4 address: 0x3F + user_characters: + - position: 0 + data: + - 0b00000 + - 0b01010 + - 0b00000 + - 0b00100 + - 0b00100 + - 0b10001 + - 0b01110 + - 0b00000 lambda: |- it.print("Hello World!"); i2c_id: i2c_bus From 9a82057303627d6ee656032df962c87a2456afde Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 28 Mar 2022 01:07:48 +0200 Subject: [PATCH 182/238] Font allow using google fonts directly (#3243) --- esphome/components/font/__init__.py | 151 +++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 4 deletions(-) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 6af5be45d4..9317b2ec94 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -1,12 +1,29 @@ import functools +from pathlib import Path +import hashlib +import re + +import requests 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_RAW_DATA_ID, CONF_SIZE +from esphome.const import ( + CONF_FAMILY, + CONF_FILE, + CONF_GLYPHS, + CONF_ID, + CONF_RAW_DATA_ID, + CONF_TYPE, + CONF_SIZE, + CONF_PATH, + CONF_WEIGHT, +) from esphome.core import CORE, HexInt + +DOMAIN = "font" DEPENDENCIES = ["display"] MULTI_CONF = True @@ -71,6 +88,128 @@ def validate_truetype_file(value): return cv.file_(value) +def _compute_gfonts_local_path(value) -> Path: + name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1" + base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN + h = hashlib.new("sha256") + h.update(name.encode()) + return base_dir / h.hexdigest()[:8] / "font.ttf" + + +TYPE_LOCAL = "local" +TYPE_GFONTS = "gfonts" +LOCAL_SCHEMA = cv.Schema( + { + cv.Required(CONF_PATH): validate_truetype_file, + } +) +CONF_ITALIC = "italic" +FONT_WEIGHTS = { + "thin": 100, + "extra-light": 200, + "light": 300, + "regular": 400, + "medium": 500, + "semi-bold": 600, + "bold": 700, + "extra-bold": 800, + "black": 900, +} + + +def validate_weight_name(value): + return FONT_WEIGHTS[cv.one_of(*FONT_WEIGHTS, lower=True, space="-")(value)] + + +def download_gfonts(value): + wght = value[CONF_WEIGHT] + if value[CONF_ITALIC]: + wght = f"1,{wght}" + name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}" + url = f"https://fonts.googleapis.com/css2?family={value[CONF_FAMILY]}:wght@{wght}" + + path = _compute_gfonts_local_path(value) + if path.is_file(): + return value + try: + req = requests.get(url) + req.raise_for_status() + except requests.exceptions.RequestException as e: + raise cv.Invalid( + f"Could not download font for {name}, please check the fonts exists " + f"at google fonts ({e})" + ) + match = re.search(r"src:\s+url\((.+)\)\s+format\('truetype'\);", req.text) + if match is None: + raise cv.Invalid( + f"Could not extract ttf file from gfonts response for {name}, " + f"please report this." + ) + + ttf_url = match.group(1) + try: + req = requests.get(ttf_url) + req.raise_for_status() + except requests.exceptions.RequestException as e: + raise cv.Invalid(f"Could not download ttf file for {name} ({ttf_url}): {e}") + + path.parent.mkdir(exist_ok=True, parents=True) + path.write_bytes(req.content) + return value + + +GFONTS_SCHEMA = cv.All( + { + cv.Required(CONF_FAMILY): cv.string_strict, + cv.Optional(CONF_WEIGHT, default="regular"): cv.Any( + cv.int_, validate_weight_name + ), + cv.Optional(CONF_ITALIC, default=False): cv.boolean, + }, + download_gfonts, +) + + +def validate_file_shorthand(value): + value = cv.string_strict(value) + if value.startswith("gfonts://"): + match = re.match(r"^gfonts://([^@]+)(@.+)?$", value) + if match is None: + raise cv.Invalid("Could not parse gfonts shorthand syntax, please check it") + family = match.group(1) + weight = match.group(2) + data = { + CONF_TYPE: TYPE_GFONTS, + CONF_FAMILY: family, + } + if weight is not None: + data[CONF_WEIGHT] = weight[1:] + return FILE_SCHEMA(data) + return FILE_SCHEMA( + { + CONF_TYPE: TYPE_LOCAL, + CONF_PATH: value, + } + ) + + +TYPED_FILE_SCHEMA = cv.typed_schema( + { + TYPE_LOCAL: LOCAL_SCHEMA, + TYPE_GFONTS: GFONTS_SCHEMA, + } +) + + +def _file_schema(value): + if isinstance(value, str): + return validate_file_shorthand(value) + return TYPED_FILE_SCHEMA(value) + + +FILE_SCHEMA = cv.Schema(_file_schema) + + DEFAULT_GLYPHS = ( ' !"%()+=,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' ) @@ -79,7 +218,7 @@ CONF_RAW_GLYPH_ID = "raw_glyph_id" FONT_SCHEMA = cv.Schema( { cv.Required(CONF_ID): cv.declare_id(Font), - cv.Required(CONF_FILE): validate_truetype_file, + cv.Required(CONF_FILE): FILE_SCHEMA, cv.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs, cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1), cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), @@ -93,9 +232,13 @@ CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA) async def to_code(config): from PIL import ImageFont - path = CORE.relative_config_path(config[CONF_FILE]) + conf = config[CONF_FILE] + if conf[CONF_TYPE] == TYPE_LOCAL: + path = CORE.relative_config_path(conf[CONF_PATH]) + elif conf[CONF_TYPE] == TYPE_GFONTS: + path = _compute_gfonts_local_path(conf) try: - font = ImageFont.truetype(path, config[CONF_SIZE]) + font = ImageFont.truetype(str(path), config[CONF_SIZE]) except Exception as e: raise core.EsphomeError(f"Could not load truetype file {path}: {e}") From 6b9371d1053f4e47c290af22900a8fd1ffcae23a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 28 Mar 2022 17:04:25 +1300 Subject: [PATCH 183/238] Actually increase request memory for json parsing (#3331) --- esphome/components/json/json_util.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 2070b312e8..10179c9954 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -54,9 +54,8 @@ void parse_json(const std::string &data, const json_parse_t &f) { const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL); #endif bool pass = false; + size_t request_size = std::min(free_heap - 2048, (size_t)(data.size() * 1.5)); do { - const size_t request_size = std::min(free_heap - 2048, (size_t)(data.size() * 1.5)); - DynamicJsonDocument json_document(request_size); if (json_document.memoryPool().buffer() == nullptr) { ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size, @@ -76,7 +75,8 @@ void parse_json(const std::string &data, const json_parse_t &f) { ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller"); return; } - ESP_LOGW(TAG, "Increasing memory allocation."); + ESP_LOGV(TAG, "Increasing memory allocation."); + request_size *= 2; continue; } else { ESP_LOGE(TAG, "JSON parse error: %s", err.c_str()); From cf5c640ae4ed59bd98460dbfeb3dd528f67d23e2 Mon Sep 17 00:00:00 2001 From: Dan Jackson Date: Tue, 29 Mar 2022 02:05:38 -0700 Subject: [PATCH 184/238] Change beginning of file comments to avoid creating doxygen tag for `esphome` namespace (#3314) --- esphome/components/rc522_spi/rc522_spi.h | 15 +++++++-------- esphome/components/sx1509/sx1509_registers.h | 13 +++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/esphome/components/rc522_spi/rc522_spi.h b/esphome/components/rc522_spi/rc522_spi.h index 58edbbed4f..0ccbcd7588 100644 --- a/esphome/components/rc522_spi/rc522_spi.h +++ b/esphome/components/rc522_spi/rc522_spi.h @@ -1,3 +1,10 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/rc522/rc522.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { /** * Library based on https://github.com/miguelbalboa/rfid * and adapted to ESPHome by @glmnet @@ -6,14 +13,6 @@ * * */ - -#pragma once - -#include "esphome/core/component.h" -#include "esphome/components/rc522/rc522.h" -#include "esphome/components/spi/spi.h" - -namespace esphome { namespace rc522_spi { class RC522Spi : public rc522::RC522, diff --git a/esphome/components/sx1509/sx1509_registers.h b/esphome/components/sx1509/sx1509_registers.h index b97b85993f..9712cacf9b 100644 --- a/esphome/components/sx1509/sx1509_registers.h +++ b/esphome/components/sx1509/sx1509_registers.h @@ -1,11 +1,15 @@ -/****************************************************************************** +/* sx1509_registers.h Register definitions for SX1509. Jim Lindblom @ SparkFun Electronics Original Creation Date: September 21, 2015 https://github.com/sparkfun/SparkFun_SX1509_Arduino_Library +*/ +#pragma once -Here you'll find the Arduino code used to interface with the SX1509 I2C +namespace esphome { +/** + Here you'll find the Arduino code used to interface with the SX1509 I2C 16 I/O expander. There are functions to take advantage of everything the SX1509 provides - input/output setting, writing pins high/low, reading the input value of pins, LED driver utilities (blink, breath, pwm), and @@ -20,10 +24,7 @@ This code is beerware; if you see me (or any other SparkFun employee) at the local, and you've found our code helpful, please buy us a round! Distributed as-is; no warranty is given. -******************************************************************************/ -#pragma once - -namespace esphome { +*/ namespace sx1509 { const uint8_t REG_INPUT_DISABLE_B = From 7f7175b1840c2ad38104159e9881cf2ad24d071c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 29 Mar 2022 22:22:11 +1300 Subject: [PATCH 185/238] Publish custom data when modbus number lambda fills vector (#3295) --- .../modbus_controller/modbus_controller.cpp | 22 ++++++++ .../modbus_controller/modbus_controller.h | 20 +++++-- .../number/modbus_number.cpp | 55 +++++++++++-------- 3 files changed, 69 insertions(+), 28 deletions(-) diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 64046b9578..91e0dcc45f 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -455,6 +455,28 @@ ModbusCommandItem ModbusCommandItem::create_custom_command( 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 register_type, uint16_t start_address, const std::vector &data) { + ESP_LOGI(TAG, "Custom Command sent"); + }; + } else { + cmd.on_data_func = handler; + } + for (auto v : values) { + cmd.payload.push_back((v >> 8) & 0xFF); + cmd.payload.push_back(v & 0xFF); + } + + 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(), diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 09395f29b3..6aecf7f8a4 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -2,12 +2,12 @@ #include "esphome/core/component.h" -#include "esphome/core/automation.h" #include "esphome/components/modbus/modbus.h" +#include "esphome/core/automation.h" #include -#include #include +#include #include namespace esphome { @@ -374,8 +374,8 @@ class ModbusCommandItem { 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 values byte vector of data to be sent to the device. The complete payload must be provided with the + * exception of the crc codes * @param handler function called when the response is received. Default is just logging a response * @return ModbusCommandItem with the prepared command */ @@ -383,6 +383,18 @@ class ModbusCommandItem { ModbusController *modbusdevice, const std::vector &values, std::function &data)> &&handler = nullptr); + + /** Create custom modbus command + * @param modbusdevice pointer to the device to execute the command + * @param values word vector of data to be sent to the device. The complete payload must be provided with the + * exception of the crc codes + * @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. diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index a0e990d272..001cfb5787 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -26,6 +26,7 @@ void ModbusNumber::parse_and_publish(const std::vector &data) { } void ModbusNumber::control(float value) { + ModbusCommandItem write_cmd; std::vector data; float write_value = value; // Is there are lambda configured? @@ -45,33 +46,39 @@ void ModbusNumber::control(float value) { write_value = multiply_by_ * write_value; } - // lambda didn't set payload - if (data.empty()) { - 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, write_value); - - // Create and send the write command - ModbusCommandItem write_cmd; - if (this->register_count == 1 && !this->use_write_multiple_) { - // since offset is in bytes and a register is 16 bits we get the start by adding offset/2 - write_cmd = - ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]); + if (!data.empty()) { + ESP_LOGV(TAG, "Modbus Number write raw: %s", format_hex_pretty(data).c_str()); + write_cmd = ModbusCommandItem::create_custom_command( + this->parent_, data, + [this, write_cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { + this->parent_->on_write_register_response(write_cmd.register_type, this->start_address, data); + }); } else { - write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, - this->register_count, data); + 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, write_value); + + // Create and send the write command + if (this->register_count == 1 && !this->use_write_multiple_) { + // since offset is in bytes and a register is 16 bits we get the start by adding offset/2 + write_cmd = + ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]); + } else { + write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, + 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); + }; } - // 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); + this->publish_state(value); } void ModbusNumber::dump_config() { LOG_NUMBER(TAG, "Modbus Number", this); } From 9de61fcf587514f203f8215ffaa186b280975da8 Mon Sep 17 00:00:00 2001 From: Ian Reinhart Geiser Date: Thu, 31 Mar 2022 23:46:39 -0400 Subject: [PATCH 186/238] Define touchscreen support when in use. (#3296) --- esphome/components/touchscreen/__init__.py | 7 +++++++ esphome/core/defines.h | 1 + 2 files changed, 8 insertions(+) diff --git a/esphome/components/touchscreen/__init__.py b/esphome/components/touchscreen/__init__.py index 125103e2b8..a4bdc8cafd 100644 --- a/esphome/components/touchscreen/__init__.py +++ b/esphome/components/touchscreen/__init__.py @@ -4,6 +4,7 @@ import esphome.codegen as cg from esphome.components import display from esphome import automation from esphome.const import CONF_ON_TOUCH +from esphome.core import coroutine_with_priority CODEOWNERS = ["@jesserockz"] DEPENDENCIES = ["display"] @@ -39,3 +40,9 @@ async def register_touchscreen(var, config): [(TouchPoint, "touch")], config[CONF_ON_TOUCH], ) + + +@coroutine_with_priority(100.0) +async def to_code(config): + cg.add_global(touchscreen_ns.using) + cg.add_define("USE_TOUCHSCREEN") diff --git a/esphome/core/defines.h b/esphome/core/defines.h index aabb5510f4..b5c82338b3 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -40,6 +40,7 @@ #define USE_SWITCH #define USE_TEXT_SENSOR #define USE_TIME +#define USE_TOUCHSCREEN #define USE_UART_DEBUGGER #define USE_WIFI From 05dc97099a4efe6810ca682939f322106deb0f2d Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Sun, 3 Apr 2022 04:30:22 -0300 Subject: [PATCH 187/238] New vscode schema gen (#3336) --- esphome/automation.py | 21 +- esphome/components/logger/__init__.py | 18 +- .../components/modbus_controller/__init__.py | 8 +- .../modbus_controller/select/__init__.py | 2 - esphome/config_validation.py | 10 +- esphome/jsonschema.py | 14 +- script/build_jsonschema.py | 59 +- script/build_language_schema.py | 813 ++++++++++++++++++ 8 files changed, 887 insertions(+), 58 deletions(-) create mode 100644 script/build_language_schema.py diff --git a/esphome/automation.py b/esphome/automation.py index fab998527f..4007dc4c51 100644 --- a/esphome/automation.py +++ b/esphome/automation.py @@ -262,21 +262,16 @@ async def repeat_action_to_code(config, action_id, template_arg, args): return var -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: - return schema(value) - return validate_wait_until({CONF_CONDITION: value}) +_validate_wait_until = cv.maybe_simple_value( + { + cv.Required(CONF_CONDITION): validate_potentially_and_condition, + cv.Optional(CONF_TIMEOUT): cv.templatable(cv.positive_time_period_milliseconds), + }, + key=CONF_CONDITION, +) -@register_action("wait_until", WaitUntilAction, validate_wait_until) +@register_action("wait_until", WaitUntilAction, _validate_wait_until) 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) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 20a0b0f792..d11b00405d 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -203,15 +203,6 @@ async def to_code(config): ) -def maybe_simple_message(schema): - def validator(value): - if isinstance(value, dict): - return cv.Schema(schema)(value) - return cv.Schema(schema)({CONF_FORMAT: value}) - - return validator - - def validate_printf(value): # https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python cfmt = r""" @@ -234,7 +225,7 @@ def validate_printf(value): CONF_LOGGER_LOG = "logger.log" LOGGER_LOG_ACTION_SCHEMA = cv.All( - maybe_simple_message( + cv.maybe_simple_value( { cv.Required(CONF_FORMAT): cv.string, cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_), @@ -242,9 +233,10 @@ LOGGER_LOG_ACTION_SCHEMA = cv.All( *LOG_LEVEL_TO_ESP_LOG, upper=True ), cv.Optional(CONF_TAG, default="main"): cv.string, - } - ), - validate_printf, + }, + validate_printf, + key=CONF_FORMAT, + ) ) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index f919cb0678..41beb67e1c 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -71,9 +71,9 @@ SENSOR_VALUE_TYPE = { "S_DWORD": SensorValueType.S_DWORD, "S_DWORD_R": SensorValueType.S_DWORD_R, "U_QWORD": SensorValueType.U_QWORD, - "U_QWORDU_R": SensorValueType.U_QWORD_R, + "U_QWORD_R": SensorValueType.U_QWORD_R, "S_QWORD": SensorValueType.S_QWORD, - "U_QWORD_R": SensorValueType.S_QWORD_R, + "S_QWORD_R": SensorValueType.S_QWORD_R, "FP32": SensorValueType.FP32, "FP32_R": SensorValueType.FP32_R, } @@ -87,9 +87,9 @@ TYPE_REGISTER_MAP = { "S_DWORD": 2, "S_DWORD_R": 2, "U_QWORD": 4, - "U_QWORDU_R": 4, - "S_QWORD": 4, "U_QWORD_R": 4, + "S_QWORD": 4, + "S_QWORD_R": 4, "FP32": 2, "FP32_R": 2, } diff --git a/esphome/components/modbus_controller/select/__init__.py b/esphome/components/modbus_controller/select/__init__.py index 6f194ef2a3..f8ef61ddc4 100644 --- a/esphome/components/modbus_controller/select/__init__.py +++ b/esphome/components/modbus_controller/select/__init__.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import select from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC -from esphome.jsonschema import jschema_composite from .. import ( SENSOR_VALUE_TYPE, @@ -30,7 +29,6 @@ ModbusSelect = modbus_controller_ns.class_( ) -@jschema_composite def ensure_option_map(): def validator(value): cv.check_not_templatable(value) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 8e1c63a54e..3dc8011a87 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -58,7 +58,7 @@ from esphome.core import ( ) from esphome.helpers import list_starts_with, add_class_to_obj from esphome.jsonschema import ( - jschema_composite, + jschema_list, jschema_extractor, jschema_registry, jschema_typed, @@ -327,7 +327,7 @@ def boolean(value): ) -@jschema_composite +@jschema_list def ensure_list(*validators): """Validate this configuration option to be a list. @@ -494,7 +494,11 @@ def templatable(other_validators): """ schema = Schema(other_validators) + @jschema_extractor("templatable") def validator(value): + # pylint: disable=comparison-with-callable + if value == jschema_extractor: + return other_validators if isinstance(value, Lambda): return returning_lambda(value) if isinstance(other_validators, dict): @@ -1546,7 +1550,7 @@ def validate_registry(name, registry): return ensure_list(validate_registry_entry(name, registry)) -@jschema_composite +@jschema_list def maybe_simple_value(*validators, **kwargs): key = kwargs.pop("key", CONF_VALUE) validator = All(*validators) diff --git a/esphome/jsonschema.py b/esphome/jsonschema.py index 12929dc602..94325f4abc 100644 --- a/esphome/jsonschema.py +++ b/esphome/jsonschema.py @@ -1,7 +1,7 @@ """Helpers to retrieve schema from voluptuous validators. These are a helper decorators to help get schema from some -components which uses volutuous in a way where validation +components which uses voluptuous in a way where validation is hidden in local functions These decorators should not modify at all what the functions originally do. @@ -24,7 +24,7 @@ def jschema_extractor(validator_name): if EnableJsonSchemaCollect: def decorator(func): - hidden_schemas[str(func)] = validator_name + hidden_schemas[repr(func)] = validator_name return func return decorator @@ -41,7 +41,7 @@ def jschema_extended(func): def decorate(*args, **kwargs): ret = func(*args, **kwargs) assert len(args) == 2 - extended_schemas[str(ret)] = args + extended_schemas[repr(ret)] = args return ret return decorate @@ -49,13 +49,13 @@ def jschema_extended(func): return func -def jschema_composite(func): +def jschema_list(func): if EnableJsonSchemaCollect: def decorate(*args, **kwargs): ret = func(*args, **kwargs) # args length might be 2, but 2nd is always validator - list_schemas[str(ret)] = args + list_schemas[repr(ret)] = args return ret return decorate @@ -67,7 +67,7 @@ def jschema_registry(registry): if EnableJsonSchemaCollect: def decorator(func): - registry_schemas[str(func)] = registry + registry_schemas[repr(func)] = registry return func return decorator @@ -83,7 +83,7 @@ def jschema_typed(func): def decorate(*args, **kwargs): ret = func(*args, **kwargs) - typed_schemas[str(ret)] = (args, kwargs) + typed_schemas[repr(ret)] = (args, kwargs) return ret return decorate diff --git a/script/build_jsonschema.py b/script/build_jsonschema.py index 7673519916..5373d404a7 100644 --- a/script/build_jsonschema.py +++ b/script/build_jsonschema.py @@ -70,7 +70,7 @@ def add_definition_array_or_single_object(ref): def add_core(): from esphome.core.config import CONFIG_SCHEMA - base_props["esphome"] = get_jschema("esphome", CONFIG_SCHEMA.schema) + base_props["esphome"] = get_jschema("esphome", CONFIG_SCHEMA) def add_buses(): @@ -216,7 +216,7 @@ def add_components(): add_module_registries(domain, c.module) add_module_schemas(domain, c.module) - # need first to iterate all platforms then iteate components + # need first to iterate all platforms then iterate components # a platform component can have other components as properties, # e.g. climate components usually have a temperature sensor @@ -325,7 +325,9 @@ def get_entry(parent_key, vschema): if DUMP_COMMENTS: entry[JSC_COMMENT] = "entry: " + parent_key + "/" + str(vschema) - if isinstance(vschema, list): + if isinstance(vschema, dict): + entry = {"what": "is_this"} + elif isinstance(vschema, list): ref = get_jschema(parent_key + "[]", vschema[0]) entry = {"type": "array", "items": ref} elif isinstance(vschema, schema_type) and hasattr(vschema, "schema"): @@ -387,8 +389,10 @@ def get_entry(parent_key, vschema): v = vschema(None) if isinstance(v, ID): - if v.type.base != "script::Script" and ( - v.type.inherits_from(Trigger) or v.type == Automation + if ( + v.type.base != "script::Script" + and v.type.base != "switch_::Switch" + and (v.type.inherits_from(Trigger) or v.type == Automation) ): return None entry = {"type": "string", "id_type": v.type.base} @@ -410,6 +414,8 @@ def default_schema(): def is_default_schema(jschema): + if jschema is None: + return False if is_ref(jschema): jschema = unref(jschema) if not jschema: @@ -425,6 +431,9 @@ def get_jschema(path, vschema, create_return_ref=True): jschema = convert_schema(path, vschema) + if jschema is None: + return None + if is_ref(jschema): # this can happen when returned extended # schemas where all properties found in previous extended schema @@ -450,6 +459,9 @@ def get_schema_str(vschema): def create_ref(name, vschema, jschema): + if jschema is None: + raise ValueError("Cannot create a ref with null jschema for " + name) + if name in schema_names: raise ValueError("Not supported") @@ -523,6 +535,15 @@ def convert_schema(path, vschema, un_extend=True): extended = ejs.extended_schemas.get(str(vschema)) if extended: lhs = get_jschema(path, extended[0], False) + + # The midea actions are extending an empty schema (resulted in the templatize not templatizing anything) + # this causes a recursion in that this extended looks the same in extended schema as the extended[1] + if ejs.extended_schemas.get(str(vschema)) == ejs.extended_schemas.get( + str(extended[1]) + ): + assert path.startswith("midea_ac") + return convert_schema(path, extended[1], False) + rhs = get_jschema(path, extended[1], False) # check if we are not merging properties which are already in base component @@ -567,6 +588,8 @@ def convert_schema(path, vschema, un_extend=True): # we should take the valid schema, # commonly all is used to validate a schema, and then a function which # is not a schema es also given, get_schema will then return a default_schema() + if v == dict: + continue # this is a dict in the SCHEMA of packages val_schema = get_jschema(path, v, False) if is_default_schema(val_schema): if not output: @@ -673,6 +696,11 @@ def add_pin_registry(): for mode in ("INPUT", "OUTPUT"): schema_name = f"PIN.GPIO_FULL_{mode}_PIN_SCHEMA" + + # TODO: get pin definitions properly + if schema_name not in definitions: + definitions[schema_name] = {"type": ["object", "null"], JSC_PROPERTIES: {}} + internal = definitions[schema_name] definitions[schema_name]["additionalItems"] = False definitions[f"PIN.{mode}_INTERNAL"] = internal @@ -683,12 +711,11 @@ def add_pin_registry(): definitions[schema_name] = {"oneOf": schemas, "type": ["string", "object"]} for k, v in pin_registry.items(): - pin_jschema = get_jschema( - f"PIN.{mode}_" + k, v[1][0 if mode == "OUTPUT" else 1] - ) - if unref(pin_jschema): - pin_jschema["required"] = [k] - schemas.append(pin_jschema) + if isinstance(v[1], vol.validators.All): + pin_jschema = get_jschema(f"PIN.{mode}_" + k, v[1]) + if unref(pin_jschema): + pin_jschema["required"] = [k] + schemas.append(pin_jschema) def dump_schema(): @@ -730,9 +757,9 @@ def dump_schema(): cv.valid_name, cv.hex_int, cv.hex_int_range, - pins.output_pin, - pins.input_pin, - pins.input_pullup_pin, + pins.gpio_output_pin_schema, + pins.gpio_input_pin_schema, + pins.gpio_input_pullup_pin_schema, cv.float_with_unit, cv.subscribe_topic, cv.publish_topic, @@ -753,12 +780,12 @@ def dump_schema(): for v in [pins.gpio_input_pin_schema, pins.gpio_input_pullup_pin_schema]: schema_registry[v] = get_ref("PIN.GPIO_FULL_INPUT_PIN_SCHEMA") - for v in [pins.internal_gpio_input_pin_schema, pins.input_pin]: + for v in [pins.internal_gpio_input_pin_schema, pins.gpio_input_pin_schema]: schema_registry[v] = get_ref("PIN.INPUT_INTERNAL") for v in [pins.gpio_output_pin_schema, pins.internal_gpio_output_pin_schema]: schema_registry[v] = get_ref("PIN.GPIO_FULL_OUTPUT_PIN_SCHEMA") - for v in [pins.internal_gpio_output_pin_schema, pins.output_pin]: + for v in [pins.internal_gpio_output_pin_schema, pins.gpio_output_pin_schema]: schema_registry[v] = get_ref("PIN.OUTPUT_INTERNAL") add_module_schemas("CONFIG", cv) diff --git a/script/build_language_schema.py b/script/build_language_schema.py new file mode 100644 index 0000000000..ec0912e9e6 --- /dev/null +++ b/script/build_language_schema.py @@ -0,0 +1,813 @@ +import inspect +import json +import argparse +from operator import truediv +import os +import voluptuous as vol + +# NOTE: Cannot import other esphome components globally as a modification in jsonschema +# is needed before modules are loaded +import esphome.jsonschema as ejs + +ejs.EnableJsonSchemaCollect = True + +# schema format: +# Schemas are splitted in several files in json format, one for core stuff, one for each platform (sensor, binary_sensor, etc) and +# one for each component (dallas, sim800l, etc.) component can have schema for root component/hub and also for platform component, +# e.g. dallas has hub component which has pin and then has the sensor platform which has sensor name, index, etc. +# When files are loaded they are merged in a single object. +# The root format is + +S_CONFIG_VAR = "config_var" +S_CONFIG_VARS = "config_vars" +S_CONFIG_SCHEMA = "CONFIG_SCHEMA" +S_COMPONENT = "component" +S_COMPONENTS = "components" +S_PLATFORMS = "platforms" +S_SCHEMA = "schema" +S_SCHEMAS = "schemas" +S_EXTENDS = "extends" +S_TYPE = "type" +S_NAME = "name" + +parser = argparse.ArgumentParser() +parser.add_argument( + "--output-path", default=".", help="Output path", type=os.path.abspath +) + +args = parser.parse_args() + +DUMP_RAW = False +DUMP_UNKNOWN = False +DUMP_PATH = False +JSON_DUMP_PRETTY = True + +# store here dynamic load of esphome components +components = {} + +schema_core = {} + +# output is where all is built +output = {"core": schema_core} +# The full generated output is here here +schema_full = {"components": output} + +# A string, string map, key is the str(schema) and value is +# a tuple, first element is the schema reference and second is the schema path given, the schema reference is needed to test as different schemas have same key +known_schemas = {} + +solve_registry = [] + + +def get_component_names(): + # return [ + # "esphome", + # "esp32", + # "esp8266", + # "logger", + # "sensor", + # "remote_receiver", + # "binary_sensor", + # ] + from esphome.loader import CORE_COMPONENTS_PATH + + component_names = ["esphome", "sensor"] + + for d in os.listdir(CORE_COMPONENTS_PATH): + if not d.startswith("__") and os.path.isdir( + os.path.join(CORE_COMPONENTS_PATH, d) + ): + if d not in component_names: + component_names.append(d) + + return component_names + + +def load_components(): + from esphome.config import get_component + + for domain in get_component_names(): + components[domain] = get_component(domain) + + +load_components() + +# Import esphome after loading components (so schema is tracked) +# pylint: disable=wrong-import-position +import esphome.core as esphome_core +import esphome.config_validation as cv +from esphome import automation +from esphome import pins +from esphome.components import remote_base +from esphome.const import CONF_TYPE +from esphome.loader import get_platform +from esphome.helpers import write_file_if_changed +from esphome.util import Registry + +# pylint: enable=wrong-import-position + + +def write_file(name, obj): + full_path = os.path.join(args.output_path, name + ".json") + if JSON_DUMP_PRETTY: + json_str = json.dumps(obj, indent=2) + else: + json_str = json.dumps(obj, separators=(",", ":")) + write_file_if_changed(full_path, json_str) + print(f"Wrote {full_path}") + + +def register_module_schemas(key, module, manifest=None): + for name, schema in module_schemas(module): + register_known_schema(key, name, schema) + if ( + manifest and manifest.multi_conf and S_CONFIG_SCHEMA in output[key][S_SCHEMAS] + ): # not sure about 2nd part of the if, might be useless config (e.g. as3935) + output[key][S_SCHEMAS][S_CONFIG_SCHEMA]["is_list"] = True + + +def register_known_schema(module, name, schema): + if module not in output: + output[module] = {S_SCHEMAS: {}} + config = convert_config(schema, f"{module}/{name}") + if S_TYPE not in config: + print(f"Config var without type: {module}.{name}") + + output[module][S_SCHEMAS][name] = config + repr_schema = repr(schema) + if repr_schema in known_schemas: + schema_info = known_schemas[repr_schema] + schema_info.append((schema, f"{module}.{name}")) + else: + known_schemas[repr_schema] = [(schema, f"{module}.{name}")] + + +def module_schemas(module): + # This should yield elements in order so extended schemas are resolved properly + # To do this we check on the source code where the symbol is seen first. Seems to work. + try: + module_str = inspect.getsource(module) + except TypeError: + # improv + module_str = "" + except OSError: + # some empty __init__ files + module_str = "" + schemas = {} + for m_attr_name in dir(module): + m_attr_obj = getattr(module, m_attr_name) + if isConvertibleSchema(m_attr_obj): + schemas[module_str.find(m_attr_name)] = [m_attr_name, m_attr_obj] + + for pos in sorted(schemas.keys()): + yield schemas[pos] + + +found_registries = {} + +# Pin validators keys are the functions in pin which validate the pins +pin_validators = {} + + +def add_pin_validators(): + for m_attr_name in dir(pins): + if "gpio" in m_attr_name: + s = pin_validators[repr(getattr(pins, m_attr_name))] = {} + if "schema" in m_attr_name: + s["schema"] = True # else is just number + if "internal" in m_attr_name: + s["internal"] = True + if "input" in m_attr_name: + s["modes"] = ["input"] + elif "output" in m_attr_name: + s["modes"] = ["output"] + else: + s["modes"] = [] + if "pullup" in m_attr_name: + s["modes"].append("pullup") + from esphome.components.adc import sensor as adc_sensor + + pin_validators[repr(adc_sensor.validate_adc_pin)] = { + "internal": True, + "modes": ["input"], + } + + +def add_module_registries(domain, module): + for attr_name in dir(module): + attr_obj = getattr(module, attr_name) + if isinstance(attr_obj, Registry): + if attr_obj == automation.ACTION_REGISTRY: + reg_type = "action" + reg_domain = "core" + found_registries[repr(attr_obj)] = reg_type + elif attr_obj == automation.CONDITION_REGISTRY: + reg_type = "condition" + reg_domain = "core" + found_registries[repr(attr_obj)] = reg_type + else: # attr_name == "FILTER_REGISTRY": + reg_domain = domain + reg_type = attr_name.partition("_")[0].lower() + found_registries[repr(attr_obj)] = f"{domain}.{reg_type}" + + for name in attr_obj.keys(): + if "." not in name: + reg_entry_name = name + else: + parts = name.split(".") + if len(parts) == 2: + reg_domain = parts[0] + reg_entry_name = parts[1] + else: + reg_domain = ".".join([parts[1], parts[0]]) + reg_entry_name = parts[2] + + if reg_domain not in output: + output[reg_domain] = {} + if reg_type not in output[reg_domain]: + output[reg_domain][reg_type] = {} + output[reg_domain][reg_type][reg_entry_name] = convert_config( + attr_obj[name].schema, f"{reg_domain}/{reg_type}/{reg_entry_name}" + ) + + # print(f"{domain} - {attr_name} - {name}") + + +def do_pins(): + # do pin registries + pins_providers = schema_core["pins"] = [] + for pin_registry in pins.PIN_SCHEMA_REGISTRY: + s = convert_config( + pins.PIN_SCHEMA_REGISTRY[pin_registry][1], f"pins/{pin_registry}" + ) + if pin_registry not in output: + output[pin_registry] = {} # mcp23xxx does not create a component yet + output[pin_registry]["pin"] = s + pins_providers.append(pin_registry) + + +def do_esp32(): + import esphome.components.esp32.boards as esp32_boards + + setEnum( + output["esp32"]["schemas"]["CONFIG_SCHEMA"]["schema"]["config_vars"]["board"], + list(esp32_boards.BOARD_TO_VARIANT.keys()), + ) + + +def do_esp8266(): + import esphome.components.esp8266.boards as esp8266_boards + + setEnum( + output["esp8266"]["schemas"]["CONFIG_SCHEMA"]["schema"]["config_vars"]["board"], + list(esp8266_boards.ESP8266_BOARD_PINS.keys()), + ) + + +def fix_remote_receiver(): + output["remote_receiver.binary_sensor"]["schemas"]["CONFIG_SCHEMA"] = { + "type": "schema", + "schema": { + "extends": ["binary_sensor.BINARY_SENSOR_SCHEMA", "core.COMPONENT_SCHEMA"], + "config_vars": output["remote_base"]["binary"], + }, + } + + +def add_referenced_recursive(referenced_schemas, config_var, path, eat_schema=False): + assert ( + S_CONFIG_VARS not in config_var and S_EXTENDS not in config_var + ) # S_TYPE in cv or "key" in cv or len(cv) == 0 + if ( + config_var.get(S_TYPE) in ["schema", "trigger", "maybe"] + and S_SCHEMA in config_var + ): + schema = config_var[S_SCHEMA] + for k, v in schema.get(S_CONFIG_VARS, {}).items(): + if eat_schema: + new_path = path + [S_CONFIG_VARS, k] + else: + new_path = path + ["schema", S_CONFIG_VARS, k] + add_referenced_recursive(referenced_schemas, v, new_path) + for k in schema.get(S_EXTENDS, []): + if k not in referenced_schemas: + referenced_schemas[k] = [path] + else: + if path not in referenced_schemas[k]: + referenced_schemas[k].append(path) + + s1 = get_str_path_schema(k) + p = k.split(".") + if len(p) == 3 and path[0] == f"{p[0]}.{p[1]}": + # special case for schema inside platforms + add_referenced_recursive( + referenced_schemas, s1, [path[0], "schemas", p[2]] + ) + else: + add_referenced_recursive( + referenced_schemas, s1, [p[0], "schemas", p[1]] + ) + elif config_var.get(S_TYPE) == "typed": + for tk, tv in config_var.get("types").items(): + add_referenced_recursive( + referenced_schemas, + { + S_TYPE: S_SCHEMA, + S_SCHEMA: tv, + }, + path + ["types", tk], + eat_schema=True, + ) + + +def get_str_path_schema(strPath): + parts = strPath.split(".") + if len(parts) > 2: + parts[0] += "." + parts[1] + parts[1] = parts[2] + s1 = output.get(parts[0], {}).get(S_SCHEMAS, {}).get(parts[1], {}) + return s1 + + +def pop_str_path_schema(strPath): + parts = strPath.split(".") + if len(parts) > 2: + parts[0] += "." + parts[1] + parts[1] = parts[2] + output.get(parts[0], {}).get(S_SCHEMAS, {}).pop(parts[1]) + + +def get_arr_path_schema(path): + s = output + for x in path: + s = s[x] + return s + + +def merge(source, destination): + """ + run me with nosetests --with-doctest file.py + + >>> a = { 'first' : { 'all_rows' : { 'pass' : 'dog', 'number' : '1' } } } + >>> b = { 'first' : { 'all_rows' : { 'fail' : 'cat', 'number' : '5' } } } + >>> merge(b, a) == { 'first' : { 'all_rows' : { 'pass' : 'dog', 'fail' : 'cat', 'number' : '5' } } } + True + """ + for key, value in source.items(): + if isinstance(value, dict): + # get node or create one + node = destination.setdefault(key, {}) + merge(value, node) + else: + destination[key] = value + + return destination + + +def shrink(): + """Shrink the extending schemas which has just an end type, e.g. at this point + ota / port is type schema with extended pointing to core.port, this should instead be + type number. core.port is number + + This also fixes enums, as they are another schema and they are instead put in the same cv + """ + + # referenced_schemas contains a dict, keys are all that are shown in extends: [] arrays, values are lists of paths that are pointing to that extend + # e.g. key: core.COMPONENT_SCHEMA has a lot of paths of config vars which extends this schema + + pass_again = True + + while pass_again: + pass_again = False + + referenced_schemas = {} + + for k, v in output.items(): + for kv, vv in v.items(): + if kv != "pin" and isinstance(vv, dict): + for kvv, vvv in vv.items(): + add_referenced_recursive(referenced_schemas, vvv, [k, kv, kvv]) + + for x, paths in referenced_schemas.items(): + if len(paths) == 1: + key_s = get_str_path_schema(x) + arr_s = get_arr_path_schema(paths[0]) + # key_s |= arr_s + # key_s.pop(S_EXTENDS) + pass_again = True + if S_SCHEMA in arr_s: + if S_EXTENDS in arr_s[S_SCHEMA]: + arr_s[S_SCHEMA].pop(S_EXTENDS) + else: + print("expected extends here!" + x) + arr_s = merge(key_s, arr_s) + if arr_s[S_TYPE] == "enum": + arr_s.pop(S_SCHEMA) + else: + arr_s.pop(S_EXTENDS) + arr_s |= key_s[S_SCHEMA] + print(x) + + # simple types should be spread on each component, + # for enums so far these are logger.is_log_level, cover.validate_cover_state and pulse_counter.sensor.COUNT_MODE_SCHEMA + # then for some reasons sensor filter registry falls here + # then are all simple types, integer and strings + for x, paths in referenced_schemas.items(): + key_s = get_str_path_schema(x) + if key_s and key_s[S_TYPE] in ["enum", "registry", "integer", "string"]: + if key_s[S_TYPE] == "registry": + print("Spreading registry: " + x) + for target in paths: + target_s = get_arr_path_schema(target) + assert target_s[S_SCHEMA][S_EXTENDS] == [x] + target_s.pop(S_SCHEMA) + target_s |= key_s + if key_s[S_TYPE] in ["integer", "string"]: + target_s["data_type"] = x.split(".")[1] + # remove this dangling again + pop_str_path_schema(x) + elif not key_s: + for target in paths: + target_s = get_arr_path_schema(target) + assert target_s[S_SCHEMA][S_EXTENDS] == [x] + target_s.pop(S_SCHEMA) + target_s.pop(S_TYPE) # undefined + target_s["data_type"] = x.split(".")[1] + # remove this dangling again + pop_str_path_schema(x) + + # remove dangling items (unreachable schemas) + for domain, domain_schemas in output.items(): + for schema_name in list(domain_schemas.get(S_SCHEMAS, {}).keys()): + s = f"{domain}.{schema_name}" + if ( + not s.endswith("." + S_CONFIG_SCHEMA) + and s not in referenced_schemas.keys() + ): + print(f"Removing {s}") + output[domain][S_SCHEMAS].pop(schema_name) + + +def build_schema(): + print("Building schema") + + # check esphome was not loaded globally (IDE auto imports) + if len(ejs.extended_schemas) == 0: + raise Exception( + "no data collected. Did you globally import an ESPHome component?" + ) + + # Core schema + schema_core[S_SCHEMAS] = {} + register_module_schemas("core", cv) + + platforms = {} + schema_core[S_PLATFORMS] = platforms + core_components = {} + schema_core[S_COMPONENTS] = core_components + + add_pin_validators() + + # Load a preview of each component + for domain, manifest in components.items(): + if manifest.is_platform_component: + # e.g. sensor, binary sensor, add S_COMPONENTS + # note: S_COMPONENTS is not filled until loaded, e.g. + # if lock: is not used, then we don't need to know about their + # platforms yet. + output[domain] = {S_COMPONENTS: {}, S_SCHEMAS: {}} + platforms[domain] = {} + elif manifest.config_schema is not None: + # e.g. dallas + output[domain] = {S_SCHEMAS: {S_CONFIG_SCHEMA: {}}} + + # Generate platforms (e.g. sensor, binary_sensor, climate ) + for domain in platforms: + c = components[domain] + register_module_schemas(domain, c.module) + + # Generate components + for domain, manifest in components.items(): + if domain not in platforms: + if manifest.config_schema is not None: + core_components[domain] = {} + register_module_schemas(domain, manifest.module, manifest) + + for platform in platforms: + platform_manifest = get_platform(domain=platform, platform=domain) + if platform_manifest is not None: + output[platform][S_COMPONENTS][domain] = {} + register_module_schemas( + f"{domain}.{platform}", platform_manifest.module + ) + + # Do registries + add_module_registries("core", automation) + for domain, manifest in components.items(): + add_module_registries(domain, manifest.module) + add_module_registries("remote_base", remote_base) + + # update props pointing to registries + for reg_config_var in solve_registry: + (registry, config_var) = reg_config_var + config_var[S_TYPE] = "registry" + config_var["registry"] = found_registries[repr(registry)] + + do_pins() + do_esp8266() + do_esp32() + fix_remote_receiver() + shrink() + + # aggregate components, so all component info is in same file, otherwise we have dallas.json, dallas.sensor.json, etc. + data = {} + for component, component_schemas in output.items(): + if "." in component: + key = component.partition(".")[0] + if key not in data: + data[key] = {} + data[key][component] = component_schemas + else: + if component not in data: + data[component] = {} + data[component] |= {component: component_schemas} + + # bundle core inside esphome + data["esphome"]["core"] = data.pop("core")["core"] + + for c, s in data.items(): + write_file(c, s) + + +def setEnum(obj, items): + obj[S_TYPE] = "enum" + obj["values"] = items + + +def isConvertibleSchema(schema): + if schema is None: + return False + if isinstance(schema, (cv.Schema, cv.All)): + return True + if repr(schema) in ejs.hidden_schemas: + return True + if repr(schema) in ejs.typed_schemas: + return True + if repr(schema) in ejs.list_schemas: + return True + if repr(schema) in ejs.registry_schemas: + return True + if isinstance(schema, dict): + for k in schema.keys(): + if isinstance(k, (cv.Required, cv.Optional)): + return True + return False + + +def convert_config(schema, path): + converted = {} + convert_1(schema, converted, path) + return converted + + +def convert_1(schema, config_var, path): + """config_var can be a config_var or a schema: both are dicts + config_var has a S_TYPE property, if this is S_SCHEMA, then it has a S_SCHEMA property + schema does not have a type property, schema can have optionally both S_CONFIG_VARS and S_EXTENDS + """ + repr_schema = repr(schema) + + if repr_schema in known_schemas: + schema_info = known_schemas[(repr_schema)] + for (schema_instance, name) in schema_info: + if schema_instance is schema: + assert S_CONFIG_VARS not in config_var + assert S_EXTENDS not in config_var + if not S_TYPE in config_var: + config_var[S_TYPE] = S_SCHEMA + assert config_var[S_TYPE] == S_SCHEMA + + if S_SCHEMA not in config_var: + config_var[S_SCHEMA] = {} + if S_EXTENDS not in config_var[S_SCHEMA]: + config_var[S_SCHEMA][S_EXTENDS] = [name] + else: + config_var[S_SCHEMA][S_EXTENDS].append(name) + return + + # Extended schemas are tracked when the .extend() is used in a schema + if repr_schema in ejs.extended_schemas: + extended = ejs.extended_schemas.get(repr_schema) + # The midea actions are extending an empty schema (resulted in the templatize not templatizing anything) + # this causes a recursion in that this extended looks the same in extended schema as the extended[1] + if repr_schema == repr(extended[1]): + assert path.startswith("midea_ac/") + return + + assert len(extended) == 2 + convert_1(extended[0], config_var, path + "/extL") + convert_1(extended[1], config_var, path + "/extR") + return + + if isinstance(schema, cv.All): + i = 0 + for inner in schema.validators: + i = i + 1 + convert_1(inner, config_var, path + f"/val {i}") + return + + if hasattr(schema, "validators"): + i = 0 + for inner in schema.validators: + i = i + 1 + convert_1(inner, config_var, path + f"/val {i}") + + if isinstance(schema, cv.Schema): + convert_1(schema.schema, config_var, path + "/all") + return + + if isinstance(schema, dict): + convert_keys(config_var, schema, path) + return + + if repr_schema in ejs.list_schemas: + config_var["is_list"] = True + items_schema = ejs.list_schemas[repr_schema][0] + convert_1(items_schema, config_var, path + "/list") + return + + if DUMP_RAW: + config_var["raw"] = repr_schema + + # pylint: disable=comparison-with-callable + if schema == cv.boolean: + config_var[S_TYPE] = "boolean" + elif schema == automation.validate_potentially_and_condition: + config_var[S_TYPE] = "registry" + config_var["registry"] = "condition" + elif schema == cv.int_ or schema == cv.int_range: + config_var[S_TYPE] = "integer" + elif schema == cv.string or schema == cv.string_strict or schema == cv.valid_name: + config_var[S_TYPE] = "string" + + elif isinstance(schema, vol.Schema): + # test: esphome/project + config_var[S_TYPE] = "schema" + config_var["schema"] = convert_config(schema.schema, path + "/s")["schema"] + + elif repr_schema in pin_validators: + config_var |= pin_validators[repr_schema] + config_var[S_TYPE] = "pin" + + elif repr_schema in ejs.hidden_schemas: + schema_type = ejs.hidden_schemas[repr_schema] + + data = schema(ejs.jschema_extractor) + + # enums, e.g. esp32/variant + if schema_type == "one_of": + config_var[S_TYPE] = "enum" + config_var["values"] = list(data) + elif schema_type == "enum": + config_var[S_TYPE] = "enum" + config_var["values"] = list(data.keys()) + elif schema_type == "maybe": + config_var[S_TYPE] = "maybe" + config_var["schema"] = convert_config(data, path + "/maybe")["schema"] + # esphome/on_boot + elif schema_type == "automation": + extra_schema = None + config_var[S_TYPE] = "trigger" + if automation.AUTOMATION_SCHEMA == ejs.extended_schemas[repr(data)][0]: + extra_schema = ejs.extended_schemas[repr(data)][1] + if ( + extra_schema is not None and len(extra_schema) > 1 + ): # usually only trigger_id here + config = convert_config(extra_schema, path + "/extra") + if "schema" in config: + automation_schema = config["schema"] + if not ( + len(automation_schema["config_vars"]) == 1 + and "trigger_id" in automation_schema["config_vars"] + ): + automation_schema["config_vars"]["then"] = {S_TYPE: "trigger"} + if "trigger_id" in automation_schema["config_vars"]: + automation_schema["config_vars"].pop("trigger_id") + + config_var[S_TYPE] = "trigger" + config_var["schema"] = automation_schema + # some triggers can have a list of actions directly, while others needs to have some other configuration, + # e.g. sensor.on_value_rang, and the list of actions is only accepted under "then" property. + try: + schema({"delay": "1s"}) + except cv.Invalid: + config_var["has_required_var"] = True + else: + print("figure out " + path) + elif schema_type == "effects": + config_var[S_TYPE] = "registry" + config_var["registry"] = "light.effects" + config_var["filter"] = data[0] + elif schema_type == "templatable": + config_var["templatable"] = True + convert_1(data, config_var, path + "/templat") + elif schema_type == "triggers": + # remote base + convert_1(data, config_var, path + "/trigger") + elif schema_type == "sensor": + schema = data + convert_1(data, config_var, path + "/trigger") + else: + raise Exception("Unknown extracted schema type") + + elif repr_schema in ejs.registry_schemas: + solve_registry.append((ejs.registry_schemas[repr_schema], config_var)) + + elif repr_schema in ejs.typed_schemas: + config_var[S_TYPE] = "typed" + types = config_var["types"] = {} + typed_schema = ejs.typed_schemas[repr_schema] + if len(typed_schema) > 1: + config_var["typed_key"] = typed_schema[1].get("key", CONF_TYPE) + for schema_key, schema_type in typed_schema[0][0].items(): + config = convert_config(schema_type, path + "/type_" + schema_key) + types[schema_key] = config["schema"] + + elif DUMP_UNKNOWN: + if S_TYPE not in config_var: + config_var["unknown"] = repr_schema + + if DUMP_PATH: + config_var["path"] = path + + +def get_overridden_config(key, converted): + # check if the key is in any extended schema in this converted schema, i.e. + # if we see a on_value_range in a dallas sensor, then this is overridden because + # it is already defined in sensor + assert S_CONFIG_VARS not in converted and S_EXTENDS not in converted + config = converted.get(S_SCHEMA, {}) + + return get_overridden_key_inner(key, config, {}) + + +def get_overridden_key_inner(key, config, ret): + if S_EXTENDS not in config: + return ret + for s in config[S_EXTENDS]: + p = s.partition(".") + s1 = output.get(p[0], {}).get(S_SCHEMAS, {}).get(p[2], {}).get(S_SCHEMA) + if s1: + if key in s1.get(S_CONFIG_VARS, {}): + for k, v in s1.get(S_CONFIG_VARS)[key].items(): + if k not in ret: # keep most overridden + ret[k] = v + get_overridden_key_inner(key, s1, ret) + + return ret + + +def convert_keys(converted, schema, path): + for k, v in schema.items(): + # deprecated stuff + if repr(v).startswith(" Date: Sun, 3 Apr 2022 15:38:44 -0500 Subject: [PATCH 188/238] protobuf: fix incomplete 64 bits implementation (#3341) --- esphome/components/api/proto.h | 23 +++++++++++++++++++++++ script/api_protobuf/api_protobuf.py | 16 ++++++++-------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index 38fd98b489..32f525990d 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -195,6 +195,20 @@ class ProtoWriteBuffer { this->write((value >> 16) & 0xFF); this->write((value >> 24) & 0xFF); } + void encode_fixed64(uint32_t field_id, uint64_t value, bool force = false) { + if (value == 0 && !force) + return; + + this->encode_field_raw(field_id, 5); + this->write((value >> 0) & 0xFF); + this->write((value >> 8) & 0xFF); + this->write((value >> 16) & 0xFF); + this->write((value >> 24) & 0xFF); + this->write((value >> 32) & 0xFF); + this->write((value >> 40) & 0xFF); + this->write((value >> 48) & 0xFF); + this->write((value >> 56) & 0xFF); + } template void encode_enum(uint32_t field_id, T value, bool force = false) { this->encode_uint32(field_id, static_cast(value), force); } @@ -229,6 +243,15 @@ class ProtoWriteBuffer { } this->encode_uint32(field_id, uvalue, force); } + void encode_sint64(uint32_t field_id, int64_t value, bool force = false) { + uint64_t uvalue; + if (value < 0) { + uvalue = ~(value << 1); + } else { + uvalue = value << 1; + } + this->encode_uint64(field_id, uvalue, force); + } template void encode_message(uint32_t field_id, const C &value, bool force = false) { this->encode_field_raw(field_id, 2); size_t begin = this->buffer_->size(); diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 016a0995b9..26bf8647af 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -236,7 +236,7 @@ class Int64Type(TypeInfo): encode_func = "encode_int64" def dump(self, name): - o = f'sprintf(buffer, "%ll", {name});\n' + o = f'sprintf(buffer, "%lld", {name});\n' o += f"out.append(buffer);" return o @@ -249,7 +249,7 @@ class UInt64Type(TypeInfo): encode_func = "encode_uint64" def dump(self, name): - o = f'sprintf(buffer, "%ull", {name});\n' + o = f'sprintf(buffer, "%llu", {name});\n' o += f"out.append(buffer);" return o @@ -275,7 +275,7 @@ class Fixed64Type(TypeInfo): encode_func = "encode_fixed64" def dump(self, name): - o = f'sprintf(buffer, "%ull", {name});\n' + o = f'sprintf(buffer, "%llu", {name});\n' o += f"out.append(buffer);" return o @@ -417,7 +417,7 @@ class SFixed64Type(TypeInfo): encode_func = "encode_sfixed64" def dump(self, name): - o = f'sprintf(buffer, "%ll", {name});\n' + o = f'sprintf(buffer, "%lld", {name});\n' o += f"out.append(buffer);" return o @@ -440,10 +440,10 @@ class SInt64Type(TypeInfo): cpp_type = "int64_t" default_value = "0" decode_varint = "value.as_sint64()" - encode_func = "encode_sin64" + encode_func = "encode_sint64" def dump(self, name): - o = f'sprintf(buffer, "%ll", {name});\n' + o = f'sprintf(buffer, "%lld", {name});\n' o += f"out.append(buffer);" return o @@ -622,13 +622,13 @@ def build_message_type(desc): protected_content.insert(0, prot) if decode_64bit: decode_64bit.append("default:\n return false;") - o = f"bool {desc.name}::decode_64bit(uint32_t field_id, Proto64bit value) {{\n" + o = f"bool {desc.name}::decode_64bit(uint32_t field_id, Proto64Bit value) {{\n" o += " switch (field_id) {\n" o += indent("\n".join(decode_64bit), " ") + "\n" o += " }\n" o += "}\n" cpp += o - prot = "bool decode_64bit(uint32_t field_id, Proto64bit value) override;" + prot = "bool decode_64bit(uint32_t field_id, Proto64Bit value) override;" protected_content.insert(0, prot) o = f"void {desc.name}::encode(ProtoWriteBuffer buffer) const {{" From fa1b5117fd1b4e9a3ea5b88fe5f2b54acd436aac Mon Sep 17 00:00:00 2001 From: Branden Cash <203336+ammmze@users.noreply.github.com> Date: Sun, 3 Apr 2022 14:35:48 -0700 Subject: [PATCH 189/238] feat: support ble_client that use security w/o pin codes (#3320) --- esphome/components/ble_client/ble_client.cpp | 41 ++++++++++++++++--- esphome/components/ble_client/ble_client.h | 2 + .../esp32_ble_tracker/esp32_ble_tracker.cpp | 3 ++ .../esp32_ble_tracker/esp32_ble_tracker.h | 1 + 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 5b2701d77a..7bef0d652c 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -118,16 +118,21 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es this->set_states_(espbt::ClientState::IDLE); break; } - this->conn_id = param->open.conn_id; - auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->open.conn_id); + break; + } + case ESP_GATTC_CONNECT_EVT: { + ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str()); + this->conn_id = param->connect.conn_id; + auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->connect.conn_id); if (ret) { - ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%d", ret); + ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret); } break; } 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); + ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu, + param->cfg_mtu.status); this->set_states_(espbt::ClientState::IDLE); break; } @@ -139,7 +144,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es if (memcmp(param->disconnect.remote_bda, this->remote_bda, 6) != 0) { return; } - ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT", this->address_str().c_str()); + ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason); for (auto &svc : this->services_) delete svc; // NOLINT(cppcoreguidelines-owning-memory) this->services_.clear(); @@ -201,6 +206,32 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es } } +void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { + switch (event) { + // This event is sent by the server when it requests security + case ESP_GAP_BLE_SEC_REQ_EVT: + ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", event); + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); + break; + // This event is sent once authentication has completed + case ESP_GAP_BLE_AUTH_CMPL_EVT: + esp_bd_addr_t bd_addr; + memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); + ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str()); + if (!param->ble_security.auth_cmpl.success) { + ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason); + } else { + ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type, + param->ble_security.auth_cmpl.auth_mode); + } + break; + // There are other events we'll want to implement at some point to support things like pass key + // https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md + default: + break; + } +} + // Parse GATT values into a float for a sensor. // Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/ float BLEClient::parse_char_value(uint8_t *value, uint16_t length) { diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index e0a1bf61b9..b122bfd11e 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace esphome { namespace ble_client { @@ -86,6 +87,7 @@ class BLEClient : public espbt::ESPBTClient, public Component { 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 gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; bool parse_device(const espbt::ESPBTDevice &device) override; void on_scan_end() override {} void connect() override; diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 7614e33979..9722104e25 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -262,6 +262,9 @@ void ESP32BLETracker::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ default: break; } + for (auto *client : global_esp32_ble_tracker->clients_) { + client->gap_event_handler(event, param); + } } void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) { diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 9ff2a5a861..62fff30a20 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -155,6 +155,7 @@ class ESPBTClient : public ESPBTDeviceListener { public: virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) = 0; + virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; virtual void connect() = 0; void set_state(ClientState st) { this->state_ = st; } ClientState state() const { return state_; } From 792108686cb6b0a90b2ee7dbf6ddb24cb5487ba7 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 4 Apr 2022 01:07:20 +0200 Subject: [PATCH 190/238] Add mqtt for idf (#2930) Co-authored-by: Flaviu Tamas Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen --- esphome/components/mqtt/__init__.py | 37 ++++- esphome/components/mqtt/mqtt_backend.h | 69 ++++++++ .../components/mqtt/mqtt_backend_arduino.h | 74 +++++++++ esphome/components/mqtt/mqtt_backend_idf.cpp | 149 ++++++++++++++++++ esphome/components/mqtt/mqtt_backend_idf.h | 143 +++++++++++++++++ esphome/components/mqtt/mqtt_client.cpp | 117 +++++++------- esphome/components/mqtt/mqtt_client.h | 31 ++-- esphome/core/defines.h | 7 +- tests/test5.yaml | 13 ++ 9 files changed, 567 insertions(+), 73 deletions(-) create mode 100644 esphome/components/mqtt/mqtt_backend.h create mode 100644 esphome/components/mqtt/mqtt_backend_arduino.h create mode 100644 esphome/components/mqtt/mqtt_backend_idf.cpp create mode 100644 esphome/components/mqtt/mqtt_backend_idf.h diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 901b77474d..b2548d6081 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -9,6 +9,7 @@ from esphome.const import ( CONF_AVAILABILITY, CONF_BIRTH_MESSAGE, CONF_BROKER, + CONF_CERTIFICATE_AUTHORITY, CONF_CLIENT_ID, CONF_COMMAND_TOPIC, CONF_COMMAND_RETAIN, @@ -42,9 +43,14 @@ from esphome.const import ( CONF_WILL_MESSAGE, ) from esphome.core import coroutine_with_priority, CORE +from esphome.components.esp32 import add_idf_sdkconfig_option DEPENDENCIES = ["network"] -AUTO_LOAD = ["json", "async_tcp"] + +AUTO_LOAD = ["json"] + +CONF_IDF_SEND_ASYNC = "idf_send_async" +CONF_SKIP_CERT_CN_CHECK = "skip_cert_cn_check" def validate_message_just_topic(value): @@ -163,6 +169,15 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_USERNAME, default=""): cv.string, cv.Optional(CONF_PASSWORD, default=""): cv.string, cv.Optional(CONF_CLIENT_ID): cv.string, + cv.SplitDefault(CONF_IDF_SEND_ASYNC, esp32_idf=False): cv.All( + cv.boolean, cv.only_with_esp_idf + ), + cv.Optional(CONF_CERTIFICATE_AUTHORITY): cv.All( + cv.string, cv.only_with_esp_idf + ), + cv.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32_idf=False): cv.All( + cv.boolean, cv.only_with_esp_idf + ), cv.Optional(CONF_DISCOVERY, default=True): cv.Any( cv.boolean, cv.one_of("CLEAN", upper=True) ), @@ -217,7 +232,6 @@ CONFIG_SCHEMA = cv.All( } ), validate_config, - cv.only_with_arduino, ) @@ -238,9 +252,11 @@ def exp_mqtt_message(config): async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + # Add required libraries for arduino + if CORE.using_arduino: + # https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json + cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.6") - # https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json - cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.6") cg.add_define("USE_MQTT") cg.add_global(mqtt_ns.using) @@ -321,6 +337,19 @@ async def to_code(config): cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) + # esp-idf only + if CONF_CERTIFICATE_AUTHORITY in config: + cg.add(var.set_ca_certificate(config[CONF_CERTIFICATE_AUTHORITY])) + cg.add(var.set_skip_cert_cn_check(config[CONF_SKIP_CERT_CN_CHECK])) + + # prevent error -0x428e + # See https://github.com/espressif/esp-idf/issues/139 + add_idf_sdkconfig_option("CONFIG_MBEDTLS_HARDWARE_MPI", False) + + if CONF_IDF_SEND_ASYNC in config and config[CONF_IDF_SEND_ASYNC]: + cg.add_define("USE_MQTT_IDF_ENQUEUE") + # end esp-idf + for conf in config.get(CONF_ON_MESSAGE, []): trig = cg.new_Pvariable(conf[CONF_TRIGGER_ID], conf[CONF_TOPIC]) cg.add(trig.set_qos(conf[CONF_QOS])) diff --git a/esphome/components/mqtt/mqtt_backend.h b/esphome/components/mqtt/mqtt_backend.h new file mode 100644 index 0000000000..d23cda578d --- /dev/null +++ b/esphome/components/mqtt/mqtt_backend.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include "esphome/components/network/ip_address.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace mqtt { + +enum class MQTTClientDisconnectReason : int8_t { + TCP_DISCONNECTED = 0, + MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1, + MQTT_IDENTIFIER_REJECTED = 2, + MQTT_SERVER_UNAVAILABLE = 3, + MQTT_MALFORMED_CREDENTIALS = 4, + MQTT_NOT_AUTHORIZED = 5, + ESP8266_NOT_ENOUGH_SPACE = 6, + TLS_BAD_FINGERPRINT = 7 +}; + +/// internal struct for MQTT messages. +struct MQTTMessage { + std::string topic; + std::string payload; + uint8_t qos; ///< QoS. Only for last will testaments. + bool retain; +}; + +class MQTTBackend { + public: + using on_connect_callback_t = void(bool session_present); + using on_disconnect_callback_t = void(MQTTClientDisconnectReason reason); + using on_subscribe_callback_t = void(uint16_t packet_id, uint8_t qos); + using on_unsubscribe_callback_t = void(uint16_t packet_id); + using on_message_callback_t = void(const char *topic, const char *payload, size_t len, size_t index, size_t total); + using on_publish_user_callback_t = void(uint16_t packet_id); + + virtual void set_keep_alive(uint16_t keep_alive) = 0; + virtual void set_client_id(const char *client_id) = 0; + virtual void set_clean_session(bool clean_session) = 0; + virtual void set_credentials(const char *username, const char *password) = 0; + virtual void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) = 0; + virtual void set_server(network::IPAddress ip, uint16_t port) = 0; + virtual void set_server(const char *host, uint16_t port) = 0; + virtual void set_on_connect(std::function &&callback) = 0; + virtual void set_on_disconnect(std::function &&callback) = 0; + virtual void set_on_subscribe(std::function &&callback) = 0; + virtual void set_on_unsubscribe(std::function &&callback) = 0; + virtual void set_on_message(std::function &&callback) = 0; + virtual void set_on_publish(std::function &&callback) = 0; + virtual bool connected() const = 0; + virtual void connect() = 0; + virtual void disconnect() = 0; + virtual bool subscribe(const char *topic, uint8_t qos) = 0; + virtual bool unsubscribe(const char *topic) = 0; + virtual bool publish(const char *topic, const char *payload, size_t length, uint8_t qos, bool retain) = 0; + + virtual bool publish(const MQTTMessage &message) { + return publish(message.topic.c_str(), message.payload.c_str(), message.payload.length(), message.qos, + message.retain); + } + + // called from MQTTClient::loop() + virtual void loop() {} +}; + +} // namespace mqtt +} // namespace esphome diff --git a/esphome/components/mqtt/mqtt_backend_arduino.h b/esphome/components/mqtt/mqtt_backend_arduino.h new file mode 100644 index 0000000000..6399ec88e0 --- /dev/null +++ b/esphome/components/mqtt/mqtt_backend_arduino.h @@ -0,0 +1,74 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "mqtt_backend.h" +#include + +namespace esphome { +namespace mqtt { + +class MQTTBackendArduino final : public MQTTBackend { + public: + void set_keep_alive(uint16_t keep_alive) final { mqtt_client_.setKeepAlive(keep_alive); } + void set_client_id(const char *client_id) final { mqtt_client_.setClientId(client_id); } + void set_clean_session(bool clean_session) final { mqtt_client_.setCleanSession(clean_session); } + void set_credentials(const char *username, const char *password) final { + mqtt_client_.setCredentials(username, password); + } + void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) final { + mqtt_client_.setWill(topic, qos, retain, payload); + } + void set_server(network::IPAddress ip, uint16_t port) final { + mqtt_client_.setServer(IPAddress(static_cast(ip)), port); + } + void set_server(const char *host, uint16_t port) final { mqtt_client_.setServer(host, port); } +#if ASYNC_TCP_SSL_ENABLED + void set_secure(bool secure) { mqtt_client.setSecure(secure); } + void add_server_fingerprint(const uint8_t *fingerprint) { mqtt_client.addServerFingerprint(fingerprint); } +#endif + + void set_on_connect(std::function &&callback) final { + this->mqtt_client_.onConnect(std::move(callback)); + } + void set_on_disconnect(std::function &&callback) final { + auto async_callback = [callback](AsyncMqttClientDisconnectReason reason) { + // int based enum so casting isn't a problem + callback(static_cast(reason)); + }; + this->mqtt_client_.onDisconnect(std::move(async_callback)); + } + void set_on_subscribe(std::function &&callback) final { + this->mqtt_client_.onSubscribe(std::move(callback)); + } + void set_on_unsubscribe(std::function &&callback) final { + this->mqtt_client_.onUnsubscribe(std::move(callback)); + } + void set_on_message(std::function &&callback) final { + auto async_callback = [callback](const char *topic, const char *payload, + AsyncMqttClientMessageProperties async_properties, size_t len, size_t index, + size_t total) { callback(topic, payload, len, index, total); }; + mqtt_client_.onMessage(std::move(async_callback)); + } + void set_on_publish(std::function &&callback) final { + this->mqtt_client_.onPublish(std::move(callback)); + } + + bool connected() const final { return mqtt_client_.connected(); } + void connect() final { mqtt_client_.connect(); } + void disconnect() final { mqtt_client_.disconnect(true); } + bool subscribe(const char *topic, uint8_t qos) final { return mqtt_client_.subscribe(topic, qos) != 0; } + bool unsubscribe(const char *topic) final { return mqtt_client_.unsubscribe(topic) != 0; } + bool publish(const char *topic, const char *payload, size_t length, uint8_t qos, bool retain) final { + return mqtt_client_.publish(topic, qos, retain, payload, length, false, 0) != 0; + } + using MQTTBackend::publish; + + protected: + AsyncMqttClient mqtt_client_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif // defined(USE_ARDUINO) diff --git a/esphome/components/mqtt/mqtt_backend_idf.cpp b/esphome/components/mqtt/mqtt_backend_idf.cpp new file mode 100644 index 0000000000..0726f72567 --- /dev/null +++ b/esphome/components/mqtt/mqtt_backend_idf.cpp @@ -0,0 +1,149 @@ +#ifdef USE_ESP_IDF + +#include +#include "mqtt_backend_idf.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace mqtt { + +static const char *const TAG = "mqtt.idf"; + +bool MQTTBackendIDF::initialize_() { + mqtt_cfg_.user_context = (void *) this; + mqtt_cfg_.buffer_size = MQTT_BUFFER_SIZE; + + mqtt_cfg_.host = this->host_.c_str(); + mqtt_cfg_.port = this->port_; + mqtt_cfg_.keepalive = this->keep_alive_; + mqtt_cfg_.disable_clean_session = !this->clean_session_; + + if (!this->username_.empty()) { + mqtt_cfg_.username = this->username_.c_str(); + if (!this->password_.empty()) { + mqtt_cfg_.password = this->password_.c_str(); + } + } + + if (!this->lwt_topic_.empty()) { + mqtt_cfg_.lwt_topic = this->lwt_topic_.c_str(); + this->mqtt_cfg_.lwt_qos = this->lwt_qos_; + this->mqtt_cfg_.lwt_retain = this->lwt_retain_; + + if (!this->lwt_message_.empty()) { + mqtt_cfg_.lwt_msg = this->lwt_message_.c_str(); + mqtt_cfg_.lwt_msg_len = this->lwt_message_.size(); + } + } + + if (!this->client_id_.empty()) { + mqtt_cfg_.client_id = this->client_id_.c_str(); + } + if (ca_certificate_.has_value()) { + mqtt_cfg_.cert_pem = ca_certificate_.value().c_str(); + mqtt_cfg_.skip_cert_common_name_check = skip_cert_cn_check_; + mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_SSL; + } else { + mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_TCP; + } + auto *mqtt_client = esp_mqtt_client_init(&mqtt_cfg_); + if (mqtt_client) { + handler_.reset(mqtt_client); + is_initalized_ = true; + esp_mqtt_client_register_event(mqtt_client, MQTT_EVENT_ANY, mqtt_event_handler, this); + return true; + } else { + ESP_LOGE(TAG, "Failed to initialize IDF-MQTT"); + return false; + } +} + +void MQTTBackendIDF::loop() { + // process new events + // handle only 1 message per loop iteration + if (!mqtt_events_.empty()) { + auto &event = mqtt_events_.front(); + mqtt_event_handler_(event); + mqtt_events_.pop(); + } +} + +void MQTTBackendIDF::mqtt_event_handler_(const esp_mqtt_event_t &event) { + ESP_LOGV(TAG, "Event dispatched from event loop event_id=%d", event.event_id); + switch (event.event_id) { + case MQTT_EVENT_BEFORE_CONNECT: + ESP_LOGV(TAG, "MQTT_EVENT_BEFORE_CONNECT"); + break; + + case MQTT_EVENT_CONNECTED: + ESP_LOGV(TAG, "MQTT_EVENT_CONNECTED"); + // TODO session present check + this->is_connected_ = true; + this->on_connect_.call(!mqtt_cfg_.disable_clean_session); + break; + case MQTT_EVENT_DISCONNECTED: + ESP_LOGV(TAG, "MQTT_EVENT_DISCONNECTED"); + // TODO is there a way to get the disconnect reason? + this->is_connected_ = false; + this->on_disconnect_.call(MQTTClientDisconnectReason::TCP_DISCONNECTED); + break; + + case MQTT_EVENT_SUBSCRIBED: + ESP_LOGV(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event.msg_id); + // hardcode QoS to 0. QoS is not used in this context but required to mirror the AsyncMqtt interface + this->on_subscribe_.call((int) event.msg_id, 0); + break; + case MQTT_EVENT_UNSUBSCRIBED: + ESP_LOGV(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event.msg_id); + this->on_unsubscribe_.call((int) event.msg_id); + break; + case MQTT_EVENT_PUBLISHED: + ESP_LOGV(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event.msg_id); + this->on_publish_.call((int) event.msg_id); + break; + case MQTT_EVENT_DATA: { + static std::string topic; + if (event.topic) { + // not 0 terminated - create a string from it + topic = std::string(event.topic, event.topic_len); + } + ESP_LOGV(TAG, "MQTT_EVENT_DATA %s", topic.c_str()); + auto data_len = event.data_len; + if (data_len == 0) + data_len = strlen(event.data); + this->on_message_.call(event.topic ? const_cast(topic.c_str()) : nullptr, event.data, data_len, + event.current_data_offset, event.total_data_len); + } break; + case MQTT_EVENT_ERROR: + ESP_LOGE(TAG, "MQTT_EVENT_ERROR"); + if (event.error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) { + ESP_LOGE(TAG, "Last error code reported from esp-tls: 0x%x", event.error_handle->esp_tls_last_esp_err); + ESP_LOGE(TAG, "Last tls stack error number: 0x%x", event.error_handle->esp_tls_stack_err); + ESP_LOGE(TAG, "Last captured errno : %d (%s)", event.error_handle->esp_transport_sock_errno, + strerror(event.error_handle->esp_transport_sock_errno)); + } else if (event.error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) { + ESP_LOGE(TAG, "Connection refused error: 0x%x", event.error_handle->connect_return_code); + } else { + ESP_LOGE(TAG, "Unknown error type: 0x%x", event.error_handle->error_type); + } + break; + default: + ESP_LOGV(TAG, "Other event id:%d", event.event_id); + break; + } +} + +/// static - Dispatch event to instance method +void MQTTBackendIDF::mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { + MQTTBackendIDF *instance = static_cast(handler_args); + // queue event to decouple processing + if (instance) { + auto event = *static_cast(event_data); + instance->mqtt_events_.push(event); + } +} + +} // namespace mqtt +} // namespace esphome +#endif // USE_ESP_IDF diff --git a/esphome/components/mqtt/mqtt_backend_idf.h b/esphome/components/mqtt/mqtt_backend_idf.h new file mode 100644 index 0000000000..77b5592d72 --- /dev/null +++ b/esphome/components/mqtt/mqtt_backend_idf.h @@ -0,0 +1,143 @@ +#pragma once + +#ifdef USE_ESP_IDF + +#include +#include +#include +#include "esphome/components/network/ip_address.h" +#include "esphome/core/helpers.h" +#include "mqtt_backend.h" + +namespace esphome { +namespace mqtt { + +class MQTTBackendIDF final : public MQTTBackend { + public: + static const size_t MQTT_BUFFER_SIZE = 4096; + + void set_keep_alive(uint16_t keep_alive) final { this->keep_alive_ = keep_alive; } + void set_client_id(const char *client_id) final { this->client_id_ = client_id; } + void set_clean_session(bool clean_session) final { this->clean_session_ = clean_session; } + + void set_credentials(const char *username, const char *password) final { + if (username) + this->username_ = username; + if (password) + this->password_ = password; + } + void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) final { + if (topic) + this->lwt_topic_ = topic; + this->lwt_qos_ = qos; + if (payload) + this->lwt_message_ = payload; + this->lwt_retain_ = retain; + } + void set_server(network::IPAddress ip, uint16_t port) final { + this->host_ = ip.str(); + this->port_ = port; + } + void set_server(const char *host, uint16_t port) final { + this->host_ = host; + this->port_ = port; + } + void set_on_connect(std::function &&callback) final { + this->on_connect_.add(std::move(callback)); + } + void set_on_disconnect(std::function &&callback) final { + this->on_disconnect_.add(std::move(callback)); + } + void set_on_subscribe(std::function &&callback) final { + this->on_subscribe_.add(std::move(callback)); + } + void set_on_unsubscribe(std::function &&callback) final { + this->on_unsubscribe_.add(std::move(callback)); + } + void set_on_message(std::function &&callback) final { + this->on_message_.add(std::move(callback)); + } + void set_on_publish(std::function &&callback) final { + this->on_publish_.add(std::move(callback)); + } + bool connected() const final { return this->is_connected_; } + + void connect() final { + if (!is_initalized_) { + if (initialize_()) { + esp_mqtt_client_start(handler_.get()); + } + } + } + void disconnect() final { + if (is_initalized_) + esp_mqtt_client_disconnect(handler_.get()); + } + + bool subscribe(const char *topic, uint8_t qos) final { + return esp_mqtt_client_subscribe(handler_.get(), topic, qos) != -1; + } + bool unsubscribe(const char *topic) final { return esp_mqtt_client_unsubscribe(handler_.get(), topic) != -1; } + + bool publish(const char *topic, const char *payload, size_t length, uint8_t qos, bool retain) final { +#if defined(USE_MQTT_IDF_ENQUEUE) + // use the non-blocking version + // it can delay sending a couple of seconds but won't block + return esp_mqtt_client_enqueue(handler_.get(), topic, payload, length, qos, retain, true) != -1; +#else + // might block for several seconds, either due to network timeout (10s) + // or if publishing payloads longer than internal buffer (due to message fragmentation) + return esp_mqtt_client_publish(handler_.get(), topic, payload, length, qos, retain) != -1; +#endif + } + using MQTTBackend::publish; + + void loop() final; + + void set_ca_certificate(const std::string &cert) { ca_certificate_ = cert; } + void set_skip_cert_cn_check(bool skip_check) { skip_cert_cn_check_ = skip_check; } + + protected: + bool initialize_(); + void mqtt_event_handler_(const esp_mqtt_event_t &event); + static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data); + + struct MqttClientDeleter { + void operator()(esp_mqtt_client *client_handler) { esp_mqtt_client_destroy(client_handler); } + }; + using ClientHandler_ = std::unique_ptr; + ClientHandler_ handler_; + + bool is_connected_{false}; + bool is_initalized_{false}; + + esp_mqtt_client_config_t mqtt_cfg_{}; + + std::string host_; + uint16_t port_; + std::string username_; + std::string password_; + std::string lwt_topic_; + std::string lwt_message_; + uint8_t lwt_qos_; + bool lwt_retain_; + std::string client_id_; + uint16_t keep_alive_; + bool clean_session_; + optional ca_certificate_; + bool skip_cert_cn_check_{false}; + + // callbacks + CallbackManager on_connect_; + CallbackManager on_disconnect_; + CallbackManager on_subscribe_; + CallbackManager on_unsubscribe_; + CallbackManager on_message_; + CallbackManager on_publish_; + std::queue mqtt_events_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 1fea0c80cc..3c6ce7cdfc 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -27,21 +27,21 @@ MQTTClientComponent::MQTTClientComponent() { // Connection void MQTTClientComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up MQTT..."); - this->mqtt_client_.onMessage([this](char const *topic, char *payload, AsyncMqttClientMessageProperties properties, - size_t len, size_t index, size_t total) { - if (index == 0) - this->payload_buffer_.reserve(total); + this->mqtt_backend_.set_on_message( + [this](const char *topic, const char *payload, size_t len, size_t index, size_t total) { + if (index == 0) + this->payload_buffer_.reserve(total); - // append new payload, may contain incomplete MQTT message - this->payload_buffer_.append(payload, len); + // append new payload, may contain incomplete MQTT message + this->payload_buffer_.append(payload, len); - // MQTT fully received - if (len + index == total) { - this->on_message(topic, this->payload_buffer_); - this->payload_buffer_.clear(); - } - }); - this->mqtt_client_.onDisconnect([this](AsyncMqttClientDisconnectReason reason) { + // MQTT fully received + if (len + index == total) { + this->on_message(topic, this->payload_buffer_); + this->payload_buffer_.clear(); + } + }); + this->mqtt_backend_.set_on_disconnect([this](MQTTClientDisconnectReason reason) { this->state_ = MQTT_CLIENT_DISCONNECTED; this->disconnect_reason_ = reason; }); @@ -49,8 +49,10 @@ void MQTTClientComponent::setup() { if (this->is_log_message_enabled() && logger::global_logger != nullptr) { logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { if (level <= this->log_level_ && this->is_connected()) { - this->publish(this->log_message_.topic, message, strlen(message), this->log_message_.qos, - this->log_message_.retain); + this->publish({.topic = this->log_message_.topic, + .payload = message, + .qos = this->log_message_.qos, + .retain = this->log_message_.retain}); } }); } @@ -173,9 +175,9 @@ void MQTTClientComponent::start_connect_() { ESP_LOGI(TAG, "Connecting to MQTT..."); // Force disconnect first - this->mqtt_client_.disconnect(true); + this->mqtt_backend_.disconnect(); - this->mqtt_client_.setClientId(this->credentials_.client_id.c_str()); + this->mqtt_backend_.set_client_id(this->credentials_.client_id.c_str()); const char *username = nullptr; if (!this->credentials_.username.empty()) username = this->credentials_.username.c_str(); @@ -183,24 +185,24 @@ void MQTTClientComponent::start_connect_() { if (!this->credentials_.password.empty()) password = this->credentials_.password.c_str(); - this->mqtt_client_.setCredentials(username, password); + this->mqtt_backend_.set_credentials(username, password); - this->mqtt_client_.setServer((uint32_t) this->ip_, this->credentials_.port); + this->mqtt_backend_.set_server((uint32_t) this->ip_, this->credentials_.port); if (!this->last_will_.topic.empty()) { - this->mqtt_client_.setWill(this->last_will_.topic.c_str(), this->last_will_.qos, this->last_will_.retain, - this->last_will_.payload.c_str(), this->last_will_.payload.length()); + this->mqtt_backend_.set_will(this->last_will_.topic.c_str(), this->last_will_.qos, this->last_will_.retain, + this->last_will_.payload.c_str()); } - this->mqtt_client_.connect(); + this->mqtt_backend_.connect(); this->state_ = MQTT_CLIENT_CONNECTING; this->connect_begin_ = millis(); } bool MQTTClientComponent::is_connected() { - return this->state_ == MQTT_CLIENT_CONNECTED && this->mqtt_client_.connected(); + return this->state_ == MQTT_CLIENT_CONNECTED && this->mqtt_backend_.connected(); } void MQTTClientComponent::check_connected() { - if (!this->mqtt_client_.connected()) { + if (!this->mqtt_backend_.connected()) { if (millis() - this->connect_begin_ > 60000) { this->state_ = MQTT_CLIENT_DISCONNECTED; this->start_dnslookup_(); @@ -222,31 +224,34 @@ void MQTTClientComponent::check_connected() { } void MQTTClientComponent::loop() { + // Call the backend loop first + mqtt_backend_.loop(); + if (this->disconnect_reason_.has_value()) { const LogString *reason_s; switch (*this->disconnect_reason_) { - case AsyncMqttClientDisconnectReason::TCP_DISCONNECTED: + case MQTTClientDisconnectReason::TCP_DISCONNECTED: reason_s = LOG_STR("TCP disconnected"); break; - case AsyncMqttClientDisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION: + case MQTTClientDisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION: reason_s = LOG_STR("Unacceptable Protocol Version"); break; - case AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED: + case MQTTClientDisconnectReason::MQTT_IDENTIFIER_REJECTED: reason_s = LOG_STR("Identifier Rejected"); break; - case AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE: + case MQTTClientDisconnectReason::MQTT_SERVER_UNAVAILABLE: reason_s = LOG_STR("Server Unavailable"); break; - case AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS: + case MQTTClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS: reason_s = LOG_STR("Malformed Credentials"); break; - case AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED: + case MQTTClientDisconnectReason::MQTT_NOT_AUTHORIZED: reason_s = LOG_STR("Not Authorized"); break; - case AsyncMqttClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE: + case MQTTClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE: reason_s = LOG_STR("Not Enough Space"); break; - case AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT: + case MQTTClientDisconnectReason::TLS_BAD_FINGERPRINT: reason_s = LOG_STR("TLS Bad Fingerprint"); break; default: @@ -275,7 +280,7 @@ void MQTTClientComponent::loop() { this->check_connected(); break; case MQTT_CLIENT_CONNECTED: - if (!this->mqtt_client_.connected()) { + if (!this->mqtt_backend_.connected()) { this->state_ = MQTT_CLIENT_DISCONNECTED; ESP_LOGW(TAG, "Lost MQTT Client connection!"); this->start_dnslookup_(); @@ -302,10 +307,10 @@ bool MQTTClientComponent::subscribe_(const char *topic, uint8_t qos) { if (!this->is_connected()) return false; - uint16_t ret = this->mqtt_client_.subscribe(topic, qos); + bool ret = this->mqtt_backend_.subscribe(topic, qos); yield(); - if (ret != 0) { + if (ret) { ESP_LOGV(TAG, "subscribe(topic='%s')", topic); } else { delay(5); @@ -360,9 +365,9 @@ void MQTTClientComponent::subscribe_json(const std::string &topic, const mqtt_js } void MQTTClientComponent::unsubscribe(const std::string &topic) { - uint16_t ret = this->mqtt_client_.unsubscribe(topic.c_str()); + bool ret = this->mqtt_backend_.unsubscribe(topic.c_str()); yield(); - if (ret != 0) { + if (ret) { ESP_LOGV(TAG, "unsubscribe(topic='%s')", topic.c_str()); } else { delay(5); @@ -387,34 +392,35 @@ bool MQTTClientComponent::publish(const std::string &topic, const std::string &p bool MQTTClientComponent::publish(const std::string &topic, const char *payload, size_t payload_length, uint8_t qos, bool retain) { + return publish({.topic = topic, .payload = payload, .qos = qos, .retain = retain}); +} + +bool MQTTClientComponent::publish(const MQTTMessage &message) { if (!this->is_connected()) { // critical components will re-transmit their messages return false; } - bool logging_topic = topic == this->log_message_.topic; - uint16_t ret = this->mqtt_client_.publish(topic.c_str(), qos, retain, payload, payload_length); + bool logging_topic = this->log_message_.topic == message.topic; + bool ret = this->mqtt_backend_.publish(message); delay(0); - if (ret == 0 && !logging_topic && this->is_connected()) { + if (!ret && !logging_topic && this->is_connected()) { delay(0); - ret = this->mqtt_client_.publish(topic.c_str(), qos, retain, payload, payload_length); + ret = this->mqtt_backend_.publish(message); delay(0); } if (!logging_topic) { - if (ret != 0) { - ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d)", topic.c_str(), payload, retain); + if (ret) { + ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d)", message.topic.c_str(), message.payload.c_str(), + message.retain); } else { - ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). will retry later..", topic.c_str(), - payload_length); // NOLINT + ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). will retry later..", message.topic.c_str(), + message.payload.length()); this->status_momentary_warning("publish", 1000); } } return ret != 0; } - -bool MQTTClientComponent::publish(const MQTTMessage &message) { - return this->publish(message.topic, message.payload, message.qos, message.retain); -} bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos, bool retain) { std::string message = json::build_json(f); @@ -499,10 +505,10 @@ bool MQTTClientComponent::is_log_message_enabled() const { return !this->log_mes void MQTTClientComponent::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; } void MQTTClientComponent::register_mqtt_component(MQTTComponent *component) { this->children_.push_back(component); } void MQTTClientComponent::set_log_level(int level) { this->log_level_ = level; } -void MQTTClientComponent::set_keep_alive(uint16_t keep_alive_s) { this->mqtt_client_.setKeepAlive(keep_alive_s); } +void MQTTClientComponent::set_keep_alive(uint16_t keep_alive_s) { this->mqtt_backend_.set_keep_alive(keep_alive_s); } void MQTTClientComponent::set_log_message_template(MQTTMessage &&message) { this->log_message_ = std::move(message); } const MQTTDiscoveryInfo &MQTTClientComponent::get_discovery_info() const { return this->discovery_info_; } -void MQTTClientComponent::set_topic_prefix(std::string topic_prefix) { this->topic_prefix_ = std::move(topic_prefix); } +void MQTTClientComponent::set_topic_prefix(const std::string &topic_prefix) { this->topic_prefix_ = topic_prefix; } const std::string &MQTTClientComponent::get_topic_prefix() const { return this->topic_prefix_; } void MQTTClientComponent::disable_birth_message() { this->birth_message_.topic = ""; @@ -549,7 +555,8 @@ void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscovery void MQTTClientComponent::disable_last_will() { this->last_will_.topic = ""; } void MQTTClientComponent::disable_discovery() { - this->discovery_info_ = MQTTDiscoveryInfo{.prefix = "", .retain = false}; + this->discovery_info_ = MQTTDiscoveryInfo{ + .prefix = "", .retain = false, .clean = false, .unique_id_generator = MQTT_LEGACY_UNIQUE_ID_GENERATOR}; } void MQTTClientComponent::on_shutdown() { if (!this->shutdown_message_.topic.empty()) { @@ -557,13 +564,13 @@ void MQTTClientComponent::on_shutdown() { this->publish(this->shutdown_message_); yield(); } - this->mqtt_client_.disconnect(true); + this->mqtt_backend_.disconnect(); } #if ASYNC_TCP_SSL_ENABLED void MQTTClientComponent::add_ssl_fingerprint(const std::array &fingerprint) { - this->mqtt_client_.setSecure(true); - this->mqtt_client_.addServerFingerprint(fingerprint.data()); + this->mqtt_backend_.setSecure(true); + this->mqtt_backend_.addServerFingerprint(fingerprint.data()); } #endif diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 58a4fbe166..4880bbaa5b 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -9,7 +9,11 @@ #include "esphome/core/log.h" #include "esphome/components/json/json_util.h" #include "esphome/components/network/ip_address.h" -#include +#if defined(USE_ESP_IDF) +#include "mqtt_backend_idf.h" +#elif defined(USE_ARDUINO) +#include "mqtt_backend_arduino.h" +#endif #include "lwip/ip_addr.h" namespace esphome { @@ -22,14 +26,6 @@ namespace mqtt { using mqtt_callback_t = std::function; using mqtt_json_callback_t = std::function; -/// internal struct for MQTT messages. -struct MQTTMessage { - std::string topic; - std::string payload; - uint8_t qos; ///< QoS. Only for last will testaments. - bool retain; -}; - /// internal struct for MQTT subscriptions. struct MQTTSubscription { std::string topic; @@ -139,7 +135,10 @@ class MQTTClientComponent : public Component { */ void add_ssl_fingerprint(const std::array &fingerprint); #endif - +#ifdef USE_ESP_IDF + void set_ca_certificate(const char *cert) { this->mqtt_backend_.set_ca_certificate(cert); } + void set_skip_cert_cn_check(bool skip_check) { this->mqtt_backend_.set_skip_cert_cn_check(skip_check); } +#endif const Availability &get_availability(); /** Set the topic prefix that will be prepended to all topics together with "/". This will, in most cases, @@ -150,7 +149,7 @@ class MQTTClientComponent : public Component { * * @param topic_prefix The topic prefix. The last "/" is appended automatically. */ - void set_topic_prefix(std::string topic_prefix); + void set_topic_prefix(const std::string &topic_prefix); /// Get the topic prefix of this device, using default if necessary const std::string &get_topic_prefix() const; @@ -277,6 +276,7 @@ class MQTTClientComponent : public Component { .prefix = "homeassistant", .retain = true, .clean = false, + .unique_id_generator = MQTT_LEGACY_UNIQUE_ID_GENERATOR, }; std::string topic_prefix_{}; MQTTMessage log_message_; @@ -284,7 +284,12 @@ class MQTTClientComponent : public Component { int log_level_{ESPHOME_LOG_LEVEL}; std::vector subscriptions_; - AsyncMqttClient mqtt_client_; +#if defined(USE_ESP_IDF) + MQTTBackendIDF mqtt_backend_; +#elif defined(USE_ARDUINO) + MQTTBackendArduino mqtt_backend_; +#endif + MQTTClientState state_{MQTT_CLIENT_DISCONNECTED}; network::IPAddress ip_; bool dns_resolved_{false}; @@ -293,7 +298,7 @@ class MQTTClientComponent : public Component { uint32_t reboot_timeout_{300000}; uint32_t connect_begin_; uint32_t last_connected_{0}; - optional disconnect_reason_{}; + optional disconnect_reason_{}; }; extern MQTTClientComponent *global_mqtt_client; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index b5c82338b3..f304f847a5 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -29,6 +29,7 @@ #define USE_LOCK #define USE_LOGGER #define USE_MDNS +#define USE_MQTT #define USE_NUMBER #define USE_OTA_PASSWORD #define USE_OTA_STATE_CALLBACK @@ -49,13 +50,17 @@ #define USE_CAPTIVE_PORTAL #define USE_JSON #define USE_NEXTION_TFT_UPLOAD -#define USE_MQTT #define USE_PROMETHEUS #define USE_WEBSERVER #define USE_WEBSERVER_PORT 80 // NOLINT #define USE_WIFI_WPA2_EAP #endif +// IDF-specific feature flags +#ifdef USE_ESP_IDF +#define USE_MQTT_IDF_ENQUEUE +#endif + // ESP32-specific feature flags #ifdef USE_ESP32 #define USE_ESP32_BLE_CLIENT diff --git a/tests/test5.yaml b/tests/test5.yaml index 9bfd395538..ee90cc1149 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -49,6 +49,19 @@ modbus_controller: address: 0x2 modbus_id: mod_bus1 +mqtt: + broker: test.mosquitto.org + port: 1883 + discovery: true + discovery_prefix: homeassistant + idf_send_async: false + on_message: + topic: testing/sensor/testing_sensor/state + qos: 0 + then: + - lambda: |- + ESP_LOGD("Mqtt Test","testing/sensor/testing_sensor/state=[%s]",x.c_str()); + binary_sensor: - platform: gpio pin: GPIO0 From fd7e861ff5395d360f894de344ecc2df741cf522 Mon Sep 17 00:00:00 2001 From: "Andrew J.Swan" Date: Mon, 4 Apr 2022 02:13:59 +0300 Subject: [PATCH 191/238] Added a function to load custom characters in LCD display (#3279) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/lcd_base/lcd_display.cpp | 7 +++++++ esphome/components/lcd_base/lcd_display.h | 3 +++ 2 files changed, 10 insertions(+) diff --git a/esphome/components/lcd_base/lcd_display.cpp b/esphome/components/lcd_base/lcd_display.cpp index b937e36c6c..180d5e93ac 100644 --- a/esphome/components/lcd_base/lcd_display.cpp +++ b/esphome/components/lcd_base/lcd_display.cpp @@ -167,6 +167,13 @@ void LCDDisplay::strftime(uint8_t column, uint8_t row, const char *format, time: } void LCDDisplay::strftime(const char *format, time::ESPTime time) { this->strftime(0, 0, format, time); } #endif +void LCDDisplay::loadchar(uint8_t location, uint8_t charmap[]) { + location &= 0x7; // we only have 8 locations 0-7 + this->command_(LCD_DISPLAY_COMMAND_SET_CGRAM_ADDR | (location << 3)); + for (int i = 0; i < 8; i++) { + this->send(charmap[i], true); + } +} } // namespace lcd_base } // namespace esphome diff --git a/esphome/components/lcd_base/lcd_display.h b/esphome/components/lcd_base/lcd_display.h index 0c9e59758c..c8ba39f0d4 100644 --- a/esphome/components/lcd_base/lcd_display.h +++ b/esphome/components/lcd_base/lcd_display.h @@ -51,6 +51,9 @@ class LCDDisplay : public PollingComponent { void strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0))); #endif + /// Load custom char to given location + void loadchar(uint8_t location, uint8_t charmap[]); + protected: virtual bool is_four_bit_mode() = 0; virtual void write_n_bits(uint8_t value, uint8_t n) = 0; From 2e436eae6bd89289588f5ddab2127d1aa20fb8c7 Mon Sep 17 00:00:00 2001 From: Felix Storm Date: Mon, 4 Apr 2022 01:15:51 +0200 Subject: [PATCH 192/238] CAN bus: support remote transmission request flag for canbus.send (#3193) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/canbus/__init__.py | 7 +++++++ esphome/components/canbus/canbus.cpp | 8 +++++--- esphome/components/canbus/canbus.h | 16 +++++++++++++--- tests/test1.yaml | 10 ++++++++++ 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/esphome/components/canbus/__init__.py b/esphome/components/canbus/__init__.py index 5f614eb0a4..20f2642144 100644 --- a/esphome/components/canbus/__init__.py +++ b/esphome/components/canbus/__init__.py @@ -10,6 +10,7 @@ IS_PLATFORM_COMPONENT = True CONF_CAN_ID = "can_id" CONF_CAN_ID_MASK = "can_id_mask" CONF_USE_EXTENDED_ID = "use_extended_id" +CONF_REMOTE_TRANSMISSION_REQUEST = "remote_transmission_request" CONF_CANBUS_ID = "canbus_id" CONF_BIT_RATE = "bit_rate" CONF_ON_FRAME = "on_frame" @@ -122,6 +123,7 @@ async def register_canbus(var, config): cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent), cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, + cv.Optional(CONF_REMOTE_TRANSMISSION_REQUEST, default=False): cv.boolean, cv.Required(CONF_DATA): cv.templatable(validate_raw_data), }, validate_id, @@ -140,6 +142,11 @@ async def canbus_action_to_code(config, action_id, template_arg, args): ) cg.add(var.set_use_extended_id(use_extended_id)) + remote_transmission_request = await cg.templatable( + config[CONF_REMOTE_TRANSMISSION_REQUEST], args, bool + ) + cg.add(var.set_remote_transmission_request(remote_transmission_request)) + data = config[CONF_DATA] if isinstance(data, bytes): data = [int(x) for x in data] diff --git a/esphome/components/canbus/canbus.cpp b/esphome/components/canbus/canbus.cpp index 14dc1544cf..5d9084706b 100644 --- a/esphome/components/canbus/canbus.cpp +++ b/esphome/components/canbus/canbus.cpp @@ -22,20 +22,22 @@ void Canbus::dump_config() { } } -void Canbus::send_data(uint32_t can_id, bool use_extended_id, const std::vector &data) { +void Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request, + const std::vector &data) { struct CanFrame can_message; uint8_t size = static_cast(data.size()); if (use_extended_id) { - ESP_LOGD(TAG, "send extended id=0x%08x size=%d", can_id, size); + ESP_LOGD(TAG, "send extended id=0x%08x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size); } else { - ESP_LOGD(TAG, "send extended id=0x%03x size=%d", can_id, size); + ESP_LOGD(TAG, "send extended id=0x%03x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size); } if (size > CAN_MAX_DATA_LENGTH) size = CAN_MAX_DATA_LENGTH; can_message.can_data_length_code = size; can_message.can_id = can_id; can_message.use_extended_id = use_extended_id; + can_message.remote_transmission_request = remote_transmission_request; for (int i = 0; i < size; i++) { can_message.data[i] = data[i]; diff --git a/esphome/components/canbus/canbus.h b/esphome/components/canbus/canbus.h index 0491e8d3c1..20c490c083 100644 --- a/esphome/components/canbus/canbus.h +++ b/esphome/components/canbus/canbus.h @@ -62,7 +62,12 @@ class Canbus : public Component { float get_setup_priority() const override { return setup_priority::HARDWARE; } void loop() override; - void send_data(uint32_t can_id, bool use_extended_id, const std::vector &data); + void send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request, + const std::vector &data); + void send_data(uint32_t can_id, bool use_extended_id, const std::vector &data) { + // for backwards compatibility only + this->send_data(can_id, use_extended_id, false, data); + } void set_can_id(uint32_t can_id) { this->can_id_ = can_id; } void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; } void set_bitrate(CanSpeed bit_rate) { this->bit_rate_ = bit_rate; } @@ -96,21 +101,26 @@ template class CanbusSendAction : public Action, public P void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; } + void set_remote_transmission_request(bool remote_transmission_request) { + this->remote_transmission_request_ = remote_transmission_request; + } + void play(Ts... x) override { auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_; auto use_extended_id = this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_; if (this->static_) { - this->parent_->send_data(can_id, use_extended_id, this->data_static_); + this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, this->data_static_); } else { auto val = this->data_func_(x...); - this->parent_->send_data(can_id, use_extended_id, val); + this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, val); } } protected: optional can_id_{}; optional use_extended_id_{}; + bool remote_transmission_request_{false}; bool static_{false}; std::function(Ts...)> data_func_{}; std::vector data_static_{}; diff --git a/tests/test1.yaml b/tests/test1.yaml index 181f62d3f4..77c4a76bda 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2616,6 +2616,16 @@ text_sensor: canbus_id: esp32_internal_can can_id: 23 data: [0x10, 0x20, 0x30] + - canbus.send: + canbus_id: mcp2515_can + can_id: 24 + remote_transmission_request: true + data: [] + - canbus.send: + canbus_id: esp32_internal_can + can_id: 24 + remote_transmission_request: true + data: [] - platform: template name: Template Text Sensor id: ${textname}_text From 70fafa473bed82bacac20e25649ba6087f817ca5 Mon Sep 17 00:00:00 2001 From: Michiel van Turnhout Date: Mon, 4 Apr 2022 01:42:10 +0200 Subject: [PATCH 193/238] Tm1637 binarysensor (#2792) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/tm1637/binary_sensor.py | 26 ++++++ esphome/components/tm1637/tm1637.cpp | 92 ++++++++++++++++++---- esphome/components/tm1637/tm1637.h | 31 +++++++- 3 files changed, 132 insertions(+), 17 deletions(-) create mode 100644 esphome/components/tm1637/binary_sensor.py diff --git a/esphome/components/tm1637/binary_sensor.py b/esphome/components/tm1637/binary_sensor.py new file mode 100644 index 0000000000..66b5172358 --- /dev/null +++ b/esphome/components/tm1637/binary_sensor.py @@ -0,0 +1,26 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import CONF_ID, CONF_KEY + +CONF_TM1637_ID = "tm1637_id" + +tm1637_ns = cg.esphome_ns.namespace("tm1637") +TM1637Display = tm1637_ns.class_("TM1637Display", cg.PollingComponent) +TM1637Key = tm1637_ns.class_("TM1637Key", binary_sensor.BinarySensor) + +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TM1637Key), + cv.GenerateID(CONF_TM1637_ID): cv.use_id(TM1637Display), + cv.Required(CONF_KEY): cv.int_range(min=0, max=15), + } +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await binary_sensor.register_binary_sensor(var, config) + cg.add(var.set_keycode(config[CONF_KEY])) + hub = await cg.get_variable(config[CONF_TM1637_ID]) + cg.add(hub.add_tm1637_key(var)) diff --git a/esphome/components/tm1637/tm1637.cpp b/esphome/components/tm1637/tm1637.cpp index 44f0a841b8..be2192ea22 100644 --- a/esphome/components/tm1637/tm1637.cpp +++ b/esphome/components/tm1637/tm1637.cpp @@ -7,11 +7,17 @@ namespace esphome { namespace tm1637 { static const char *const TAG = "display.tm1637"; -const uint8_t TM1637_I2C_COMM1 = 0x40; -const uint8_t TM1637_I2C_COMM2 = 0xC0; -const uint8_t TM1637_I2C_COMM3 = 0x80; +const uint8_t TM1637_CMD_DATA = 0x40; //!< Display data command +const uint8_t TM1637_CMD_CTRL = 0x80; //!< Display control command +const uint8_t TM1637_CMD_ADDR = 0xc0; //!< Display address command const uint8_t TM1637_UNKNOWN_CHAR = 0b11111111; +// Data command bits +const uint8_t TM1637_DATA_WRITE = 0x00; //!< Write data +const uint8_t TM1637_DATA_READ_KEYS = 0x02; //!< Read keys +const uint8_t TM1637_DATA_AUTO_INC_ADDR = 0x00; //!< Auto increment address +const uint8_t TM1637_DATA_FIXED_ADDR = 0x04; //!< Fixed address + // // A // --- @@ -138,6 +144,36 @@ void TM1637Display::dump_config() { LOG_UPDATE_INTERVAL(this); } +#ifdef USE_BINARY_SENSOR +void TM1637Display::loop() { + uint8_t val = this->get_keys(); + for (auto *tm1637_key : this->tm1637_keys_) + tm1637_key->process(val); +} + +uint8_t TM1637Display::get_keys() { + this->start_(); + this->send_byte_(TM1637_CMD_DATA | TM1637_DATA_READ_KEYS); + this->start_(); + uint8_t key_code = read_byte_(); + this->stop_(); + if (key_code != 0xFF) { + // Invert key_code: + // Bit | 7 6 5 4 3 2 1 0 + // ------+------------------------- + // From | S0 S1 S2 K1 K2 1 1 1 + // To | S0 S1 S2 K1 K2 0 0 0 + key_code = ~key_code; + // Shift bits to: + // Bit | 7 6 5 4 3 2 1 0 + // ------+------------------------ + // To | 0 0 0 0 K2 S2 S1 S0 + key_code = (uint8_t)((key_code & 0x80) >> 7 | (key_code & 0x40) >> 5 | (key_code & 0x20) >> 3 | (key_code & 0x08)); + } + return key_code; +} +#endif + void TM1637Display::update() { for (uint8_t &i : this->buffer_) i = 0; @@ -165,14 +201,14 @@ void TM1637Display::stop_() { void TM1637Display::display() { ESP_LOGVV(TAG, "Display %02X%02X%02X%02X", buffer_[0], buffer_[1], buffer_[2], buffer_[3]); - // Write COMM1 + // Write DATA CMND this->start_(); - this->send_byte_(TM1637_I2C_COMM1); + this->send_byte_(TM1637_CMD_DATA); this->stop_(); - // Write COMM2 + first digit address + // Write ADDR CMD + first digit address this->start_(); - this->send_byte_(TM1637_I2C_COMM2); + this->send_byte_(TM1637_CMD_ADDR); // Write the data bytes if (this->inverted_) { @@ -187,20 +223,17 @@ void TM1637Display::display() { this->stop_(); - // Write COMM3 + brightness + // Write display CTRL CMND + brightness this->start_(); - this->send_byte_(TM1637_I2C_COMM3 + ((this->intensity_ & 0x7) | 0x08)); + this->send_byte_(TM1637_CMD_CTRL + ((this->intensity_ & 0x7) | 0x08)); this->stop_(); } bool TM1637Display::send_byte_(uint8_t b) { uint8_t data = b; - - // 8 Data Bits for (uint8_t i = 0; i < 8; i++) { // CLK low this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT); this->bit_delay_(); - // Set data bit if (data & 0x01) { this->dio_pin_->pin_mode(gpio::FLAG_INPUT); @@ -209,19 +242,16 @@ bool TM1637Display::send_byte_(uint8_t b) { } this->bit_delay_(); - // CLK high this->clk_pin_->pin_mode(gpio::FLAG_INPUT); this->bit_delay_(); data = data >> 1; } - // Wait for acknowledge // CLK to zero this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT); this->dio_pin_->pin_mode(gpio::FLAG_INPUT); this->bit_delay_(); - // CLK to high this->clk_pin_->pin_mode(gpio::FLAG_INPUT); this->bit_delay_(); @@ -237,8 +267,38 @@ bool TM1637Display::send_byte_(uint8_t b) { return ack; } +uint8_t TM1637Display::read_byte_() { + uint8_t retval = 0; + // Prepare DIO to read data + this->dio_pin_->pin_mode(gpio::FLAG_INPUT); + this->bit_delay_(); + // Data is shifted out by the TM1637 on the CLK falling edge + for (uint8_t bit = 0; bit < 8; bit++) { + this->clk_pin_->pin_mode(gpio::FLAG_INPUT); + this->bit_delay_(); + // Read next bit + retval <<= 1; + if (this->dio_pin_->digital_read()) { + retval |= 0x01; + } + this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->bit_delay_(); + } + // Return DIO to output mode + // Dummy ACK + this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->bit_delay_(); + this->clk_pin_->pin_mode(gpio::FLAG_INPUT); + this->bit_delay_(); + this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->bit_delay_(); + this->dio_pin_->pin_mode(gpio::FLAG_INPUT); + this->bit_delay_(); + return retval; +} + uint8_t TM1637Display::print(uint8_t start_pos, const char *str) { - ESP_LOGV(TAG, "Print at %d: %s", start_pos, str); + // ESP_LOGV(TAG, "Print at %d: %s", start_pos, str); uint8_t pos = start_pos; for (; *str != '\0'; str++) { uint8_t data = TM1637_UNKNOWN_CHAR; diff --git a/esphome/components/tm1637/tm1637.h b/esphome/components/tm1637/tm1637.h index 9b2f014ff9..0a77acaabe 100644 --- a/esphome/components/tm1637/tm1637.h +++ b/esphome/components/tm1637/tm1637.h @@ -8,10 +8,17 @@ #include "esphome/components/time/real_time_clock.h" #endif +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif + namespace esphome { namespace tm1637 { class TM1637Display; +#ifdef USE_BINARY_SENSOR +class TM1637Key; +#endif using tm1637_writer_t = std::function; @@ -46,10 +53,15 @@ class TM1637Display : public PollingComponent { void display(); +#ifdef USE_BINARY_SENSOR + void loop() override; + uint8_t get_keys(); + void add_tm1637_key(TM1637Key *tm1637_key) { this->tm1637_keys_.push_back(tm1637_key); } +#endif + #ifdef USE_TIME /// Evaluate the strftime-format and print the result at the given position. uint8_t strftime(uint8_t pos, const char *format, time::ESPTime time) __attribute__((format(strftime, 3, 0))); - /// Evaluate the strftime-format and print the result at position 0. uint8_t strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0))); #endif @@ -58,6 +70,7 @@ class TM1637Display : public PollingComponent { void bit_delay_(); void setup_pins_(); bool send_byte_(uint8_t b); + uint8_t read_byte_(); void start_(); void stop_(); @@ -68,7 +81,23 @@ class TM1637Display : public PollingComponent { bool inverted_; optional writer_{}; uint8_t buffer_[6] = {0}; +#ifdef USE_BINARY_SENSOR + std::vector tm1637_keys_{}; +#endif }; +#ifdef USE_BINARY_SENSOR +class TM1637Key : public binary_sensor::BinarySensor { + friend class TM1637Display; + + public: + void set_keycode(uint8_t key_code) { key_code_ = key_code; } + void process(uint8_t data) { this->publish_state(static_cast(data == this->key_code_)); } + + protected: + uint8_t key_code_{0}; +}; +#endif + } // namespace tm1637 } // namespace esphome From c54c20ab3cbcd4963db2f969d3c9357cebea4f2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 19:10:53 +0200 Subject: [PATCH 194/238] Bump click from 8.0.4 to 8.1.2 (#3351) 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 e934eb2c6f..8b67068505 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.5 # When updating platformio, also update Dockerfile esptool==3.3 -click==8.0.4 +click==8.1.2 esphome-dashboard==20220309.0 aioesphomeapi==10.8.2 zeroconf==0.38.4 From de96376565a50f318f396c0765c2abca6f8f903a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 19:11:04 +0200 Subject: [PATCH 195/238] Bump pylint from 2.12.2 to 2.13.4 (#3348) 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 8da8945c8b..a9dafcf063 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.12.2 +pylint==2.13.4 flake8==4.0.1 black==22.1.0 pyupgrade==2.31.0 From a39d87460075e520fafe1bf2bbbf2689e0919c07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 19:13:06 +0200 Subject: [PATCH 196/238] Bump pytest-asyncio from 0.18.2 to 0.18.3 (#3335) 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 a9dafcf063..f7bfac4c16 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -8,6 +8,6 @@ pre-commit pytest==7.0.1 pytest-cov==3.0.0 pytest-mock==3.7.0 -pytest-asyncio==0.18.2 +pytest-asyncio==0.18.3 asyncmock==0.4.2 hypothesis==5.49.0 From 061e1a471dbf69fac34766cd08afa64861480713 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 19:13:11 +0200 Subject: [PATCH 197/238] Bump pytest from 7.0.1 to 7.1.1 (#3313) 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 f7bfac4c16..066b1ae32f 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ pyupgrade==2.31.0 pre-commit # Unit tests -pytest==7.0.1 +pytest==7.1.1 pytest-cov==3.0.0 pytest-mock==3.7.0 pytest-asyncio==0.18.3 From 0b1161f7ef24eeb5e791a6a4b12ec9af2b5baab8 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 4 Apr 2022 19:21:43 +0200 Subject: [PATCH 198/238] Bump docker dependencies (#3354) --- docker/Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index ad4891d62e..610b689298 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -10,9 +10,9 @@ FROM ghcr.io/hassio-addons/debian-base/amd64:5.3.0 AS base-hassio-amd64 FROM ghcr.io/hassio-addons/debian-base/aarch64:5.3.0 AS base-hassio-arm64 FROM ghcr.io/hassio-addons/debian-base/armv7:5.3.0 AS base-hassio-armv7 # https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye -FROM debian:bullseye-20220228-slim AS base-docker-amd64 -FROM debian:bullseye-20220228-slim AS base-docker-arm64 -FROM debian:bullseye-20220228-slim AS base-docker-armv7 +FROM debian:bullseye-20220328-slim AS base-docker-amd64 +FROM debian:bullseye-20220328-slim AS base-docker-arm64 +FROM debian:bullseye-20220328-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 @@ -23,7 +23,7 @@ RUN \ # Use pinned versions so that we get updates with build caching && apt-get install -y --no-install-recommends \ python3=3.9.2-3 \ - python3-pip=20.3.4-4 \ + python3-pip=20.3.4-4+deb11u1 \ python3-setuptools=52.0.0-4 \ python3-pil=8.1.2+dfsg-0.3+deb11u1 \ python3-cryptography=3.3.2-1 \ From d97c3a7e017474a74db90637052a592135688a8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 19:37:35 +0200 Subject: [PATCH 199/238] Bump voluptuous from 0.12.2 to 0.13.0 (#3355) 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 8b67068505..410cdb6b1d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -voluptuous==0.12.2 +voluptuous==0.13.0 PyYAML==6.0 paho-mqtt==1.6.1 colorama==0.4.4 From d48ffa2913edf6727f84229ec65ec49f9f562391 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 19:39:45 +0200 Subject: [PATCH 200/238] Bump tzlocal from 4.1 to 4.2 (#3356) 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 410cdb6b1d..4386099e2b 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.1 # from time +tzlocal==4.2 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.5 # When updating platformio, also update Dockerfile From bff06e448b5aed849e1054d63e980f24c24d8218 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 20:06:42 +0200 Subject: [PATCH 201/238] Bump pyupgrade from 2.31.0 to 2.31.1 (#3292) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Otto Winter --- .pre-commit-config.yaml | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9549d5cedc..e94d8aa236 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: - --branch=release - --branch=beta - repo: https://github.com/asottile/pyupgrade - rev: v2.31.0 + rev: v2.31.1 hooks: - id: pyupgrade args: [--py38-plus] diff --git a/requirements_test.txt b/requirements_test.txt index 066b1ae32f..28a8e1db72 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,7 +1,7 @@ pylint==2.13.4 flake8==4.0.1 black==22.1.0 -pyupgrade==2.31.0 +pyupgrade==2.31.1 pre-commit # Unit tests From 06f4ad922c7fe3cf7c0bb99f17c8d1e621d6a4c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Apr 2022 11:50:51 +0200 Subject: [PATCH 202/238] Bump black from 22.1.0 to 22.3.0 (#3357) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Otto Winter --- .pre-commit-config.yaml | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e94d8aa236..1e666f20af 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/ambv/black - rev: 22.1.0 + rev: 22.3.0 hooks: - id: black args: diff --git a/requirements_test.txt b/requirements_test.txt index 28a8e1db72..bc5316ca71 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==2.13.4 flake8==4.0.1 -black==22.1.0 +black==22.3.0 pyupgrade==2.31.1 pre-commit From ba8d255cb4f09e35e94253c8bb3e3992d21448b4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 5 Apr 2022 22:06:36 +1200 Subject: [PATCH 203/238] Allow on_value_range for sensor and number to be templated (#3359) --- esphome/components/number/__init__.py | 4 ++-- esphome/components/sensor/__init__.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 71e288a4cc..89788f1e98 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -63,8 +63,8 @@ NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger), - cv.Optional(CONF_ABOVE): cv.float_, - cv.Optional(CONF_BELOW): cv.float_, + cv.Optional(CONF_ABOVE): cv.templatable(cv.float_), + cv.Optional(CONF_BELOW): cv.templatable(cv.float_), }, cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), ), diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 65ae7b2168..0c38ceeb37 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -212,8 +212,8 @@ SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger), - cv.Optional(CONF_ABOVE): cv.float_, - cv.Optional(CONF_BELOW): cv.float_, + cv.Optional(CONF_ABOVE): cv.templatable(cv.float_), + cv.Optional(CONF_BELOW): cv.templatable(cv.float_), }, cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), ), From d4ff98680abffbb6d1f52db61c1c18e215bde113 Mon Sep 17 00:00:00 2001 From: Tim Smeets Date: Thu, 7 Apr 2022 22:04:00 +0200 Subject: [PATCH 204/238] Add support for Electrolux heatpump and bump arduino-heatpumpir version (#3353) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/heatpumpir/climate.py | 5 ++--- esphome/components/heatpumpir/heatpumpir.cpp | 1 + esphome/components/heatpumpir/heatpumpir.h | 1 + platformio.ini | 4 +--- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py index 744ef5e527..a253a778de 100644 --- a/esphome/components/heatpumpir/climate.py +++ b/esphome/components/heatpumpir/climate.py @@ -25,6 +25,7 @@ PROTOCOLS = { "daikin_arc417": Protocol.PROTOCOL_DAIKIN_ARC417, "daikin_arc480": Protocol.PROTOCOL_DAIKIN_ARC480, "daikin": Protocol.PROTOCOL_DAIKIN, + "electroluxyal": Protocol.PROTOCOL_ELECTROLUXYAL, "fuego": Protocol.PROTOCOL_FUEGO, "fujitsu_awyz": Protocol.PROTOCOL_FUJITSU_AWYZ, "gree": Protocol.PROTOCOL_GREE, @@ -112,6 +113,4 @@ def to_code(config): 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 - cg.add_library("", "", "https://github.com/ToniA/arduino-heatpumpir.git#1.0.18") + cg.add_library("tonia/HeatpumpIR", "1.0.20") diff --git a/esphome/components/heatpumpir/heatpumpir.cpp b/esphome/components/heatpumpir/heatpumpir.cpp index ad3731b955..cd24411763 100644 --- a/esphome/components/heatpumpir/heatpumpir.cpp +++ b/esphome/components/heatpumpir/heatpumpir.cpp @@ -20,6 +20,7 @@ const std::map> PROTOCOL_CONSTRUCTOR_MAP {PROTOCOL_DAIKIN_ARC417, []() { return new DaikinHeatpumpARC417IR(); }}, // NOLINT {PROTOCOL_DAIKIN_ARC480, []() { return new DaikinHeatpumpARC480A14IR(); }}, // NOLINT {PROTOCOL_DAIKIN, []() { return new DaikinHeatpumpIR(); }}, // NOLINT + {PROTOCOL_ELECTROLUXYAL, []() { return new ElectroluxYALHeatpumpIR(); }}, // NOLINT {PROTOCOL_FUEGO, []() { return new FuegoHeatpumpIR(); }}, // NOLINT {PROTOCOL_FUJITSU_AWYZ, []() { return new FujitsuHeatpumpIR(); }}, // NOLINT {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }}, // NOLINT diff --git a/esphome/components/heatpumpir/heatpumpir.h b/esphome/components/heatpumpir/heatpumpir.h index 18d9b5040f..decf1eae07 100644 --- a/esphome/components/heatpumpir/heatpumpir.h +++ b/esphome/components/heatpumpir/heatpumpir.h @@ -20,6 +20,7 @@ enum Protocol { PROTOCOL_DAIKIN_ARC417, PROTOCOL_DAIKIN_ARC480, PROTOCOL_DAIKIN, + PROTOCOL_ELECTROLUXYAL, PROTOCOL_FUEGO, PROTOCOL_FUJITSU_AWYZ, PROTOCOL_GREE, diff --git a/platformio.ini b/platformio.ini index 8775b28156..bc2cddb9f7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -62,9 +62,7 @@ lib_deps = glmnet/Dsmr@0.5 ; dsmr rweather/Crypto@0.2.0 ; dsmr dudanov/MideaUART@1.1.8 ; midea - ; 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 + tonia/HeatpumpIR@1.0.20 ; heatpumpir build_flags = ${common.build_flags} -DUSE_ARDUINO From 5e79a1f500b91a5706044b5645af6acde5a607dc Mon Sep 17 00:00:00 2001 From: djwlindenaar <32413299+djwlindenaar@users.noreply.github.com> Date: Sun, 10 Apr 2022 22:06:11 +0200 Subject: [PATCH 205/238] Implement newer RTU protocol of Growatt inverters (#3315) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Daniel Lindenaar --- .../growatt_solar/growatt_solar.cpp | 89 ++++++++++++++----- .../components/growatt_solar/growatt_solar.h | 8 ++ esphome/components/growatt_solar/sensor.py | 14 ++- 3 files changed, 86 insertions(+), 25 deletions(-) diff --git a/esphome/components/growatt_solar/growatt_solar.cpp b/esphome/components/growatt_solar/growatt_solar.cpp index ed7240ab6c..ed753c4d3f 100644 --- a/esphome/components/growatt_solar/growatt_solar.cpp +++ b/esphome/components/growatt_solar/growatt_solar.cpp @@ -7,9 +7,11 @@ 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; +static const uint8_t MODBUS_REGISTER_COUNT[] = {33, 95}; // indexed with enum GrowattProtocolVersion -void GrowattSolar::update() { this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT); } +void GrowattSolar::update() { + this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT[this->protocol_version_]); +} void GrowattSolar::on_modbus_data(const std::vector &data) { auto publish_1_reg_sensor_state = [&](sensor::Sensor *sensor, size_t i, float unit) -> void { @@ -27,37 +29,76 @@ void GrowattSolar::on_modbus_data(const std::vector &data) { sensor->publish_state(value); }; - publish_1_reg_sensor_state(this->inverter_status_, 0, 1); + switch (this->protocol_version_) { + case RTU: { + 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_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_[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_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_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_[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_[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_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_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); + publish_1_reg_sensor_state(this->inverter_module_temp_, 32, ONE_DEC_UNIT); + break; + } + case RTU2: { + 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_, 35, 36, ONE_DEC_UNIT); + publish_1_reg_sensor_state(this->grid_frequency_sensor_, 37, TWO_DEC_UNIT); + + publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 38, ONE_DEC_UNIT); + publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 39, ONE_DEC_UNIT); + publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 40, 41, ONE_DEC_UNIT); + + publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 42, ONE_DEC_UNIT); + publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 43, ONE_DEC_UNIT); + publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 44, 45, ONE_DEC_UNIT); + + publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 46, ONE_DEC_UNIT); + publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 47, ONE_DEC_UNIT); + publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 48, 49, ONE_DEC_UNIT); + + publish_2_reg_sensor_state(this->today_production_, 53, 54, ONE_DEC_UNIT); + publish_2_reg_sensor_state(this->total_energy_production_, 55, 56, ONE_DEC_UNIT); + + publish_1_reg_sensor_state(this->inverter_module_temp_, 93, ONE_DEC_UNIT); + break; + } + } } void GrowattSolar::dump_config() { diff --git a/esphome/components/growatt_solar/growatt_solar.h b/esphome/components/growatt_solar/growatt_solar.h index 5356ac907a..0067998133 100644 --- a/esphome/components/growatt_solar/growatt_solar.h +++ b/esphome/components/growatt_solar/growatt_solar.h @@ -10,12 +10,19 @@ namespace growatt_solar { static const float TWO_DEC_UNIT = 0.01; static const float ONE_DEC_UNIT = 0.1; +enum GrowattProtocolVersion { + RTU = 0, + RTU2, +}; + 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_protocol_version(GrowattProtocolVersion protocol_version) { this->protocol_version_ = protocol_version; } + 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; } @@ -67,6 +74,7 @@ class GrowattSolar : public PollingComponent, public modbus::ModbusDevice { sensor::Sensor *today_production_{nullptr}; sensor::Sensor *total_energy_production_{nullptr}; sensor::Sensor *inverter_module_temp_{nullptr}; + GrowattProtocolVersion protocol_version_; }; } // namespace growatt_solar diff --git a/esphome/components/growatt_solar/sensor.py b/esphome/components/growatt_solar/sensor.py index 99936c33ee..4961595505 100644 --- a/esphome/components/growatt_solar/sensor.py +++ b/esphome/components/growatt_solar/sensor.py @@ -39,7 +39,7 @@ UNIT_MILLIAMPERE = "mA" CONF_INVERTER_STATUS = "inverter_status" CONF_PV_ACTIVE_POWER = "pv_active_power" CONF_INVERTER_MODULE_TEMP = "inverter_module_temp" - +CONF_PROTOCOL_VERSION = "protocol_version" AUTO_LOAD = ["modbus"] CODEOWNERS = ["@leeuwte"] @@ -95,10 +95,20 @@ PV_SCHEMA = cv.Schema( {cv.Optional(sensor): schema for sensor, schema in PV_SENSORS.items()} ) +GrowattProtocolVersion = growatt_solar_ns.enum("GrowattProtocolVersion") +PROTOCOL_VERSIONS = { + "RTU": GrowattProtocolVersion.RTU, + "RTU2": GrowattProtocolVersion.RTU2, +} + + CONFIG_SCHEMA = ( cv.Schema( { cv.GenerateID(): cv.declare_id(GrowattSolar), + cv.Optional(CONF_PROTOCOL_VERSION, default="RTU"): cv.enum( + PROTOCOL_VERSIONS, upper=True + ), cv.Optional(CONF_PHASE_A): PHASE_SCHEMA, cv.Optional(CONF_PHASE_B): PHASE_SCHEMA, cv.Optional(CONF_PHASE_C): PHASE_SCHEMA, @@ -152,6 +162,8 @@ async def to_code(config): await cg.register_component(var, config) await modbus.register_modbus_device(var, config) + cg.add(var.set_protocol_version(config[CONF_PROTOCOL_VERSION])) + if CONF_INVERTER_STATUS in config: sens = await sensor.new_sensor(config[CONF_INVERTER_STATUS]) cg.add(var.set_inverter_status_sensor(sens)) From a9e653724c53fcd79b2c8745f5b2c8ec33f0b09f Mon Sep 17 00:00:00 2001 From: Keilin Bickar Date: Sun, 10 Apr 2022 16:38:29 -0400 Subject: [PATCH 206/238] Add parameter to control i2c stop signal at endTransmission (#3370) --- esphome/components/i2c/i2c.h | 22 +++++++++++++--------- esphome/components/i2c/i2c_bus.h | 10 ++++++++-- esphome/components/i2c/i2c_bus_arduino.cpp | 4 ++-- esphome/components/i2c/i2c_bus_arduino.h | 2 +- esphome/components/i2c/i2c_bus_esp_idf.cpp | 2 +- esphome/components/i2c/i2c_bus_esp_idf.h | 2 +- esphome/components/tca9548a/tca9548a.cpp | 4 ++-- esphome/components/tca9548a/tca9548a.h | 2 +- 8 files changed, 29 insertions(+), 19 deletions(-) diff --git a/esphome/components/i2c/i2c.h b/esphome/components/i2c/i2c.h index 50a0b3ae50..ffc0dadf81 100644 --- a/esphome/components/i2c/i2c.h +++ b/esphome/components/i2c/i2c.h @@ -46,21 +46,21 @@ class I2CDevice { I2CRegister reg(uint8_t a_register) { return {this, a_register}; } ErrorCode read(uint8_t *data, size_t len) { return bus_->read(address_, data, len); } - ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len) { - ErrorCode err = this->write(&a_register, 1); + ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop = true) { + ErrorCode err = this->write(&a_register, 1, stop); if (err != ERROR_OK) return err; return this->read(data, len); } - ErrorCode write(const uint8_t *data, uint8_t len) { return bus_->write(address_, data, len); } - ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len) { + ErrorCode write(const uint8_t *data, uint8_t len, bool stop = true) { return bus_->write(address_, data, len, stop); } + ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop = true) { WriteBuffer buffers[2]; buffers[0].data = &a_register; buffers[0].len = 1; buffers[1].data = data; buffers[1].len = len; - return bus_->writev(address_, buffers, 2); + return bus_->writev(address_, buffers, 2, stop); } // Compat APIs @@ -93,7 +93,9 @@ class I2CDevice { return true; } - bool read_byte(uint8_t a_register, uint8_t *data) { return read_register(a_register, data, 1) == ERROR_OK; } + bool read_byte(uint8_t a_register, uint8_t *data, bool stop = true) { + return read_register(a_register, data, 1, stop) == ERROR_OK; + } optional read_byte(uint8_t a_register) { uint8_t data; @@ -104,8 +106,8 @@ class I2CDevice { bool read_byte_16(uint8_t a_register, uint16_t *data) { return read_bytes_16(a_register, data, 1); } - bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) { - return write_register(a_register, data, len) == ERROR_OK; + bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len, bool stop = true) { + return write_register(a_register, data, len, stop) == ERROR_OK; } bool write_bytes(uint8_t a_register, const std::vector &data) { @@ -118,7 +120,9 @@ class I2CDevice { bool write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len); - bool write_byte(uint8_t a_register, uint8_t data) { return write_bytes(a_register, &data, 1); } + bool write_byte(uint8_t a_register, uint8_t data, bool stop = true) { + return write_bytes(a_register, &data, 1, stop); + } bool write_byte_16(uint8_t a_register, uint16_t data) { return write_bytes_16(a_register, &data, 1); } diff --git a/esphome/components/i2c/i2c_bus.h b/esphome/components/i2c/i2c_bus.h index 71f6b1d15b..c07a2dd1dd 100644 --- a/esphome/components/i2c/i2c_bus.h +++ b/esphome/components/i2c/i2c_bus.h @@ -36,12 +36,18 @@ class I2CBus { } virtual ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) = 0; virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len) { + return write(address, buffer, len, true); + } + virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len, bool stop) { WriteBuffer buf; buf.data = buffer; buf.len = len; - return writev(address, &buf, 1); + return writev(address, &buf, 1, stop); } - virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) = 0; + virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { + return writev(address, buffers, cnt, true); + } + virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) = 0; protected: void i2c_scan_() { diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 693b869bf7..cfdf818112 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -104,7 +104,7 @@ ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) return ERROR_OK; } -ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { +ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) { // logging is only enabled with vv level, if warnings are shown the caller // should log them if (!initialized_) { @@ -139,7 +139,7 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn return ERROR_UNKNOWN; } } - uint8_t status = wire_->endTransmission(true); + uint8_t status = wire_->endTransmission(stop); if (status == 0) { return ERROR_OK; } else if (status == 1) { diff --git a/esphome/components/i2c/i2c_bus_arduino.h b/esphome/components/i2c/i2c_bus_arduino.h index f4151e4f37..7298c3a1c9 100644 --- a/esphome/components/i2c/i2c_bus_arduino.h +++ b/esphome/components/i2c/i2c_bus_arduino.h @@ -20,7 +20,7 @@ class ArduinoI2CBus : public I2CBus, public Component { void setup() override; void dump_config() override; ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override; - ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) override; + ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) override; float get_setup_priority() const override { return setup_priority::BUS; } void set_scan(bool scan) { scan_ = scan; } diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 606583fd7c..160b1b96d8 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -142,7 +142,7 @@ ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { return ERROR_OK; } -ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { +ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) { // logging is only enabled with vv level, if warnings are shown the caller // should log them if (!initialized_) { diff --git a/esphome/components/i2c/i2c_bus_esp_idf.h b/esphome/components/i2c/i2c_bus_esp_idf.h index d4b0626467..c80ea8c99d 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.h +++ b/esphome/components/i2c/i2c_bus_esp_idf.h @@ -20,7 +20,7 @@ class IDFI2CBus : public I2CBus, public Component { void setup() override; void dump_config() override; ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override; - ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) override; + ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) override; float get_setup_priority() const override { return setup_priority::BUS; } void set_scan(bool scan) { scan_ = scan; } diff --git a/esphome/components/tca9548a/tca9548a.cpp b/esphome/components/tca9548a/tca9548a.cpp index f3f8685287..de0d21b968 100644 --- a/esphome/components/tca9548a/tca9548a.cpp +++ b/esphome/components/tca9548a/tca9548a.cpp @@ -12,11 +12,11 @@ i2c::ErrorCode TCA9548AChannel::readv(uint8_t address, i2c::ReadBuffer *buffers, return err; return parent_->bus_->readv(address, buffers, cnt); } -i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt) { +i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt, bool stop) { auto err = parent_->switch_to_channel(channel_); if (err != i2c::ERROR_OK) return err; - return parent_->bus_->writev(address, buffers, cnt); + return parent_->bus_->writev(address, buffers, cnt, stop); } void TCA9548AComponent::setup() { diff --git a/esphome/components/tca9548a/tca9548a.h b/esphome/components/tca9548a/tca9548a.h index 39d07c2eb4..02553f8cd0 100644 --- a/esphome/components/tca9548a/tca9548a.h +++ b/esphome/components/tca9548a/tca9548a.h @@ -13,7 +13,7 @@ class TCA9548AChannel : public i2c::I2CBus { void set_parent(TCA9548AComponent *parent) { parent_ = parent; } i2c::ErrorCode readv(uint8_t address, i2c::ReadBuffer *buffers, size_t cnt) override; - i2c::ErrorCode writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt) override; + i2c::ErrorCode writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt, bool stop) override; protected: uint8_t channel_; From 3297267a16c96fea48abff308a4d5421d727b4de Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Sun, 10 Apr 2022 13:42:31 -0700 Subject: [PATCH 207/238] Fix SHTC3 sensor detection (#3365) Co-authored-by: Samuel Sieb --- esphome/components/shtcx/shtcx.cpp | 33 +++++++++++++++++++----------- esphome/components/shtcx/shtcx.h | 1 + 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/esphome/components/shtcx/shtcx.cpp b/esphome/components/shtcx/shtcx.cpp index 867c26df1d..4112270c02 100644 --- a/esphome/components/shtcx/shtcx.cpp +++ b/esphome/components/shtcx/shtcx.cpp @@ -35,15 +35,17 @@ void SHTCXComponent::setup() { return; } - uint16_t device_id_register[1]; - if (!this->read_data_(device_id_register, 1)) { + uint16_t device_id_register; + if (!this->read_data_(&device_id_register, 1)) { ESP_LOGE(TAG, "Error reading Device ID"); this->mark_failed(); return; } - if (((device_id_register[0] << 2) & 0x1C) == 0x1C) { - if ((device_id_register[0] & 0x847) == 0x847) { + this->sensor_id_ = device_id_register; + + if ((device_id_register & 0x3F) == 0x07) { + if (device_id_register & 0x800) { this->type_ = SHTCX_TYPE_SHTC3; } else { this->type_ = SHTCX_TYPE_SHTC1; @@ -51,11 +53,11 @@ void SHTCXComponent::setup() { } else { this->type_ = SHTCX_TYPE_UNKNOWN; } - ESP_LOGCONFIG(TAG, " Device identified: %s", to_string(this->type_)); + ESP_LOGCONFIG(TAG, " Device identified: %s (%04x)", to_string(this->type_), device_id_register); } void SHTCXComponent::dump_config() { ESP_LOGCONFIG(TAG, "SHTCx:"); - ESP_LOGCONFIG(TAG, " Model: %s", to_string(this->type_)); + ESP_LOGCONFIG(TAG, " Model: %s (%04x)", to_string(this->type_), this->sensor_id_); LOG_I2C_DEVICE(this); if (this->is_failed()) { ESP_LOGE(TAG, "Communication with SHTCx failed!"); @@ -75,21 +77,28 @@ void SHTCXComponent::update() { this->wake_up(); } if (!this->write_command_(SHTCX_COMMAND_POLLING_H)) { + ESP_LOGE(TAG, "sensor polling failed"); + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(NAN); + if (this->humidity_sensor_ != nullptr) + this->humidity_sensor_->publish_state(NAN); this->status_set_warning(); return; } this->set_timeout(50, [this]() { + float temperature = NAN; + float humidity = NAN; uint16_t raw_data[2]; if (!this->read_data_(raw_data, 2)) { + ESP_LOGE(TAG, "sensor read failed"); this->status_set_warning(); - return; + } else { + temperature = 175.0f * float(raw_data[0]) / 65536.0f - 45.0f; + humidity = 100.0f * float(raw_data[1]) / 65536.0f; + + ESP_LOGD(TAG, "Got temperature=%.2f°C humidity=%.2f%%", temperature, humidity); } - - float temperature = 175.0f * float(raw_data[0]) / 65536.0f - 45.0f; - float humidity = 100.0f * float(raw_data[1]) / 65536.0f; - - ESP_LOGD(TAG, "Got temperature=%.2f°C humidity=%.2f%%", temperature, humidity); if (this->temperature_sensor_ != nullptr) this->temperature_sensor_->publish_state(temperature); if (this->humidity_sensor_ != nullptr) diff --git a/esphome/components/shtcx/shtcx.h b/esphome/components/shtcx/shtcx.h index ccc6533bfa..cb2b46d348 100644 --- a/esphome/components/shtcx/shtcx.h +++ b/esphome/components/shtcx/shtcx.h @@ -27,6 +27,7 @@ class SHTCXComponent : public PollingComponent, public i2c::I2CDevice { bool write_command_(uint16_t command); bool read_data_(uint16_t *data, uint8_t len); SHTCXType type_; + uint16_t sensor_id_; sensor::Sensor *temperature_sensor_; sensor::Sensor *humidity_sensor_; }; From 2b91c23bf3c3fcd6ec86ff86fe4a3c0ac4909bc0 Mon Sep 17 00:00:00 2001 From: RadekHvizdos <10856567+RadekHvizdos@users.noreply.github.com> Date: Sun, 10 Apr 2022 22:44:11 +0200 Subject: [PATCH 208/238] Extend mcp3204 to support 8 channels (mcp3208 variant) (#3332) --- esphome/components/mcp3204/mcp3204.cpp | 2 +- esphome/components/mcp3204/sensor/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/mcp3204/mcp3204.cpp b/esphome/components/mcp3204/mcp3204.cpp index 44044349a3..283df4ccdc 100644 --- a/esphome/components/mcp3204/mcp3204.cpp +++ b/esphome/components/mcp3204/mcp3204.cpp @@ -20,7 +20,7 @@ void MCP3204::dump_config() { } float MCP3204::read_data(uint8_t pin) { - uint8_t adc_primary_config = 0b00000110 & 0b00000111; + uint8_t adc_primary_config = 0b00000110 | (pin >> 2); uint8_t adc_secondary_config = pin << 6; this->enable(); this->transfer_byte(adc_primary_config); diff --git a/esphome/components/mcp3204/sensor/__init__.py b/esphome/components/mcp3204/sensor/__init__.py index 1d8701a91e..404880d405 100644 --- a/esphome/components/mcp3204/sensor/__init__.py +++ b/esphome/components/mcp3204/sensor/__init__.py @@ -17,7 +17,7 @@ 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), + cv.Required(CONF_NUMBER): cv.int_range(min=0, max=7), } ).extend(cv.polling_component_schema("60s")) From 84666b54b96e0af8841fc43d9ed3c4b1d8cfaf85 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 09:22:54 +1200 Subject: [PATCH 209/238] Bump pyupgrade from 2.31.1 to 2.32.0 (#3366) 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 bc5316ca71..ece84cd53d 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,7 +1,7 @@ pylint==2.13.4 flake8==4.0.1 black==22.3.0 -pyupgrade==2.31.1 +pyupgrade==2.32.0 pre-commit # Unit tests From c2cacb3478f4cff7fb41b7404d3949f7268ef320 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 09:23:13 +1200 Subject: [PATCH 210/238] Bump voluptuous from 0.13.0 to 0.13.1 (#3364) 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 4386099e2b..465d961cb6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -voluptuous==0.13.0 +voluptuous==0.13.1 PyYAML==6.0 paho-mqtt==1.6.1 colorama==0.4.4 From 7663716ae839ea8ec4a6a0be3782f6ba87b33e79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 09:23:47 +1200 Subject: [PATCH 211/238] Bump pylint from 2.13.4 to 2.13.5 (#3363) 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 ece84cd53d..083050252d 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.13.4 +pylint==2.13.5 flake8==4.0.1 black==22.3.0 pyupgrade==2.32.0 From a2d0c1bf18258f60709fae36fbae4796f499f5d6 Mon Sep 17 00:00:00 2001 From: calco88 Date: Sun, 10 Apr 2022 15:14:53 -0700 Subject: [PATCH 212/238] Fix HM3301 AQI int8 overflow (#3361) --- esphome/components/hm3301/abstract_aqi_calculator.h | 2 +- esphome/components/hm3301/aqi_calculator.h | 2 +- esphome/components/hm3301/caqi_calculator.h | 2 +- esphome/components/hm3301/hm3301.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/hm3301/abstract_aqi_calculator.h b/esphome/components/hm3301/abstract_aqi_calculator.h index 42d900a262..038828e9de 100644 --- a/esphome/components/hm3301/abstract_aqi_calculator.h +++ b/esphome/components/hm3301/abstract_aqi_calculator.h @@ -7,7 +7,7 @@ namespace hm3301 { class AbstractAQICalculator { public: - virtual uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0; + virtual uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0; }; } // namespace hm3301 diff --git a/esphome/components/hm3301/aqi_calculator.h b/esphome/components/hm3301/aqi_calculator.h index 08d1dc2921..6c830f9bad 100644 --- a/esphome/components/hm3301/aqi_calculator.h +++ b/esphome/components/hm3301/aqi_calculator.h @@ -7,7 +7,7 @@ namespace hm3301 { class AQICalculator : public AbstractAQICalculator { public: - uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { + uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_); int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_); diff --git a/esphome/components/hm3301/caqi_calculator.h b/esphome/components/hm3301/caqi_calculator.h index 1ec61f2416..3f338776d8 100644 --- a/esphome/components/hm3301/caqi_calculator.h +++ b/esphome/components/hm3301/caqi_calculator.h @@ -8,7 +8,7 @@ namespace hm3301 { class CAQICalculator : public AbstractAQICalculator { public: - uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { + uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_); int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_); diff --git a/esphome/components/hm3301/hm3301.cpp b/esphome/components/hm3301/hm3301.cpp index a2bef2a01d..379c4dbc5a 100644 --- a/esphome/components/hm3301/hm3301.cpp +++ b/esphome/components/hm3301/hm3301.cpp @@ -62,7 +62,7 @@ void HM3301Component::update() { pm_10_0_value = get_sensor_value_(data_buffer_, PM_10_0_VALUE_INDEX); } - int8_t aqi_value = -1; + int16_t aqi_value = -1; if (this->aqi_sensor_ != nullptr && pm_2_5_value != -1 && pm_10_0_value != -1) { AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_); aqi_value = calculator->get_aqi(pm_2_5_value, pm_10_0_value); From 9e3e34acf5b01515e43d1ecf289576f97df77222 Mon Sep 17 00:00:00 2001 From: rrooggiieerr Date: Mon, 11 Apr 2022 00:55:45 +0200 Subject: [PATCH 213/238] Add cover toggle support to endstop cover (#3358) --- esphome/components/endstop/endstop_cover.cpp | 17 +++++++++++++++++ esphome/components/endstop/endstop_cover.h | 1 + tests/test3.yaml | 2 ++ 3 files changed, 20 insertions(+) diff --git a/esphome/components/endstop/endstop_cover.cpp b/esphome/components/endstop/endstop_cover.cpp index 67c6a4ebd3..f468d13492 100644 --- a/esphome/components/endstop/endstop_cover.cpp +++ b/esphome/components/endstop/endstop_cover.cpp @@ -12,6 +12,7 @@ using namespace esphome::cover; CoverTraits EndstopCover::get_traits() { auto traits = CoverTraits(); traits.set_supports_position(true); + traits.set_supports_toggle(true); traits.set_is_assumed_state(false); return traits; } @@ -20,6 +21,20 @@ void EndstopCover::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) { @@ -125,9 +140,11 @@ void EndstopCover::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/endstop/endstop_cover.h b/esphome/components/endstop/endstop_cover.h index f8d2746234..6ae15de8c1 100644 --- a/esphome/components/endstop/endstop_cover.h +++ b/esphome/components/endstop/endstop_cover.h @@ -51,6 +51,7 @@ class EndstopCover : public cover::Cover, public Component { uint32_t start_dir_time_{0}; uint32_t last_publish_time_{0}; float target_position_{0}; + cover::CoverOperation last_operation_{cover::COVER_OPERATION_OPENING}; }; } // namespace endstop diff --git a/tests/test3.yaml b/tests/test3.yaml index 853d7bd389..f0975f9918 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -795,6 +795,7 @@ binary_sensor: on_press: then: - cover.toggle: time_based_cover + - cover.toggle: endstop_cover - platform: template id: 'pzemac_reset_energy' on_press: @@ -1060,6 +1061,7 @@ climate: cover: - platform: endstop name: Endstop Cover + id: endstop_cover stop_action: - switch.turn_on: gpio_switch1 open_endstop: my_binary_sensor From efa6fd03e5694d2856c877a02d9e1b714cbccf64 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 11 Apr 2022 12:45:15 +1200 Subject: [PATCH 214/238] Make home_assistant imported sensors internal by default (#3372) --- esphome/components/homeassistant/__init__.py | 16 ++++++++++++ .../homeassistant/binary_sensor/__init__.py | 26 +++++++------------ .../homeassistant/sensor/__init__.py | 26 +++++++------------ .../homeassistant/text_sensor/__init__.py | 21 ++++++--------- 4 files changed, 43 insertions(+), 46 deletions(-) diff --git a/esphome/components/homeassistant/__init__.py b/esphome/components/homeassistant/__init__.py index c151abc250..776aa7fd7b 100644 --- a/esphome/components/homeassistant/__init__.py +++ b/esphome/components/homeassistant/__init__.py @@ -1,4 +1,20 @@ import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_INTERNAL CODEOWNERS = ["@OttoWinter"] homeassistant_ns = cg.esphome_ns.namespace("homeassistant") + +HOME_ASSISTANT_IMPORT_SCHEMA = cv.Schema( + { + cv.Required(CONF_ENTITY_ID): cv.entity_id, + cv.Optional(CONF_ATTRIBUTE): cv.string, + cv.Optional(CONF_INTERNAL, default=True): cv.boolean, + } +) + + +def setup_home_assistant_entity(var, config): + cg.add(var.set_entity_id(config[CONF_ENTITY_ID])) + if CONF_ATTRIBUTE in config: + cg.add(var.set_attribute(config[CONF_ATTRIBUTE])) diff --git a/esphome/components/homeassistant/binary_sensor/__init__.py b/esphome/components/homeassistant/binary_sensor/__init__.py index a4f854c16e..a943368dd7 100644 --- a/esphome/components/homeassistant/binary_sensor/__init__.py +++ b/esphome/components/homeassistant/binary_sensor/__init__.py @@ -1,30 +1,24 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID -from .. import homeassistant_ns + +from .. import ( + HOME_ASSISTANT_IMPORT_SCHEMA, + homeassistant_ns, + setup_home_assistant_entity, +) DEPENDENCIES = ["api"] + HomeassistantBinarySensor = homeassistant_ns.class_( "HomeassistantBinarySensor", binary_sensor.BinarySensor, cg.Component ) -CONFIG_SCHEMA = ( - binary_sensor.binary_sensor_schema(HomeassistantBinarySensor) - .extend( - { - cv.Required(CONF_ENTITY_ID): cv.entity_id, - cv.Optional(CONF_ATTRIBUTE): cv.string, - } - ) - .extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(HomeassistantBinarySensor).extend( + HOME_ASSISTANT_IMPORT_SCHEMA ) async def to_code(config): var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) - - cg.add(var.set_entity_id(config[CONF_ENTITY_ID])) - if CONF_ATTRIBUTE in config: - cg.add(var.set_attribute(config[CONF_ATTRIBUTE])) + setup_home_assistant_entity(var, config) diff --git a/esphome/components/homeassistant/sensor/__init__.py b/esphome/components/homeassistant/sensor/__init__.py index 28fee9f7f6..6437476827 100644 --- a/esphome/components/homeassistant/sensor/__init__.py +++ b/esphome/components/homeassistant/sensor/__init__.py @@ -1,12 +1,11 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import sensor -from esphome.const import ( - CONF_ATTRIBUTE, - CONF_ENTITY_ID, - CONF_ID, + +from .. import ( + HOME_ASSISTANT_IMPORT_SCHEMA, + homeassistant_ns, + setup_home_assistant_entity, ) -from .. import homeassistant_ns DEPENDENCIES = ["api"] @@ -14,19 +13,12 @@ HomeassistantSensor = homeassistant_ns.class_( "HomeassistantSensor", sensor.Sensor, cg.Component ) -CONFIG_SCHEMA = sensor.sensor_schema(HomeassistantSensor, accuracy_decimals=1,).extend( - { - cv.Required(CONF_ENTITY_ID): cv.entity_id, - cv.Optional(CONF_ATTRIBUTE): cv.string, - } +CONFIG_SCHEMA = sensor.sensor_schema(HomeassistantSensor, accuracy_decimals=1).extend( + HOME_ASSISTANT_IMPORT_SCHEMA ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) - - cg.add(var.set_entity_id(config[CONF_ENTITY_ID])) - if CONF_ATTRIBUTE in config: - cg.add(var.set_attribute(config[CONF_ATTRIBUTE])) + setup_home_assistant_entity(var, config) diff --git a/esphome/components/homeassistant/text_sensor/__init__.py b/esphome/components/homeassistant/text_sensor/__init__.py index be59bab676..b59f9d23df 100644 --- a/esphome/components/homeassistant/text_sensor/__init__.py +++ b/esphome/components/homeassistant/text_sensor/__init__.py @@ -1,9 +1,11 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import text_sensor -from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID -from .. import homeassistant_ns +from .. import ( + HOME_ASSISTANT_IMPORT_SCHEMA, + homeassistant_ns, + setup_home_assistant_entity, +) DEPENDENCIES = ["api"] @@ -11,19 +13,12 @@ HomeassistantTextSensor = homeassistant_ns.class_( "HomeassistantTextSensor", text_sensor.TextSensor, cg.Component ) -CONFIG_SCHEMA = text_sensor.text_sensor_schema().extend( - { - cv.GenerateID(): cv.declare_id(HomeassistantTextSensor), - cv.Required(CONF_ENTITY_ID): cv.entity_id, - cv.Optional(CONF_ATTRIBUTE): cv.string, - } +CONFIG_SCHEMA = text_sensor.text_sensor_schema(HomeassistantTextSensor).extend( + HOME_ASSISTANT_IMPORT_SCHEMA ) async def to_code(config): var = await text_sensor.new_text_sensor(config) await cg.register_component(var, config) - - cg.add(var.set_entity_id(config[CONF_ENTITY_ID])) - if CONF_ATTRIBUTE in config: - cg.add(var.set_attribute(config[CONF_ATTRIBUTE])) + setup_home_assistant_entity(var, config) From fdda47db6e3103a44b9a20028e67b7e7a4934a36 Mon Sep 17 00:00:00 2001 From: functionpointer Date: Mon, 11 Apr 2022 04:50:56 +0200 Subject: [PATCH 215/238] Add integration hydreon_rgxx for rain sensors by Hydreon (#2711) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/hydreon_rgxx/__init__.py | 11 + .../components/hydreon_rgxx/binary_sensor.py | 36 +++ .../components/hydreon_rgxx/hydreon_rgxx.cpp | 211 ++++++++++++++++++ .../components/hydreon_rgxx/hydreon_rgxx.h | 76 +++++++ esphome/components/hydreon_rgxx/sensor.py | 119 ++++++++++ tests/test3.yaml | 22 ++ 7 files changed, 476 insertions(+) create mode 100644 esphome/components/hydreon_rgxx/__init__.py create mode 100644 esphome/components/hydreon_rgxx/binary_sensor.py create mode 100644 esphome/components/hydreon_rgxx/hydreon_rgxx.cpp create mode 100644 esphome/components/hydreon_rgxx/hydreon_rgxx.h create mode 100644 esphome/components/hydreon_rgxx/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 8958aa2928..309f6f4d51 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -82,6 +82,7 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/homeassistant/* @OttoWinter esphome/components/honeywellabp/* @RubyBailey esphome/components/hrxl_maxsonar_wr/* @netmikey +esphome/components/hydreon_rgxx/* @functionpointer esphome/components/i2c/* @esphome/core esphome/components/improv_serial/* @esphome/core esphome/components/ina260/* @MrEditor97 diff --git a/esphome/components/hydreon_rgxx/__init__.py b/esphome/components/hydreon_rgxx/__init__.py new file mode 100644 index 0000000000..5fe050edf2 --- /dev/null +++ b/esphome/components/hydreon_rgxx/__init__.py @@ -0,0 +1,11 @@ +import esphome.codegen as cg +from esphome.components import uart + +CODEOWNERS = ["@functionpointer"] +DEPENDENCIES = ["uart"] + +hydreon_rgxx_ns = cg.esphome_ns.namespace("hydreon_rgxx") +RGModel = hydreon_rgxx_ns.enum("RGModel") +HydreonRGxxComponent = hydreon_rgxx_ns.class_( + "HydreonRGxxComponent", cg.PollingComponent, uart.UARTDevice +) diff --git a/esphome/components/hydreon_rgxx/binary_sensor.py b/esphome/components/hydreon_rgxx/binary_sensor.py new file mode 100644 index 0000000000..0d489ebcb7 --- /dev/null +++ b/esphome/components/hydreon_rgxx/binary_sensor.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_COLD, +) + +from . import hydreon_rgxx_ns, HydreonRGxxComponent + +CONF_HYDREON_RGXX_ID = "hydreon_rgxx_id" +CONF_TOO_COLD = "too_cold" + +HydreonRGxxBinarySensor = hydreon_rgxx_ns.class_( + "HydreonRGxxBinaryComponent", cg.Component +) + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(HydreonRGxxBinarySensor), + cv.GenerateID(CONF_HYDREON_RGXX_ID): cv.use_id(HydreonRGxxComponent), + cv.Optional(CONF_TOO_COLD): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_COLD + ), + } +) + + +async def to_code(config): + main_sensor = await cg.get_variable(config[CONF_HYDREON_RGXX_ID]) + bin_component = cg.new_Pvariable(config[CONF_ID], main_sensor) + await cg.register_component(bin_component, config) + if CONF_TOO_COLD in config: + tc = await binary_sensor.new_binary_sensor(config[CONF_TOO_COLD]) + cg.add(main_sensor.set_too_cold_sensor(tc)) diff --git a/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp b/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp new file mode 100644 index 0000000000..3ed65831ae --- /dev/null +++ b/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp @@ -0,0 +1,211 @@ +#include "hydreon_rgxx.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace hydreon_rgxx { + +static const char *const TAG = "hydreon_rgxx.sensor"; +static const int MAX_DATA_LENGTH_BYTES = 80; +static const uint8_t ASCII_LF = 0x0A; +#define HYDREON_RGXX_COMMA , +static const char *const PROTOCOL_NAMES[] = {HYDREON_RGXX_PROTOCOL_LIST(, HYDREON_RGXX_COMMA)}; + +void HydreonRGxxComponent::dump_config() { + this->check_uart_settings(9600, 1, esphome::uart::UART_CONFIG_PARITY_NONE, 8); + ESP_LOGCONFIG(TAG, "hydreon_rgxx:"); + if (this->is_failed()) { + ESP_LOGE(TAG, "Connection with hydreon_rgxx failed!"); + } + LOG_UPDATE_INTERVAL(this); + + int i = 0; +#define HYDREON_RGXX_LOG_SENSOR(s) \ + if (this->sensors_[i++] != nullptr) { \ + LOG_SENSOR(" ", #s, this->sensors_[i - 1]); \ + } + HYDREON_RGXX_PROTOCOL_LIST(HYDREON_RGXX_LOG_SENSOR, ); +} + +void HydreonRGxxComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up hydreon_rgxx..."); + while (this->available() != 0) { + this->read(); + } + this->schedule_reboot_(); +} + +bool HydreonRGxxComponent::sensor_missing_() { + if (this->sensors_received_ == -1) { + // no request sent yet, don't check + return false; + } else { + if (this->sensors_received_ == 0) { + ESP_LOGW(TAG, "No data at all"); + return true; + } + for (int i = 0; i < NUM_SENSORS; i++) { + if (this->sensors_[i] == nullptr) { + continue; + } + if ((this->sensors_received_ >> i & 1) == 0) { + ESP_LOGW(TAG, "Missing %s", PROTOCOL_NAMES[i]); + return true; + } + } + return false; + } +} + +void HydreonRGxxComponent::update() { + if (this->boot_count_ > 0) { + if (this->sensor_missing_()) { + this->no_response_count_++; + ESP_LOGE(TAG, "data missing %d times", this->no_response_count_); + if (this->no_response_count_ > 15) { + ESP_LOGE(TAG, "asking sensor to reboot"); + for (auto &sensor : this->sensors_) { + if (sensor != nullptr) { + sensor->publish_state(NAN); + } + } + this->schedule_reboot_(); + return; + } + } else { + this->no_response_count_ = 0; + } + this->write_str("R\n"); +#ifdef USE_BINARY_SENSOR + if (this->too_cold_sensor_ != nullptr) { + this->too_cold_sensor_->publish_state(this->too_cold_); + } +#endif + this->too_cold_ = false; + this->sensors_received_ = 0; + } +} + +void HydreonRGxxComponent::loop() { + uint8_t data; + while (this->available() > 0) { + if (this->read_byte(&data)) { + buffer_ += (char) data; + if (this->buffer_.back() == static_cast(ASCII_LF) || this->buffer_.length() >= MAX_DATA_LENGTH_BYTES) { + // complete line received + this->process_line_(); + this->buffer_.clear(); + } + } + } +} + +/** + * Communication with the sensor is asynchronous. + * We send requests and let esphome continue doing its thing. + * Once we have received a complete line, we process it. + * + * Catching communication failures is done in two layers: + * + * 1. We check if all requested data has been received + * before we send out the next request. If data keeps + * missing, we escalate. + * 2. Request the sensor to reboot. We retry based on + * a timeout. If the sensor does not respond after + * several boot attempts, we give up. + */ +void HydreonRGxxComponent::schedule_reboot_() { + this->boot_count_ = 0; + this->set_interval("reboot", 5000, [this]() { + if (this->boot_count_ < 0) { + ESP_LOGW(TAG, "hydreon_rgxx failed to boot %d times", -this->boot_count_); + } + this->boot_count_--; + this->write_str("K\n"); + if (this->boot_count_ < -5) { + ESP_LOGE(TAG, "hydreon_rgxx can't boot, giving up"); + for (auto &sensor : this->sensors_) { + if (sensor != nullptr) { + sensor->publish_state(NAN); + } + } + this->mark_failed(); + } + }); +} + +bool HydreonRGxxComponent::buffer_starts_with_(const std::string &prefix) { + return this->buffer_starts_with_(prefix.c_str()); +} + +bool HydreonRGxxComponent::buffer_starts_with_(const char *prefix) { return buffer_.rfind(prefix, 0) == 0; } + +void HydreonRGxxComponent::process_line_() { + ESP_LOGV(TAG, "Read from serial: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); + + if (buffer_[0] == ';') { + ESP_LOGI(TAG, "Comment: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); + return; + } + if (this->buffer_starts_with_("PwrDays")) { + if (this->boot_count_ <= 0) { + this->boot_count_ = 1; + } else { + this->boot_count_++; + } + this->cancel_interval("reboot"); + this->no_response_count_ = 0; + ESP_LOGI(TAG, "Boot detected: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); + this->write_str("P\nH\nM\n"); // set sensor to polling mode, high res mode, metric mode + return; + } + if (this->buffer_starts_with_("SW")) { + std::string::size_type majend = this->buffer_.find('.'); + std::string::size_type endversion = this->buffer_.find(' ', 3); + if (majend == std::string::npos || endversion == std::string::npos || majend > endversion) { + ESP_LOGW(TAG, "invalid version string: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); + } + int major = strtol(this->buffer_.substr(3, majend - 3).c_str(), nullptr, 10); + int minor = strtol(this->buffer_.substr(majend + 1, endversion - (majend + 1)).c_str(), nullptr, 10); + + if (major > 10 || minor >= 1000 || minor < 0 || major < 0) { + ESP_LOGW(TAG, "invalid version: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); + } + this->sw_version_ = major * 1000 + minor; + ESP_LOGI(TAG, "detected sw version %i", this->sw_version_); + return; + } + bool is_data_line = false; + for (int i = 0; i < NUM_SENSORS; i++) { + if (this->sensors_[i] != nullptr && this->buffer_starts_with_(PROTOCOL_NAMES[i])) { + is_data_line = true; + break; + } + } + if (is_data_line) { + std::string::size_type tc = this->buffer_.find("TooCold"); + this->too_cold_ |= tc != std::string::npos; + if (this->too_cold_) { + ESP_LOGD(TAG, "Received TooCold"); + } + for (int i = 0; i < NUM_SENSORS; i++) { + if (this->sensors_[i] == nullptr) { + continue; + } + std::string::size_type n = this->buffer_.find(PROTOCOL_NAMES[i]); + if (n == std::string::npos) { + continue; + } + int data = strtol(this->buffer_.substr(n + strlen(PROTOCOL_NAMES[i])).c_str(), nullptr, 10); + this->sensors_[i]->publish_state(data); + ESP_LOGD(TAG, "Received %s: %f", PROTOCOL_NAMES[i], this->sensors_[i]->get_raw_state()); + this->sensors_received_ |= (1 << i); + } + } else { + ESP_LOGI(TAG, "Got unknown line: %s", this->buffer_.c_str()); + } +} + +float HydreonRGxxComponent::get_setup_priority() const { return setup_priority::DATA; } + +} // namespace hydreon_rgxx +} // namespace esphome diff --git a/esphome/components/hydreon_rgxx/hydreon_rgxx.h b/esphome/components/hydreon_rgxx/hydreon_rgxx.h new file mode 100644 index 0000000000..ebe4a35b19 --- /dev/null +++ b/esphome/components/hydreon_rgxx/hydreon_rgxx.h @@ -0,0 +1,76 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/components/sensor/sensor.h" +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace hydreon_rgxx { + +enum RGModel { + RG9 = 1, + RG15 = 2, +}; + +#ifdef HYDREON_RGXX_NUM_SENSORS +static const uint8_t NUM_SENSORS = HYDREON_RGXX_NUM_SENSORS; +#else +static const uint8_t NUM_SENSORS = 1; +#endif + +#ifndef HYDREON_RGXX_PROTOCOL_LIST +#define HYDREON_RGXX_PROTOCOL_LIST(F, SEP) F("") +#endif + +class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { + public: + void set_sensor(sensor::Sensor *sensor, int index) { this->sensors_[index] = sensor; } +#ifdef USE_BINARY_SENSOR + void set_too_cold_sensor(binary_sensor::BinarySensor *sensor) { this->too_cold_sensor_ = sensor; } +#endif + void set_model(RGModel model) { model_ = model; } + + /// Schedule data readings. + void update() override; + /// Read data once available + void loop() override; + /// Setup the sensor and test for a connection. + void setup() override; + void dump_config() override; + + float get_setup_priority() const override; + + protected: + void process_line_(); + void schedule_reboot_(); + bool buffer_starts_with_(const std::string &prefix); + bool buffer_starts_with_(const char *prefix); + bool sensor_missing_(); + + sensor::Sensor *sensors_[NUM_SENSORS] = {nullptr}; +#ifdef USE_BINARY_SENSOR + binary_sensor::BinarySensor *too_cold_sensor_ = nullptr; +#endif + + int16_t boot_count_ = 0; + int16_t no_response_count_ = 0; + std::string buffer_; + RGModel model_ = RG9; + int sw_version_ = 0; + bool too_cold_ = false; + + // bit field showing which sensors we have received data for + int sensors_received_ = -1; +}; + +class HydreonRGxxBinaryComponent : public Component { + public: + HydreonRGxxBinaryComponent(HydreonRGxxComponent *parent) {} +}; + +} // namespace hydreon_rgxx +} // namespace esphome diff --git a/esphome/components/hydreon_rgxx/sensor.py b/esphome/components/hydreon_rgxx/sensor.py new file mode 100644 index 0000000000..409500305a --- /dev/null +++ b/esphome/components/hydreon_rgxx/sensor.py @@ -0,0 +1,119 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart, sensor +from esphome.const import ( + CONF_ID, + CONF_MODEL, + CONF_MOISTURE, + DEVICE_CLASS_HUMIDITY, + STATE_CLASS_MEASUREMENT, +) + +from . import RGModel, HydreonRGxxComponent + +UNIT_INTENSITY = "intensity" +UNIT_MILLIMETERS = "mm" +UNIT_MILLIMETERS_PER_HOUR = "mm/h" + +CONF_ACC = "acc" +CONF_EVENT_ACC = "event_acc" +CONF_TOTAL_ACC = "total_acc" +CONF_R_INT = "r_int" + +RG_MODELS = { + "RG_9": RGModel.RG9, + "RG_15": RGModel.RG15, + # https://rainsensors.com/wp-content/uploads/sites/3/2020/07/rg-15_instructions_sw_1.000.pdf + # https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2020.08.25-rg-9_instructions.pdf + # https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2021.03.11-rg-9_instructions.pdf +} +SUPPORTED_SENSORS = { + CONF_ACC: ["RG_15"], + CONF_EVENT_ACC: ["RG_15"], + CONF_TOTAL_ACC: ["RG_15"], + CONF_R_INT: ["RG_15"], + CONF_MOISTURE: ["RG_9"], +} +PROTOCOL_NAMES = { + CONF_MOISTURE: "R", + CONF_ACC: "Acc", + CONF_R_INT: "Rint", + CONF_EVENT_ACC: "EventAcc", + CONF_TOTAL_ACC: "TotalAcc", +} + + +def _validate(config): + for conf, models in SUPPORTED_SENSORS.items(): + if conf in config: + if config[CONF_MODEL] not in models: + raise cv.Invalid( + f"{conf} is only available on {' and '.join(models)}, not {config[CONF_MODEL]}" + ) + return config + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HydreonRGxxComponent), + cv.Required(CONF_MODEL): cv.enum( + RG_MODELS, + upper=True, + space="_", + ), + cv.Optional(CONF_ACC): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETERS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_EVENT_ACC): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETERS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TOTAL_ACC): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETERS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_R_INT): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETERS_PER_HOUR, + accuracy_decimals=2, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_MOISTURE): sensor.sensor_schema( + unit_of_measurement=UNIT_INTENSITY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(uart.UART_DEVICE_SCHEMA), + _validate, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + + cg.add_define( + "HYDREON_RGXX_PROTOCOL_LIST(F, sep)", + cg.RawExpression( + " sep ".join([f'F("{name}")' for name in PROTOCOL_NAMES.values()]) + ), + ) + cg.add_define("HYDREON_RGXX_NUM_SENSORS", len(PROTOCOL_NAMES)) + + for i, conf in enumerate(PROTOCOL_NAMES): + if conf in config: + sens = await sensor.new_sensor(config[conf]) + cg.add(var.set_sensor(sens, i)) diff --git a/tests/test3.yaml b/tests/test3.yaml index f0975f9918..a6ad6b9e92 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -349,6 +349,24 @@ sensor: name: 'Temperature' humidity: name: 'Humidity' + - platform: hydreon_rgxx + model: "RG 9" + uart_id: uart6 + id: "hydreon_rg9" + moisture: + name: "hydreon_rain" + id: hydreon_rain + - platform: hydreon_rgxx + model: "RG_15" + uart_id: uart6 + acc: + name: "hydreon_acc" + event_acc: + name: "hydreon_event_acc" + total_acc: + name: "hydreon_total_acc" + r_int: + name: "hydreon_r_int" - platform: adc pin: VCC id: my_sensor @@ -796,6 +814,10 @@ binary_sensor: then: - cover.toggle: time_based_cover - cover.toggle: endstop_cover + - platform: hydreon_rgxx + hydreon_rgxx_id: "hydreon_rg9" + too_cold: + name: "rg9_toocold" - platform: template id: 'pzemac_reset_energy' on_press: From dabd27d4be62e450932aa7090f8af836fe09c39c Mon Sep 17 00:00:00 2001 From: andrewpc Date: Tue, 12 Apr 2022 10:45:54 +1000 Subject: [PATCH 216/238] Addition of Deep Sleep RTC pin definition for ESP32-S2 (#3303) --- esphome/components/deep_sleep/__init__.py | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 2a74d0c1bb..24fc0cabb0 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -15,6 +15,7 @@ from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32.const import ( VARIANT_ESP32, VARIANT_ESP32C3, + VARIANT_ESP32S2, ) WAKEUP_PINS = { @@ -39,6 +40,30 @@ WAKEUP_PINS = { 39, ], VARIANT_ESP32C3: [0, 1, 2, 3, 4, 5], + VARIANT_ESP32S2: [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + ], } From da336247eb853c02a5d977bbcfb0914d8f87df68 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 12 Apr 2022 16:19:16 +1200 Subject: [PATCH 217/238] Add Xiaomi RTCGQ02LM - Mi Motion Sensor 2 (#3186) --- CODEOWNERS | 1 + esphome/components/xiaomi_ble/xiaomi_ble.cpp | 97 +++++++++++-------- esphome/components/xiaomi_ble/xiaomi_ble.h | 8 +- .../components/xiaomi_rtcgq02lm/__init__.py | 36 +++++++ .../xiaomi_rtcgq02lm/binary_sensor.py | 64 ++++++++++++ esphome/components/xiaomi_rtcgq02lm/sensor.py | 37 +++++++ .../xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp | 91 +++++++++++++++++ .../xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h | 61 ++++++++++++ esphome/core/helpers.h | 4 + tests/test2.yaml | 17 ++++ 10 files changed, 373 insertions(+), 43 deletions(-) create mode 100644 esphome/components/xiaomi_rtcgq02lm/__init__.py create mode 100644 esphome/components/xiaomi_rtcgq02lm/binary_sensor.py create mode 100644 esphome/components/xiaomi_rtcgq02lm/sensor.py create mode 100644 esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp create mode 100644 esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h diff --git a/CODEOWNERS b/CODEOWNERS index 309f6f4d51..5a1220354a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -224,4 +224,5 @@ esphome/components/whirlpool/* @glmnet esphome/components/xiaomi_lywsd03mmc/* @ahpohl esphome/components/xiaomi_mhoc303/* @drug123 esphome/components/xiaomi_mhoc401/* @vevsvevs +esphome/components/xiaomi_rtcgq02lm/* @jesserockz esphome/components/xpt2046/* @numo68 diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index bdd745b859..95d97defe2 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -1,6 +1,6 @@ #include "xiaomi_ble.h" -#include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/log.h" #ifdef USE_ESP32 @@ -12,67 +12,74 @@ namespace xiaomi_ble { static const char *const TAG = "xiaomi_ble"; -bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result) { +bool parse_xiaomi_value(uint16_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result) { + // button pressed, 3 bytes, only byte 3 is used for supported devices so far + if ((value_type == 0x1001) && (value_length == 3)) { + result.button_press = data[2] == 0; + return true; + } // motion detection, 1 byte, 8-bit unsigned integer - if ((value_type == 0x03) && (value_length == 1)) { + else if ((value_type == 0x0003) && (value_length == 1)) { result.has_motion = data[0]; } // temperature, 2 bytes, 16-bit signed integer (LE), 0.1 °C - else if ((value_type == 0x04) && (value_length == 2)) { - const int16_t temperature = uint16_t(data[0]) | (uint16_t(data[1]) << 8); + else if ((value_type == 0x1004) && (value_length == 2)) { + const int16_t temperature = encode_uint16(data[1], data[0]); result.temperature = temperature / 10.0f; } // humidity, 2 bytes, 16-bit signed integer (LE), 0.1 % - else if ((value_type == 0x06) && (value_length == 2)) { - const int16_t humidity = uint16_t(data[0]) | (uint16_t(data[1]) << 8); + else if ((value_type == 0x1006) && (value_length == 2)) { + const int16_t humidity = encode_uint16(data[1], data[0]); result.humidity = humidity / 10.0f; } // illuminance (+ motion), 3 bytes, 24-bit unsigned integer (LE), 1 lx - else if (((value_type == 0x07) || (value_type == 0x0F)) && (value_length == 3)) { - const uint32_t illuminance = uint32_t(data[0]) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]) << 16); + else if (((value_type == 0x1007) || (value_type == 0x000F)) && (value_length == 3)) { + const uint32_t illuminance = encode_uint24(data[2], data[1], data[0]); result.illuminance = illuminance; - result.is_light = illuminance == 100; + result.is_light = illuminance >= 100; if (value_type == 0x0F) result.has_motion = true; } // soil moisture, 1 byte, 8-bit unsigned integer, 1 % - else if ((value_type == 0x08) && (value_length == 1)) { + else if ((value_type == 0x1008) && (value_length == 1)) { result.moisture = data[0]; } // conductivity, 2 bytes, 16-bit unsigned integer (LE), 1 µS/cm - else if ((value_type == 0x09) && (value_length == 2)) { - const uint16_t conductivity = uint16_t(data[0]) | (uint16_t(data[1]) << 8); + else if ((value_type == 0x1009) && (value_length == 2)) { + const uint16_t conductivity = encode_uint16(data[1], data[0]); result.conductivity = conductivity; } // battery, 1 byte, 8-bit unsigned integer, 1 % - else if ((value_type == 0x0A) && (value_length == 1)) { + else if ((value_type == 0x100A) && (value_length == 1)) { result.battery_level = data[0]; } // temperature + humidity, 4 bytes, 16-bit signed integer (LE) each, 0.1 °C, 0.1 % - else if ((value_type == 0x0D) && (value_length == 4)) { - const int16_t temperature = uint16_t(data[0]) | (uint16_t(data[1]) << 8); - const int16_t humidity = uint16_t(data[2]) | (uint16_t(data[3]) << 8); + else if ((value_type == 0x100D) && (value_length == 4)) { + const int16_t temperature = encode_uint16(data[1], data[0]); + const int16_t humidity = encode_uint16(data[3], data[2]); result.temperature = temperature / 10.0f; result.humidity = humidity / 10.0f; } // formaldehyde, 2 bytes, 16-bit unsigned integer (LE), 0.01 mg / m3 - else if ((value_type == 0x10) && (value_length == 2)) { - const uint16_t formaldehyde = uint16_t(data[0]) | (uint16_t(data[1]) << 8); + else if ((value_type == 0x1010) && (value_length == 2)) { + const uint16_t formaldehyde = encode_uint16(data[1], data[0]); result.formaldehyde = formaldehyde / 100.0f; } // on/off state, 1 byte, 8-bit unsigned integer - else if ((value_type == 0x12) && (value_length == 1)) { + else if ((value_type == 0x1012) && (value_length == 1)) { result.is_active = data[0]; } // mosquito tablet, 1 byte, 8-bit unsigned integer, 1 % - else if ((value_type == 0x13) && (value_length == 1)) { + else if ((value_type == 0x1013) && (value_length == 1)) { result.tablet = data[0]; } // idle time since last motion, 4 byte, 32-bit unsigned integer, 1 min - else if ((value_type == 0x17) && (value_length == 4)) { + else if ((value_type == 0x1017) && (value_length == 4)) { const uint32_t idle_time = encode_uint32(data[3], data[2], data[1], data[0]); result.idle_time = idle_time / 60.0f; result.has_motion = !idle_time; + } else if ((value_type == 0x1018) && (value_length == 1)) { + result.is_light = data[0]; } else { return false; } @@ -115,7 +122,7 @@ bool parse_xiaomi_message(const std::vector &message, XiaomiParseResult break; } - const uint8_t value_type = payload[payload_offset + 0]; + const uint16_t value_type = encode_uint16(payload[payload_offset + 1], payload[payload_offset + 0]); const uint8_t *data = &payload[payload_offset + 3]; if (parse_xiaomi_value(value_type, data, value_length, result)) @@ -155,60 +162,67 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service result.is_duplicate = false; result.raw_offset = result.has_capability ? 12 : 11; - if ((raw[2] == 0x98) && (raw[3] == 0x00)) { // MiFlora + const uint16_t device_uuid = encode_uint16(raw[3], raw[2]); + + if (device_uuid == 0x0098) { // MiFlora result.type = XiaomiParseResult::TYPE_HHCCJCY01; result.name = "HHCCJCY01"; - } else if ((raw[2] == 0xaa) && (raw[3] == 0x01)) { // round body, segment LCD + } else if (device_uuid == 0x01aa) { // round body, segment LCD result.type = XiaomiParseResult::TYPE_LYWSDCGQ; result.name = "LYWSDCGQ"; - } else if ((raw[2] == 0x5d) && (raw[3] == 0x01)) { // FlowerPot, RoPot + } else if (device_uuid == 0x015d) { // FlowerPot, RoPot result.type = XiaomiParseResult::TYPE_HHCCPOT002; result.name = "HHCCPOT002"; - } else if ((raw[2] == 0xdf) && (raw[3] == 0x02)) { // Xiaomi (Honeywell) formaldehyde sensor, OLED display + } else if (device_uuid == 0x02df) { // Xiaomi (Honeywell) formaldehyde sensor, OLED display result.type = XiaomiParseResult::TYPE_JQJCY01YM; result.name = "JQJCY01YM"; - } else if ((raw[2] == 0xdd) && (raw[3] == 0x03)) { // Philips/Xiaomi BLE nightlight + } else if (device_uuid == 0x03dd) { // Philips/Xiaomi BLE nightlight 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 - (raw[2] == 0x48 && raw[3] == 0x0B)) { // Qingping-branded, round body, e-ink display — with bindkeys + } else if (device_uuid == 0x0347 || // ClearGrass-branded, round body, e-ink display + device_uuid == 0x0B48) { // 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 + } else if (device_uuid == 0x03bc) { // VegTrug Grow Care Garden result.type = XiaomiParseResult::TYPE_GCLS002; result.name = "GCLS002"; - } else if ((raw[2] == 0x5b) && (raw[3] == 0x04)) { // rectangular body, e-ink display + } else if (device_uuid == 0x045b) { // rectangular body, e-ink display result.type = XiaomiParseResult::TYPE_LYWSD02; result.name = "LYWSD02"; - } else if ((raw[2] == 0x0a) && (raw[3] == 0x04)) { // Mosquito Repellent Smart Version + } else if (device_uuid == 0x040a) { // Mosquito Repellent Smart Version result.type = XiaomiParseResult::TYPE_WX08ZM; result.name = "WX08ZM"; - } else if ((raw[2] == 0x76) && (raw[3] == 0x05)) { // Cleargrass (Qingping) alarm clock, segment LCD + } else if (device_uuid == 0x0576) { // Cleargrass (Qingping) alarm clock, segment LCD result.type = XiaomiParseResult::TYPE_CGD1; result.name = "CGD1"; - } else if ((raw[2] == 0x6F) && (raw[3] == 0x06)) { // Cleargrass (Qingping) Temp & RH Lite + } else if (device_uuid == 0x066F) { // Cleargrass (Qingping) Temp & RH Lite result.type = XiaomiParseResult::TYPE_CGDK2; result.name = "CGDK2"; - } else if ((raw[2] == 0x5b) && (raw[3] == 0x05)) { // small square body, segment LCD, encrypted + } else if (device_uuid == 0x055b) { // small square body, segment LCD, encrypted result.type = XiaomiParseResult::TYPE_LYWSD03MMC; result.name = "LYWSD03MMC"; - } else if ((raw[2] == 0xf6) && (raw[3] == 0x07)) { // Xiaomi-Yeelight BLE nightlight + } else if (device_uuid == 0x07f6) { // Xiaomi-Yeelight BLE nightlight result.type = XiaomiParseResult::TYPE_MJYD02YLA; result.name = "MJYD02YLA"; if (raw.size() == 19) result.raw_offset -= 6; - } else if ((raw[2] == 0xd3) && (raw[3] == 0x06)) { // rectangular body, e-ink display with alarm + } else if (device_uuid == 0x06d3) { // rectangular body, e-ink display with alarm result.type = XiaomiParseResult::TYPE_MHOC303; result.name = "MHOC303"; - } else if ((raw[2] == 0x87) && (raw[3] == 0x03)) { // square body, e-ink display + } else if (device_uuid == 0x0387) { // square body, e-ink display result.type = XiaomiParseResult::TYPE_MHOC401; result.name = "MHOC401"; - } else if ((raw[2] == 0x83) && (raw[3] == 0x0A)) { // Qingping-branded, motion & ambient light sensor + } else if (device_uuid == 0x0A83) { // Qingping-branded, motion & ambient light sensor result.type = XiaomiParseResult::TYPE_CGPR1; result.name = "CGPR1"; if (raw.size() == 19) result.raw_offset -= 6; + } else if (device_uuid == 0x0A8D) { // Xiaomi Mi Motion Sensor 2 + result.type = XiaomiParseResult::TYPE_RTCGQ02LM; + result.name = "RTCGQ02LM"; + if (raw.size() == 19) + result.raw_offset -= 6; } else { ESP_LOGVV(TAG, "parse_xiaomi_header(): unknown device, no magic bytes."); return {}; @@ -343,6 +357,9 @@ bool report_xiaomi_results(const optional &result, const std: if (result->is_light.has_value()) { ESP_LOGD(TAG, " Light: %s", (*result->is_light) ? "on" : "off"); } + if (result->button_press.has_value()) { + ESP_LOGD(TAG, " Button: %s", (*result->button_press) ? "pressed" : ""); + } return true; } diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.h b/esphome/components/xiaomi_ble/xiaomi_ble.h index ee65d7c82f..399bef83b8 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.h +++ b/esphome/components/xiaomi_ble/xiaomi_ble.h @@ -1,7 +1,7 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/core/component.h" #ifdef USE_ESP32 @@ -25,7 +25,8 @@ struct XiaomiParseResult { TYPE_MJYD02YLA, TYPE_MHOC303, TYPE_MHOC401, - TYPE_CGPR1 + TYPE_CGPR1, + TYPE_RTCGQ02LM, } type; std::string name; optional temperature; @@ -40,6 +41,7 @@ struct XiaomiParseResult { optional is_active; optional has_motion; optional is_light; + optional button_press; bool has_data; // 0x40 bool has_capability; // 0x20 bool has_encryption; // 0x08 @@ -61,7 +63,7 @@ struct XiaomiAESVector { size_t ivsize; }; -bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result); +bool parse_xiaomi_value(uint16_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result); bool parse_xiaomi_message(const std::vector &message, XiaomiParseResult &result); optional parse_xiaomi_header(const esp32_ble_tracker::ServiceData &service_data); bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, const uint64_t &address); diff --git a/esphome/components/xiaomi_rtcgq02lm/__init__.py b/esphome/components/xiaomi_rtcgq02lm/__init__.py new file mode 100644 index 0000000000..0c8331db09 --- /dev/null +++ b/esphome/components/xiaomi_rtcgq02lm/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import esp32_ble_tracker +from esphome.const import CONF_MAC_ADDRESS, CONF_ID, CONF_BINDKEY + + +AUTO_LOAD = ["xiaomi_ble"] +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["esp32_ble_tracker"] +MULTI_CONF = True + +xiaomi_rtcgq02lm_ns = cg.esphome_ns.namespace("xiaomi_rtcgq02lm") +XiaomiRTCGQ02LM = xiaomi_rtcgq02lm_ns.class_( + "XiaomiRTCGQ02LM", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiRTCGQ02LM), + cv.Required(CONF_BINDKEY): cv.bind_key, + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + } + ) + .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)) + cg.add(var.set_bindkey(config[CONF_BINDKEY])) diff --git a/esphome/components/xiaomi_rtcgq02lm/binary_sensor.py b/esphome/components/xiaomi_rtcgq02lm/binary_sensor.py new file mode 100644 index 0000000000..8eee10685e --- /dev/null +++ b/esphome/components/xiaomi_rtcgq02lm/binary_sensor.py @@ -0,0 +1,64 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + CONF_LIGHT, + CONF_MOTION, + CONF_TIMEOUT, + DEVICE_CLASS_LIGHT, + DEVICE_CLASS_MOTION, + CONF_ID, +) +from esphome.core import TimePeriod + +from . import XiaomiRTCGQ02LM + +DEPENDENCIES = ["xiaomi_rtcgq02lm"] + +CONF_BUTTON = "button" + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(XiaomiRTCGQ02LM), + cv.Optional(CONF_MOTION): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_MOTION + ).extend( + { + cv.Optional(CONF_TIMEOUT, default="5s"): cv.All( + cv.positive_time_period_milliseconds, + cv.Range(max=TimePeriod(milliseconds=65535)), + ), + } + ), + cv.Optional(CONF_LIGHT): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_LIGHT + ), + cv.Optional(CONF_BUTTON): binary_sensor.binary_sensor_schema().extend( + { + cv.Optional(CONF_TIMEOUT, default="200ms"): cv.All( + cv.positive_time_period_milliseconds, + cv.Range(max=TimePeriod(milliseconds=65535)), + ), + } + ), + } +) + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_ID]) + + if CONF_MOTION in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_MOTION]) + cg.add(parent.set_motion(sens)) + cg.add(parent.set_motion_timeout(config[CONF_MOTION][CONF_TIMEOUT])) + + if CONF_LIGHT in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_LIGHT]) + cg.add(parent.set_light(sens)) + + if CONF_BUTTON in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_BUTTON]) + cg.add(parent.set_button(sens)) + cg.add(parent.set_button_timeout(config[CONF_BUTTON][CONF_TIMEOUT])) diff --git a/esphome/components/xiaomi_rtcgq02lm/sensor.py b/esphome/components/xiaomi_rtcgq02lm/sensor.py new file mode 100644 index 0000000000..558e3623e5 --- /dev/null +++ b/esphome/components/xiaomi_rtcgq02lm/sensor.py @@ -0,0 +1,37 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_BATTERY_LEVEL, + ENTITY_CATEGORY_DIAGNOSTIC, + STATE_CLASS_MEASUREMENT, + UNIT_PERCENT, + CONF_ID, + DEVICE_CLASS_BATTERY, +) + +from . import XiaomiRTCGQ02LM + +DEPENDENCIES = ["xiaomi_rtcgq02lm"] + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(XiaomiRTCGQ02LM), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } +) + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_ID]) + + if CONF_BATTERY_LEVEL in config: + sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) + cg.add(parent.set_battery_level(sens)) diff --git a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp new file mode 100644 index 0000000000..498e724368 --- /dev/null +++ b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp @@ -0,0 +1,91 @@ +#include "xiaomi_rtcgq02lm.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace xiaomi_rtcgq02lm { + +static const char *const TAG = "xiaomi_rtcgq02lm"; + +void XiaomiRTCGQ02LM::dump_config() { + ESP_LOGCONFIG(TAG, "Xiaomi RTCGQ02LM"); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "Motion", this->motion_); + LOG_BINARY_SENSOR(" ", "Light", this->light_); + LOG_BINARY_SENSOR(" ", "Button", this->button_); +#endif +#ifdef USE_SENSOR + LOG_SENSOR(" ", "Battery Level", this->battery_level_); +#endif +} + +bool XiaomiRTCGQ02LM::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 = xiaomi_ble::parse_xiaomi_header(service_data); + if (!res.has_value()) { + continue; + } + if (res->is_duplicate) { + continue; + } + if (res->has_encryption && + (!(xiaomi_ble::decrypt_xiaomi_payload(const_cast &>(service_data.data), this->bindkey_, + this->address_)))) { + continue; + } + if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { + continue; + } + + if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + continue; + } +#ifdef USE_BINARY_SENSOR + if (res->has_motion.has_value() && this->motion_ != nullptr) { + this->motion_->publish_state(*res->has_motion); + this->set_timeout("motion_timeout", this->motion_timeout_, + [this, res]() { this->motion_->publish_state(false); }); + } + if (res->is_light.has_value() && this->light_ != nullptr) + this->light_->publish_state(*res->is_light); + if (res->button_press.has_value() && this->button_ != nullptr) { + this->button_->publish_state(*res->button_press); + this->set_timeout("button_timeout", this->button_timeout_, + [this, res]() { this->button_->publish_state(false); }); + } +#endif +#ifdef USE_SENSOR + if (res->battery_level.has_value() && this->battery_level_ != nullptr) + this->battery_level_->publish_state(*res->battery_level); +#endif + success = true; + } + + return success; +} + +void XiaomiRTCGQ02LM::set_bindkey(const std::string &bindkey) { + memset(bindkey_, 0, 16); + if (bindkey.size() != 32) { + return; + } + char temp[3] = {0}; + for (int i = 0; i < 16; i++) { + strncpy(temp, &(bindkey.c_str()[i * 2]), 2); + bindkey_[i] = std::strtoul(temp, nullptr, 16); + } +} + +} // namespace xiaomi_rtcgq02lm +} // namespace esphome + +#endif diff --git a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h new file mode 100644 index 0000000000..a16c5209d9 --- /dev/null +++ b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h @@ -0,0 +1,61 @@ +#pragma once + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/core/defines.h" +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif +#include "esphome/components/xiaomi_ble/xiaomi_ble.h" +#include "esphome/core/component.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace xiaomi_rtcgq02lm { + +class XiaomiRTCGQ02LM : public Component, public esp32_ble_tracker::ESPBTDeviceListener { + public: + void set_address(uint64_t address) { address_ = address; }; + void set_bindkey(const std::string &bindkey); + + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + +#ifdef USE_BINARY_SENSOR + void set_motion(binary_sensor::BinarySensor *motion) { this->motion_ = motion; } + void set_motion_timeout(uint16_t timeout) { this->motion_timeout_ = timeout; } + + void set_light(binary_sensor::BinarySensor *light) { this->light_ = light; } + void set_button(binary_sensor::BinarySensor *button) { this->button_ = button; } + void set_button_timeout(uint16_t timeout) { this->button_timeout_ = timeout; } +#endif + +#ifdef USE_SENSOR + void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } +#endif + + protected: + uint64_t address_; + uint8_t bindkey_[16]; + +#ifdef USE_BINARY_SENSOR + uint16_t motion_timeout_; + uint16_t button_timeout_; + + binary_sensor::BinarySensor *motion_{nullptr}; + binary_sensor::BinarySensor *light_{nullptr}; + binary_sensor::BinarySensor *button_{nullptr}; +#endif +#ifdef USE_SENSOR + sensor::Sensor *battery_level_{nullptr}; +#endif +}; + +} // namespace xiaomi_rtcgq02lm +} // namespace esphome + +#endif diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 074bea6fd1..0972d6ccd6 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -173,6 +173,10 @@ constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, ui return (static_cast(byte1) << 24) | (static_cast(byte2) << 16) | (static_cast(byte3) << 8) | (static_cast(byte4)); } +/// Encode a 24-bit value given three bytes in most to least significant byte order. +constexpr uint32_t encode_uint24(uint8_t byte1, uint8_t byte2, uint8_t byte3) { + return ((static_cast(byte1) << 16) | (static_cast(byte2) << 8) | (static_cast(byte3))); +} /// Encode a value from its constituent bytes (from most to least significant) in an array with length sizeof(T). template::value, int> = 0> diff --git a/tests/test2.yaml b/tests/test2.yaml index ec3ccff70c..a7a9ef9661 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -263,6 +263,10 @@ sensor: name: 'Inkbird IBS-TH1 Humidity' battery_level: name: 'Inkbird IBS-TH1 Battery Level' + - platform: xiaomi_rtcgq02lm + id: motion_rtcgq02lm + battery_level: + name: 'Mi Motion Sensor 2 Battery level' - platform: ltr390 uv: name: "LTR390 UV" @@ -417,6 +421,14 @@ binary_sensor: name: 'CGPR1 Idle Time' illuminance: name: 'CGPR1 Illuminance' + - platform: xiaomi_rtcgq02lm + id: motion_rtcgq02lm + motion: + name: 'Mi Motion Sensor 2' + light: + name: 'Mi Motion Sensor 2 Light' + button: + name: 'Mi Motion Sensor 2 Button' esp32_ble_tracker: on_ble_advertise: @@ -457,6 +469,11 @@ xiaomi_ble: mopeka_ble: +xiaomi_rtcgq02lm: + - id: motion_rtcgq02lm + mac_address: 01:02:03:04:05:06 + bindkey: '48403ebe2d385db8d0c187f81e62cb64' + #esp32_ble_beacon: # type: iBeacon # uuid: 'c29ce823-e67a-4e71-bff2-abaa32e77a98' From 8b2c032da6a30c049dd89218b3c533cccde63308 Mon Sep 17 00:00:00 2001 From: anatoly-savchenkov <48646998+anatoly-savchenkov@users.noreply.github.com> Date: Tue, 12 Apr 2022 08:03:32 +0300 Subject: [PATCH 218/238] Add Sonoff D1 Dimmer support (#2775) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/sonoff_d1/__init__.py | 1 + esphome/components/sonoff_d1/light.py | 43 +++ esphome/components/sonoff_d1/sonoff_d1.cpp | 308 +++++++++++++++++++++ esphome/components/sonoff_d1/sonoff_d1.h | 85 ++++++ tests/test3.yaml | 6 + 6 files changed, 444 insertions(+) create mode 100644 esphome/components/sonoff_d1/__init__.py create mode 100644 esphome/components/sonoff_d1/light.py create mode 100644 esphome/components/sonoff_d1/sonoff_d1.cpp create mode 100644 esphome/components/sonoff_d1/sonoff_d1.h diff --git a/CODEOWNERS b/CODEOWNERS index 5a1220354a..79626c4a38 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -177,6 +177,7 @@ esphome/components/shutdown/* @esphome/core @jsuanet esphome/components/sim800l/* @glmnet esphome/components/sm2135/* @BoukeHaarsma23 esphome/components/socket/* @esphome/core +esphome/components/sonoff_d1/* @anatoly-savchenkov esphome/components/spi/* @esphome/core esphome/components/ssd1322_base/* @kbx81 esphome/components/ssd1322_spi/* @kbx81 diff --git a/esphome/components/sonoff_d1/__init__.py b/esphome/components/sonoff_d1/__init__.py new file mode 100644 index 0000000000..18b4d30d18 --- /dev/null +++ b/esphome/components/sonoff_d1/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@anatoly-savchenkov"] diff --git a/esphome/components/sonoff_d1/light.py b/esphome/components/sonoff_d1/light.py new file mode 100644 index 0000000000..8ffe60224e --- /dev/null +++ b/esphome/components/sonoff_d1/light.py @@ -0,0 +1,43 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart, light +from esphome.const import ( + CONF_OUTPUT_ID, + CONF_MIN_VALUE, + CONF_MAX_VALUE, +) + +CONF_USE_RM433_REMOTE = "use_rm433_remote" + +DEPENDENCIES = ["uart", "light"] + +sonoff_d1_ns = cg.esphome_ns.namespace("sonoff_d1") +SonoffD1Output = sonoff_d1_ns.class_( + "SonoffD1Output", cg.Component, uart.UARTDevice, light.LightOutput +) + +CONFIG_SCHEMA = ( + light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SonoffD1Output), + cv.Optional(CONF_USE_RM433_REMOTE, default=False): cv.boolean, + cv.Optional(CONF_MIN_VALUE, default=0): cv.int_range(min=0, max=100), + cv.Optional(CONF_MAX_VALUE, default=100): cv.int_range(min=0, max=100), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA) +) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "sonoff_d1", baud_rate=9600, require_tx=True, require_rx=True +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + cg.add(var.set_use_rm433_remote(config[CONF_USE_RM433_REMOTE])) + cg.add(var.set_min_value(config[CONF_MIN_VALUE])) + cg.add(var.set_max_value(config[CONF_MAX_VALUE])) + await light.register_light(var, config) diff --git a/esphome/components/sonoff_d1/sonoff_d1.cpp b/esphome/components/sonoff_d1/sonoff_d1.cpp new file mode 100644 index 0000000000..b4bcbc6760 --- /dev/null +++ b/esphome/components/sonoff_d1/sonoff_d1.cpp @@ -0,0 +1,308 @@ +/* + sonoff_d1.cpp - Sonoff D1 Dimmer support for ESPHome + + Copyright © 2021 Anatoly Savchenkov + Copyright © 2020 Jeff Rescignano + + 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. + + ----- + + If modifying this file, in addition to the license above, please ensure to include links back to the original code: + https://jeffresc.dev/blog/2020-10-10 + https://github.com/JeffResc/Sonoff-D1-Dimmer + https://github.com/arendst/Tasmota/blob/2d4a6a29ebc7153dbe2717e3615574ac1c84ba1d/tasmota/xdrv_37_sonoff_d1.ino#L119-L131 + + ----- +*/ + +/*********************************************************************************************\ + * Sonoff D1 dimmer 433 + * Mandatory/Optional + * ^ 0 1 2 3 4 5 6 7 8 9 A B C D E F 10 + * M AA 55 - Header + * M 01 04 - Version? + * M 00 0A - Following data length (10 bytes) + * O 01 - Power state (00 = off, 01 = on, FF = ignore) + * O 64 - Dimmer percentage (01 to 64 = 1 to 100%, 0 - ignore) + * O FF FF FF FF FF FF FF FF - Not used + * M 6C - CRC over bytes 2 to F (Addition) +\*********************************************************************************************/ +#include +#include "sonoff_d1.h" + +namespace esphome { +namespace sonoff_d1 { + +static const char *const TAG = "sonoff_d1"; + +uint8_t SonoffD1Output::calc_checksum_(const uint8_t *cmd, const size_t len) { + uint8_t crc = 0; + for (int i = 2; i < len - 1; i++) { + crc += cmd[i]; + } + return crc; +} + +void SonoffD1Output::populate_checksum_(uint8_t *cmd, const size_t len) { + // Update the checksum + cmd[len - 1] = this->calc_checksum_(cmd, len); +} + +void SonoffD1Output::skip_command_() { + size_t garbage = 0; + // Read out everything from the UART FIFO + while (this->available()) { + uint8_t value = this->read(); + ESP_LOGW(TAG, "[%04d] Skip %02d: 0x%02x from the dimmer", this->write_count_, garbage, value); + garbage++; + } + + // Warn about unexpected bytes in the protocol with UART dimmer + if (garbage) + ESP_LOGW(TAG, "[%04d] Skip %d bytes from the dimmer", this->write_count_, garbage); +} + +// This assumes some data is already available +bool SonoffD1Output::read_command_(uint8_t *cmd, size_t &len) { + // Do consistency check + if (cmd == nullptr || len < 7) { + ESP_LOGW(TAG, "[%04d] Too short command buffer (actual len is %d bytes, minimal is 7)", this->write_count_, len); + return false; + } + + // Read a minimal packet + if (this->read_array(cmd, 6)) { + ESP_LOGV(TAG, "[%04d] Reading from dimmer:", this->write_count_); + ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(cmd, 6).c_str()); + + if (cmd[0] != 0xAA || cmd[1] != 0x55) { + ESP_LOGW(TAG, "[%04d] RX: wrong header (%x%x, must be AA55)", this->write_count_, cmd[0], cmd[1]); + this->skip_command_(); + return false; + } + if ((cmd[5] + 7 /*mandatory header + crc suffix length*/) > len) { + ESP_LOGW(TAG, "[%04d] RX: Payload length is unexpected (%d, max expected %d)", this->write_count_, cmd[5], + len - 7); + this->skip_command_(); + return false; + } + if (this->read_array(&cmd[6], cmd[5] + 1 /*checksum suffix*/)) { + ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(&cmd[6], cmd[5] + 1).c_str()); + + // Check the checksum + uint8_t valid_checksum = this->calc_checksum_(cmd, cmd[5] + 7); + if (valid_checksum != cmd[cmd[5] + 7 - 1]) { + ESP_LOGW(TAG, "[%04d] RX: checksum mismatch (%d, expected %d)", this->write_count_, cmd[cmd[5] + 7 - 1], + valid_checksum); + this->skip_command_(); + return false; + } + len = cmd[5] + 7 /*mandatory header + suffix length*/; + + // Read remaining gardbled data (just in case, I don't see where this can appear now) + this->skip_command_(); + return true; + } + } else { + ESP_LOGW(TAG, "[%04d] RX: feedback timeout", this->write_count_); + this->skip_command_(); + } + return false; +} + +bool SonoffD1Output::read_ack_(const uint8_t *cmd, const size_t len) { + // Expected acknowledgement from rf chip + uint8_t ref_buffer[7] = {0xAA, 0x55, cmd[2], cmd[3], 0x00, 0x00, 0x00}; + uint8_t buffer[sizeof(ref_buffer)] = {0}; + uint32_t pos = 0, buf_len = sizeof(ref_buffer); + + // Update the reference checksum + this->populate_checksum_(ref_buffer, sizeof(ref_buffer)); + + // Read ack code, this either reads 7 bytes or exits with a timeout + this->read_command_(buffer, buf_len); + + // Compare response with expected response + while (pos < sizeof(ref_buffer) && ref_buffer[pos] == buffer[pos]) { + pos++; + } + if (pos == sizeof(ref_buffer)) { + ESP_LOGD(TAG, "[%04d] Acknowledge received", this->write_count_); + return true; + } else { + ESP_LOGW(TAG, "[%04d] Unexpected acknowledge received (possible clash of RF/HA commands), expected ack was:", + this->write_count_); + ESP_LOGW(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(ref_buffer, sizeof(ref_buffer)).c_str()); + } + return false; +} + +bool SonoffD1Output::write_command_(uint8_t *cmd, const size_t len, bool needs_ack) { + // Do some consistency checks + if (len < 7) { + ESP_LOGW(TAG, "[%04d] Too short command (actual len is %d bytes, minimal is 7)", this->write_count_, len); + return false; + } + if (cmd[0] != 0xAA || cmd[1] != 0x55) { + ESP_LOGW(TAG, "[%04d] Wrong header (%x%x, must be AA55)", this->write_count_, cmd[0], cmd[1]); + return false; + } + if ((cmd[5] + 7 /*mandatory header + suffix length*/) != len) { + ESP_LOGW(TAG, "[%04d] Payload length field does not match packet lenght (%d, expected %d)", this->write_count_, + cmd[5], len - 7); + return false; + } + this->populate_checksum_(cmd, len); + + // Need retries here to handle the following cases: + // 1. On power up companion MCU starts to respond with a delay, so few first commands are ignored + // 2. UART command initiated by this component can clash with a command initiated by RF + uint32_t retries = 10; + do { + ESP_LOGV(TAG, "[%04d] Writing to the dimmer:", this->write_count_); + ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(cmd, len).c_str()); + this->write_array(cmd, len); + this->write_count_++; + if (!needs_ack) + return true; + retries--; + } while (!this->read_ack_(cmd, len) && retries > 0); + + if (retries) { + return true; + } else { + ESP_LOGE(TAG, "[%04d] Unable to write to the dimmer", this->write_count_); + } + return false; +} + +bool SonoffD1Output::control_dimmer_(const bool binary, const uint8_t brightness) { + // Include our basic code from the Tasmota project, thank you again! + // 0 1 2 3 4 5 6 7 8 + uint8_t cmd[17] = {0xAA, 0x55, 0x01, 0x04, 0x00, 0x0A, 0x00, 0x00, 0xFF, + // 9 10 11 12 13 14 15 16 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00}; + + cmd[6] = binary; + cmd[7] = remap(brightness, 0, 100, this->min_value_, this->max_value_); + ESP_LOGI(TAG, "[%04d] Setting dimmer state to %s, raw brightness=%d", this->write_count_, ONOFF(binary), cmd[7]); + return this->write_command_(cmd, sizeof(cmd)); +} + +void SonoffD1Output::process_command_(const uint8_t *cmd, const size_t len) { + if (cmd[2] == 0x01 && cmd[3] == 0x04 && cmd[4] == 0x00 && cmd[5] == 0x0A) { + uint8_t ack_buffer[7] = {0xAA, 0x55, cmd[2], cmd[3], 0x00, 0x00, 0x00}; + // Ack a command from RF to ESP to prevent repeating commands + this->write_command_(ack_buffer, sizeof(ack_buffer), false); + ESP_LOGI(TAG, "[%04d] RF sets dimmer state to %s, raw brightness=%d", this->write_count_, ONOFF(cmd[6]), cmd[7]); + const uint8_t new_brightness = remap(cmd[7], this->min_value_, this->max_value_, 0, 100); + const bool new_state = cmd[6]; + + // Got light change state command. In all cases we revert the command immediately + // since we want to rely on ESP controlled transitions + if (new_state != this->last_binary_ || new_brightness != this->last_brightness_) { + this->control_dimmer_(this->last_binary_, this->last_brightness_); + } + + if (!this->use_rm433_remote_) { + // If RF remote is not used, this is a known ghost RF command + ESP_LOGI(TAG, "[%04d] Ghost command from RF detected, reverted", this->write_count_); + } else { + // If remote is used, initiate transition to the new state + this->publish_state_(new_state, new_brightness); + } + } else { + ESP_LOGW(TAG, "[%04d] Unexpected command received", this->write_count_); + } +} + +void SonoffD1Output::publish_state_(const bool is_on, const uint8_t brightness) { + if (light_state_) { + ESP_LOGV(TAG, "Publishing new state: %s, brightness=%d", ONOFF(is_on), brightness); + auto call = light_state_->make_call(); + call.set_state(is_on); + if (brightness != 0) { + // Brightness equal to 0 has a special meaning. + // D1 uses 0 as "previously set brightness". + // Usually zero brightness comes inside light ON command triggered by RF remote. + // Since we unconditionally override commands coming from RF remote in process_command_(), + // here we mimic the original behavior but with LightCall functionality + call.set_brightness((float) brightness / 100.0f); + } + call.perform(); + } +} + +// Set the device's traits +light::LightTraits SonoffD1Output::get_traits() { + auto traits = light::LightTraits(); + traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS}); + return traits; +} + +void SonoffD1Output::write_state(light::LightState *state) { + bool binary; + float brightness; + + // Fill our variables with the device's current state + state->current_values_as_binary(&binary); + state->current_values_as_brightness(&brightness); + + // Convert ESPHome's brightness (0-1) to the device's internal brightness (0-100) + const uint8_t calculated_brightness = std::round(brightness * 100); + + if (calculated_brightness == 0) { + // if(binary) ESP_LOGD(TAG, "current_values_as_binary() returns true for zero brightness"); + binary = false; + } + + // If a new value, write to the dimmer + if (binary != this->last_binary_ || calculated_brightness != this->last_brightness_) { + if (this->control_dimmer_(binary, calculated_brightness)) { + this->last_brightness_ = calculated_brightness; + this->last_binary_ = binary; + } else { + // Return to original value if failed to write to the dimmer + // TODO: Test me, can be tested if high-voltage part is not connected + ESP_LOGW(TAG, "Failed to update the dimmer, publishing the previous state"); + this->publish_state_(this->last_binary_, this->last_brightness_); + } + } +} + +void SonoffD1Output::dump_config() { + ESP_LOGCONFIG(TAG, "Sonoff D1 Dimmer: '%s'", this->light_state_ ? this->light_state_->get_name().c_str() : ""); + ESP_LOGCONFIG(TAG, " Use RM433 Remote: %s", ONOFF(this->use_rm433_remote_)); + ESP_LOGCONFIG(TAG, " Minimal brightness: %d", this->min_value_); + ESP_LOGCONFIG(TAG, " Maximal brightness: %d", this->max_value_); +} + +void SonoffD1Output::loop() { + // Read commands from the dimmer + // RF chip notifies ESP about remotely changed state with the same commands as we send + if (this->available()) { + ESP_LOGV(TAG, "Have some UART data in loop()"); + uint8_t buffer[17] = {0}; + size_t len = sizeof(buffer); + if (this->read_command_(buffer, len)) { + this->process_command_(buffer, len); + } + } +} + +} // namespace sonoff_d1 +} // namespace esphome diff --git a/esphome/components/sonoff_d1/sonoff_d1.h b/esphome/components/sonoff_d1/sonoff_d1.h new file mode 100644 index 0000000000..4df0f5afb2 --- /dev/null +++ b/esphome/components/sonoff_d1/sonoff_d1.h @@ -0,0 +1,85 @@ +#pragma once + +/* + sonoff_d1.h - Sonoff D1 Dimmer support for ESPHome + + Copyright © 2021 Anatoly Savchenkov + Copyright © 2020 Jeff Rescignano + + 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. + + ----- + + If modifying this file, in addition to the license above, please ensure to include links back to the original code: + https://jeffresc.dev/blog/2020-10-10 + https://github.com/JeffResc/Sonoff-D1-Dimmer + https://github.com/arendst/Tasmota/blob/2d4a6a29ebc7153dbe2717e3615574ac1c84ba1d/tasmota/xdrv_37_sonoff_d1.ino#L119-L131 + + ----- +*/ + +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/light/light_output.h" +#include "esphome/components/light/light_state.h" +#include "esphome/components/light/light_traits.h" + +namespace esphome { +namespace sonoff_d1 { + +class SonoffD1Output : public light::LightOutput, public uart::UARTDevice, public Component { + public: + // LightOutput methods + light::LightTraits get_traits() override; + void setup_state(light::LightState *state) override { this->light_state_ = state; } + void write_state(light::LightState *state) override; + + // Component methods + void setup() override{}; + void loop() override; + void dump_config() override; + float get_setup_priority() const override { return esphome::setup_priority::DATA; } + + // Custom methods + void set_use_rm433_remote(const bool use_rm433_remote) { this->use_rm433_remote_ = use_rm433_remote; } + void set_min_value(const uint8_t min_value) { this->min_value_ = min_value; } + void set_max_value(const uint8_t max_value) { this->max_value_ = max_value; } + + protected: + uint8_t min_value_{0}; + uint8_t max_value_{100}; + bool use_rm433_remote_{false}; + bool last_binary_{false}; + uint8_t last_brightness_{0}; + int write_count_{0}; + int read_count_{0}; + light::LightState *light_state_{nullptr}; + + uint8_t calc_checksum_(const uint8_t *cmd, size_t len); + void populate_checksum_(uint8_t *cmd, size_t len); + void skip_command_(); + bool read_command_(uint8_t *cmd, size_t &len); + bool read_ack_(const uint8_t *cmd, size_t len); + bool write_command_(uint8_t *cmd, size_t len, bool needs_ack = true); + bool control_dimmer_(bool binary, uint8_t brightness); + void process_command_(const uint8_t *cmd, size_t len); + void publish_state_(bool is_on, uint8_t brightness); +}; + +} // namespace sonoff_d1 +} // namespace esphome diff --git a/tests/test3.yaml b/tests/test3.yaml index a6ad6b9e92..58cb14740f 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1229,6 +1229,12 @@ light: name: Icicle Lights pin_a: out pin_b: out2 + - platform: sonoff_d1 + uart_id: uart2 + use_rm433_remote: False + name: Sonoff D1 Dimmer + id: d1_light + restore_mode: RESTORE_DEFAULT_OFF servo: id: my_servo From 7895cd92cd8d05d72fdb6631bd17151aff207e2e Mon Sep 17 00:00:00 2001 From: cvwillegen Date: Tue, 12 Apr 2022 21:39:38 +0200 Subject: [PATCH 219/238] Remote base pronto receive (#2826) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .../remote_base/pronto_protocol.cpp | 101 +++++++++++++++++- .../components/remote_base/pronto_protocol.h | 12 ++- 2 files changed, 110 insertions(+), 3 deletions(-) diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp index 4f6ace720c..a2b1a16e07 100644 --- a/esphome/components/remote_base/pronto_protocol.cpp +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -40,6 +40,24 @@ namespace remote_base { static const char *const TAG = "remote.pronto"; +bool ProntoData::operator==(const ProntoData &rhs) const { + std::vector data1 = encode_pronto(data); + std::vector data2 = encode_pronto(rhs.data); + + uint32_t total_diff = 0; + // Don't need to check the last one, it's the large gap at the end. + for (std::vector::size_type i = 0; i < data1.size() - 1; ++i) { + int diff = data2[i] - data1[i]; + diff *= diff; + if (diff > 9) + return false; + + total_diff += diff; + } + + return total_diff <= data1.size() * 3; +} + // DO NOT EXPORT from this file static const uint16_t MICROSECONDS_T_MAX = 0xFFFFU; static const uint16_t LEARNED_TOKEN = 0x0000U; @@ -52,6 +70,7 @@ 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 const uint16_t MARK_EXCESS_MICROS = 20; static uint16_t to_frequency_k_hz(uint16_t code) { if (code == 0) @@ -107,7 +126,7 @@ void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::vector encode_pronto(const std::string &str) { size_t len = str.length() / (DIGITS_IN_PRONTO_NUMBER + 1) + 1; std::vector data; const char *p = str.c_str(); @@ -122,12 +141,90 @@ void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &st data.push_back(x); // If input is conforming, there can be no overflow! p = *endptr; } + + return data; +} + +void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &str) { + std::vector data = encode_pronto(str); send_pronto_(dst, data); } void ProntoProtocol::encode(RemoteTransmitData *dst, const ProntoData &data) { send_pronto_(dst, data.data); } -optional ProntoProtocol::decode(RemoteReceiveData src) { return {}; } +uint16_t ProntoProtocol::effective_frequency_(uint16_t frequency) { + return frequency > 0 ? frequency : FALLBACK_FREQUENCY; +} + +uint16_t ProntoProtocol::to_timebase_(uint16_t frequency) { + return MICROSECONDS_IN_SECONDS / effective_frequency_(frequency); +} + +uint16_t ProntoProtocol::to_frequency_code_(uint16_t frequency) { + return REFERENCE_FREQUENCY / effective_frequency_(frequency); +} + +std::string ProntoProtocol::dump_digit_(uint8_t x) { + return std::string(1, (char) (x <= 9 ? ('0' + x) : ('A' + (x - 10)))); +} + +std::string ProntoProtocol::dump_number_(uint16_t number, bool end /* = false */) { + std::string num; + + for (uint8_t i = 0; i < DIGITS_IN_PRONTO_NUMBER; ++i) { + uint8_t shifts = BITS_IN_HEXADECIMAL * (DIGITS_IN_PRONTO_NUMBER - 1 - i); + num += dump_digit_((number >> shifts) & HEX_MASK); + } + + if (!end) + num += ' '; + + return num; +} + +std::string ProntoProtocol::dump_duration_(uint32_t duration, uint16_t timebase, bool end /* = false */) { + return dump_number_((duration + timebase / 2) / timebase, end); +} + +std::string ProntoProtocol::compensate_and_dump_sequence_(std::vector *data, uint16_t timebase) { + std::string out; + + for (std::vector::size_type i = 0; i < data->size() - 1; i++) { + int32_t t_length = data->at(i); + uint32_t t_duration; + if (t_length > 0) { + // Mark + t_duration = t_length - MARK_EXCESS_MICROS; + } else { + t_duration = -t_length + MARK_EXCESS_MICROS; + } + out += dump_duration_(t_duration, timebase); + } + + // append minimum gap + out += dump_duration_(PRONTO_DEFAULT_GAP, timebase, true); + + return out; +} + +optional ProntoProtocol::decode(RemoteReceiveData src) { + ProntoData out; + + uint16_t frequency = 38000U; + std::vector *data = src.get_raw_data(); + std::string prontodata; + + prontodata += dump_number_(frequency > 0 ? LEARNED_TOKEN : LEARNED_NON_MODULATED_TOKEN); + prontodata += dump_number_(to_frequency_code_(frequency)); + prontodata += dump_number_((data->size() + 1) / 2); + prontodata += dump_number_(0); + uint16_t timebase = to_timebase_(frequency); + prontodata += compensate_and_dump_sequence_(data, timebase); + + out.data = prontodata; + + return out; +} void ProntoProtocol::dump(const ProntoData &data) { ESP_LOGD(TAG, "Received Pronto: data=%s", data.data.c_str()); } diff --git a/esphome/components/remote_base/pronto_protocol.h b/esphome/components/remote_base/pronto_protocol.h index e96511383f..291bb8a99b 100644 --- a/esphome/components/remote_base/pronto_protocol.h +++ b/esphome/components/remote_base/pronto_protocol.h @@ -6,10 +6,12 @@ namespace esphome { namespace remote_base { +std::vector encode_pronto(const std::string &str); + struct ProntoData { std::string data; - bool operator==(const ProntoData &rhs) const { return data == rhs.data; } + bool operator==(const ProntoData &rhs) const; }; class ProntoProtocol : public RemoteProtocol { @@ -17,6 +19,14 @@ class ProntoProtocol : public RemoteProtocol { void send_pronto_(RemoteTransmitData *dst, const std::vector &data); void send_pronto_(RemoteTransmitData *dst, const std::string &str); + uint16_t effective_frequency_(uint16_t frequency); + uint16_t to_timebase_(uint16_t frequency); + uint16_t to_frequency_code_(uint16_t frequency); + std::string dump_digit_(uint8_t x); + std::string dump_number_(uint16_t number, bool end = false); + std::string dump_duration_(uint32_t duration, uint16_t timebase, bool end = false); + std::string compensate_and_dump_sequence_(std::vector *data, uint16_t timebase); + public: void encode(RemoteTransmitData *dst, const ProntoData &data) override; optional decode(RemoteReceiveData src) override; From 99335d986e8e90907639bc1c76038ed06319abf0 Mon Sep 17 00:00:00 2001 From: Janez Troha <239513+dz0ny@users.noreply.github.com> Date: Wed, 13 Apr 2022 00:14:21 +0200 Subject: [PATCH 220/238] Use correct http defines (#3378) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/web_server/web_server.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 73813ecfa1..bd7acd91a0 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -88,12 +88,12 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle an index request under '/'. void handle_index_request(AsyncWebServerRequest *request); -#ifdef WEBSERVER_CSS_INCLUDE +#ifdef USE_WEBSERVER_CSS_INCLUDE /// Handle included css request under '/0.css'. void handle_css_request(AsyncWebServerRequest *request); #endif -#ifdef WEBSERVER_JS_INCLUDE +#ifdef USE_WEBSERVER_JS_INCLUDE /// Handle included js request under '/0.js'. void handle_js_request(AsyncWebServerRequest *request); #endif From d620b6dd5e5193ad37abd91b1626c89fa49f0276 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Wed, 13 Apr 2022 00:19:48 +0200 Subject: [PATCH 221/238] Refactor Sensirion Sensors (#3374) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/i2c/i2c_bus.h | 1 + esphome/components/scd30/scd30.cpp | 91 ++-------- esphome/components/scd30/scd30.h | 8 +- esphome/components/scd30/sensor.py | 6 +- esphome/components/scd4x/scd4x.cpp | 111 ++----------- esphome/components/scd4x/scd4x.h | 8 +- esphome/components/scd4x/sensor.py | 7 +- esphome/components/sdp3x/sdp3x.cpp | 79 +++------ esphome/components/sdp3x/sdp3x.h | 6 +- esphome/components/sdp3x/sensor.py | 6 +- .../components/sensirion_common/__init__.py | 10 ++ .../sensirion_common/i2c_sensirion.cpp | 128 +++++++++++++++ .../sensirion_common/i2c_sensirion.h | 155 ++++++++++++++++++ esphome/components/sgp30/sensor.py | 13 +- esphome/components/sgp30/sgp30.cpp | 79 +-------- esphome/components/sgp30/sgp30.h | 7 +- esphome/components/sgp40/sensor.py | 13 +- esphome/components/sgp40/sgp40.cpp | 116 +++---------- esphome/components/sgp40/sgp40.h | 8 +- esphome/components/sht3xd/sensor.py | 5 +- esphome/components/sht3xd/sht3xd.cpp | 64 +------- esphome/components/sht3xd/sht3xd.h | 7 +- esphome/components/sht4x/sensor.py | 7 +- esphome/components/sht4x/sht4x.cpp | 17 +- esphome/components/sht4x/sht4x.h | 4 +- esphome/components/shtcx/sensor.py | 7 +- esphome/components/shtcx/shtcx.cpp | 65 +------- esphome/components/shtcx/shtcx.h | 6 +- esphome/components/sps30/sensor.py | 7 +- esphome/components/sps30/sps30.cpp | 81 +-------- esphome/components/sps30/sps30.h | 7 +- esphome/components/sts3x/sensor.py | 1 + esphome/components/sts3x/sts3x.cpp | 61 +------ esphome/components/sts3x/sts3x.h | 8 +- esphome/const.py | 2 + 36 files changed, 484 insertions(+), 718 deletions(-) create mode 100644 esphome/components/sensirion_common/__init__.py create mode 100644 esphome/components/sensirion_common/i2c_sensirion.cpp create mode 100644 esphome/components/sensirion_common/i2c_sensirion.h diff --git a/CODEOWNERS b/CODEOWNERS index 79626c4a38..7595fc52e2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -170,6 +170,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdp3x/* @Azimath esphome/components/selec_meter/* @sourabhjaiswal esphome/components/select/* @esphome/core +esphome/components/sensirion_common/* @martgras esphome/components/sensor/* @esphome/core esphome/components/sgp40/* @SenexCrenshaw esphome/components/sht4x/* @sjtrny diff --git a/esphome/components/i2c/i2c_bus.h b/esphome/components/i2c/i2c_bus.h index c07a2dd1dd..2633a7adf6 100644 --- a/esphome/components/i2c/i2c_bus.h +++ b/esphome/components/i2c/i2c_bus.h @@ -15,6 +15,7 @@ enum ErrorCode { ERROR_NOT_INITIALIZED = 4, ERROR_TOO_LARGE = 5, ERROR_UNKNOWN = 6, + ERROR_CRC = 7, }; struct ReadBuffer { diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index 8603072bd5..103b7a255d 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -33,14 +33,8 @@ void SCD30Component::setup() { #endif /// Firmware version identification - if (!this->write_command_(SCD30_CMD_GET_FIRMWARE_VERSION)) { - this->error_code_ = COMMUNICATION_FAILED; - this->mark_failed(); - return; - } uint16_t raw_firmware_version[3]; - - if (!this->read_data_(raw_firmware_version, 3)) { + if (!this->get_register(SCD30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version, 3)) { this->error_code_ = FIRMWARE_IDENTIFICATION_FAILED; this->mark_failed(); return; @@ -49,7 +43,7 @@ void SCD30Component::setup() { uint16_t(raw_firmware_version[0] & 0xFF)); if (this->temperature_offset_ != 0) { - if (!this->write_command_(SCD30_CMD_TEMPERATURE_OFFSET, (uint16_t)(temperature_offset_ * 100.0))) { + if (!this->write_command(SCD30_CMD_TEMPERATURE_OFFSET, (uint16_t)(temperature_offset_ * 100.0))) { ESP_LOGE(TAG, "Sensor SCD30 error setting temperature offset."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); @@ -69,7 +63,7 @@ void SCD30Component::setup() { delay(30); #endif - if (!this->write_command_(SCD30_CMD_MEASUREMENT_INTERVAL, update_interval_)) { + 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(); @@ -81,7 +75,7 @@ void SCD30Component::setup() { // 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_)) { + if (!this->write_command(SCD30_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { ESP_LOGE(TAG, "Sensor SCD30 error setting altitude compensation."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); @@ -92,7 +86,7 @@ void SCD30Component::setup() { delay(30); #endif - if (!this->write_command_(SCD30_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) { + if (!this->write_command(SCD30_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) { ESP_LOGE(TAG, "Sensor SCD30 error setting automatic self calibration."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); @@ -103,7 +97,7 @@ void SCD30Component::setup() { #endif /// Sensor initialization - if (!this->write_command_(SCD30_CMD_START_CONTINUOUS_MEASUREMENTS, this->ambient_pressure_compensation_)) { + if (!this->write_command(SCD30_CMD_START_CONTINUOUS_MEASUREMENTS, this->ambient_pressure_compensation_)) { ESP_LOGE(TAG, "Sensor SCD30 error starting continuous measurements."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); @@ -151,14 +145,14 @@ void SCD30Component::dump_config() { } void SCD30Component::update() { - 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) || raw_read_status == 0x00) { this->status_set_warning(); ESP_LOGW(TAG, "Data not ready yet!"); return; } - if (!this->write_command_(SCD30_CMD_READ_MEASUREMENT)) { + if (!this->write_command(SCD30_CMD_READ_MEASUREMENT)) { ESP_LOGW(TAG, "Error reading measurement!"); this->status_set_warning(); return; @@ -166,7 +160,7 @@ void SCD30Component::update() { this->set_timeout(50, [this]() { uint16_t raw_data[6]; - if (!this->read_data_(raw_data, 6)) { + if (!this->read_data(raw_data, 6)) { this->status_set_warning(); return; } @@ -197,77 +191,16 @@ void SCD30Component::update() { } bool SCD30Component::is_data_ready_() { - if (!this->write_command_(SCD30_CMD_GET_DATA_READY_STATUS)) { + 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)) { + 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); -} - -bool SCD30Component::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; -} - -uint8_t SCD30Component::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 SCD30Component::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; -} - } // namespace scd30 } // namespace esphome diff --git a/esphome/components/scd30/scd30.h b/esphome/components/scd30/scd30.h index 64193d0cb6..c434bf0dea 100644 --- a/esphome/components/scd30/scd30.h +++ b/esphome/components/scd30/scd30.h @@ -2,13 +2,13 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" namespace esphome { namespace scd30 { /// This class implements support for the Sensirion scd30 i2c GAS (VOC and CO2eq) sensors. -class SCD30Component : public Component, public i2c::I2CDevice { +class SCD30Component : public Component, public sensirion_common::SensirionI2CDevice { public: void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; } void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } @@ -27,10 +27,6 @@ class SCD30Component : public Component, public i2c::I2CDevice { float get_setup_priority() const override { return setup_priority::DATA; } protected: - bool write_command_(uint16_t command); - 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 { diff --git a/esphome/components/scd30/sensor.py b/esphome/components/scd30/sensor.py index cd25649f2a..3cfd861a63 100644 --- a/esphome/components/scd30/sensor.py +++ b/esphome/components/scd30/sensor.py @@ -2,6 +2,7 @@ from esphome import core import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor +from esphome.components import sensirion_common from esphome.const import ( CONF_ID, CONF_HUMIDITY, @@ -18,9 +19,12 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] scd30_ns = cg.esphome_ns.namespace("scd30") -SCD30Component = scd30_ns.class_("SCD30Component", cg.Component, i2c.I2CDevice) +SCD30Component = scd30_ns.class_( + "SCD30Component", cg.Component, sensirion_common.SensirionI2CDevice +) CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" CONF_ALTITUDE_COMPENSATION = "altitude_compensation" diff --git a/esphome/components/scd4x/scd4x.cpp b/esphome/components/scd4x/scd4x.cpp index 4bd512394f..559c95df32 100644 --- a/esphome/components/scd4x/scd4x.cpp +++ b/esphome/components/scd4x/scd4x.cpp @@ -25,15 +25,8 @@ void SCD4XComponent::setup() { // 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)) { + uint16_t raw_read_status; + if (!this->get_register(SCD4X_CMD_GET_DATA_READY_STATUS, raw_read_status)) { ESP_LOGE(TAG, "Failed to read data ready status"); this->mark_failed(); return; @@ -41,9 +34,9 @@ void SCD4XComponent::setup() { uint32_t stop_measurement_delay = 0; // In order to query the device periodic measurement must be ceased - if (raw_read_status[0]) { + if (raw_read_status) { ESP_LOGD(TAG, "Sensor has data available, stopping periodic measurement"); - if (!this->write_command_(SCD4X_CMD_STOP_MEASUREMENTS)) { + if (!this->write_command(SCD4X_CMD_STOP_MEASUREMENTS)) { ESP_LOGE(TAG, "Failed to stop measurements"); this->mark_failed(); return; @@ -53,15 +46,8 @@ void SCD4XComponent::setup() { 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; - } - uint16_t raw_serial_number[3]; - if (!this->read_data_(raw_serial_number, 3)) { + if (!this->get_register(SCD4X_CMD_GET_SERIAL_NUMBER, raw_serial_number, 3, 1)) { ESP_LOGE(TAG, "Failed to read serial number"); this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED; this->mark_failed(); @@ -70,8 +56,8 @@ void SCD4XComponent::setup() { 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))) { + 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(); @@ -88,7 +74,7 @@ void SCD4XComponent::setup() { return; } } else { - if (!this->write_command_(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { + 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(); @@ -96,7 +82,7 @@ void SCD4XComponent::setup() { } } - if (!this->write_command_(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) { + 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(); @@ -104,7 +90,7 @@ void SCD4XComponent::setup() { } // Finally start sensor measurements - if (!this->write_command_(SCD4X_CMD_START_CONTINUOUS_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(); @@ -164,19 +150,19 @@ void SCD4XComponent::update() { } // Check if data is ready - if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) { + 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) { + uint16_t raw_read_status; + if (!this->read_data(raw_read_status) || raw_read_status == 0x00) { this->status_set_warning(); ESP_LOGW(TAG, "Data not ready yet!"); return; } - if (!this->write_command_(SCD4X_CMD_READ_MEASUREMENT)) { + if (!this->write_command(SCD4X_CMD_READ_MEASUREMENT)) { ESP_LOGW(TAG, "Error reading measurement!"); this->status_set_warning(); return; @@ -184,7 +170,7 @@ void SCD4XComponent::update() { // Read off sensor data uint16_t raw_data[3]; - if (!this->read_data_(raw_data, 3)) { + if (!this->read_data(raw_data, 3)) { this->status_set_warning(); return; } @@ -218,7 +204,7 @@ void SCD4XComponent::set_ambient_pressure_compensation(float pressure_in_bar) { } bool SCD4XComponent::update_ambient_pressure_compensation_(uint16_t pressure_in_hpa) { - if (this->write_command_(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, 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 { @@ -227,70 +213,5 @@ bool SCD4XComponent::update_ambient_pressure_compensation_(uint16_t pressure_in_ } } -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 index 4fe2bf14cc..3e534bcf98 100644 --- a/esphome/components/scd4x/scd4x.h +++ b/esphome/components/scd4x/scd4x.h @@ -2,14 +2,14 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" namespace esphome { namespace scd4x { enum ERRORCODE { COMMUNICATION_FAILED, SERIAL_NUMBER_IDENTIFICATION_FAILED, MEASUREMENT_INIT_FAILED, UNKNOWN }; -class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { +class SCD4XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice { public: float get_setup_priority() const override { return setup_priority::DATA; } void setup() override; @@ -27,10 +27,6 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { 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); bool update_ambient_pressure_compensation_(uint16_t pressure_in_hpa); ERRORCODE error_code_; diff --git a/esphome/components/scd4x/sensor.py b/esphome/components/scd4x/sensor.py index 3e814ffe78..6ab0e1ba99 100644 --- a/esphome/components/scd4x/sensor.py +++ b/esphome/components/scd4x/sensor.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor - +from esphome.components import sensirion_common from esphome.const import ( CONF_ID, CONF_CO2, @@ -21,9 +21,12 @@ from esphome.const import ( CODEOWNERS = ["@sjtrny"] DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] scd4x_ns = cg.esphome_ns.namespace("scd4x") -SCD4XComponent = scd4x_ns.class_("SCD4XComponent", cg.PollingComponent, i2c.I2CDevice) +SCD4XComponent = scd4x_ns.class_( + "SCD4XComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice +) CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" CONF_ALTITUDE_COMPENSATION = "altitude_compensation" diff --git a/esphome/components/sdp3x/sdp3x.cpp b/esphome/components/sdp3x/sdp3x.cpp index eb1543f2c2..251d59607a 100644 --- a/esphome/components/sdp3x/sdp3x.cpp +++ b/esphome/components/sdp3x/sdp3x.cpp @@ -7,55 +7,50 @@ namespace esphome { namespace sdp3x { static const char *const TAG = "sdp3x.sensor"; -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}; +static const uint16_t SDP3X_SOFT_RESET = 0x0006; +static const uint16_t SDP3X_READ_ID1 = 0x367C; +static const uint16_t SDP3X_READ_ID2 = 0xE102; +static const uint16_t SDP3X_START_DP_AVG = 0x3615; +static const uint16_t SDP3X_START_MASS_FLOW_AVG = 0x3603; +static const uint16_t SDP3X_STOP_MEAS = 0x3FF9; void SDP3XComponent::update() { this->read_pressure_(); } void SDP3XComponent::setup() { ESP_LOGD(TAG, "Setting up SDP3X..."); - if (this->write(SDP3X_STOP_MEAS, 2) != i2c::ERROR_OK) { + if (!this->write_command(SDP3X_STOP_MEAS)) { ESP_LOGW(TAG, "Stop SDP3X failed!"); // This sometimes fails for no good reason } - if (this->write(SDP3X_SOFT_RESET, 2) != i2c::ERROR_OK) { + if (!this->write_command(SDP3X_SOFT_RESET)) { ESP_LOGW(TAG, "Soft Reset SDP3X failed!"); // This sometimes fails for no good reason } this->set_timeout(20, [this] { - if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) { + if (!this->write_command(SDP3X_READ_ID1)) { ESP_LOGE(TAG, "Read ID1 SDP3X failed!"); this->mark_failed(); return; } - if (this->write(SDP3X_READ_ID2, 2) != i2c::ERROR_OK) { + if (!this->write_command(SDP3X_READ_ID2)) { ESP_LOGE(TAG, "Read ID2 SDP3X failed!"); this->mark_failed(); return; } - uint8_t data[18]; - if (this->read(data, 18) != i2c::ERROR_OK) { + uint16_t data[6]; + if (this->read_data(data, 6) != 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; - } // 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]) { + if (data[1] >> 8 == 0x02) { + switch (data[1] & 0xFF) { case 0x01: // SDP800-500Pa ESP_LOGCONFIG(TAG, "Sensor is SDP800-500Pa"); break; @@ -75,15 +70,16 @@ void SDP3XComponent::setup() { ESP_LOGCONFIG(TAG, "Sensor is SDP810-125Pa"); break; } - } else if (data[2] == 0x01) { - if (data[3] == 0x01) { + } else if (data[1] >> 8 == 0x01) { + if ((data[1] & 0xFF) == 0x01) { ESP_LOGCONFIG(TAG, "Sensor is SDP31-500Pa"); - } else if (data[3] == 0x02) { + } else if ((data[1] & 0xFF) == 0x02) { ESP_LOGCONFIG(TAG, "Sensor is SDP32-125Pa"); } } - if (this->write(measurement_mode_ == DP_AVG ? SDP3X_START_DP_AVG : SDP3X_START_MASS_FLOW_AVG, 2) != i2c::ERROR_OK) { + if (this->write_command(measurement_mode_ == DP_AVG ? SDP3X_START_DP_AVG : SDP3X_START_MASS_FLOW_AVG) != + i2c::ERROR_OK) { ESP_LOGE(TAG, "Start Measurements SDP3X failed!"); this->mark_failed(); return; @@ -101,22 +97,16 @@ void SDP3XComponent::dump_config() { } void SDP3XComponent::read_pressure_() { - uint8_t data[9]; - if (this->read(data, 9) != i2c::ERROR_OK) { + uint16_t data[3]; + if (this->read_data(data, 3) != i2c::ERROR_OK) { ESP_LOGW(TAG, "Couldn't read SDP3X data!"); this->status_set_warning(); return; } - if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]) && check_crc_(&data[6], 2, data[8]))) { - ESP_LOGW(TAG, "Invalid SDP3X data!"); - this->status_set_warning(); - return; - } - - int16_t pressure_raw = encode_uint16(data[0], data[1]); - int16_t temperature_raw = encode_uint16(data[3], data[4]); - int16_t scale_factor_raw = encode_uint16(data[6], data[7]); + int16_t pressure_raw = data[0]; + int16_t temperature_raw = data[1]; + int16_t scale_factor_raw = data[2]; // 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, @@ -129,26 +119,5 @@ void SDP3XComponent::read_pressure_() { float SDP3XComponent::get_setup_priority() const { return setup_priority::DATA; } -// Check CRC function from SDP3X sample code provided by sensirion -// Returns true if a checksum is OK -bool SDP3XComponent::check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum) { - uint8_t crc = 0xFF; - - // calculates 8-Bit checksum with given polynomial 0x31 (x^8 + x^5 + x^4 + 1) - for (int i = 0; i < size; i++) { - crc ^= (data[i]); - for (uint8_t bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x31; - } else { - crc = (crc << 1); - } - } - } - - // verify checksum - return (crc == checksum); -} - } // namespace sdp3x } // namespace esphome diff --git a/esphome/components/sdp3x/sdp3x.h b/esphome/components/sdp3x/sdp3x.h index 0e74d0883d..e3d3533c74 100644 --- a/esphome/components/sdp3x/sdp3x.h +++ b/esphome/components/sdp3x/sdp3x.h @@ -2,14 +2,14 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" namespace esphome { namespace sdp3x { enum MeasurementMode { MASS_FLOW_AVG, DP_AVG }; -class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor { +class SDP3XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice, public sensor::Sensor { public: /// Schedule temperature+pressure readings. void update() override; @@ -23,8 +23,6 @@ class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public se 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); MeasurementMode measurement_mode_; }; diff --git a/esphome/components/sdp3x/sensor.py b/esphome/components/sdp3x/sensor.py index 66ee475b11..60608818a2 100644 --- a/esphome/components/sdp3x/sensor.py +++ b/esphome/components/sdp3x/sensor.py @@ -1,6 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor +from esphome.components import sensirion_common from esphome.const import ( DEVICE_CLASS_PRESSURE, STATE_CLASS_MEASUREMENT, @@ -8,10 +9,13 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] CODEOWNERS = ["@Azimath"] sdp3x_ns = cg.esphome_ns.namespace("sdp3x") -SDP3XComponent = sdp3x_ns.class_("SDP3XComponent", cg.PollingComponent, i2c.I2CDevice) +SDP3XComponent = sdp3x_ns.class_( + "SDP3XComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice +) MeasurementMode = sdp3x_ns.enum("MeasurementMode") diff --git a/esphome/components/sensirion_common/__init__.py b/esphome/components/sensirion_common/__init__.py new file mode 100644 index 0000000000..b27f37099d --- /dev/null +++ b/esphome/components/sensirion_common/__init__.py @@ -0,0 +1,10 @@ +import esphome.codegen as cg + +from esphome.components import i2c + + +CODEOWNERS = ["@martgras"] + +sensirion_common_ns = cg.esphome_ns.namespace("sensirion_common") + +SensirionI2CDevice = sensirion_common_ns.class_("SensirionI2CDevice", i2c.I2CDevice) diff --git a/esphome/components/sensirion_common/i2c_sensirion.cpp b/esphome/components/sensirion_common/i2c_sensirion.cpp new file mode 100644 index 0000000000..a2232a7d1b --- /dev/null +++ b/esphome/components/sensirion_common/i2c_sensirion.cpp @@ -0,0 +1,128 @@ +#include "i2c_sensirion.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" +#include + +namespace esphome { +namespace sensirion_common { + +static const char *const TAG = "sensirion_i2c"; +// To avoid memory allocations for small writes a stack buffer is used +static const size_t BUFFER_STACK_SIZE = 16; + +bool SensirionI2CDevice::read_data(uint16_t *data, uint8_t len) { + const uint8_t num_bytes = len * 3; + std::vector buf(num_bytes); + + last_error_ = this->read(buf.data(), num_bytes); + if (last_error_ != 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 at pos %d! 0x%02X != 0x%02X", i, buf[j + 2], crc); + last_error_ = i2c::ERROR_CRC; + return false; + } + data[i] = encode_uint16(buf[j], buf[j + 1]); + } + return true; +} +/*** + * write command with parameters and insert crc + * use stack array for less than 4 paramaters. Most sensirion i2c commands have less parameters + */ +bool SensirionI2CDevice::write_command_(uint16_t command, CommandLen command_len, const uint16_t *data, + uint8_t data_len) { + uint8_t temp_stack[BUFFER_STACK_SIZE]; + std::unique_ptr temp_heap; + uint8_t *temp; + size_t required_buffer_len = data_len * 3 + 2; + + // Is a dynamic allocation required ? + if (required_buffer_len >= BUFFER_STACK_SIZE) { + temp_heap = std::unique_ptr(new uint8_t[required_buffer_len]); + temp = temp_heap.get(); + } else { + temp = temp_stack; + } + // First byte or word is the command + uint8_t raw_idx = 0; + if (command_len == 1) { + temp[raw_idx++] = command & 0xFF; + } else { + // command is 2 bytes +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + temp[raw_idx++] = command >> 8; + temp[raw_idx++] = command & 0xFF; +#else + temp[raw_idx++] = command & 0xFF; + temp[raw_idx++] = command >> 8; +#endif + } + // add parameters folllowed by crc + // skipped if len == 0 + for (size_t i = 0; i < data_len; i++) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + temp[raw_idx++] = data[i] >> 8; + temp[raw_idx++] = data[i] & 0xFF; +#else + temp[raw_idx++] = data[i] & 0xFF; + temp[raw_idx++] = data[i] >> 8; +#endif + temp[raw_idx++] = sht_crc_(data[i]); + } + last_error_ = this->write(temp, raw_idx); + return last_error_ == i2c::ERROR_OK; +} + +bool SensirionI2CDevice::get_register_(uint16_t reg, CommandLen command_len, uint16_t *data, uint8_t len, + uint8_t delay_ms) { + if (!this->write_command_(reg, command_len, nullptr, 0)) { + ESP_LOGE(TAG, "Failed to write i2c register=0x%X (%d) err=%d,", reg, command_len, this->last_error_); + return false; + } + delay(delay_ms); + bool result = this->read_data(data, len); + if (!result) { + ESP_LOGE(TAG, "Failed to read data from register=0x%X err=%d,", reg, this->last_error_); + } + return result; +} + +// The 8-bit CRC checksum is transmitted after each data word +uint8_t SensirionI2CDevice::sht_crc_(uint16_t data) { + uint8_t bit; + uint8_t crc = 0xFF; +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + crc ^= data >> 8; +#else + crc ^= data & 0xFF; +#endif + for (bit = 8; bit > 0; --bit) { + if (crc & 0x80) { + crc = (crc << 1) ^ crc_polynomial_; + } else { + crc = (crc << 1); + } + } +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + crc ^= data & 0xFF; +#else + crc ^= data >> 8; +#endif + for (bit = 8; bit > 0; --bit) { + if (crc & 0x80) { + crc = (crc << 1) ^ crc_polynomial_; + } else { + crc = (crc << 1); + } + } + return crc; +} + +} // namespace sensirion_common +} // namespace esphome diff --git a/esphome/components/sensirion_common/i2c_sensirion.h b/esphome/components/sensirion_common/i2c_sensirion.h new file mode 100644 index 0000000000..88e1d59984 --- /dev/null +++ b/esphome/components/sensirion_common/i2c_sensirion.h @@ -0,0 +1,155 @@ +#pragma once +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace sensirion_common { + +/** + * Implementation of a i2c functions for Sensirion sensors + * Sensirion data requires crc checking. + * Each 16 bit word is/must be followed 8 bit CRC code + * (Applies to read and write - note the i2c command code doesn't need a CRC) + * Format: + * | 16 Bit Command Code | 16 bit Data word 1 | CRC of DW 1 | 16 bit Data word 1 | CRC of DW 2 | .. + */ +class SensirionI2CDevice : public i2c::I2CDevice { + public: + enum CommandLen : uint8_t { ADDR_8_BIT = 1, ADDR_16_BIT = 2 }; + + /** Read data words from i2c device. + * handles crc check used by Sensirion sensors + * @param data pointer to raw result + * @param len number of words to read + * @return true if reading succeded + */ + bool read_data(uint16_t *data, uint8_t len); + + /** Read 1 data word from i2c device. + * @param data reference to raw result + * @return true if reading succeded + */ + bool read_data(uint16_t &data) { return this->read_data(&data, 1); } + + /** get data words from i2c register. + * handles crc check used by Sensirion sensors + * @param i2c register + * @param data pointer to raw result + * @param len number of words to read + * @param delay milliseconds to to wait between sending the i2c commmand and reading the result + * @return true if reading succeded + */ + bool get_register(uint16_t command, uint16_t *data, uint8_t len, uint8_t delay = 0) { + return get_register_(command, ADDR_16_BIT, data, len, delay); + } + /** Read 1 data word from 16 bit i2c register. + * @param i2c register + * @param data reference to raw result + * @param delay milliseconds to to wait between sending the i2c commmand and reading the result + * @return true if reading succeded + */ + bool get_register(uint16_t i2c_register, uint16_t &data, uint8_t delay = 0) { + return this->get_register_(i2c_register, ADDR_16_BIT, &data, 1, delay); + } + + /** get data words from i2c register. + * handles crc check used by Sensirion sensors + * @param i2c register + * @param data pointer to raw result + * @param len number of words to read + * @param delay milliseconds to to wait between sending the i2c commmand and reading the result + * @return true if reading succeded + */ + bool get_8bit_register(uint8_t i2c_register, uint16_t *data, uint8_t len, uint8_t delay = 0) { + return get_register_(i2c_register, ADDR_8_BIT, data, len, delay); + } + + /** Read 1 data word from 8 bit i2c register. + * @param i2c register + * @param data reference to raw result + * @param delay milliseconds to to wait between sending the i2c commmand and reading the result + * @return true if reading succeded + */ + bool get_8bit_register(uint8_t i2c_register, uint16_t &data, uint8_t delay = 0) { + return this->get_register_(i2c_register, ADDR_8_BIT, &data, 1, delay); + } + + /** Write a command to the i2c device. + * @param command i2c command to send + * @return true if reading succeded + */ + template bool write_command(T i2c_register) { return write_command(i2c_register, nullptr, 0); } + + /** Write a command and one data word to the i2c device . + * @param command i2c command to send + * @param data argument for the i2c command + * @return true if reading succeded + */ + template bool write_command(T i2c_register, uint16_t data) { return write_command(i2c_register, &data, 1); } + + /** Write a command with arguments as words + * @param i2c_register i2c command to send - an be uint8_t or uint16_t + * @param data vector arguments for the i2c command + * @return true if reading succeded + */ + template bool write_command(T i2c_register, const std::vector &data) { + return write_command_(i2c_register, sizeof(T), data.data(), data.size()); + } + + /** Write a command with arguments as words + * @param i2c_register i2c command to send - an be uint8_t or uint16_t + * @param data arguments for the i2c command + * @param len number of arguments (words) + * @return true if reading succeded + */ + template bool write_command(T i2c_register, const uint16_t *data, uint8_t len) { + // limit to 8 or 16 bit only + static_assert(sizeof(i2c_register) == 1 || sizeof(i2c_register) == 2, + "only 8 or 16 bit command types are supported."); + return write_command_(i2c_register, CommandLen(sizeof(T)), data, len); + } + + protected: + uint8_t crc_polynomial_{0x31u}; // default for sensirion + /** Write a command with arguments as words + * @param command i2c command to send can be uint8_t or uint16_t + * @param command_len either 1 for short 8 bit command or 2 for 16 bit command codes + * @param data arguments for the i2c command + * @param data_len number of arguments (words) + * @return true if reading succeded + */ + bool write_command_(uint16_t command, CommandLen command_len, const uint16_t *data, uint8_t data_len); + + /** get data words from i2c register. + * handles crc check used by Sensirion sensors + * @param i2c register + * @param command_len either 1 for short 8 bit command or 2 for 16 bit command codes + * @param data pointer to raw result + * @param len number of words to read + * @param delay milliseconds to to wait between sending the i2c commmand and reading the result + * @return true if reading succeded + */ + bool get_register_(uint16_t reg, CommandLen command_len, uint16_t *data, uint8_t len, uint8_t delay); + + /** 8-bit CRC checksum that is transmitted after each data word for read and write operation + * @param command i2c command to send + * @param data data word for which the crc8 checksum is calculated + * @param len number of arguments (words) + * @return 8 Bit CRC + */ + uint8_t sht_crc_(uint16_t data); + + /** 8-bit CRC checksum that is transmitted after each data word for read and write operation + * @param command i2c command to send + * @param data1 high byte of data word + * @param data2 low byte of data word + * @return 8 Bit CRC + */ + uint8_t sht_crc_(uint8_t data1, uint8_t data2) { return sht_crc_(encode_uint16(data1, data2)); } + + /** last error code from i2c operation + */ + i2c::ErrorCode last_error_; +}; + +} // namespace sensirion_common +} // namespace esphome diff --git a/esphome/components/sgp30/sensor.py b/esphome/components/sgp30/sensor.py index 14a078b501..0029e2c515 100644 --- a/esphome/components/sgp30/sensor.py +++ b/esphome/components/sgp30/sensor.py @@ -1,10 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import i2c, sensor +from esphome.components import i2c, sensor, sensirion_common + from esphome.const import ( CONF_ID, CONF_BASELINE, CONF_ECO2, + CONF_STORE_BASELINE, + CONF_TEMPERATURE_SOURCE, CONF_TVOC, ICON_RADIATOR, DEVICE_CLASS_CARBON_DIOXIDE, @@ -17,17 +20,19 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] sgp30_ns = cg.esphome_ns.namespace("sgp30") -SGP30Component = sgp30_ns.class_("SGP30Component", cg.PollingComponent, i2c.I2CDevice) +SGP30Component = sgp30_ns.class_( + "SGP30Component", cg.PollingComponent, sensirion_common.SensirionI2CDevice +) CONF_ECO2_BASELINE = "eco2_baseline" CONF_TVOC_BASELINE = "tvoc_baseline" -CONF_STORE_BASELINE = "store_baseline" CONF_UPTIME = "uptime" CONF_COMPENSATION = "compensation" CONF_HUMIDITY_SOURCE = "humidity_source" -CONF_TEMPERATURE_SOURCE = "temperature_source" + CONFIG_SCHEMA = ( cv.Schema( diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index b55097fcd0..a6572620c4 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -36,14 +36,8 @@ void SGP30Component::setup() { ESP_LOGCONFIG(TAG, "Setting up SGP30..."); // Serial Number identification - if (!this->write_command_(SGP30_CMD_GET_SERIAL_ID)) { - this->error_code_ = COMMUNICATION_FAILED; - this->mark_failed(); - return; - } uint16_t raw_serial_number[3]; - - if (!this->read_data_(raw_serial_number, 3)) { + if (!this->get_register(SGP30_CMD_GET_SERIAL_ID, raw_serial_number, 3)) { this->mark_failed(); return; } @@ -52,16 +46,12 @@ void SGP30Component::setup() { ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_); // Featureset identification for future use - if (!this->write_command_(SGP30_CMD_GET_FEATURESET)) { + uint16_t raw_featureset; + if (!this->get_register(SGP30_CMD_GET_FEATURESET, raw_featureset)) { this->mark_failed(); return; } - uint16_t raw_featureset[1]; - if (!this->read_data_(raw_featureset, 1)) { - this->mark_failed(); - return; - } - this->featureset_ = raw_featureset[0]; + this->featureset_ = raw_featureset; if (uint16_t(this->featureset_ >> 12) != 0x0) { if (uint16_t(this->featureset_ >> 12) == 0x1) { // ID matching a different sensor: SGPC3 @@ -76,7 +66,7 @@ void SGP30Component::setup() { ESP_LOGD(TAG, "Product version: 0x%0X", uint16_t(this->featureset_ & 0x1FF)); // Sensor initialization - if (!this->write_command_(SGP30_CMD_IAQ_INIT)) { + if (!this->write_command(SGP30_CMD_IAQ_INIT)) { ESP_LOGE(TAG, "Sensor sgp30_iaq_init failed."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); @@ -119,14 +109,14 @@ bool SGP30Component::is_sensor_baseline_reliable_() { void SGP30Component::read_iaq_baseline_() { if (this->is_sensor_baseline_reliable_()) { - if (!this->write_command_(SGP30_CMD_GET_IAQ_BASELINE)) { + if (!this->write_command(SGP30_CMD_GET_IAQ_BASELINE)) { ESP_LOGD(TAG, "Error getting baseline"); this->status_set_warning(); return; } this->set_timeout(50, [this]() { uint16_t raw_data[2]; - if (!this->read_data_(raw_data, 2)) { + if (!this->read_data(raw_data, 2)) { this->status_set_warning(); return; } @@ -274,14 +264,14 @@ void SGP30Component::dump_config() { } void SGP30Component::update() { - if (!this->write_command_(SGP30_CMD_MEASURE_IAQ)) { + if (!this->write_command(SGP30_CMD_MEASURE_IAQ)) { this->status_set_warning(); return; } this->seconds_since_last_store_ += this->update_interval_ / 1000; this->set_timeout(50, [this]() { uint16_t raw_data[2]; - if (!this->read_data_(raw_data, 2)) { + if (!this->read_data(raw_data, 2)) { this->status_set_warning(); return; } @@ -305,56 +295,5 @@ void SGP30Component::update() { }); } -bool SGP30Component::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); -} - -uint8_t SGP30Component::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 SGP30Component::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; -} - } // namespace sgp30 } // namespace esphome diff --git a/esphome/components/sgp30/sgp30.h b/esphome/components/sgp30/sgp30.h index 91a1c1e9c7..d61eee00db 100644 --- a/esphome/components/sgp30/sgp30.h +++ b/esphome/components/sgp30/sgp30.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" #include "esphome/core/preferences.h" #include @@ -15,7 +15,7 @@ struct SGP30Baselines { } PACKED; /// This class implements support for the Sensirion SGP30 i2c GAS (VOC and CO2eq) sensors. -class SGP30Component : public PollingComponent, public i2c::I2CDevice { +class SGP30Component : public PollingComponent, public sensirion_common::SensirionI2CDevice { public: void set_eco2_sensor(sensor::Sensor *eco2) { eco2_sensor_ = eco2; } void set_tvoc_sensor(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } @@ -33,13 +33,10 @@ class SGP30Component : public PollingComponent, public i2c::I2CDevice { float get_setup_priority() const override { return setup_priority::DATA; } protected: - bool write_command_(uint16_t command); - bool read_data_(uint16_t *data, uint8_t len); void send_env_data_(); void read_iaq_baseline_(); bool is_sensor_baseline_reliable_(); void write_iaq_baseline_(uint16_t eco2_baseline, uint16_t tvoc_baseline); - uint8_t sht_crc_(uint8_t data1, uint8_t data2); uint64_t serial_number_; uint16_t featureset_; uint32_t required_warm_up_time_; diff --git a/esphome/components/sgp40/sensor.py b/esphome/components/sgp40/sensor.py index 6f27b54fb0..ee267d6062 100644 --- a/esphome/components/sgp40/sensor.py +++ b/esphome/components/sgp40/sensor.py @@ -1,25 +1,30 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import i2c, sensor +from esphome.components import i2c, sensor, sensirion_common + from esphome.const import ( + CONF_STORE_BASELINE, + CONF_TEMPERATURE_SOURCE, ICON_RADIATOR, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, STATE_CLASS_MEASUREMENT, ) DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] CODEOWNERS = ["@SenexCrenshaw"] sgp40_ns = cg.esphome_ns.namespace("sgp40") SGP40Component = sgp40_ns.class_( - "SGP40Component", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice + "SGP40Component", + sensor.Sensor, + cg.PollingComponent, + sensirion_common.SensirionI2CDevice, ) CONF_COMPENSATION = "compensation" CONF_HUMIDITY_SOURCE = "humidity_source" -CONF_TEMPERATURE_SOURCE = "temperature_source" -CONF_STORE_BASELINE = "store_baseline" CONF_VOC_BASELINE = "voc_baseline" CONFIG_SCHEMA = ( diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index 829c00a218..9d78572b50 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -12,14 +12,14 @@ void SGP40Component::setup() { ESP_LOGCONFIG(TAG, "Setting up SGP40..."); // Serial Number identification - if (!this->write_command_(SGP40_CMD_GET_SERIAL_ID)) { + if (!this->write_command(SGP40_CMD_GET_SERIAL_ID)) { this->error_code_ = COMMUNICATION_FAILED; this->mark_failed(); return; } uint16_t raw_serial_number[3]; - if (!this->read_data_(raw_serial_number, 3)) { + if (!this->read_data(raw_serial_number, 3)) { this->mark_failed(); return; } @@ -28,19 +28,19 @@ void SGP40Component::setup() { ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_); // Featureset identification for future use - if (!this->write_command_(SGP40_CMD_GET_FEATURESET)) { + if (!this->write_command(SGP40_CMD_GET_FEATURESET)) { ESP_LOGD(TAG, "raw_featureset write_command_ failed"); this->mark_failed(); return; } - uint16_t raw_featureset[1]; - if (!this->read_data_(raw_featureset, 1)) { + uint16_t raw_featureset; + if (!this->read_data(raw_featureset)) { ESP_LOGD(TAG, "raw_featureset read_data_ failed"); this->mark_failed(); return; } - this->featureset_ = raw_featureset[0]; + this->featureset_ = raw_featureset; if ((this->featureset_ & 0x1FF) != SGP40_FEATURESET) { ESP_LOGD(TAG, "Product feature set failed 0x%0X , expecting 0x%0X", uint16_t(this->featureset_ & 0x1FF), SGP40_FEATURESET); @@ -95,21 +95,21 @@ void SGP40Component::setup() { void SGP40Component::self_test_() { ESP_LOGD(TAG, "Self-test started"); - if (!this->write_command_(SGP40_CMD_SELF_TEST)) { + if (!this->write_command(SGP40_CMD_SELF_TEST)) { this->error_code_ = COMMUNICATION_FAILED; ESP_LOGD(TAG, "Self-test communication failed"); this->mark_failed(); } this->set_timeout(250, [this]() { - uint16_t reply[1]; - if (!this->read_data_(reply, 1)) { + uint16_t reply; + if (!this->read_data(reply)) { ESP_LOGD(TAG, "Self-test read_data_ failed"); this->mark_failed(); return; } - if (reply[0] == 0xD400) { + if (reply == 0xD400) { this->self_test_complete_ = true; ESP_LOGD(TAG, "Self-test completed"); return; @@ -192,51 +192,28 @@ uint16_t SGP40Component::measure_raw_() { temperature = 25; } - uint8_t command[8]; - - command[0] = 0x26; - command[1] = 0x0F; - + uint16_t data[2]; uint16_t rhticks = llround((uint16_t)((humidity * 65535) / 100)); - command[2] = rhticks >> 8; - command[3] = rhticks & 0xFF; - command[4] = generate_crc_(command + 2, 2); uint16_t tempticks = (uint16_t)(((temperature + 45) * 65535) / 175); - command[5] = tempticks >> 8; - command[6] = tempticks & 0xFF; - command[7] = generate_crc_(command + 5, 2); + // first paramater is the relative humidity ticks + data[0] = rhticks; + // second paramater is the temperature ticks + data[1] = tempticks; - if (this->write(command, 8) != i2c::ERROR_OK) { + if (!this->write_command(SGP40_CMD_MEASURE_RAW, data, 2)) { this->status_set_warning(); - ESP_LOGD(TAG, "write error"); - return UINT16_MAX; + ESP_LOGD(TAG, "write error (%d)", this->last_error_); + return false; } delay(30); - uint16_t raw_data[1]; - if (!this->read_data_(raw_data, 1)) { + uint16_t raw_data; + if (!this->read_data(raw_data)) { this->status_set_warning(); ESP_LOGD(TAG, "read_data_ error"); return UINT16_MAX; } - return raw_data[0]; -} - -uint8_t SGP40Component::generate_crc_(const uint8_t *data, uint8_t datalen) { - // calculates 8-Bit checksum with given polynomial - uint8_t crc = SGP40_CRC8_INIT; - - for (uint8_t i = 0; i < datalen; i++) { - crc ^= data[i]; - for (uint8_t b = 0; b < 8; b++) { - if (crc & 0x80) { - crc = (crc << 1) ^ SGP40_CRC8_POLYNOMIAL; - } else { - crc <<= 1; - } - } - } - return crc; + return raw_data; } void SGP40Component::update_voc_index() { @@ -293,56 +270,5 @@ void SGP40Component::dump_config() { } } -bool SGP40Component::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); -} - -uint8_t SGP40Component::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 SGP40Component::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; -} - } // namespace sgp40 } // namespace esphome diff --git a/esphome/components/sgp40/sgp40.h b/esphome/components/sgp40/sgp40.h index c854b21060..c5b7d2dfa0 100644 --- a/esphome/components/sgp40/sgp40.h +++ b/esphome/components/sgp40/sgp40.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" #include "esphome/core/application.h" #include "esphome/core/preferences.h" #include "sensirion_voc_algorithm.h" @@ -28,6 +28,7 @@ static const uint8_t SGP40_WORD_LEN = 2; ///< 2 bytes per word static const uint16_t SGP40_CMD_GET_SERIAL_ID = 0x3682; static const uint16_t SGP40_CMD_GET_FEATURESET = 0x202f; static const uint16_t SGP40_CMD_SELF_TEST = 0x280e; +static const uint16_t SGP40_CMD_MEASURE_RAW = 0x260F; // Shortest time interval of 3H for storing baseline values. // Prevents wear of the flash because of too many write operations @@ -39,7 +40,7 @@ const uint32_t MAXIMUM_STORAGE_DIFF = 50; class SGP40Component; /// This class implements support for the Sensirion sgp40 i2c GAS (VOC) sensors. -class SGP40Component : public PollingComponent, public sensor::Sensor, public i2c::I2CDevice { +class SGP40Component : public PollingComponent, public sensor::Sensor, public sensirion_common::SensirionI2CDevice { public: void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } void set_temperature_sensor(sensor::Sensor *temperature) { temperature_sensor_ = temperature; } @@ -55,11 +56,8 @@ class SGP40Component : public PollingComponent, public sensor::Sensor, public i2 /// Input sensor for humidity and temperature compensation. sensor::Sensor *humidity_sensor_{nullptr}; sensor::Sensor *temperature_sensor_{nullptr}; - bool write_command_(uint16_t command); - bool read_data_(uint16_t *data, uint8_t len); int16_t sensirion_init_sensors_(); int16_t sgp40_probe_(); - uint8_t sht_crc_(uint8_t data1, uint8_t data2); uint64_t serial_number_; uint16_t featureset_; int32_t measure_voc_index_(); diff --git a/esphome/components/sht3xd/sensor.py b/esphome/components/sht3xd/sensor.py index b9e7bce733..8e1ef426ad 100644 --- a/esphome/components/sht3xd/sensor.py +++ b/esphome/components/sht3xd/sensor.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import i2c, sensor +from esphome.components import i2c, sensor, sensirion_common from esphome.const import ( CONF_HUMIDITY, CONF_ID, @@ -13,10 +13,11 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] sht3xd_ns = cg.esphome_ns.namespace("sht3xd") SHT3XDComponent = sht3xd_ns.class_( - "SHT3XDComponent", cg.PollingComponent, i2c.I2CDevice + "SHT3XDComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice ) CONFIG_SCHEMA = ( diff --git a/esphome/components/sht3xd/sht3xd.cpp b/esphome/components/sht3xd/sht3xd.cpp index e7981b64cf..4e1c9742bc 100644 --- a/esphome/components/sht3xd/sht3xd.cpp +++ b/esphome/components/sht3xd/sht3xd.cpp @@ -17,13 +17,8 @@ static const uint16_t SHT3XD_COMMAND_FETCH_DATA = 0xE000; void SHT3XDComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up SHT3xD..."); - if (!this->write_command_(SHT3XD_COMMAND_READ_SERIAL_NUMBER)) { - this->mark_failed(); - return; - } - uint16_t raw_serial_number[2]; - if (!this->read_data_(raw_serial_number, 2)) { + if (!this->get_register(SHT3XD_COMMAND_READ_SERIAL_NUMBER, raw_serial_number, 2)) { this->mark_failed(); return; } @@ -45,16 +40,16 @@ float SHT3XDComponent::get_setup_priority() const { return setup_priority::DATA; void SHT3XDComponent::update() { if (this->status_has_warning()) { ESP_LOGD(TAG, "Retrying to reconnect the sensor."); - this->write_command_(SHT3XD_COMMAND_SOFT_RESET); + this->write_command(SHT3XD_COMMAND_SOFT_RESET); } - if (!this->write_command_(SHT3XD_COMMAND_POLLING_H)) { + if (!this->write_command(SHT3XD_COMMAND_POLLING_H)) { this->status_set_warning(); return; } this->set_timeout(50, [this]() { uint16_t raw_data[2]; - if (!this->read_data_(raw_data, 2)) { + if (!this->read_data(raw_data, 2)) { this->status_set_warning(); return; } @@ -71,56 +66,5 @@ void SHT3XDComponent::update() { }); } -bool SHT3XDComponent::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); -} - -uint8_t 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 SHT3XDComponent::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; -} - } // namespace sht3xd } // namespace esphome diff --git a/esphome/components/sht3xd/sht3xd.h b/esphome/components/sht3xd/sht3xd.h index 709f8aebe7..3164aa0687 100644 --- a/esphome/components/sht3xd/sht3xd.h +++ b/esphome/components/sht3xd/sht3xd.h @@ -2,13 +2,13 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" namespace esphome { namespace sht3xd { /// This class implements support for the SHT3x-DIS family of temperature+humidity i2c sensors. -class SHT3XDComponent : public PollingComponent, public i2c::I2CDevice { +class SHT3XDComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice { public: void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } @@ -19,9 +19,6 @@ class SHT3XDComponent : public PollingComponent, public i2c::I2CDevice { void update() override; protected: - bool write_command_(uint16_t command); - bool read_data_(uint16_t *data, uint8_t len); - sensor::Sensor *temperature_sensor_; sensor::Sensor *humidity_sensor_; }; diff --git a/esphome/components/sht4x/sensor.py b/esphome/components/sht4x/sensor.py index a66ca1a526..9fb8fc969e 100644 --- a/esphome/components/sht4x/sensor.py +++ b/esphome/components/sht4x/sensor.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import i2c, sensor +from esphome.components import i2c, sensor, sensirion_common from esphome.const import ( CONF_ID, CONF_TEMPERATURE, @@ -16,10 +16,13 @@ from esphome.const import ( CODEOWNERS = ["@sjtrny"] DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] sht4x_ns = cg.esphome_ns.namespace("sht4x") -SHT4XComponent = sht4x_ns.class_("SHT4XComponent", cg.PollingComponent, i2c.I2CDevice) +SHT4XComponent = sht4x_ns.class_( + "SHT4XComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice +) CONF_PRECISION = "precision" SHT4XPRECISION = sht4x_ns.enum("SHT4XPRECISION") diff --git a/esphome/components/sht4x/sht4x.cpp b/esphome/components/sht4x/sht4x.cpp index 248f32c4de..bdc3e62d2f 100644 --- a/esphome/components/sht4x/sht4x.cpp +++ b/esphome/components/sht4x/sht4x.cpp @@ -50,31 +50,28 @@ void SHT4XComponent::setup() { void SHT4XComponent::dump_config() { LOG_I2C_DEVICE(this); } void SHT4XComponent::update() { - uint8_t cmd[] = {MEASURECOMMANDS[this->precision_]}; - // Send command - this->write(cmd, 1); + this->write_command(MEASURECOMMANDS[this->precision_]); this->set_timeout(10, [this]() { - const uint8_t num_bytes = 6; - uint8_t buffer[num_bytes]; + uint16_t buffer[2]; // Read measurement - bool read_status = this->read_bytes_raw(buffer, num_bytes); + bool read_status = this->read_data(buffer, 2); if (read_status) { // Evaluate and publish measurements if (this->temp_sensor_ != nullptr) { - // Temp is contained in the first 16 bits - float sensor_value_temp = (buffer[0] << 8) + buffer[1]; + // Temp is contained in the first result word + float sensor_value_temp = buffer[0]; float temp = -45 + 175 * sensor_value_temp / 65535; this->temp_sensor_->publish_state(temp); } if (this->humidity_sensor_ != nullptr) { - // Relative humidity is in the last 16 bits - float sensor_value_rh = (buffer[3] << 8) + buffer[4]; + // Relative humidity is in the second result word + float sensor_value_rh = buffer[1]; float rh = -6 + 125 * sensor_value_rh / 65535; this->humidity_sensor_->publish_state(rh); diff --git a/esphome/components/sht4x/sht4x.h b/esphome/components/sht4x/sht4x.h index 8694bd9879..01553d5c15 100644 --- a/esphome/components/sht4x/sht4x.h +++ b/esphome/components/sht4x/sht4x.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" namespace esphome { namespace sht4x { @@ -13,7 +13,7 @@ enum SHT4XHEATERPOWER { SHT4X_HEATERPOWER_HIGH, SHT4X_HEATERPOWER_MED, SHT4X_HEA enum SHT4XHEATERTIME { SHT4X_HEATERTIME_LONG = 1100, SHT4X_HEATERTIME_SHORT = 110 }; -class SHT4XComponent : public PollingComponent, public i2c::I2CDevice { +class SHT4XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice { public: float get_setup_priority() const override { return setup_priority::DATA; } void setup() override; diff --git a/esphome/components/shtcx/sensor.py b/esphome/components/shtcx/sensor.py index ba2283a9b4..c8b56cfe30 100644 --- a/esphome/components/shtcx/sensor.py +++ b/esphome/components/shtcx/sensor.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import i2c, sensor +from esphome.components import i2c, sensor, sensirion_common from esphome.const import ( CONF_HUMIDITY, CONF_ID, @@ -13,9 +13,12 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] shtcx_ns = cg.esphome_ns.namespace("shtcx") -SHTCXComponent = shtcx_ns.class_("SHTCXComponent", cg.PollingComponent, i2c.I2CDevice) +SHTCXComponent = shtcx_ns.class_( + "SHTCXComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice +) SHTCXType = shtcx_ns.enum("SHTCXType") diff --git a/esphome/components/shtcx/shtcx.cpp b/esphome/components/shtcx/shtcx.cpp index 4112270c02..0de56a8044 100644 --- a/esphome/components/shtcx/shtcx.cpp +++ b/esphome/components/shtcx/shtcx.cpp @@ -29,14 +29,14 @@ void SHTCXComponent::setup() { this->wake_up(); this->soft_reset(); - if (!this->write_command_(SHTCX_COMMAND_READ_ID_REGISTER)) { + if (!this->write_command(SHTCX_COMMAND_READ_ID_REGISTER)) { ESP_LOGE(TAG, "Error requesting Device ID"); this->mark_failed(); return; } uint16_t device_id_register; - if (!this->read_data_(&device_id_register, 1)) { + if (!this->read_data(&device_id_register, 1)) { ESP_LOGE(TAG, "Error reading Device ID"); this->mark_failed(); return; @@ -76,7 +76,7 @@ void SHTCXComponent::update() { if (this->type_ != SHTCX_TYPE_SHTC1) { this->wake_up(); } - if (!this->write_command_(SHTCX_COMMAND_POLLING_H)) { + if (!this->write_command(SHTCX_COMMAND_POLLING_H)) { ESP_LOGE(TAG, "sensor polling failed"); if (this->temperature_sensor_ != nullptr) this->temperature_sensor_->publish_state(NAN); @@ -90,7 +90,7 @@ void SHTCXComponent::update() { float temperature = NAN; float humidity = NAN; uint16_t raw_data[2]; - if (!this->read_data_(raw_data, 2)) { + if (!this->read_data(raw_data, 2)) { ESP_LOGE(TAG, "sensor read failed"); this->status_set_warning(); } else { @@ -110,65 +110,14 @@ void SHTCXComponent::update() { }); } -bool SHTCXComponent::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); -} - -uint8_t 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 SHTCXComponent::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; -} - void SHTCXComponent::soft_reset() { - this->write_command_(SHTCX_COMMAND_SOFT_RESET); + this->write_command(SHTCX_COMMAND_SOFT_RESET); delayMicroseconds(200); } -void SHTCXComponent::sleep() { this->write_command_(SHTCX_COMMAND_SLEEP); } +void SHTCXComponent::sleep() { this->write_command(SHTCX_COMMAND_SLEEP); } void SHTCXComponent::wake_up() { - this->write_command_(SHTCX_COMMAND_WAKEUP); + this->write_command(SHTCX_COMMAND_WAKEUP); delayMicroseconds(200); } diff --git a/esphome/components/shtcx/shtcx.h b/esphome/components/shtcx/shtcx.h index cb2b46d348..c44fb9d9c1 100644 --- a/esphome/components/shtcx/shtcx.h +++ b/esphome/components/shtcx/shtcx.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" namespace esphome { namespace shtcx { @@ -10,7 +10,7 @@ namespace shtcx { enum SHTCXType { SHTCX_TYPE_SHTC3 = 0, SHTCX_TYPE_SHTC1, SHTCX_TYPE_UNKNOWN }; /// This class implements support for the SHT3x-DIS family of temperature+humidity i2c sensors. -class SHTCXComponent : public PollingComponent, public i2c::I2CDevice { +class SHTCXComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice { public: void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } @@ -24,8 +24,6 @@ class SHTCXComponent : public PollingComponent, public i2c::I2CDevice { void wake_up(); protected: - bool write_command_(uint16_t command); - bool read_data_(uint16_t *data, uint8_t len); SHTCXType type_; uint16_t sensor_id_; sensor::Sensor *temperature_sensor_; diff --git a/esphome/components/sps30/sensor.py b/esphome/components/sps30/sensor.py index 27264cf942..89cb25c24f 100644 --- a/esphome/components/sps30/sensor.py +++ b/esphome/components/sps30/sensor.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import i2c, sensor +from esphome.components import i2c, sensor, sensirion_common from esphome.const import ( CONF_ID, CONF_PM_1_0, @@ -26,9 +26,12 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] sps30_ns = cg.esphome_ns.namespace("sps30") -SPS30Component = sps30_ns.class_("SPS30Component", cg.PollingComponent, i2c.I2CDevice) +SPS30Component = sps30_ns.class_( + "SPS30Component", cg.PollingComponent, sensirion_common.SensirionI2CDevice +) CONFIG_SCHEMA = ( cv.Schema( diff --git a/esphome/components/sps30/sps30.cpp b/esphome/components/sps30/sps30.cpp index 2bd7bcb458..2885125a8a 100644 --- a/esphome/components/sps30/sps30.cpp +++ b/esphome/components/sps30/sps30.cpp @@ -22,30 +22,18 @@ static const uint8_t MAX_SKIPPED_DATA_CYCLES_BEFORE_ERROR = 5; void SPS30Component::setup() { ESP_LOGCONFIG(TAG, "Setting up sps30..."); - this->write_command_(SPS30_CMD_SOFT_RESET); + this->write_command(SPS30_CMD_SOFT_RESET); /// Deferred Sensor initialization this->set_timeout(500, [this]() { /// Firmware version identification - if (!this->write_command_(SPS30_CMD_GET_FIRMWARE_VERSION)) { - this->error_code_ = FIRMWARE_VERSION_REQUEST_FAILED; - this->mark_failed(); - return; - } - - if (!this->read_data_(&raw_firmware_version_, 1)) { + if (!this->get_register(SPS30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version_, 1)) { this->error_code_ = FIRMWARE_VERSION_READ_FAILED; this->mark_failed(); return; } /// Serial number identification - if (!this->write_command_(SPS30_CMD_GET_SERIAL_NUMBER)) { - this->error_code_ = SERIAL_NUMBER_REQUEST_FAILED; - this->mark_failed(); - return; - } - uint16_t raw_serial_number[8]; - if (!this->read_data_(raw_serial_number, 8)) { + if (!this->get_register(SPS30_CMD_GET_SERIAL_NUMBER, raw_serial_number, 8, 1)) { this->error_code_ = SERIAL_NUMBER_READ_FAILED; this->mark_failed(); return; @@ -109,7 +97,7 @@ void SPS30Component::update() { /// Check if warning flag active (sensor reconnected?) if (this->status_has_warning()) { ESP_LOGD(TAG, "Trying to reconnect the sensor..."); - if (this->write_command_(SPS30_CMD_SOFT_RESET)) { + if (this->write_command(SPS30_CMD_SOFT_RESET)) { ESP_LOGD(TAG, "Sensor has soft-reset successfully. Waiting for reconnection in 500ms..."); this->set_timeout(500, [this]() { this->start_continuous_measurement_(); @@ -124,13 +112,13 @@ void SPS30Component::update() { return; } /// Check if measurement is ready before reading the value - if (!this->write_command_(SPS30_CMD_GET_DATA_READY_STATUS)) { + if (!this->write_command(SPS30_CMD_GET_DATA_READY_STATUS)) { this->status_set_warning(); return; } uint16_t raw_read_status; - if (!this->read_data_(&raw_read_status, 1) || raw_read_status == 0x00) { + 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 @@ -142,7 +130,7 @@ void SPS30Component::update() { return; } - if (!this->write_command_(SPS30_CMD_READ_MEASUREMENT)) { + if (!this->write_command(SPS30_CMD_READ_MEASUREMENT)) { ESP_LOGW(TAG, "Error reading measurement status!"); this->status_set_warning(); return; @@ -150,7 +138,7 @@ void SPS30Component::update() { this->set_timeout(50, [this]() { uint16_t raw_data[20]; - if (!this->read_data_(raw_data, 20)) { + if (!this->read_data(raw_data, 20)) { ESP_LOGW(TAG, "Error reading measurement data!"); this->status_set_warning(); return; @@ -205,69 +193,18 @@ void SPS30Component::update() { }); } -bool SPS30Component::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); -} - -uint8_t SPS30Component::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 SPS30Component::start_continuous_measurement_() { uint8_t data[4]; data[0] = SPS30_CMD_START_CONTINUOUS_MEASUREMENTS & 0xFF; data[1] = 0x03; data[2] = 0x00; data[3] = sht_crc_(0x03, 0x00); - if (!this->write_bytes(SPS30_CMD_START_CONTINUOUS_MEASUREMENTS >> 8, data, 4)) { + if (!this->write_command(SPS30_CMD_START_CONTINUOUS_MEASUREMENTS, SPS30_CMD_START_CONTINUOUS_MEASUREMENTS_ARG)) { ESP_LOGE(TAG, "Error initiating measurements"); return false; } return true; } -bool SPS30Component::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; -} - } // namespace sps30 } // namespace esphome diff --git a/esphome/components/sps30/sps30.h b/esphome/components/sps30/sps30.h index bae33a46e1..9a93df8597 100644 --- a/esphome/components/sps30/sps30.h +++ b/esphome/components/sps30/sps30.h @@ -2,14 +2,14 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" namespace esphome { namespace sps30 { /// This class implements support for the Sensirion SPS30 i2c/UART Particulate Matter /// PM1.0, PM2.5, PM4, PM10 Air Quality sensors. -class SPS30Component : public PollingComponent, public i2c::I2CDevice { +class SPS30Component : public PollingComponent, public sensirion_common::SensirionI2CDevice { public: void set_pm_1_0_sensor(sensor::Sensor *pm_1_0) { pm_1_0_sensor_ = pm_1_0; } void set_pm_2_5_sensor(sensor::Sensor *pm_2_5) { pm_2_5_sensor_ = pm_2_5; } @@ -29,9 +29,6 @@ class SPS30Component : public PollingComponent, public i2c::I2CDevice { float get_setup_priority() const override { return setup_priority::DATA; } protected: - bool write_command_(uint16_t command); - 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_(); diff --git a/esphome/components/sts3x/sensor.py b/esphome/components/sts3x/sensor.py index aa7573aaf2..a99503a2b8 100644 --- a/esphome/components/sts3x/sensor.py +++ b/esphome/components/sts3x/sensor.py @@ -8,6 +8,7 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] sts3x_ns = cg.esphome_ns.namespace("sts3x") diff --git a/esphome/components/sts3x/sts3x.cpp b/esphome/components/sts3x/sts3x.cpp index ce166f2055..5af808b6e7 100644 --- a/esphome/components/sts3x/sts3x.cpp +++ b/esphome/components/sts3x/sts3x.cpp @@ -19,13 +19,13 @@ static const uint16_t STS3X_COMMAND_FETCH_DATA = 0xE000; void STS3XComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up STS3x..."); - if (!this->write_command_(STS3X_COMMAND_READ_SERIAL_NUMBER)) { + if (!this->write_command(STS3X_COMMAND_READ_SERIAL_NUMBER)) { this->mark_failed(); return; } uint16_t raw_serial_number[2]; - if (!this->read_data_(raw_serial_number, 1)) { + if (!this->read_data(raw_serial_number, 1)) { this->mark_failed(); return; } @@ -46,16 +46,16 @@ float STS3XComponent::get_setup_priority() const { return setup_priority::DATA; void STS3XComponent::update() { if (this->status_has_warning()) { ESP_LOGD(TAG, "Retrying to reconnect the sensor."); - this->write_command_(STS3X_COMMAND_SOFT_RESET); + this->write_command(STS3X_COMMAND_SOFT_RESET); } - if (!this->write_command_(STS3X_COMMAND_POLLING_H)) { + if (!this->write_command(STS3X_COMMAND_POLLING_H)) { this->status_set_warning(); return; } this->set_timeout(50, [this]() { uint16_t raw_data[1]; - if (!this->read_data_(raw_data, 1)) { + if (!this->read_data(raw_data, 1)) { this->status_set_warning(); return; } @@ -67,56 +67,5 @@ void STS3XComponent::update() { }); } -bool STS3XComponent::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); -} - -uint8_t sts3x_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 STS3XComponent::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 = sts3x_crc(buf[j], buf[j + 1]); - if (crc != buf[j + 2]) { - ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - return false; - } - data[i] = (buf[j] << 8) | buf[j + 1]; - } - - return true; -} - } // namespace sts3x } // namespace esphome diff --git a/esphome/components/sts3x/sts3x.h b/esphome/components/sts3x/sts3x.h index 436cf938d8..261033efad 100644 --- a/esphome/components/sts3x/sts3x.h +++ b/esphome/components/sts3x/sts3x.h @@ -2,22 +2,18 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" namespace esphome { namespace sts3x { /// This class implements support for the ST3x-DIS family of temperature i2c sensors. -class STS3XComponent : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { +class STS3XComponent : public sensor::Sensor, public PollingComponent, public sensirion_common::SensirionI2CDevice { public: void setup() override; void dump_config() override; float get_setup_priority() const override; void update() override; - - protected: - bool write_command_(uint16_t command); - bool read_data_(uint16_t *data, uint8_t len); }; } // namespace sts3x diff --git a/esphome/const.py b/esphome/const.py index 46c9a659be..9096e66f4e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -644,6 +644,7 @@ CONF_STEP_MODE = "step_mode" CONF_STEP_PIN = "step_pin" CONF_STOP = "stop" CONF_STOP_ACTION = "stop_action" +CONF_STORE_BASELINE = "store_baseline" CONF_SUBNET = "subnet" CONF_SUBSTITUTIONS = "substitutions" CONF_SUPPLEMENTAL_COOLING_ACTION = "supplemental_cooling_action" @@ -680,6 +681,7 @@ CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC = "target_temperature_low_command_topi 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_SOURCE = "temperature_source" CONF_TEMPERATURE_STEP = "temperature_step" CONF_TEXT_SENSORS = "text_sensors" CONF_THEN = "then" From a519e5c4754f655bd130e726173e45010ff1e90b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Apr 2022 12:26:25 +1200 Subject: [PATCH 222/238] Fix silent config errors (#3380) --- esphome/config.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/esphome/config.py b/esphome/config.py index af6c5b0b64..a878f3ef79 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -161,6 +161,7 @@ class Config(OrderedDict, fv.FinalValidateConfig): # type: (ConfigPath) -> Optional[vol.Invalid] for err in self.errors: if self.get_deepest_path(err.path) == path: + self.errors.remove(err) return err return None @@ -647,7 +648,7 @@ class FinalValidateValidationStep(ConfigValidationStep): fv.full_config.reset(token) -def validate_config(config, command_line_substitutions): +def validate_config(config, command_line_substitutions) -> Config: result = Config() loader.clear_component_meta_finders() @@ -734,9 +735,6 @@ def validate_config(config, command_line_substitutions): result.add_validation_step(LoadValidationStep(key, config[key])) result.run_validation_steps() - if result.errors: - return result - for domain, conf in config.items(): result.add_validation_step(LoadValidationStep(domain, conf)) result.add_validation_step(IDPassValidationStep()) @@ -991,5 +989,10 @@ def read_config(command_line_substitutions): errstr += f" {errline}" safe_print(errstr) safe_print(indent(dump_dict(res, path)[0])) + + for err in res.errors: + safe_print(color(Fore.BOLD_RED, err.msg)) + safe_print("") + return None return OrderedDict(res) From b622a8fa5896a67d44ddae3f77c9d9381ca6b81e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Apr 2022 12:26:55 +1200 Subject: [PATCH 223/238] Move PN532OnTagTrigger to nfc::NfcOnTagTrigger (#3379) --- esphome/components/nfc/__init__.py | 5 +++++ esphome/components/nfc/automation.cpp | 9 +++++++++ esphome/components/nfc/automation.h | 17 +++++++++++++++++ esphome/components/pn532/__init__.py | 7 ++----- esphome/components/pn532/pn532.cpp | 7 ++----- esphome/components/pn532/pn532.h | 15 +++++---------- 6 files changed, 40 insertions(+), 20 deletions(-) create mode 100644 esphome/components/nfc/automation.cpp create mode 100644 esphome/components/nfc/automation.h diff --git a/esphome/components/nfc/__init__.py b/esphome/components/nfc/__init__.py index b795a5d5ca..c3bbc50bf9 100644 --- a/esphome/components/nfc/__init__.py +++ b/esphome/components/nfc/__init__.py @@ -1,3 +1,4 @@ +from esphome import automation import esphome.codegen as cg CODEOWNERS = ["@jesserockz"] @@ -5,3 +6,7 @@ CODEOWNERS = ["@jesserockz"] nfc_ns = cg.esphome_ns.namespace("nfc") NfcTag = nfc_ns.class_("NfcTag") + +NfcOnTagTrigger = nfc_ns.class_( + "NfcOnTagTrigger", automation.Trigger.template(cg.std_string, NfcTag) +) diff --git a/esphome/components/nfc/automation.cpp b/esphome/components/nfc/automation.cpp new file mode 100644 index 0000000000..ff00340df0 --- /dev/null +++ b/esphome/components/nfc/automation.cpp @@ -0,0 +1,9 @@ +#include "automation.h" + +namespace esphome { +namespace nfc { + +void NfcOnTagTrigger::process(const std::unique_ptr &tag) { this->trigger(format_uid(tag->get_uid()), *tag); } + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/automation.h b/esphome/components/nfc/automation.h new file mode 100644 index 0000000000..565b71bdd9 --- /dev/null +++ b/esphome/components/nfc/automation.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include "esphome/core/automation.h" + +#include "nfc.h" + +namespace esphome { +namespace nfc { + +class NfcOnTagTrigger : public Trigger { + public: + void process(const std::unique_ptr &tag); +}; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/pn532/__init__.py b/esphome/components/pn532/__init__.py index b902e8e3d0..2f120bc983 100644 --- a/esphome/components/pn532/__init__.py +++ b/esphome/components/pn532/__init__.py @@ -14,9 +14,6 @@ CONF_ON_FINISHED_WRITE = "on_finished_write" pn532_ns = cg.esphome_ns.namespace("pn532") PN532 = pn532_ns.class_("PN532", cg.PollingComponent) -PN532OnTagTrigger = pn532_ns.class_( - "PN532OnTagTrigger", automation.Trigger.template(cg.std_string, nfc.NfcTag) -) PN532OnFinishedWriteTrigger = pn532_ns.class_( "PN532OnFinishedWriteTrigger", automation.Trigger.template() ) @@ -30,7 +27,7 @@ PN532_SCHEMA = cv.Schema( cv.GenerateID(): cv.declare_id(PN532), cv.Optional(CONF_ON_TAG): automation.validate_automation( { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532OnTagTrigger), + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger), } ), cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation( @@ -42,7 +39,7 @@ PN532_SCHEMA = cv.Schema( ), cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation( { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532OnTagTrigger), + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger), } ), } diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index 0c46ff8a57..7ebf328cff 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -144,9 +144,9 @@ void PN532::loop() { } if (nfcid.size() == this->current_uid_.size()) { - bool same_uid = false; + bool same_uid = true; for (size_t i = 0; i < nfcid.size(); i++) - same_uid |= nfcid[i] == this->current_uid_[i]; + same_uid &= nfcid[i] == this->current_uid_[i]; if (same_uid) return; } @@ -376,9 +376,6 @@ bool PN532BinarySensor::process(std::vector &data) { this->found_ = true; return true; } -void PN532OnTagTrigger::process(const std::unique_ptr &tag) { - this->trigger(nfc::format_uid(tag->get_uid()), *tag); -} } // namespace pn532 } // namespace esphome diff --git a/esphome/components/pn532/pn532.h b/esphome/components/pn532/pn532.h index 692a5011e6..4f688dacc2 100644 --- a/esphome/components/pn532/pn532.h +++ b/esphome/components/pn532/pn532.h @@ -5,6 +5,7 @@ #include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/components/nfc/nfc_tag.h" #include "esphome/components/nfc/nfc.h" +#include "esphome/components/nfc/automation.h" namespace esphome { namespace pn532 { @@ -16,7 +17,6 @@ static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40; static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A; class PN532BinarySensor; -class PN532OnTagTrigger; class PN532 : public PollingComponent { public: @@ -30,8 +30,8 @@ class PN532 : public PollingComponent { void loop() override; void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); } - void register_ontag_trigger(PN532OnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); } - void register_ontagremoved_trigger(PN532OnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); } + void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); } + void register_ontagremoved_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); } void add_on_finished_write_callback(std::function callback) { this->on_finished_write_callback_.add(std::move(callback)); @@ -79,8 +79,8 @@ class PN532 : public PollingComponent { bool requested_read_{false}; std::vector binary_sensors_; - std::vector triggers_ontag_; - std::vector triggers_ontagremoved_; + std::vector triggers_ontag_; + std::vector triggers_ontagremoved_; std::vector current_uid_; nfc::NdefMessage *next_task_message_to_write_; enum NfcTask { @@ -115,11 +115,6 @@ class PN532BinarySensor : public binary_sensor::BinarySensor { bool found_{false}; }; -class PN532OnTagTrigger : public Trigger { - public: - void process(const std::unique_ptr &tag); -}; - class PN532OnFinishedWriteTrigger : public Trigger<> { public: explicit PN532OnFinishedWriteTrigger(PN532 *parent) { From 8be704e591ce236e027448d1b40f72ab74378295 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Apr 2022 12:55:26 +1200 Subject: [PATCH 224/238] Allow specifying deep sleep wakup clock time (#3312) --- esphome/components/deep_sleep/__init__.py | 34 ++++++++--- .../deep_sleep/deep_sleep_component.cpp | 5 +- .../deep_sleep/deep_sleep_component.h | 60 +++++++++++++++++++ esphome/components/time/real_time_clock.cpp | 25 ++++++++ esphome/components/time/real_time_clock.h | 2 + 5 files changed, 118 insertions(+), 8 deletions(-) diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 24fc0cabb0..058358fa67 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -1,13 +1,18 @@ import esphome.codegen as cg +from esphome.components import time import esphome.config_validation as cv from esphome import pins, automation from esphome.const import ( + CONF_HOUR, CONF_ID, + CONF_MINUTE, CONF_MODE, CONF_NUMBER, CONF_PINS, CONF_RUN_DURATION, + CONF_SECOND, CONF_SLEEP_DURATION, + CONF_TIME_ID, CONF_WAKEUP_PIN, ) @@ -112,6 +117,7 @@ CONF_TOUCH_WAKEUP = "touch_wakeup" CONF_DEFAULT = "default" CONF_GPIO_WAKEUP_REASON = "gpio_wakeup_reason" CONF_TOUCH_WAKEUP_REASON = "touch_wakeup_reason" +CONF_UNTIL = "until" WAKEUP_CAUSES_SCHEMA = cv.Schema( { @@ -202,13 +208,19 @@ async def to_code(config): cg.add_define("USE_DEEP_SLEEP") -DEEP_SLEEP_ENTER_SCHEMA = automation.maybe_simple_id( - { - cv.GenerateID(): cv.use_id(DeepSleepComponent), - cv.Optional(CONF_SLEEP_DURATION): cv.templatable( - cv.positive_time_period_milliseconds - ), - } +DEEP_SLEEP_ENTER_SCHEMA = cv.All( + automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(DeepSleepComponent), + cv.Exclusive(CONF_SLEEP_DURATION, "time"): cv.templatable( + cv.positive_time_period_milliseconds + ), + # Only on ESP32 due to how long the RTC on ESP8266 can stay asleep + cv.Exclusive(CONF_UNTIL, "time"): cv.All(cv.only_on_esp32, cv.time_of_day), + cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), + } + ), + cv.has_none_or_all_keys(CONF_UNTIL, CONF_TIME_ID), ) @@ -228,6 +240,14 @@ async def deep_sleep_enter_to_code(config, action_id, template_arg, args): if CONF_SLEEP_DURATION in config: template_ = await cg.templatable(config[CONF_SLEEP_DURATION], args, cg.int32) cg.add(var.set_sleep_duration(template_)) + + if CONF_UNTIL in config: + until = config[CONF_UNTIL] + cg.add(var.set_until(until[CONF_HOUR], until[CONF_MINUTE], until[CONF_SECOND])) + + time_ = await cg.get_variable(config[CONF_TIME_ID]) + cg.add(var.set_time(time_)) + return var diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 82751b538b..1bb70e0d7e 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -1,6 +1,7 @@ #include "deep_sleep_component.h" -#include "esphome/core/log.h" +#include #include "esphome/core/application.h" +#include "esphome/core/log.h" #ifdef USE_ESP8266 #include @@ -101,6 +102,8 @@ void DeepSleepComponent::begin_sleep(bool manual) { #endif ESP_LOGI(TAG, "Beginning Deep Sleep"); + if (this->sleep_duration_.has_value()) + ESP_LOGI(TAG, "Sleeping for %" PRId64 "us", *this->sleep_duration_); App.run_safe_shutdown_hooks(); diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index 057d992427..5e90d4b89d 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -9,6 +9,10 @@ #include #endif +#ifdef USE_TIME +#include "esphome/components/time/real_time_clock.h" +#endif + namespace esphome { namespace deep_sleep { @@ -116,15 +120,71 @@ template class EnterDeepSleepAction : public Action { EnterDeepSleepAction(DeepSleepComponent *deep_sleep) : deep_sleep_(deep_sleep) {} TEMPLATABLE_VALUE(uint32_t, sleep_duration); +#ifdef USE_TIME + void set_until(uint8_t hour, uint8_t minute, uint8_t second) { + this->hour_ = hour; + this->minute_ = minute; + this->second_ = second; + } + + void set_time(time::RealTimeClock *time) { this->time_ = time; } +#endif + void play(Ts... x) override { if (this->sleep_duration_.has_value()) { this->deep_sleep_->set_sleep_duration(this->sleep_duration_.value(x...)); } +#ifdef USE_TIME + + if (this->hour_.has_value()) { + auto time = this->time_->now(); + const uint32_t timestamp_now = time.timestamp; + + bool after_time = false; + if (time.hour > this->hour_) { + after_time = true; + } else { + if (time.hour == this->hour_) { + if (time.minute > this->minute_) { + after_time = true; + } else { + if (time.minute == this->minute_) { + if (time.second > this->second_) { + after_time = true; + } + } + } + } + } + + time.hour = *this->hour_; + time.minute = *this->minute_; + time.second = *this->second_; + time.recalc_timestamp_utc(); + + time_t timestamp = time.timestamp; // timestamp in local time zone + + if (after_time) + timestamp += 60 * 60 * 24; + + int32_t offset = time::ESPTime::timezone_offset(); + timestamp -= offset; // Change timestamp to utc + const uint32_t ms_left = (timestamp - timestamp_now) * 1000; + this->deep_sleep_->set_sleep_duration(ms_left); + } +#endif this->deep_sleep_->begin_sleep(true); } protected: DeepSleepComponent *deep_sleep_; +#ifdef USE_TIME + optional hour_; + optional minute_; + optional second_; + + time::RealTimeClock *time_; +#endif }; template class PreventDeepSleepAction : public Action { diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 0469ba2c37..36c5f4161d 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -176,6 +176,31 @@ void ESPTime::recalc_timestamp_utc(bool use_day_of_year) { res += this->second; this->timestamp = res; } + +int32_t ESPTime::timezone_offset() { + int32_t offset = 0; + time_t now = ::time(nullptr); + auto local = ESPTime::from_epoch_local(now); + auto utc = ESPTime::from_epoch_utc(now); + bool negative = utc.hour > local.hour && local.day_of_year <= utc.day_of_year; + + if (utc.minute > local.minute) { + local.minute += 60; + local.hour -= 1; + } + offset += (local.minute - utc.minute) * 60; + + if (negative) { + offset -= (utc.hour - local.hour) * 3600; + } else { + if (utc.hour > local.hour) { + local.hour += 24; + } + offset += (local.hour - utc.hour) * 3600; + } + return offset; +} + bool ESPTime::operator<(ESPTime other) { return this->timestamp < other.timestamp; } bool ESPTime::operator<=(ESPTime other) { return this->timestamp <= other.timestamp; } bool ESPTime::operator==(ESPTime other) { return this->timestamp == other.timestamp; } diff --git a/esphome/components/time/real_time_clock.h b/esphome/components/time/real_time_clock.h index c45deb0be5..b22c6f04d7 100644 --- a/esphome/components/time/real_time_clock.h +++ b/esphome/components/time/real_time_clock.h @@ -88,6 +88,8 @@ struct ESPTime { /// Convert this ESPTime instance back to a tm struct. struct tm to_c_tm(); + static int32_t timezone_offset(); + /// Increment this clock instance by one second. void increment_second(); /// Increment this clock instance by one day. From b778eed4193d14c261e4faee497077454fca987e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Apr 2022 13:42:28 +1200 Subject: [PATCH 225/238] Bump version to 2022.5.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 9096e66f4e..fa5baf4fe2 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.4.0-dev" +__version__ = "2022.5.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From b4a86ce6cfc110d71dfdcb25ea02a69d96c8c02e Mon Sep 17 00:00:00 2001 From: matthias882 <30553262+matthias882@users.noreply.github.com> Date: Wed, 13 Apr 2022 23:36:16 +0200 Subject: [PATCH 226/238] Changes accuracy of single cell voltage (#3387) --- esphome/components/daly_bms/sensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/daly_bms/sensor.py b/esphome/components/daly_bms/sensor.py index e2e8528317..2274a2153a 100644 --- a/esphome/components/daly_bms/sensor.py +++ b/esphome/components/daly_bms/sensor.py @@ -98,6 +98,8 @@ CELL_VOLTAGE_SCHEMA = sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, + icon=ICON_FLASH, + accuracy_decimals=3, ) CONFIG_SCHEMA = cv.All( From 047c18eac0ca65a6609c58d4fd419e389968031f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 14 Apr 2022 11:25:31 +1200 Subject: [PATCH 227/238] Add default object_id_generator for mqtt (#3389) --- esphome/components/mqtt/mqtt_client.cpp | 7 ++++++- esphome/components/mqtt/mqtt_client.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 3c6ce7cdfc..12a43dc232 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -556,7 +556,12 @@ void MQTTClientComponent::disable_last_will() { this->last_will_.topic = ""; } void MQTTClientComponent::disable_discovery() { this->discovery_info_ = MQTTDiscoveryInfo{ - .prefix = "", .retain = false, .clean = false, .unique_id_generator = MQTT_LEGACY_UNIQUE_ID_GENERATOR}; + .prefix = "", + .retain = false, + .clean = false, + .unique_id_generator = MQTT_LEGACY_UNIQUE_ID_GENERATOR, + .object_id_generator = MQTT_NONE_OBJECT_ID_GENERATOR, + }; } void MQTTClientComponent::on_shutdown() { if (!this->shutdown_message_.topic.empty()) { diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 4880bbaa5b..20b174a66f 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -277,6 +277,7 @@ class MQTTClientComponent : public Component { .retain = true, .clean = false, .unique_id_generator = MQTT_LEGACY_UNIQUE_ID_GENERATOR, + .object_id_generator = MQTT_NONE_OBJECT_ID_GENERATOR, }; std::string topic_prefix_{}; MQTTMessage log_message_; From 70a35656e463252ac6bfad44da9e41d5894de3d8 Mon Sep 17 00:00:00 2001 From: rnauber <7414650+rnauber@users.noreply.github.com> Date: Thu, 14 Apr 2022 03:13:51 +0200 Subject: [PATCH 228/238] Add support for Shelly Dimmer 2 (#2954) Co-authored-by: Niclas Larsson Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Jernej Kos Co-authored-by: Richard Nauber --- CODEOWNERS | 1 + esphome/components/shelly_dimmer/LICENSE.txt | 2 + esphome/components/shelly_dimmer/__init__.py | 1 + esphome/components/shelly_dimmer/dev_table.h | 158 +++ esphome/components/shelly_dimmer/light.py | 219 ++++ .../shelly_dimmer/shelly_dimmer.cpp | 526 ++++++++ .../components/shelly_dimmer/shelly_dimmer.h | 117 ++ .../components/shelly_dimmer/stm32flash.cpp | 1061 +++++++++++++++++ esphome/components/shelly_dimmer/stm32flash.h | 129 ++ esphome/core/defines.h | 6 + tests/test1.yaml | 12 + 11 files changed, 2232 insertions(+) create mode 100644 esphome/components/shelly_dimmer/LICENSE.txt create mode 100644 esphome/components/shelly_dimmer/__init__.py create mode 100644 esphome/components/shelly_dimmer/dev_table.h create mode 100644 esphome/components/shelly_dimmer/light.py create mode 100644 esphome/components/shelly_dimmer/shelly_dimmer.cpp create mode 100644 esphome/components/shelly_dimmer/shelly_dimmer.h create mode 100644 esphome/components/shelly_dimmer/stm32flash.cpp create mode 100644 esphome/components/shelly_dimmer/stm32flash.h diff --git a/CODEOWNERS b/CODEOWNERS index 7595fc52e2..02945ec0a4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -173,6 +173,7 @@ esphome/components/select/* @esphome/core esphome/components/sensirion_common/* @martgras esphome/components/sensor/* @esphome/core esphome/components/sgp40/* @SenexCrenshaw +esphome/components/shelly_dimmer/* @edge90 @rnauber esphome/components/sht4x/* @sjtrny esphome/components/shutdown/* @esphome/core @jsuanet esphome/components/sim800l/* @glmnet diff --git a/esphome/components/shelly_dimmer/LICENSE.txt b/esphome/components/shelly_dimmer/LICENSE.txt new file mode 100644 index 0000000000..524fe0d514 --- /dev/null +++ b/esphome/components/shelly_dimmer/LICENSE.txt @@ -0,0 +1,2 @@ +The firmware files for the STM microcontroller (shelly-dimmer-stm32_*.bin) are taken from +https://github.com/jamesturton/shelly-dimmer-stm32 and GPLv3 licensed. diff --git a/esphome/components/shelly_dimmer/__init__.py b/esphome/components/shelly_dimmer/__init__.py new file mode 100644 index 0000000000..accefbbc34 --- /dev/null +++ b/esphome/components/shelly_dimmer/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@rnauber", "@edge90"] diff --git a/esphome/components/shelly_dimmer/dev_table.h b/esphome/components/shelly_dimmer/dev_table.h new file mode 100644 index 0000000000..f4bf7778f2 --- /dev/null +++ b/esphome/components/shelly_dimmer/dev_table.h @@ -0,0 +1,158 @@ +/* + stm32flash - Open Source ST STM32 flash program for Arduino + Copyright (C) 2010 Geoffrey McRae + Copyright (C) 2014-2015 Antonio Borneo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +#pragma once + +#include "esphome/core/defines.h" +#ifdef USE_SHD_FIRMWARE_DATA +#include "stm32flash.h" + +namespace esphome { +namespace shelly_dimmer { + +constexpr uint32_t SZ_128 = 0x00000080; +constexpr uint32_t SZ_256 = 0x00000100; +constexpr uint32_t SZ_1K = 0x00000400; +constexpr uint32_t SZ_2K = 0x00000800; +constexpr uint32_t SZ_16K = 0x00004000; +constexpr uint32_t SZ_32K = 0x00008000; +constexpr uint32_t SZ_64K = 0x00010000; +constexpr uint32_t SZ_128K = 0x00020000; +constexpr uint32_t SZ_256K = 0x00040000; + +/* + * Page-size for page-by-page flash erase. + * Arrays are zero terminated; last non-zero value is automatically repeated + */ + +/* fixed size pages */ +constexpr uint32_t p_128[] = {SZ_128, 0}; // NOLINT +constexpr uint32_t p_256[] = {SZ_256, 0}; // NOLINT +constexpr uint32_t p_1k[] = {SZ_1K, 0}; // NOLINT +constexpr uint32_t p_2k[] = {SZ_2K, 0}; // NOLINT +/* F2 and F4 page size */ +constexpr uint32_t f2f4[] = {SZ_16K, SZ_16K, SZ_16K, SZ_16K, SZ_64K, SZ_128K, 0}; // NOLINT +/* F4 dual bank page size */ +constexpr uint32_t f4db[] = {SZ_16K, SZ_16K, SZ_16K, SZ_16K, SZ_64K, SZ_128K, SZ_128K, // NOLINT + SZ_128K, SZ_16K, SZ_16K, SZ_16K, SZ_16K, SZ_64K, SZ_128K, 0}; +/* F7 page size */ +constexpr uint32_t f7[] = {SZ_32K, SZ_32K, SZ_32K, SZ_32K, SZ_128K, SZ_256K, 0}; // NOLINT + +/* + * Device table, corresponds to the "Bootloader device-dependant parameters" + * table in ST document AN2606. + * Note that the option bytes upper range is inclusive! + */ +constexpr stm32_dev_t DEVICES[] = { + /* ID "name" SRAM-address-range FLASH-address-range PPS PSize + Option-byte-addr-range System-mem-addr-range Flags */ + /* F0 */ + {0x440, "STM32F030x8/F05xxx", 0x20000800, 0x20002000, 0x08000000, 0x08010000, 4, p_1k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFEC00, 0x1FFFF800, 0}, + {0x442, "STM32F030xC/F09xxx", 0x20001800, 0x20008000, 0x08000000, 0x08040000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFC800, 0x1FFFF800, F_OBLL}, + {0x444, "STM32F03xx4/6", 0x20000800, 0x20001000, 0x08000000, 0x08008000, 4, p_1k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFEC00, 0x1FFFF800, 0}, + {0x445, "STM32F04xxx/F070x6", 0x20001800, 0x20001800, 0x08000000, 0x08008000, 4, p_1k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFC400, 0x1FFFF800, 0}, + {0x448, "STM32F070xB/F071xx/F72xx", 0x20001800, 0x20004000, 0x08000000, 0x08020000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFC800, 0x1FFFF800, 0}, + /* F1 */ + {0x412, "STM32F10xxx Low-density", 0x20000200, 0x20002800, 0x08000000, 0x08008000, 4, p_1k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFF000, 0x1FFFF800, 0}, + {0x410, "STM32F10xxx Medium-density", 0x20000200, 0x20005000, 0x08000000, 0x08020000, 4, p_1k, 0x1FFFF800, + 0x1FFFF80F, 0x1FFFF000, 0x1FFFF800, 0}, + {0x414, "STM32F10xxx High-density", 0x20000200, 0x20010000, 0x08000000, 0x08080000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFF000, 0x1FFFF800, 0}, + {0x420, "STM32F10xxx Medium-density VL", 0x20000200, 0x20002000, 0x08000000, 0x08020000, 4, p_1k, 0x1FFFF800, + 0x1FFFF80F, 0x1FFFF000, 0x1FFFF800, 0}, + {0x428, "STM32F10xxx High-density VL", 0x20000200, 0x20008000, 0x08000000, 0x08080000, 2, p_2k, 0x1FFFF800, + 0x1FFFF80F, 0x1FFFF000, 0x1FFFF800, 0}, + {0x418, "STM32F105xx/F107xx", 0x20001000, 0x20010000, 0x08000000, 0x08040000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFB000, 0x1FFFF800, 0}, + {0x430, "STM32F10xxx XL-density", 0x20000800, 0x20018000, 0x08000000, 0x08100000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFE000, 0x1FFFF800, 0}, + /* F2 */ + {0x411, "STM32F2xxxx", 0x20002000, 0x20020000, 0x08000000, 0x08100000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, 0x1FFF0000, + 0x1FFF7800, 0}, + /* F3 */ + {0x432, "STM32F373xx/F378xx", 0x20001400, 0x20008000, 0x08000000, 0x08040000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFD800, 0x1FFFF800, 0}, + {0x422, "STM32F302xB(C)/F303xB(C)/F358xx", 0x20001400, 0x2000A000, 0x08000000, 0x08040000, 2, p_2k, 0x1FFFF800, + 0x1FFFF80F, 0x1FFFD800, 0x1FFFF800, 0}, + {0x439, "STM32F301xx/F302x4(6/8)/F318xx", 0x20001800, 0x20004000, 0x08000000, 0x08010000, 2, p_2k, 0x1FFFF800, + 0x1FFFF80F, 0x1FFFD800, 0x1FFFF800, 0}, + {0x438, "STM32F303x4(6/8)/F334xx/F328xx", 0x20001800, 0x20003000, 0x08000000, 0x08010000, 2, p_2k, 0x1FFFF800, + 0x1FFFF80F, 0x1FFFD800, 0x1FFFF800, 0}, + {0x446, "STM32F302xD(E)/F303xD(E)/F398xx", 0x20001800, 0x20010000, 0x08000000, 0x08080000, 2, p_2k, 0x1FFFF800, + 0x1FFFF80F, 0x1FFFD800, 0x1FFFF800, 0}, + /* F4 */ + {0x413, "STM32F40xxx/41xxx", 0x20003000, 0x20020000, 0x08000000, 0x08100000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, + 0x1FFF0000, 0x1FFF7800, 0}, + {0x419, "STM32F42xxx/43xxx", 0x20003000, 0x20030000, 0x08000000, 0x08200000, 1, f4db, 0x1FFEC000, 0x1FFFC00F, + 0x1FFF0000, 0x1FFF7800, 0}, + {0x423, "STM32F401xB(C)", 0x20003000, 0x20010000, 0x08000000, 0x08040000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, + 0x1FFF0000, 0x1FFF7800, 0}, + {0x433, "STM32F401xD(E)", 0x20003000, 0x20018000, 0x08000000, 0x08080000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, + 0x1FFF0000, 0x1FFF7800, 0}, + {0x458, "STM32F410xx", 0x20003000, 0x20008000, 0x08000000, 0x08020000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, 0x1FFF0000, + 0x1FFF7800, 0}, + {0x431, "STM32F411xx", 0x20003000, 0x20020000, 0x08000000, 0x08080000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, 0x1FFF0000, + 0x1FFF7800, 0}, + {0x421, "STM32F446xx", 0x20003000, 0x20020000, 0x08000000, 0x08080000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, 0x1FFF0000, + 0x1FFF7800, 0}, + {0x434, "STM32F469xx", 0x20003000, 0x20060000, 0x08000000, 0x08200000, 1, f4db, 0x1FFEC000, 0x1FFFC00F, 0x1FFF0000, + 0x1FFF7800, 0}, + /* F7 */ + {0x449, "STM32F74xxx/75xxx", 0x20004000, 0x20050000, 0x08000000, 0x08100000, 1, f7, 0x1FFF0000, 0x1FFF001F, + 0x1FF00000, 0x1FF0EDC0, 0}, + /* L0 */ + {0x425, "STM32L031xx/041xx", 0x20001000, 0x20002000, 0x08000000, 0x08008000, 32, p_128, 0x1FF80000, 0x1FF8001F, + 0x1FF00000, 0x1FF01000, 0}, + {0x417, "STM32L05xxx/06xxx", 0x20001000, 0x20002000, 0x08000000, 0x08010000, 32, p_128, 0x1FF80000, 0x1FF8001F, + 0x1FF00000, 0x1FF01000, 0}, + {0x447, "STM32L07xxx/08xxx", 0x20002000, 0x20005000, 0x08000000, 0x08030000, 32, p_128, 0x1FF80000, 0x1FF8001F, + 0x1FF00000, 0x1FF02000, 0}, + /* L1 */ + {0x416, "STM32L1xxx6(8/B)", 0x20000800, 0x20004000, 0x08000000, 0x08020000, 16, p_256, 0x1FF80000, 0x1FF8001F, + 0x1FF00000, 0x1FF01000, F_NO_ME}, + {0x429, "STM32L1xxx6(8/B)A", 0x20001000, 0x20008000, 0x08000000, 0x08020000, 16, p_256, 0x1FF80000, 0x1FF8001F, + 0x1FF00000, 0x1FF01000, 0}, + {0x427, "STM32L1xxxC", 0x20001000, 0x20008000, 0x08000000, 0x08040000, 16, p_256, 0x1FF80000, 0x1FF8001F, + 0x1FF00000, 0x1FF02000, 0}, + {0x436, "STM32L1xxxD", 0x20001000, 0x2000C000, 0x08000000, 0x08060000, 16, p_256, 0x1FF80000, 0x1FF8009F, + 0x1FF00000, 0x1FF02000, 0}, + {0x437, "STM32L1xxxE", 0x20001000, 0x20014000, 0x08000000, 0x08080000, 16, p_256, 0x1FF80000, 0x1FF8009F, + 0x1FF00000, 0x1FF02000, F_NO_ME}, + /* L4 */ + {0x415, "STM32L476xx/486xx", 0x20003100, 0x20018000, 0x08000000, 0x08100000, 1, p_2k, 0x1FFF7800, 0x1FFFF80F, + 0x1FFF0000, 0x1FFF7000, 0}, + /* These are not (yet) in AN2606: */ + {0x641, "Medium_Density PL", 0x20000200, 0x20005000, 0x08000000, 0x08020000, 4, p_1k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFF000, 0x1FFFF800, 0}, + {0x9a8, "STM32W-128K", 0x20000200, 0x20002000, 0x08000000, 0x08020000, 4, p_1k, 0x08040800, 0x0804080F, 0x08040000, + 0x08040800, 0}, + {0x9b0, "STM32W-256K", 0x20000200, 0x20004000, 0x08000000, 0x08040000, 4, p_2k, 0x08040800, 0x0804080F, 0x08040000, + 0x08040800, 0}, + {0x0, "", 0x0, 0x0, 0x0, 0x0, 0x0, nullptr, 0x0, 0x0, 0x0, 0x0, 0x0}, +}; + +} // namespace shelly_dimmer +} // namespace esphome +#endif diff --git a/esphome/components/shelly_dimmer/light.py b/esphome/components/shelly_dimmer/light.py new file mode 100644 index 0000000000..003498c090 --- /dev/null +++ b/esphome/components/shelly_dimmer/light.py @@ -0,0 +1,219 @@ +from pathlib import Path +import hashlib +import re +import requests + + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import light, sensor, uart +from esphome.const import ( + CONF_OUTPUT_ID, + CONF_GAMMA_CORRECT, + CONF_POWER, + CONF_VOLTAGE, + CONF_CURRENT, + CONF_VERSION, + CONF_URL, + CONF_UPDATE_INTERVAL, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, +) +from esphome.core import HexInt, CORE + +DOMAIN = "shelly_dimmer" +DEPENDENCIES = ["sensor", "uart"] + +shelly_dimmer_ns = cg.esphome_ns.namespace("shelly_dimmer") +ShellyDimmer = shelly_dimmer_ns.class_( + "ShellyDimmer", light.LightOutput, cg.PollingComponent, uart.UARTDevice +) + +CONF_FIRMWARE = "firmware" +CONF_SHA256 = "sha256" +CONF_UPDATE = "update" + +CONF_LEADING_EDGE = "leading_edge" +CONF_WARMUP_BRIGHTNESS = "warmup_brightness" +# CONF_WARMUP_TIME = "warmup_time" +CONF_MIN_BRIGHTNESS = "min_brightness" +CONF_MAX_BRIGHTNESS = "max_brightness" + +CONF_NRST_PIN = "nrst_pin" +CONF_BOOT0_PIN = "boot0_pin" + +KNOWN_FIRMWARE = { + "51.5": ( + "https://github.com/jamesturton/shelly-dimmer-stm32/releases/download/v51.5/shelly-dimmer-stm32_v51.5.bin", + "553fc1d78ed113227af7683eaa9c26189a961c4ea9a48000fb5aa8f8ac5d7b60", + ), + "51.6": ( + "https://github.com/jamesturton/shelly-dimmer-stm32/releases/download/v51.6/shelly-dimmer-stm32_v51.6.bin", + "eda483e111c914723a33f5088f1397d5c0b19333db4a88dc965636b976c16c36", + ), +} + + +def parse_firmware_version(value): + match = re.match(r"(\d+).(\d+)", value) + if match is None: + raise ValueError(f"Not a valid version number {value}") + major = int(match[1]) + minor = int(match[2]) + return major, minor + + +def get_firmware(value): + if not value[CONF_UPDATE]: + return None + + def dl(url): + try: + req = requests.get(url) + req.raise_for_status() + except requests.exceptions.RequestException as e: + raise cv.Invalid(f"Could not download firmware file ({url}): {e}") + + h = hashlib.new("sha256") + h.update(req.content) + return req.content, h.hexdigest() + + url = value[CONF_URL] + + if CONF_SHA256 in value: # we have a hash, enable caching + path = ( + Path(CORE.config_dir) + / ".esphome" + / DOMAIN + / (value[CONF_SHA256] + "_fw_stm.bin") + ) + + if not path.is_file(): + firmware_data, dl_hash = dl(url) + + if dl_hash != value[CONF_SHA256]: + raise cv.Invalid( + f"Hash mismatch for {url}: {dl_hash} != {value[CONF_SHA256]}" + ) + + path.parent.mkdir(exist_ok=True, parents=True) + path.write_bytes(firmware_data) + + else: + firmware_data = path.read_bytes() + else: # no caching, download every time + firmware_data, dl_hash = dl(url) + + return [HexInt(x) for x in firmware_data] + + +def validate_firmware(value): + config = value.copy() + if CONF_URL not in config: + try: + config[CONF_URL], config[CONF_SHA256] = KNOWN_FIRMWARE[config[CONF_VERSION]] + except KeyError as e: + raise cv.Invalid( + f"Firmware {config[CONF_VERSION]} is unknown, please specify an '{CONF_URL}' ..." + ) from e + get_firmware(config) + return config + + +def validate_sha256(value): + value = cv.string(value) + if not value.isalnum() or not len(value) == 64: + raise ValueError(f"Not a valid SHA256 hex string: {value}") + return value + + +def validate_version(value): + parse_firmware_version(value) + return value + + +CONFIG_SCHEMA = ( + light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(ShellyDimmer), + cv.Optional(CONF_FIRMWARE, default="51.6"): cv.maybe_simple_value( + { + cv.Optional(CONF_URL): cv.url, + cv.Optional(CONF_SHA256): validate_sha256, + cv.Required(CONF_VERSION): validate_version, + cv.Optional(CONF_UPDATE, default=False): cv.boolean, + }, + validate_firmware, # converts a simple version key to generate the full url + key=CONF_VERSION, + ), + cv.Optional(CONF_NRST_PIN, default="GPIO5"): pins.gpio_output_pin_schema, + cv.Optional(CONF_BOOT0_PIN, default="GPIO4"): pins.gpio_output_pin_schema, + cv.Optional(CONF_LEADING_EDGE, default=False): cv.boolean, + cv.Optional(CONF_WARMUP_BRIGHTNESS, default=100): cv.uint16_t, + # cv.Optional(CONF_WARMUP_TIME, default=20): cv.uint16_t, + cv.Optional(CONF_MIN_BRIGHTNESS, default=0): cv.uint16_t, + cv.Optional(CONF_MAX_BRIGHTNESS, default=1000): cv.uint16_t, + cv.Optional(CONF_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + ), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + device_class=DEVICE_CLASS_POWER, + accuracy_decimals=2, + ), + # Change the default gamma_correct setting. + cv.Optional(CONF_GAMMA_CORRECT, default=1.0): cv.positive_float, + } + ) + .extend(cv.polling_component_schema("10s")) + .extend(uart.UART_DEVICE_SCHEMA) +) + + +def to_code(config): + fw_hex = get_firmware(config[CONF_FIRMWARE]) + fw_major, fw_minor = parse_firmware_version(config[CONF_FIRMWARE][CONF_VERSION]) + + if fw_hex is not None: + cg.add_define("USE_SHD_FIRMWARE_DATA", fw_hex) + cg.add_define("USE_SHD_FIRMWARE_MAJOR_VERSION", fw_major) + cg.add_define("USE_SHD_FIRMWARE_MINOR_VERSION", fw_minor) + + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + yield cg.register_component(var, config) + config.pop( + CONF_UPDATE_INTERVAL + ) # drop UPDATE_INTERVAL as it does not apply to the light component + + yield light.register_light(var, config) + yield uart.register_uart_device(var, config) + + nrst_pin = yield cg.gpio_pin_expression(config[CONF_NRST_PIN]) + cg.add(var.set_nrst_pin(nrst_pin)) + boot0_pin = yield cg.gpio_pin_expression(config[CONF_BOOT0_PIN]) + cg.add(var.set_boot0_pin(boot0_pin)) + + cg.add(var.set_leading_edge(config[CONF_LEADING_EDGE])) + cg.add(var.set_warmup_brightness(config[CONF_WARMUP_BRIGHTNESS])) + # cg.add(var.set_warmup_time(config[CONF_WARMUP_TIME])) + cg.add(var.set_min_brightness(config[CONF_MIN_BRIGHTNESS])) + cg.add(var.set_max_brightness(config[CONF_MAX_BRIGHTNESS])) + + for key in [CONF_POWER, CONF_VOLTAGE, CONF_CURRENT]: + if key not in config: + continue + + conf = config[key] + sens = yield sensor.new_sensor(conf) + cg.add(getattr(var, f"set_{key}_sensor")(sens)) diff --git a/esphome/components/shelly_dimmer/shelly_dimmer.cpp b/esphome/components/shelly_dimmer/shelly_dimmer.cpp new file mode 100644 index 0000000000..3b79d0bf57 --- /dev/null +++ b/esphome/components/shelly_dimmer/shelly_dimmer.cpp @@ -0,0 +1,526 @@ +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" + +#include "shelly_dimmer.h" +#ifdef USE_SHD_FIRMWARE_DATA +#include "stm32flash.h" +#endif + +#ifndef USE_ESP_IDF +#include +#endif + +#include +#include +#include +#include + +namespace { + +constexpr char TAG[] = "shelly_dimmer"; + +constexpr uint8_t SHELLY_DIMMER_ACK_TIMEOUT = 200; // ms +constexpr uint8_t SHELLY_DIMMER_MAX_RETRIES = 3; +constexpr uint16_t SHELLY_DIMMER_MAX_BRIGHTNESS = 1000; // 100% + +// Protocol framing. +constexpr uint8_t SHELLY_DIMMER_PROTO_START_BYTE = 0x01; +constexpr uint8_t SHELLY_DIMMER_PROTO_END_BYTE = 0x04; + +// Supported commands. +constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SWITCH = 0x01; +constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_POLL = 0x10; +constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_VERSION = 0x11; +constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SETTINGS = 0x20; + +// Command payload sizes. +constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SWITCH_SIZE = 2; +constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SETTINGS_SIZE = 10; +constexpr uint8_t SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE = 4 + 72 + 3; + +// STM Firmware +#ifdef USE_SHD_FIRMWARE_DATA +constexpr uint8_t STM_FIRMWARE[] PROGMEM = USE_SHD_FIRMWARE_DATA; +constexpr uint32_t STM_FIRMWARE_SIZE_IN_BYTES = sizeof(STM_FIRMWARE); +#endif + +// Scaling Constants +constexpr float POWER_SCALING_FACTOR = 880373; +constexpr float VOLTAGE_SCALING_FACTOR = 347800; +constexpr float CURRENT_SCALING_FACTOR = 1448; + +// Esentially std::size() for pre c++17 +template constexpr size_t size(const T (&/*unused*/)[N]) noexcept { return N; } + +} // Anonymous namespace + +namespace esphome { +namespace shelly_dimmer { + +/// Computes a crappy checksum as defined by the Shelly Dimmer protocol. +uint16_t shelly_dimmer_checksum(const uint8_t *buf, int len) { + return std::accumulate(buf, buf + len, 0); +} + +void ShellyDimmer::setup() { + this->pin_nrst_->setup(); + this->pin_boot0_->setup(); + + ESP_LOGI(TAG, "Initializing Shelly Dimmer..."); + + // Reset the STM32 and check the firmware version. + for (int i = 0; i < 2; i++) { + this->reset_normal_boot_(); + this->send_command_(SHELLY_DIMMER_PROTO_CMD_VERSION, nullptr, 0); + ESP_LOGI(TAG, "STM32 current firmware version: %d.%d, desired version: %d.%d", this->version_major_, + this->version_minor_, USE_SHD_FIRMWARE_MAJOR_VERSION, USE_SHD_FIRMWARE_MINOR_VERSION); + if (this->version_major_ != USE_SHD_FIRMWARE_MAJOR_VERSION || + this->version_minor_ != USE_SHD_FIRMWARE_MINOR_VERSION) { +#ifdef USE_SHD_FIRMWARE_DATA + // Update firmware if needed. + ESP_LOGW(TAG, "Unsupported STM32 firmware version, flashing"); + if (i > 0) { + // Upgrade was already performed but the reported version is still not right. + ESP_LOGE(TAG, "STM32 firmware upgrade already performed, but version is still incorrect"); + this->mark_failed(); + return; + } + + if (!this->upgrade_firmware_()) { + ESP_LOGW(TAG, "Failed to upgrade firmware"); + this->mark_failed(); + return; + } + + // Firmware upgrade completed, do the checks again. + continue; +#else + ESP_LOGW(TAG, "Firmware version mismatch, put 'update: true' in the yaml to flash an update."); + this->mark_failed(); + return; +#endif + } + break; + } + + this->send_settings_(); + // Do an immediate poll to refresh current state. + this->send_command_(SHELLY_DIMMER_PROTO_CMD_POLL, nullptr, 0); + + this->ready_ = true; +} + +void ShellyDimmer::update() { this->send_command_(SHELLY_DIMMER_PROTO_CMD_POLL, nullptr, 0); } + +void ShellyDimmer::dump_config() { + ESP_LOGCONFIG(TAG, "ShellyDimmer:"); + LOG_PIN(" NRST Pin: ", this->pin_nrst_); + LOG_PIN(" BOOT0 Pin: ", this->pin_boot0_); + + ESP_LOGCONFIG(TAG, " Leading Edge: %s", YESNO(this->leading_edge_)); + ESP_LOGCONFIG(TAG, " Warmup Brightness: %d", this->warmup_brightness_); + // ESP_LOGCONFIG(TAG, " Warmup Time: %d", this->warmup_time_); + // ESP_LOGCONFIG(TAG, " Fade Rate: %d", this->fade_rate_); + ESP_LOGCONFIG(TAG, " Minimum Brightness: %d", this->min_brightness_); + ESP_LOGCONFIG(TAG, " Maximum Brightness: %d", this->max_brightness_); + + LOG_UPDATE_INTERVAL(this); + + ESP_LOGCONFIG(TAG, " STM32 current firmware version: %d.%d ", this->version_major_, this->version_minor_); + ESP_LOGCONFIG(TAG, " STM32 required firmware version: %d.%d", USE_SHD_FIRMWARE_MAJOR_VERSION, + USE_SHD_FIRMWARE_MINOR_VERSION); + + if (this->version_major_ != USE_SHD_FIRMWARE_MAJOR_VERSION || + this->version_minor_ != USE_SHD_FIRMWARE_MINOR_VERSION) { + ESP_LOGE(TAG, " Firmware version mismatch, put 'update: true' in the yaml to flash an update."); + } +} + +void ShellyDimmer::write_state(light::LightState *state) { + if (!this->ready_) { + return; + } + + float brightness; + state->current_values_as_brightness(&brightness); + + const uint16_t brightness_int = this->convert_brightness_(brightness); + if (brightness_int == this->brightness_) { + ESP_LOGV(TAG, "Not sending unchanged value"); + return; + } + ESP_LOGD(TAG, "Brightness update: %d (raw: %f)", brightness_int, brightness); + + this->send_brightness_(brightness_int); +} +#ifdef USE_SHD_FIRMWARE_DATA +bool ShellyDimmer::upgrade_firmware_() { + ESP_LOGW(TAG, "Starting STM32 firmware upgrade"); + this->reset_dfu_boot_(); + + // Could be constexpr in c++17 + static const auto CLOSE = [](stm32_t *stm32) { stm32_close(stm32); }; + + // Cleanup with RAII + std::unique_ptr stm32{stm32_init(this, STREAM_SERIAL, 1), CLOSE}; + + if (!stm32) { + ESP_LOGW(TAG, "Failed to initialize STM32"); + return false; + } + + // Erase STM32 flash. + if (stm32_erase_memory(stm32.get(), 0, STM32_MASS_ERASE) != STM32_ERR_OK) { + ESP_LOGW(TAG, "Failed to erase STM32 flash memory"); + return false; + } + + static constexpr uint32_t BUFFER_SIZE = 256; + + // Copy the STM32 firmware over in 256-byte chunks. Note that the firmware is stored + // in flash memory so all accesses need to be 4-byte aligned. + uint8_t buffer[BUFFER_SIZE]; + const uint8_t *p = STM_FIRMWARE; + uint32_t offset = 0; + uint32_t addr = stm32->dev->fl_start; + const uint32_t end = addr + STM_FIRMWARE_SIZE_IN_BYTES; + + while (addr < end && offset < STM_FIRMWARE_SIZE_IN_BYTES) { + const uint32_t left_of_buffer = std::min(end - addr, BUFFER_SIZE); + const uint32_t len = std::min(left_of_buffer, STM_FIRMWARE_SIZE_IN_BYTES - offset); + + if (len == 0) { + break; + } + + std::memcpy(buffer, p, BUFFER_SIZE); + p += BUFFER_SIZE; + + if (stm32_write_memory(stm32.get(), addr, buffer, len) != STM32_ERR_OK) { + ESP_LOGW(TAG, "Failed to write to STM32 flash memory"); + return false; + } + + addr += len; + offset += len; + } + + ESP_LOGI(TAG, "STM32 firmware upgrade successful"); + + return true; +} +#endif + +uint16_t ShellyDimmer::convert_brightness_(float brightness) { + // Special case for zero as only zero means turn off completely. + if (brightness == 0.0) { + return 0; + } + + return remap(brightness, 0.0f, 1.0f, this->min_brightness_, this->max_brightness_); +} + +void ShellyDimmer::send_brightness_(uint16_t brightness) { + const uint8_t payload[] = { + // Brightness (%) * 10. + static_cast(brightness & 0xff), + static_cast(brightness >> 8), + }; + static_assert(size(payload) == SHELLY_DIMMER_PROTO_CMD_SWITCH_SIZE, "Invalid payload size"); + + this->send_command_(SHELLY_DIMMER_PROTO_CMD_SWITCH, payload, SHELLY_DIMMER_PROTO_CMD_SWITCH_SIZE); + + this->brightness_ = brightness; +} + +void ShellyDimmer::send_settings_() { + const uint16_t fade_rate = std::min(uint16_t{100}, this->fade_rate_); + + float brightness = 0.0; + if (this->state_ != nullptr) { + this->state_->current_values_as_brightness(&brightness); + } + const uint16_t brightness_int = this->convert_brightness_(brightness); + ESP_LOGD(TAG, "Brightness update: %d (raw: %f)", brightness_int, brightness); + + const uint8_t payload[] = { + // Brightness (%) * 10. + static_cast(brightness_int & 0xff), + static_cast(brightness_int >> 8), + // Leading / trailing edge [0x01 = leading, 0x02 = trailing]. + this->leading_edge_ ? uint8_t{0x01} : uint8_t{0x02}, + 0x00, + // Fade rate. + static_cast(fade_rate & 0xff), + static_cast(fade_rate >> 8), + // Warmup brightness. + static_cast(this->warmup_brightness_ & 0xff), + static_cast(this->warmup_brightness_ >> 8), + // Warmup time. + static_cast(this->warmup_time_ & 0xff), + static_cast(this->warmup_time_ >> 8), + }; + static_assert(size(payload) == SHELLY_DIMMER_PROTO_CMD_SETTINGS_SIZE, "Invalid payload size"); + + this->send_command_(SHELLY_DIMMER_PROTO_CMD_SETTINGS, payload, SHELLY_DIMMER_PROTO_CMD_SETTINGS_SIZE); + + // Also send brightness separately as it is ignored above. + this->send_brightness_(brightness_int); +} + +bool ShellyDimmer::send_command_(uint8_t cmd, const uint8_t *const payload, uint8_t len) { + ESP_LOGD(TAG, "Sending command: 0x%02x (%d bytes) payload 0x%s", cmd, len, format_hex(payload, len).c_str()); + + // Prepare a command frame. + uint8_t frame[SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE]; + const size_t frame_len = this->frame_command_(frame, cmd, payload, len); + + // Write the frame and wait for acknowledgement. + int retries = SHELLY_DIMMER_MAX_RETRIES; + while (retries--) { + this->write_array(frame, frame_len); + this->flush(); + + ESP_LOGD(TAG, "Command sent, waiting for reply"); + const uint32_t tx_time = millis(); + while (millis() - tx_time < SHELLY_DIMMER_ACK_TIMEOUT) { + if (this->read_frame_()) { + return true; + } + delay(1); + } + ESP_LOGW(TAG, "Timeout while waiting for reply"); + } + ESP_LOGW(TAG, "Failed to send command"); + return false; +} + +size_t ShellyDimmer::frame_command_(uint8_t *data, uint8_t cmd, const uint8_t *const payload, size_t len) { + size_t pos = 0; + + // Generate a frame. + data[0] = SHELLY_DIMMER_PROTO_START_BYTE; + data[1] = ++this->seq_; + data[2] = cmd; + data[3] = len; + pos += 4; + + if (payload != nullptr) { + std::memcpy(data + 4, payload, len); + pos += len; + } + + // Calculate checksum for the payload. + const uint16_t csum = shelly_dimmer_checksum(data + 1, 3 + len); + data[pos++] = static_cast(csum >> 8); + data[pos++] = static_cast(csum & 0xff); + data[pos++] = SHELLY_DIMMER_PROTO_END_BYTE; + return pos; +} + +int ShellyDimmer::handle_byte_(uint8_t c) { + const uint8_t pos = this->buffer_pos_; + + if (pos == 0) { + // Must be start byte. + return c == SHELLY_DIMMER_PROTO_START_BYTE ? 1 : -1; + } else if (pos < 4) { + // Header. + return 1; + } + + // Decode payload length from header. + const uint8_t payload_len = this->buffer_[3]; + if ((4 + payload_len + 3) > SHELLY_DIMMER_BUFFER_SIZE) { + return -1; + } + + if (pos < 4 + payload_len + 1) { + // Payload. + return 1; + } + + if (pos == 4 + payload_len + 1) { + // Verify checksum. + const uint16_t csum = (this->buffer_[pos - 1] << 8 | c); + const uint16_t csum_verify = shelly_dimmer_checksum(&this->buffer_[1], 3 + payload_len); + if (csum != csum_verify) { + return -1; + } + return 1; + } + + if (pos == 4 + payload_len + 2) { + // Must be end byte. + return c == SHELLY_DIMMER_PROTO_END_BYTE ? 0 : -1; + } + return -1; +} + +bool ShellyDimmer::read_frame_() { + while (this->available()) { + const uint8_t c = this->read(); + this->buffer_[this->buffer_pos_] = c; + + ESP_LOGV(TAG, "Read byte: 0x%02x (pos %d)", c, this->buffer_pos_); + + switch (this->handle_byte_(c)) { + case 0: { + // Frame successfully received. + this->handle_frame_(); + this->buffer_pos_ = 0; + return true; + } + case -1: { + // Failure. + this->buffer_pos_ = 0; + break; + } + case 1: { + // Need more data. + this->buffer_pos_++; + break; + } + } + } + return false; +} + +bool ShellyDimmer::handle_frame_() { + const uint8_t seq = this->buffer_[1]; + const uint8_t cmd = this->buffer_[2]; + const uint8_t payload_len = this->buffer_[3]; + + ESP_LOGD(TAG, "Got frame: 0x%02x", cmd); + + // Compare with expected identifier as the frame is always a response to + // our previously sent command. + if (seq != this->seq_) { + return false; + } + + const uint8_t *payload = &this->buffer_[4]; + + // Handle response. + switch (cmd) { + case SHELLY_DIMMER_PROTO_CMD_POLL: { + if (payload_len < 16) { + return false; + } + + const uint8_t hw_version = payload[0]; + // payload[1] is unused. + const uint16_t brightness = encode_uint16(payload[3], payload[2]); + + const uint32_t power_raw = encode_uint32(payload[7], payload[6], payload[5], payload[4]); + + const uint32_t voltage_raw = encode_uint32(payload[11], payload[10], payload[9], payload[8]); + + const uint32_t current_raw = encode_uint32(payload[15], payload[14], payload[13], payload[12]); + + const uint16_t fade_rate = payload[16]; + + float power = 0; + if (power_raw > 0) { + power = POWER_SCALING_FACTOR / static_cast(power_raw); + } + + float voltage = 0; + if (voltage_raw > 0) { + voltage = VOLTAGE_SCALING_FACTOR / static_cast(voltage_raw); + } + + float current = 0; + if (current_raw > 0) { + current = CURRENT_SCALING_FACTOR / static_cast(current_raw); + } + + ESP_LOGI(TAG, "Got dimmer data:"); + ESP_LOGI(TAG, " HW version: %d", hw_version); + ESP_LOGI(TAG, " Brightness: %d", brightness); + ESP_LOGI(TAG, " Fade rate: %d", fade_rate); + ESP_LOGI(TAG, " Power: %f W", power); + ESP_LOGI(TAG, " Voltage: %f V", voltage); + ESP_LOGI(TAG, " Current: %f A", current); + + // Update sensors. + if (this->power_sensor_ != nullptr) { + this->power_sensor_->publish_state(power); + } + if (this->voltage_sensor_ != nullptr) { + this->voltage_sensor_->publish_state(voltage); + } + if (this->current_sensor_ != nullptr) { + this->current_sensor_->publish_state(current); + } + + return true; + } + case SHELLY_DIMMER_PROTO_CMD_VERSION: { + if (payload_len < 2) { + return false; + } + + this->version_minor_ = payload[0]; + this->version_major_ = payload[1]; + return true; + } + case SHELLY_DIMMER_PROTO_CMD_SWITCH: + case SHELLY_DIMMER_PROTO_CMD_SETTINGS: { + return !(payload_len < 1 || payload[0] != 0x01); + } + default: { + return false; + } + } +} + +void ShellyDimmer::reset_(bool boot0) { + ESP_LOGD(TAG, "Reset STM32, boot0=%d", boot0); + + this->pin_boot0_->digital_write(boot0); + this->pin_nrst_->digital_write(false); + + // Wait 50ms for the STM32 to reset. + delay(50); // NOLINT + + // Clear receive buffer. + while (this->available()) { + this->read(); + } + + this->pin_nrst_->digital_write(true); + // Wait 50ms for the STM32 to boot. + delay(50); // NOLINT + + ESP_LOGD(TAG, "Reset STM32 done"); +} + +void ShellyDimmer::reset_normal_boot_() { + // set NONE parity in normal mode + +#ifndef USE_ESP_IDF // workaround for reconfiguring the uart + Serial.end(); + Serial.begin(115200, SERIAL_8N1); + Serial.flush(); +#endif + + this->flush(); + this->reset_(false); +} + +void ShellyDimmer::reset_dfu_boot_() { + // set EVEN parity in bootloader mode + +#ifndef USE_ESP_IDF // workaround for reconfiguring the uart + Serial.end(); + Serial.begin(115200, SERIAL_8E1); + Serial.flush(); +#endif + + this->flush(); + this->reset_(true); +} + +} // namespace shelly_dimmer +} // namespace esphome diff --git a/esphome/components/shelly_dimmer/shelly_dimmer.h b/esphome/components/shelly_dimmer/shelly_dimmer.h new file mode 100644 index 0000000000..b7d476279e --- /dev/null +++ b/esphome/components/shelly_dimmer/shelly_dimmer.h @@ -0,0 +1,117 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/log.h" +#include "esphome/components/light/light_output.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" + +#include + +namespace esphome { +namespace shelly_dimmer { + +class ShellyDimmer : public PollingComponent, public light::LightOutput, public uart::UARTDevice { + private: + static constexpr uint16_t SHELLY_DIMMER_BUFFER_SIZE = 256; + + public: + float get_setup_priority() const override { return setup_priority::LATE; } + + void setup() override; + void update() override; + void dump_config() override; + + light::LightTraits get_traits() override { + auto traits = light::LightTraits(); + traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS}); + return traits; + } + + void setup_state(light::LightState *state) override { this->state_ = state; } + void write_state(light::LightState *state) override; + + void set_nrst_pin(GPIOPin *nrst_pin) { this->pin_nrst_ = nrst_pin; } + void set_boot0_pin(GPIOPin *boot0_pin) { this->pin_boot0_ = boot0_pin; } + + void set_leading_edge(bool leading_edge) { this->leading_edge_ = leading_edge; } + void set_warmup_brightness(uint16_t warmup_brightness) { this->warmup_brightness_ = warmup_brightness; } + void set_warmup_time(uint16_t warmup_time) { this->warmup_time_ = warmup_time; } + void set_fade_rate(uint16_t fade_rate) { this->fade_rate_ = fade_rate; } + void set_min_brightness(uint16_t min_brightness) { this->min_brightness_ = min_brightness; } + void set_max_brightness(uint16_t max_brightness) { this->max_brightness_ = max_brightness; } + + void set_power_sensor(sensor::Sensor *power_sensor) { this->power_sensor_ = power_sensor; } + void set_voltage_sensor(sensor::Sensor *voltage_sensor) { this->voltage_sensor_ = voltage_sensor; } + void set_current_sensor(sensor::Sensor *current_sensor) { this->current_sensor_ = current_sensor; } + + protected: + GPIOPin *pin_nrst_; + GPIOPin *pin_boot0_; + + // Frame parser state. + uint8_t seq_{0}; + std::array buffer_; + uint8_t buffer_pos_{0}; + + // Firmware version. + uint8_t version_major_; + uint8_t version_minor_; + + // Configuration. + bool leading_edge_{false}; + uint16_t warmup_brightness_{100}; + uint16_t warmup_time_{20}; + uint16_t fade_rate_{0}; + uint16_t min_brightness_{0}; + uint16_t max_brightness_{1000}; + + light::LightState *state_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + + bool ready_{false}; + uint16_t brightness_; + + /// Convert relative brightness into a dimmer brightness value. + uint16_t convert_brightness_(float brightness); + + /// Sends the given brightness value. + void send_brightness_(uint16_t brightness); + + /// Sends dimmer configuration. + void send_settings_(); + + /// Performs a firmware upgrade. + bool upgrade_firmware_(); + + /// Sends a command and waits for an acknowledgement. + bool send_command_(uint8_t cmd, const uint8_t *payload, uint8_t len); + + /// Frames a given command payload. + size_t frame_command_(uint8_t *data, uint8_t cmd, const uint8_t *payload, size_t len); + + /// Handles a single byte as part of a protocol frame. + /// + /// Returns -1 on failure, 0 when finished and 1 when more bytes needed. + int handle_byte_(uint8_t c); + + /// Reads a response frame. + bool read_frame_(); + + /// Handles a complete frame. + bool handle_frame_(); + + /// Reset STM32 with the BOOT0 pin set to the given value. + void reset_(bool boot0); + + /// Reset STM32 to boot the regular firmware. + void reset_normal_boot_(); + + /// Reset STM32 to boot into DFU mode to enable firmware upgrades. + void reset_dfu_boot_(); +}; + +} // namespace shelly_dimmer +} // namespace esphome diff --git a/esphome/components/shelly_dimmer/stm32flash.cpp b/esphome/components/shelly_dimmer/stm32flash.cpp new file mode 100644 index 0000000000..4c777776fb --- /dev/null +++ b/esphome/components/shelly_dimmer/stm32flash.cpp @@ -0,0 +1,1061 @@ +/* + stm32flash - Open Source ST STM32 flash program for Arduino + Copyright 2010 Geoffrey McRae + Copyright 2012-2014 Tormod Volden + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "esphome/core/defines.h" +#ifdef USE_SHD_FIRMWARE_DATA + +#include + +#include "stm32flash.h" +#include "debug.h" + +#include "dev_table.h" +#include "esphome/core/log.h" + +#include +#include + +namespace { + +constexpr uint8_t STM32_ACK = 0x79; +constexpr uint8_t STM32_NACK = 0x1F; +constexpr uint8_t STM32_BUSY = 0x76; + +constexpr uint8_t STM32_CMD_INIT = 0x7F; +constexpr uint8_t STM32_CMD_GET = 0x00; /* get the version and command supported */ +constexpr uint8_t STM32_CMD_GVR = 0x01; /* get version and read protection status */ +constexpr uint8_t STM32_CMD_GID = 0x02; /* get ID */ +constexpr uint8_t STM32_CMD_RM = 0x11; /* read memory */ +constexpr uint8_t STM32_CMD_GO = 0x21; /* go */ +constexpr uint8_t STM32_CMD_WM = 0x31; /* write memory */ +constexpr uint8_t STM32_CMD_WM_NS = 0x32; /* no-stretch write memory */ +constexpr uint8_t STM32_CMD_ER = 0x43; /* erase */ +constexpr uint8_t STM32_CMD_EE = 0x44; /* extended erase */ +constexpr uint8_t STM32_CMD_EE_NS = 0x45; /* extended erase no-stretch */ +constexpr uint8_t STM32_CMD_WP = 0x63; /* write protect */ +constexpr uint8_t STM32_CMD_WP_NS = 0x64; /* write protect no-stretch */ +constexpr uint8_t STM32_CMD_UW = 0x73; /* write unprotect */ +constexpr uint8_t STM32_CMD_UW_NS = 0x74; /* write unprotect no-stretch */ +constexpr uint8_t STM32_CMD_RP = 0x82; /* readout protect */ +constexpr uint8_t STM32_CMD_RP_NS = 0x83; /* readout protect no-stretch */ +constexpr uint8_t STM32_CMD_UR = 0x92; /* readout unprotect */ +constexpr uint8_t STM32_CMD_UR_NS = 0x93; /* readout unprotect no-stretch */ +constexpr uint8_t STM32_CMD_CRC = 0xA1; /* compute CRC */ +constexpr uint8_t STM32_CMD_ERR = 0xFF; /* not a valid command */ + +constexpr uint32_t STM32_RESYNC_TIMEOUT = 35 * 1000; /* milliseconds */ +constexpr uint32_t STM32_MASSERASE_TIMEOUT = 35 * 1000; /* milliseconds */ +constexpr uint32_t STM32_PAGEERASE_TIMEOUT = 5 * 1000; /* milliseconds */ +constexpr uint32_t STM32_BLKWRITE_TIMEOUT = 1 * 1000; /* milliseconds */ +constexpr uint32_t STM32_WUNPROT_TIMEOUT = 1 * 1000; /* milliseconds */ +constexpr uint32_t STM32_WPROT_TIMEOUT = 1 * 1000; /* milliseconds */ +constexpr uint32_t STM32_RPROT_TIMEOUT = 1 * 1000; /* milliseconds */ +constexpr uint32_t DEFAULT_TIMEOUT = 5 * 1000; /* milliseconds */ + +constexpr uint8_t STM32_CMD_GET_LENGTH = 17; /* bytes in the reply */ + +/* Reset code for ARMv7-M (Cortex-M3) and ARMv6-M (Cortex-M0) + * see ARMv7-M or ARMv6-M Architecture Reference Manual (table B3-8) + * or "The definitive guide to the ARM Cortex-M3", section 14.4. + */ +constexpr uint8_t STM_RESET_CODE[] = { + 0x01, 0x49, // ldr r1, [pc, #4] ; () + 0x02, 0x4A, // ldr r2, [pc, #8] ; () + 0x0A, 0x60, // str r2, [r1, #0] + 0xfe, 0xe7, // endless: b endless + 0x0c, 0xed, 0x00, 0xe0, // .word 0xe000ed0c = NVIC AIRCR register address + 0x04, 0x00, 0xfa, 0x05 // .word 0x05fa0004 = VECTKEY | SYSRESETREQ +}; + +constexpr uint32_t STM_RESET_CODE_SIZE = sizeof(STM_RESET_CODE); + +/* RM0360, Empty check + * On STM32F070x6 and STM32F030xC devices only, internal empty check flag is + * implemented to allow easy programming of the virgin devices by the boot loader. This flag is + * used when BOOT0 pin is defining Main Flash memory as the target boot space. When the + * flag is set, the device is considered as empty and System memory (boot loader) is selected + * instead of the Main Flash as a boot space to allow user to program the Flash memory. + * This flag is updated only during Option bytes loading: it is set when the content of the + * address 0x08000 0000 is read as 0xFFFF FFFF, otherwise it is cleared. It means a power + * on or setting of OBL_LAUNCH bit in FLASH_CR register is needed to clear this flag after + * programming of a virgin device to execute user code after System reset. + */ +constexpr uint8_t STM_OBL_LAUNCH_CODE[] = { + 0x01, 0x49, // ldr r1, [pc, #4] ; () + 0x02, 0x4A, // ldr r2, [pc, #8] ; () + 0x0A, 0x60, // str r2, [r1, #0] + 0xfe, 0xe7, // endless: b endless + 0x10, 0x20, 0x02, 0x40, // address: FLASH_CR = 40022010 + 0x00, 0x20, 0x00, 0x00 // value: OBL_LAUNCH = 00002000 +}; + +constexpr uint32_t STM_OBL_LAUNCH_CODE_SIZE = sizeof(STM_OBL_LAUNCH_CODE); + +constexpr char TAG[] = "stm32flash"; + +} // Anonymous namespace + +namespace esphome { +namespace shelly_dimmer { + +namespace { + +int flash_addr_to_page_ceil(const stm32_t *stm, uint32_t addr) { + if (!(addr >= stm->dev->fl_start && addr <= stm->dev->fl_end)) + return 0; + + int page = 0; + addr -= stm->dev->fl_start; + const auto *psize = stm->dev->fl_ps; + + while (addr >= psize[0]) { + addr -= psize[0]; + page++; + if (psize[1]) + psize++; + } + + return addr ? page + 1 : page; +} + +stm32_err_t stm32_get_ack_timeout(const stm32_t *stm, uint32_t timeout) { + auto *stream = stm->stream; + uint8_t rxbyte; + + if (!(stm->flags & STREAM_OPT_RETRY)) + timeout = 0; + + if (timeout == 0) + timeout = DEFAULT_TIMEOUT; + + const uint32_t start_time = millis(); + do { + yield(); + if (!stream->available()) { + if (millis() < start_time + timeout) + continue; + ESP_LOGD(TAG, "Failed to read ACK timeout=%i", timeout); + return STM32_ERR_UNKNOWN; + } + + stream->read_byte(&rxbyte); + + if (rxbyte == STM32_ACK) + return STM32_ERR_OK; + if (rxbyte == STM32_NACK) + return STM32_ERR_NACK; + if (rxbyte != STM32_BUSY) { + ESP_LOGD(TAG, "Got byte 0x%02x instead of ACK", rxbyte); + return STM32_ERR_UNKNOWN; + } + } while (true); +} + +stm32_err_t stm32_get_ack(const stm32_t *stm) { return stm32_get_ack_timeout(stm, 0); } + +stm32_err_t stm32_send_command_timeout(const stm32_t *stm, const uint8_t cmd, const uint32_t timeout) { + auto *const stream = stm->stream; + + static constexpr auto BUFFER_SIZE = 2; + const uint8_t buf[] = { + cmd, + static_cast(cmd ^ 0xFF), + }; + static_assert(sizeof(buf) == BUFFER_SIZE, "Buf expected to be 2 bytes"); + + stream->write_array(buf, BUFFER_SIZE); + stream->flush(); + + stm32_err_t s_err = stm32_get_ack_timeout(stm, timeout); + if (s_err == STM32_ERR_OK) + return STM32_ERR_OK; + if (s_err == STM32_ERR_NACK) { + ESP_LOGD(TAG, "Got NACK from device on command 0x%02x", cmd); + } else { + ESP_LOGD(TAG, "Unexpected reply from device on command 0x%02x", cmd); + } + return STM32_ERR_UNKNOWN; +} + +stm32_err_t stm32_send_command(const stm32_t *stm, const uint8_t cmd) { + return stm32_send_command_timeout(stm, cmd, 0); +} + +/* if we have lost sync, send a wrong command and expect a NACK */ +stm32_err_t stm32_resync(const stm32_t *stm) { + auto *const stream = stm->stream; + uint32_t t0 = millis(); + auto t1 = t0; + + static constexpr auto BUFFER_SIZE = 2; + const uint8_t buf[] = { + STM32_CMD_ERR, + static_cast(STM32_CMD_ERR ^ 0xFF), + }; + static_assert(sizeof(buf) == BUFFER_SIZE, "Buf expected to be 2 bytes"); + + uint8_t ack; + while (t1 < t0 + STM32_RESYNC_TIMEOUT) { + stream->write_array(buf, BUFFER_SIZE); + stream->flush(); + if (!stream->read_array(&ack, 1)) { + t1 = millis(); + continue; + } + if (ack == STM32_NACK) + return STM32_ERR_OK; + t1 = millis(); + } + return STM32_ERR_UNKNOWN; +} + +/* + * some command receive reply frame with variable length, and length is + * embedded in reply frame itself. + * We can guess the length, but if we guess wrong the protocol gets out + * of sync. + * Use resync for frame oriented interfaces (e.g. I2C) and byte-by-byte + * read for byte oriented interfaces (e.g. UART). + * + * to run safely, data buffer should be allocated for 256+1 bytes + * + * len is value of the first byte in the frame. + */ +stm32_err_t stm32_guess_len_cmd(const stm32_t *stm, const uint8_t cmd, uint8_t *const data, unsigned int len) { + auto *const stream = stm->stream; + + if (stm32_send_command(stm, cmd) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + if (stm->flags & STREAM_OPT_BYTE) { + /* interface is UART-like */ + if (!stream->read_array(data, 1)) + return STM32_ERR_UNKNOWN; + len = data[0]; + if (!stream->read_array(data + 1, len + 1)) + return STM32_ERR_UNKNOWN; + return STM32_ERR_OK; + } + + const auto ret = stream->read_array(data, len + 2); + if (ret && len == data[0]) + return STM32_ERR_OK; + if (!ret) { + /* restart with only one byte */ + if (stm32_resync(stm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + if (stm32_send_command(stm, cmd) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + if (!stream->read_array(data, 1)) + return STM32_ERR_UNKNOWN; + } + + ESP_LOGD(TAG, "Re sync (len = %d)", data[0]); + if (stm32_resync(stm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + len = data[0]; + if (stm32_send_command(stm, cmd) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + if (!stream->read_array(data, len + 2)) + return STM32_ERR_UNKNOWN; + return STM32_ERR_OK; +} + +/* + * Some interface, e.g. UART, requires a specific init sequence to let STM32 + * autodetect the interface speed. + * The sequence is only required one time after reset. + * This function sends the init sequence and, in case of timeout, recovers + * the interface. + */ +stm32_err_t stm32_send_init_seq(const stm32_t *stm) { + auto *const stream = stm->stream; + + stream->write_array(&STM32_CMD_INIT, 1); + stream->flush(); + + uint8_t byte; + bool ret = stream->read_array(&byte, 1); + if (ret && byte == STM32_ACK) + return STM32_ERR_OK; + if (ret && byte == STM32_NACK) { + /* We could get error later, but let's continue, for now. */ + ESP_LOGD(TAG, "Warning: the interface was not closed properly."); + return STM32_ERR_OK; + } + if (!ret) { + ESP_LOGD(TAG, "Failed to init device."); + return STM32_ERR_UNKNOWN; + } + + /* + * Check if previous STM32_CMD_INIT was taken as first byte + * of a command. Send a new byte, we should get back a NACK. + */ + stream->write_array(&STM32_CMD_INIT, 1); + stream->flush(); + + ret = stream->read_array(&byte, 1); + if (ret && byte == STM32_NACK) + return STM32_ERR_OK; + ESP_LOGD(TAG, "Failed to init device."); + return STM32_ERR_UNKNOWN; +} + +stm32_err_t stm32_mass_erase(const stm32_t *stm) { + auto *const stream = stm->stream; + + if (stm32_send_command(stm, stm->cmd->er) != STM32_ERR_OK) { + ESP_LOGD(TAG, "Can't initiate chip mass erase!"); + return STM32_ERR_UNKNOWN; + } + + /* regular erase (0x43) */ + if (stm->cmd->er == STM32_CMD_ER) { + const auto s_err = stm32_send_command_timeout(stm, 0xFF, STM32_MASSERASE_TIMEOUT); + if (s_err != STM32_ERR_OK) { + return STM32_ERR_UNKNOWN; + } + return STM32_ERR_OK; + } + + /* extended erase */ + static constexpr auto BUFFER_SIZE = 3; + const uint8_t buf[] = { + 0xFF, /* 0xFFFF the magic number for mass erase */ + 0xFF, 0x00, /* checksum */ + }; + static_assert(sizeof(buf) == BUFFER_SIZE, "Expected the buffer to be 3 bytes"); + stream->write_array(buf, 3); + stream->flush(); + + const auto s_err = stm32_get_ack_timeout(stm, STM32_MASSERASE_TIMEOUT); + if (s_err != STM32_ERR_OK) { + ESP_LOGD(TAG, "Mass erase failed. Try specifying the number of pages to be erased."); + return STM32_ERR_UNKNOWN; + } + return STM32_ERR_OK; +} + +template std::unique_ptr malloc_array_raii(size_t size) { + // Could be constexpr in c++17 + static const auto DELETOR = [](T *memory) { + free(memory); // NOLINT + }; + return std::unique_ptr{static_cast(malloc(size)), // NOLINT + DELETOR}; +} + +stm32_err_t stm32_pages_erase(const stm32_t *stm, const uint32_t spage, const uint32_t pages) { + auto *const stream = stm->stream; + uint8_t cs = 0; + int i = 0; + + /* The erase command reported by the bootloader is either 0x43, 0x44 or 0x45 */ + /* 0x44 is Extended Erase, a 2 byte based protocol and needs to be handled differently. */ + /* 0x45 is clock no-stretching version of Extended Erase for I2C port. */ + if (stm32_send_command(stm, stm->cmd->er) != STM32_ERR_OK) { + ESP_LOGD(TAG, "Can't initiate chip mass erase!"); + return STM32_ERR_UNKNOWN; + } + + /* regular erase (0x43) */ + if (stm->cmd->er == STM32_CMD_ER) { + // Free memory with RAII + auto buf = malloc_array_raii(1 + pages + 1); + + if (!buf) + return STM32_ERR_UNKNOWN; + + buf[i++] = pages - 1; + cs ^= (pages - 1); + for (auto pg_num = spage; pg_num < (pages + spage); pg_num++) { + buf[i++] = pg_num; + cs ^= pg_num; + } + buf[i++] = cs; + stream->write_array(&buf[0], i); + stream->flush(); + + const auto s_err = stm32_get_ack_timeout(stm, pages * STM32_PAGEERASE_TIMEOUT); + if (s_err != STM32_ERR_OK) { + return STM32_ERR_UNKNOWN; + } + return STM32_ERR_OK; + } + + /* extended erase */ + + // Free memory with RAII + auto buf = malloc_array_raii(2 + 2 * pages + 1); + + if (!buf) + return STM32_ERR_UNKNOWN; + + /* Number of pages to be erased - 1, two bytes, MSB first */ + uint8_t pg_byte = (pages - 1) >> 8; + buf[i++] = pg_byte; + cs ^= pg_byte; + pg_byte = (pages - 1) & 0xFF; + buf[i++] = pg_byte; + cs ^= pg_byte; + + for (auto pg_num = spage; pg_num < spage + pages; pg_num++) { + pg_byte = pg_num >> 8; + cs ^= pg_byte; + buf[i++] = pg_byte; + pg_byte = pg_num & 0xFF; + cs ^= pg_byte; + buf[i++] = pg_byte; + } + buf[i++] = cs; + stream->write_array(&buf[0], i); + stream->flush(); + + const auto s_err = stm32_get_ack_timeout(stm, pages * STM32_PAGEERASE_TIMEOUT); + if (s_err != STM32_ERR_OK) { + ESP_LOGD(TAG, "Page-by-page erase failed. Check the maximum pages your device supports."); + return STM32_ERR_UNKNOWN; + } + + return STM32_ERR_OK; +} + +template stm32_err_t stm32_check_ack_timeout(const stm32_err_t s_err, const T &&log) { + switch (s_err) { + case STM32_ERR_OK: + return STM32_ERR_OK; + case STM32_ERR_NACK: + log(); + // TODO: c++17 [[fallthrough]] + /* fallthrough */ + default: + return STM32_ERR_UNKNOWN; + } +} + +/* detect CPU endian */ +bool cpu_le() { + static constexpr int N = 1; + + // returns true if little endian + return *reinterpret_cast(&N) == 1; +} + +uint32_t le_u32(const uint32_t v) { + if (!cpu_le()) + return ((v & 0xFF000000) >> 24) | ((v & 0x00FF0000) >> 8) | ((v & 0x0000FF00) << 8) | ((v & 0x000000FF) << 24); + return v; +} + +template void populate_buffer_with_address(uint8_t (&buffer)[N], uint32_t address) { + buffer[0] = static_cast(address >> 24); + buffer[1] = static_cast((address >> 16) & 0xFF); + buffer[2] = static_cast((address >> 8) & 0xFF); + buffer[3] = static_cast(address & 0xFF); + buffer[4] = static_cast(buffer[0] ^ buffer[1] ^ buffer[2] ^ buffer[3]); +} + +} // Anonymous namespace + +} // namespace shelly_dimmer +} // namespace esphome + +namespace esphome { +namespace shelly_dimmer { + +/* find newer command by higher code */ +#define newer(prev, a) (((prev) == STM32_CMD_ERR) ? (a) : (((prev) > (a)) ? (prev) : (a))) + +stm32_t *stm32_init(uart::UARTDevice *stream, const uint8_t flags, const char init) { + uint8_t buf[257]; + + // Could be constexpr in c++17 + static const auto CLOSE = [](stm32_t *stm32) { stm32_close(stm32); }; + + // Cleanup with RAII + std::unique_ptr stm{static_cast(calloc(sizeof(stm32_t), 1)), // NOLINT + CLOSE}; + + if (!stm) { + return nullptr; + } + stm->stream = stream; + stm->flags = flags; + + stm->cmd = static_cast(malloc(sizeof(stm32_cmd_t))); // NOLINT + if (!stm->cmd) { + return nullptr; + } + memset(stm->cmd, STM32_CMD_ERR, sizeof(stm32_cmd_t)); + + if ((stm->flags & STREAM_OPT_CMD_INIT) && init) { + if (stm32_send_init_seq(stm.get()) != STM32_ERR_OK) + return nullptr; // NOLINT + } + + /* get the version and read protection status */ + if (stm32_send_command(stm.get(), STM32_CMD_GVR) != STM32_ERR_OK) { + return nullptr; // NOLINT + } + + /* From AN, only UART bootloader returns 3 bytes */ + { + const auto len = (stm->flags & STREAM_OPT_GVR_ETX) ? 3 : 1; + if (!stream->read_array(buf, len)) + return nullptr; // NOLINT + stm->version = buf[0]; + stm->option1 = (stm->flags & STREAM_OPT_GVR_ETX) ? buf[1] : 0; + stm->option2 = (stm->flags & STREAM_OPT_GVR_ETX) ? buf[2] : 0; + if (stm32_get_ack(stm.get()) != STM32_ERR_OK) { + return nullptr; + } + } + + { + const auto len = ([&]() { + /* get the bootloader information */ + if (stm->cmd_get_reply) { + for (auto i = 0; stm->cmd_get_reply[i].length; ++i) { + if (stm->version == stm->cmd_get_reply[i].version) { + return stm->cmd_get_reply[i].length; + } + } + } + + return STM32_CMD_GET_LENGTH; + })(); + + if (stm32_guess_len_cmd(stm.get(), STM32_CMD_GET, buf, len) != STM32_ERR_OK) + return nullptr; + } + + const auto stop = buf[0] + 1; + stm->bl_version = buf[1]; + int new_cmds = 0; + for (auto i = 1; i < stop; ++i) { + const auto val = buf[i + 1]; + switch (val) { + case STM32_CMD_GET: + stm->cmd->get = val; + break; + case STM32_CMD_GVR: + stm->cmd->gvr = val; + break; + case STM32_CMD_GID: + stm->cmd->gid = val; + break; + case STM32_CMD_RM: + stm->cmd->rm = val; + break; + case STM32_CMD_GO: + stm->cmd->go = val; + break; + case STM32_CMD_WM: + case STM32_CMD_WM_NS: + stm->cmd->wm = newer(stm->cmd->wm, val); + break; + case STM32_CMD_ER: + case STM32_CMD_EE: + case STM32_CMD_EE_NS: + stm->cmd->er = newer(stm->cmd->er, val); + break; + case STM32_CMD_WP: + case STM32_CMD_WP_NS: + stm->cmd->wp = newer(stm->cmd->wp, val); + break; + case STM32_CMD_UW: + case STM32_CMD_UW_NS: + stm->cmd->uw = newer(stm->cmd->uw, val); + break; + case STM32_CMD_RP: + case STM32_CMD_RP_NS: + stm->cmd->rp = newer(stm->cmd->rp, val); + break; + case STM32_CMD_UR: + case STM32_CMD_UR_NS: + stm->cmd->ur = newer(stm->cmd->ur, val); + break; + case STM32_CMD_CRC: + stm->cmd->crc = newer(stm->cmd->crc, val); + break; + default: + if (new_cmds++ == 0) { + ESP_LOGD(TAG, "GET returns unknown commands (0x%2x", val); + } else { + ESP_LOGD(TAG, ", 0x%2x", val); + } + } + } + if (new_cmds) + ESP_LOGD(TAG, ")"); + if (stm32_get_ack(stm.get()) != STM32_ERR_OK) { + return nullptr; + } + + if (stm->cmd->get == STM32_CMD_ERR || stm->cmd->gvr == STM32_CMD_ERR || stm->cmd->gid == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: bootloader did not returned correct information from GET command"); + return nullptr; + } + + /* get the device ID */ + if (stm32_guess_len_cmd(stm.get(), stm->cmd->gid, buf, 1) != STM32_ERR_OK) { + return nullptr; + } + const auto returned = buf[0] + 1; + if (returned < 2) { + ESP_LOGD(TAG, "Only %d bytes sent in the PID, unknown/unsupported device", returned); + return nullptr; + } + stm->pid = (buf[1] << 8) | buf[2]; + if (returned > 2) { + ESP_LOGD(TAG, "This bootloader returns %d extra bytes in PID:", returned); + for (auto i = 2; i <= returned; i++) + ESP_LOGD(TAG, " %02x", buf[i]); + } + if (stm32_get_ack(stm.get()) != STM32_ERR_OK) { + return nullptr; + } + + stm->dev = DEVICES; + while (stm->dev->id != 0x00 && stm->dev->id != stm->pid) + ++stm->dev; + + if (!stm->dev->id) { + ESP_LOGD(TAG, "Unknown/unsupported device (Device ID: 0x%03x)", stm->pid); + return nullptr; + } + + // TODO: Would be much better if the unique_ptr was returned from this function + // Release ownership of unique_ptr + return stm.release(); // NOLINT +} + +void stm32_close(stm32_t *stm) { + if (stm) + free(stm->cmd); // NOLINT + free(stm); // NOLINT +} + +stm32_err_t stm32_read_memory(const stm32_t *stm, const uint32_t address, uint8_t *data, const unsigned int len) { + auto *const stream = stm->stream; + + if (!len) + return STM32_ERR_OK; + + if (len > 256) { + ESP_LOGD(TAG, "Error: READ length limit at 256 bytes"); + return STM32_ERR_UNKNOWN; + } + + if (stm->cmd->rm == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: READ command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + if (stm32_send_command(stm, stm->cmd->rm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + static constexpr auto BUFFER_SIZE = 5; + uint8_t buf[BUFFER_SIZE]; + populate_buffer_with_address(buf, address); + + stream->write_array(buf, BUFFER_SIZE); + stream->flush(); + + if (stm32_get_ack(stm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + if (stm32_send_command(stm, len - 1) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + if (!stream->read_array(data, len)) + return STM32_ERR_UNKNOWN; + + return STM32_ERR_OK; +} + +stm32_err_t stm32_write_memory(const stm32_t *stm, uint32_t address, const uint8_t *data, const unsigned int len) { + auto *const stream = stm->stream; + + if (!len) + return STM32_ERR_OK; + + if (len > 256) { + ESP_LOGD(TAG, "Error: READ length limit at 256 bytes"); + return STM32_ERR_UNKNOWN; + } + + /* must be 32bit aligned */ + if (address & 0x3) { + ESP_LOGD(TAG, "Error: WRITE address must be 4 byte aligned"); + return STM32_ERR_UNKNOWN; + } + + if (stm->cmd->wm == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: WRITE command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + /* send the address and checksum */ + if (stm32_send_command(stm, stm->cmd->wm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + static constexpr auto BUFFER_SIZE = 5; + uint8_t buf1[BUFFER_SIZE]; + populate_buffer_with_address(buf1, address); + + stream->write_array(buf1, BUFFER_SIZE); + stream->flush(); + if (stm32_get_ack(stm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + const unsigned int aligned_len = (len + 3) & ~3; + uint8_t cs = aligned_len - 1; + uint8_t buf[256 + 2]; + + buf[0] = aligned_len - 1; + for (auto i = 0; i < len; i++) { + cs ^= data[i]; + buf[i + 1] = data[i]; + } + /* padding data */ + for (auto i = len; i < aligned_len; i++) { + cs ^= 0xFF; + buf[i + 1] = 0xFF; + } + buf[aligned_len + 1] = cs; + stream->write_array(buf, aligned_len + 2); + stream->flush(); + + const auto s_err = stm32_get_ack_timeout(stm, STM32_BLKWRITE_TIMEOUT); + if (s_err != STM32_ERR_OK) { + return STM32_ERR_UNKNOWN; + } + return STM32_ERR_OK; +} + +stm32_err_t stm32_wunprot_memory(const stm32_t *stm) { + if (stm->cmd->uw == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: WRITE UNPROTECT command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + if (stm32_send_command(stm, stm->cmd->uw) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + return stm32_check_ack_timeout(stm32_get_ack_timeout(stm, STM32_WUNPROT_TIMEOUT), + []() { ESP_LOGD(TAG, "Error: Failed to WRITE UNPROTECT"); }); +} + +stm32_err_t stm32_wprot_memory(const stm32_t *stm) { + if (stm->cmd->wp == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: WRITE PROTECT command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + if (stm32_send_command(stm, stm->cmd->wp) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + return stm32_check_ack_timeout(stm32_get_ack_timeout(stm, STM32_WPROT_TIMEOUT), + []() { ESP_LOGD(TAG, "Error: Failed to WRITE PROTECT"); }); +} + +stm32_err_t stm32_runprot_memory(const stm32_t *stm) { + if (stm->cmd->ur == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: READOUT UNPROTECT command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + if (stm32_send_command(stm, stm->cmd->ur) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + return stm32_check_ack_timeout(stm32_get_ack_timeout(stm, STM32_MASSERASE_TIMEOUT), + []() { ESP_LOGD(TAG, "Error: Failed to READOUT UNPROTECT"); }); +} + +stm32_err_t stm32_readprot_memory(const stm32_t *stm) { + if (stm->cmd->rp == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: READOUT PROTECT command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + if (stm32_send_command(stm, stm->cmd->rp) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + return stm32_check_ack_timeout(stm32_get_ack_timeout(stm, STM32_RPROT_TIMEOUT), + []() { ESP_LOGD(TAG, "Error: Failed to READOUT PROTECT"); }); +} + +stm32_err_t stm32_erase_memory(const stm32_t *stm, uint32_t spage, uint32_t pages) { + if (!pages || spage > STM32_MAX_PAGES || ((pages != STM32_MASS_ERASE) && ((spage + pages) > STM32_MAX_PAGES))) + return STM32_ERR_OK; + + if (stm->cmd->er == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: ERASE command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + if (pages == STM32_MASS_ERASE) { + /* + * Not all chips support mass erase. + * Mass erase can be obtained executing a "readout protect" + * followed by "readout un-protect". This method is not + * suggested because can hang the target if a debug SWD/JTAG + * is connected. When the target enters in "readout + * protection" mode it will consider the debug connection as + * a tentative of intrusion and will hang. + * Erasing the flash page-by-page is the safer way to go. + */ + if (!(stm->dev->flags & F_NO_ME)) + return stm32_mass_erase(stm); + + pages = flash_addr_to_page_ceil(stm, stm->dev->fl_end); + } + + /* + * Some device, like STM32L152, cannot erase more than 512 pages in + * one command. Split the call. + */ + static constexpr uint32_t MAX_PAGE_SIZE = 512; + while (pages) { + const auto n = std::min(pages, MAX_PAGE_SIZE); + const auto s_err = stm32_pages_erase(stm, spage, n); + if (s_err != STM32_ERR_OK) + return s_err; + spage += n; + pages -= n; + } + return STM32_ERR_OK; +} + +static stm32_err_t stm32_run_raw_code(const stm32_t *stm, uint32_t target_address, const uint8_t *code, + uint32_t code_size) { + static constexpr uint32_t BUFFER_SIZE = 256; + + const auto stack_le = le_u32(0x20002000); + const auto code_address_le = le_u32(target_address + 8 + 1); // thumb mode address (!) + uint32_t length = code_size + 8; + + /* Must be 32-bit aligned */ + if (target_address & 0x3) { + ESP_LOGD(TAG, "Error: code address must be 4 byte aligned"); + return STM32_ERR_UNKNOWN; + } + + // Could be constexpr in c++17 + static const auto DELETOR = [](uint8_t *memory) { + free(memory); // NOLINT + }; + + // Free memory with RAII + std::unique_ptr mem{static_cast(malloc(length)), // NOLINT + DELETOR}; + + if (!mem) + return STM32_ERR_UNKNOWN; + + memcpy(mem.get(), &stack_le, sizeof(stack_le)); + memcpy(mem.get() + 4, &code_address_le, sizeof(code_address_le)); + memcpy(mem.get() + 8, code, code_size); + + auto *pos = mem.get(); + auto address = target_address; + while (length > 0) { + const auto w = std::min(length, BUFFER_SIZE); + if (stm32_write_memory(stm, address, pos, w) != STM32_ERR_OK) { + return STM32_ERR_UNKNOWN; + } + + address += w; + pos += w; + length -= w; + } + + return stm32_go(stm, target_address); +} + +stm32_err_t stm32_go(const stm32_t *stm, const uint32_t address) { + auto *const stream = stm->stream; + + if (stm->cmd->go == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: GO command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + if (stm32_send_command(stm, stm->cmd->go) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + static constexpr auto BUFFER_SIZE = 5; + uint8_t buf[BUFFER_SIZE]; + populate_buffer_with_address(buf, address); + + stream->write_array(buf, BUFFER_SIZE); + stream->flush(); + + if (stm32_get_ack(stm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + return STM32_ERR_OK; +} + +stm32_err_t stm32_reset_device(const stm32_t *stm) { + const auto target_address = stm->dev->ram_start; + + if (stm->dev->flags & F_OBLL) { + /* set the OBL_LAUNCH bit to reset device (see RM0360, 2.5) */ + return stm32_run_raw_code(stm, target_address, STM_OBL_LAUNCH_CODE, STM_OBL_LAUNCH_CODE_SIZE); + } else { + return stm32_run_raw_code(stm, target_address, STM_RESET_CODE, STM_RESET_CODE_SIZE); + } +} + +stm32_err_t stm32_crc_memory(const stm32_t *stm, const uint32_t address, const uint32_t length, uint32_t *const crc) { + static constexpr auto BUFFER_SIZE = 5; + auto *const stream = stm->stream; + + if (address & 0x3 || length & 0x3) { + ESP_LOGD(TAG, "Start and end addresses must be 4 byte aligned"); + return STM32_ERR_UNKNOWN; + } + + if (stm->cmd->crc == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: CRC command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + if (stm32_send_command(stm, stm->cmd->crc) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + { + static constexpr auto BUFFER_SIZE = 5; + uint8_t buf[BUFFER_SIZE]; + populate_buffer_with_address(buf, address); + + stream->write_array(buf, BUFFER_SIZE); + stream->flush(); + } + + if (stm32_get_ack(stm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + { + static constexpr auto BUFFER_SIZE = 5; + uint8_t buf[BUFFER_SIZE]; + populate_buffer_with_address(buf, address); + + stream->write_array(buf, BUFFER_SIZE); + stream->flush(); + } + + if (stm32_get_ack(stm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + if (stm32_get_ack(stm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + { + uint8_t buf[BUFFER_SIZE]; + if (!stream->read_array(buf, BUFFER_SIZE)) + return STM32_ERR_UNKNOWN; + + if (buf[4] != (buf[0] ^ buf[1] ^ buf[2] ^ buf[3])) + return STM32_ERR_UNKNOWN; + + *crc = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; + } + + return STM32_ERR_OK; +} + +/* + * CRC computed by STM32 is similar to the standard crc32_be() + * implemented, for example, in Linux kernel in ./lib/crc32.c + * But STM32 computes it on units of 32 bits word and swaps the + * bytes of the word before the computation. + * Due to byte swap, I cannot use any CRC available in existing + * libraries, so here is a simple not optimized implementation. + */ +uint32_t stm32_sw_crc(uint32_t crc, uint8_t *buf, unsigned int len) { + static constexpr uint32_t CRCPOLY_BE = 0x04c11db7; + static constexpr uint32_t CRC_MSBMASK = 0x80000000; + + if (len & 0x3) { + ESP_LOGD(TAG, "Buffer length must be multiple of 4 bytes"); + return 0; + } + + while (len) { + uint32_t data = *buf++; + data |= *buf++ << 8; + data |= *buf++ << 16; + data |= *buf++ << 24; + len -= 4; + + crc ^= data; + + for (size_t i = 0; i < 32; ++i) { + if (crc & CRC_MSBMASK) { + crc = (crc << 1) ^ CRCPOLY_BE; + } else { + crc = (crc << 1); + } + } + } + return crc; +} + +stm32_err_t stm32_crc_wrapper(const stm32_t *stm, uint32_t address, uint32_t length, uint32_t *crc) { + static constexpr uint32_t CRC_INIT_VALUE = 0xFFFFFFFF; + static constexpr uint32_t BUFFER_SIZE = 256; + + uint8_t buf[BUFFER_SIZE]; + + if (address & 0x3 || length & 0x3) { + ESP_LOGD(TAG, "Start and end addresses must be 4 byte aligned"); + return STM32_ERR_UNKNOWN; + } + + if (stm->cmd->crc != STM32_CMD_ERR) + return stm32_crc_memory(stm, address, length, crc); + + const auto start = address; + const auto total_len = length; + uint32_t current_crc = CRC_INIT_VALUE; + while (length) { + const auto len = std::min(BUFFER_SIZE, length); + if (stm32_read_memory(stm, address, buf, len) != STM32_ERR_OK) { + ESP_LOGD(TAG, "Failed to read memory at address 0x%08x, target write-protected?", address); + return STM32_ERR_UNKNOWN; + } + current_crc = stm32_sw_crc(current_crc, buf, len); + length -= len; + address += len; + + ESP_LOGD(TAG, "\rCRC address 0x%08x (%.2f%%) ", address, (100.0f / (float) total_len) * (float) (address - start)); + } + ESP_LOGD(TAG, "Done."); + *crc = current_crc; + return STM32_ERR_OK; +} + +} // namespace shelly_dimmer +} // namespace esphome +#endif diff --git a/esphome/components/shelly_dimmer/stm32flash.h b/esphome/components/shelly_dimmer/stm32flash.h new file mode 100644 index 0000000000..c561375c38 --- /dev/null +++ b/esphome/components/shelly_dimmer/stm32flash.h @@ -0,0 +1,129 @@ +/* + stm32flash - Open Source ST STM32 flash program for Arduino + Copyright (C) 2010 Geoffrey McRae + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include "esphome/core/defines.h" +#ifdef USE_SHD_FIRMWARE_DATA + +#include +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace shelly_dimmer { + +/* flags */ +constexpr auto STREAM_OPT_BYTE = (1 << 0); /* byte (not frame) oriented */ +constexpr auto STREAM_OPT_GVR_ETX = (1 << 1); /* cmd GVR returns protection status */ +constexpr auto STREAM_OPT_CMD_INIT = (1 << 2); /* use INIT cmd to autodetect speed */ +constexpr auto STREAM_OPT_RETRY = (1 << 3); /* allowed read() retry after timeout */ +constexpr auto STREAM_OPT_I2C = (1 << 4); /* i2c */ +constexpr auto STREAM_OPT_STRETCH_W = (1 << 5); /* warning for no-stretching commands */ + +constexpr auto STREAM_SERIAL = (STREAM_OPT_BYTE | STREAM_OPT_GVR_ETX | STREAM_OPT_CMD_INIT | STREAM_OPT_RETRY); +constexpr auto STREAM_I2C = (STREAM_OPT_I2C | STREAM_OPT_STRETCH_W); + +constexpr auto STM32_MAX_RX_FRAME = 256; /* cmd read memory */ +constexpr auto STM32_MAX_TX_FRAME = (1 + 256 + 1); /* cmd write memory */ + +constexpr auto STM32_MAX_PAGES = 0x0000ffff; +constexpr auto STM32_MASS_ERASE = 0x00100000; /* > 2 x max_pages */ + +using stm32_err_t = enum Stm32Err { + STM32_ERR_OK = 0, + STM32_ERR_UNKNOWN, /* Generic error */ + STM32_ERR_NACK, + STM32_ERR_NO_CMD, /* Command not available in bootloader */ +}; + +using flags_t = enum Flags { + F_NO_ME = 1 << 0, /* Mass-Erase not supported */ + F_OBLL = 1 << 1, /* OBL_LAUNCH required */ +}; + +using stm32_cmd_t = struct Stm32Cmd { + uint8_t get; + uint8_t gvr; + uint8_t gid; + uint8_t rm; + uint8_t go; + uint8_t wm; + uint8_t er; /* this may be extended erase */ + uint8_t wp; + uint8_t uw; + uint8_t rp; + uint8_t ur; + uint8_t crc; +}; + +using stm32_dev_t = struct Stm32Dev { // NOLINT + const uint16_t id; + const char *name; + const uint32_t ram_start, ram_end; + const uint32_t fl_start, fl_end; + const uint16_t fl_pps; // pages per sector + const uint32_t *fl_ps; // page size + const uint32_t opt_start, opt_end; + const uint32_t mem_start, mem_end; + const uint32_t flags; +}; + +using stm32_t = struct Stm32 { + uart::UARTDevice *stream; + uint8_t flags; + struct VarlenCmd *cmd_get_reply; + uint8_t bl_version; + uint8_t version; + uint8_t option1, option2; + uint16_t pid; + stm32_cmd_t *cmd; + const stm32_dev_t *dev; +}; + +/* + * Specify the length of reply for command GET + * This is helpful for frame-oriented protocols, e.g. i2c, to avoid time + * consuming try-fail-timeout-retry operation. + * On byte-oriented protocols, i.e. UART, this information would be skipped + * after read the first byte, so not needed. + */ +struct VarlenCmd { + uint8_t version; + uint8_t length; +}; + +stm32_t *stm32_init(uart::UARTDevice *stream, uint8_t flags, char init); +void stm32_close(stm32_t *stm); +stm32_err_t stm32_read_memory(const stm32_t *stm, uint32_t address, uint8_t *data, unsigned int len); +stm32_err_t stm32_write_memory(const stm32_t *stm, uint32_t address, const uint8_t *data, unsigned int len); +stm32_err_t stm32_wunprot_memory(const stm32_t *stm); +stm32_err_t stm32_wprot_memory(const stm32_t *stm); +stm32_err_t stm32_erase_memory(const stm32_t *stm, uint32_t spage, uint32_t pages); +stm32_err_t stm32_go(const stm32_t *stm, uint32_t address); +stm32_err_t stm32_reset_device(const stm32_t *stm); +stm32_err_t stm32_readprot_memory(const stm32_t *stm); +stm32_err_t stm32_runprot_memory(const stm32_t *stm); +stm32_err_t stm32_crc_memory(const stm32_t *stm, uint32_t address, uint32_t length, uint32_t *crc); +stm32_err_t stm32_crc_wrapper(const stm32_t *stm, uint32_t address, uint32_t length, uint32_t *crc); +uint32_t stm32_sw_crc(uint32_t crc, uint8_t *buf, unsigned int len); + +} // namespace shelly_dimmer +} // namespace esphome + +#endif // USE_SHD_FIRMWARE_DATA diff --git a/esphome/core/defines.h b/esphome/core/defines.h index f304f847a5..c854e2b987 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -93,3 +93,9 @@ //#define USE_BSEC // Requires a library with proprietary license. #define USE_DASHBOARD_IMPORT + +// Dummy firmware payload for shelly_dimmer +#define USE_SHD_FIRMWARE_MAJOR_VERSION 56 +#define USE_SHD_FIRMWARE_MINOR_VERSION 5 +#define USE_SHD_FIRMWARE_DATA \ + {} diff --git a/tests/test1.yaml b/tests/test1.yaml index 77c4a76bda..98a3ffcf4b 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1751,6 +1751,18 @@ light: to: 25 - single_light_id: ${roomname}_lights + - platform: shelly_dimmer + name: "Shelly Dimmer Light" + power: + name: "Shelly Dimmer Power" + voltage: + name: "Shelly Dimmer Voltage" + current: + name: "Shelly Dimmer Current" + max_brightness: 500 + firmware: "51.6" + uart_id: uart0 + remote_transmitter: - pin: 32 carrier_duty_percent: 100% From 6bac551d9fb10731841e7270840fe45015a66bb3 Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 13 Apr 2022 21:16:13 -0400 Subject: [PATCH 229/238] Add BedJet BLE climate component (#2452) --- CODEOWNERS | 1 + esphome/components/bedjet/__init__.py | 1 + esphome/components/bedjet/bedjet.cpp | 642 ++++++++++++++++++++++ esphome/components/bedjet/bedjet.h | 121 ++++ esphome/components/bedjet/bedjet_base.cpp | 123 +++++ esphome/components/bedjet/bedjet_base.h | 159 ++++++ esphome/components/bedjet/bedjet_const.h | 78 +++ esphome/components/bedjet/climate.py | 42 ++ tests/test1.yaml | 5 + 9 files changed, 1172 insertions(+) create mode 100644 esphome/components/bedjet/__init__.py create mode 100644 esphome/components/bedjet/bedjet.cpp create mode 100644 esphome/components/bedjet/bedjet.h create mode 100644 esphome/components/bedjet/bedjet_base.cpp create mode 100644 esphome/components/bedjet/bedjet_base.h create mode 100644 esphome/components/bedjet/bedjet_const.h create mode 100644 esphome/components/bedjet/climate.py diff --git a/CODEOWNERS b/CODEOWNERS index 02945ec0a4..c9df669f03 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -28,6 +28,7 @@ esphome/components/atc_mithermometer/* @ahpohl esphome/components/b_parasite/* @rbaron esphome/components/ballu/* @bazuchan esphome/components/bang_bang/* @OttoWinter +esphome/components/bedjet/* @jhansche esphome/components/bh1750/* @OttoWinter esphome/components/binary_sensor/* @esphome/core esphome/components/bl0940/* @tobias- diff --git a/esphome/components/bedjet/__init__.py b/esphome/components/bedjet/__init__.py new file mode 100644 index 0000000000..16821fc016 --- /dev/null +++ b/esphome/components/bedjet/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@jhansche"] diff --git a/esphome/components/bedjet/bedjet.cpp b/esphome/components/bedjet/bedjet.cpp new file mode 100644 index 0000000000..1a932da0c5 --- /dev/null +++ b/esphome/components/bedjet/bedjet.cpp @@ -0,0 +1,642 @@ +#include "bedjet.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace bedjet { + +using namespace esphome::climate; + +/// Converts a BedJet temp step into degrees Celsius. +float bedjet_temp_to_c(const uint8_t temp) { + // BedJet temp is "C*2"; to get C, divide by 2. + return temp / 2.0f; +} + +/// Converts a BedJet fan step to a speed percentage, in the range of 5% to 100%. +uint8_t bedjet_fan_step_to_speed(const uint8_t fan) { + // 0 = 5% + // 19 = 100% + return 5 * fan + 5; +} + +static const std::string *bedjet_fan_step_to_fan_mode(const uint8_t fan_step) { + if (fan_step >= 0 && fan_step <= 19) + return &BEDJET_FAN_STEP_NAME_STRINGS[fan_step]; + return nullptr; +} + +static uint8_t bedjet_fan_speed_to_step(const std::string &fan_step_percent) { + for (int i = 0; i < sizeof(BEDJET_FAN_STEP_NAME_STRINGS); i++) { + if (fan_step_percent == BEDJET_FAN_STEP_NAME_STRINGS[i]) { + return i; + } + } + return -1; +} + +void Bedjet::upgrade_firmware() { + auto *pkt = this->codec_->get_button_request(MAGIC_UPDATE); + auto status = this->write_bedjet_packet_(pkt); + + if (status) { + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + } +} + +void Bedjet::dump_config() { + LOG_CLIMATE("", "BedJet Climate", this); + auto traits = this->get_traits(); + + ESP_LOGCONFIG(TAG, " Supported modes:"); + for (auto mode : traits.get_supported_modes()) { + ESP_LOGCONFIG(TAG, " - %s", LOG_STR_ARG(climate_mode_to_string(mode))); + } + + ESP_LOGCONFIG(TAG, " Supported fan modes:"); + for (const auto &mode : traits.get_supported_fan_modes()) { + ESP_LOGCONFIG(TAG, " - %s", LOG_STR_ARG(climate_fan_mode_to_string(mode))); + } + for (const auto &mode : traits.get_supported_custom_fan_modes()) { + ESP_LOGCONFIG(TAG, " - %s (c)", mode.c_str()); + } + + ESP_LOGCONFIG(TAG, " Supported presets:"); + for (auto preset : traits.get_supported_presets()) { + ESP_LOGCONFIG(TAG, " - %s", LOG_STR_ARG(climate_preset_to_string(preset))); + } + for (const auto &preset : traits.get_supported_custom_presets()) { + ESP_LOGCONFIG(TAG, " - %s (c)", preset.c_str()); + } +} + +void Bedjet::setup() { + this->codec_ = make_unique(); + + // restore set points + auto restore = this->restore_state_(); + if (restore.has_value()) { + ESP_LOGI(TAG, "Restored previous saved state."); + restore->apply(this); + } else { + // Initial status is unknown until we connect + this->reset_state_(); + } + +#ifdef USE_TIME + this->setup_time_(); +#endif +} + +/** Resets states to defaults. */ +void Bedjet::reset_state_() { + this->mode = climate::CLIMATE_MODE_OFF; + this->action = climate::CLIMATE_ACTION_IDLE; + this->target_temperature = NAN; + this->current_temperature = NAN; + this->preset.reset(); + this->custom_preset.reset(); + this->publish_state(); +} + +void Bedjet::loop() {} + +void Bedjet::control(const ClimateCall &call) { + ESP_LOGD(TAG, "Received Bedjet::control"); + if (this->node_state != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "Not connected, cannot handle control call yet."); + return; + } + + if (call.get_mode().has_value()) { + ClimateMode mode = *call.get_mode(); + BedjetPacket *pkt; + switch (mode) { + case climate::CLIMATE_MODE_OFF: + pkt = this->codec_->get_button_request(BTN_OFF); + break; + case climate::CLIMATE_MODE_HEAT: + pkt = this->codec_->get_button_request(BTN_EXTHT); + break; + case climate::CLIMATE_MODE_FAN_ONLY: + pkt = this->codec_->get_button_request(BTN_COOL); + break; + case climate::CLIMATE_MODE_DRY: + pkt = this->codec_->get_button_request(BTN_DRY); + break; + default: + ESP_LOGW(TAG, "Unsupported mode: %d", mode); + return; + } + + auto status = this->write_bedjet_packet_(pkt); + + if (status) { + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + } else { + this->force_refresh_ = true; + this->mode = mode; + // We're using (custom) preset for Turbo & M1-3 presets, so changing climate mode will clear those + this->custom_preset.reset(); + this->preset.reset(); + } + } + + if (call.get_target_temperature().has_value()) { + auto target_temp = *call.get_target_temperature(); + auto *pkt = this->codec_->get_set_target_temp_request(target_temp); + auto status = this->write_bedjet_packet_(pkt); + + if (status) { + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + } else { + this->target_temperature = target_temp; + } + } + + if (call.get_preset().has_value()) { + ClimatePreset preset = *call.get_preset(); + BedjetPacket *pkt; + + if (preset == climate::CLIMATE_PRESET_BOOST) { + pkt = this->codec_->get_button_request(BTN_TURBO); + } else { + ESP_LOGW(TAG, "Unsupported preset: %d", preset); + return; + } + + auto status = this->write_bedjet_packet_(pkt); + if (status) { + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + } else { + // We use BOOST preset for TURBO mode, which is a short-lived/high-heat mode. + this->mode = climate::CLIMATE_MODE_HEAT; + this->preset = preset; + this->custom_preset.reset(); + this->force_refresh_ = true; + } + } else if (call.get_custom_preset().has_value()) { + std::string preset = *call.get_custom_preset(); + BedjetPacket *pkt; + + if (preset == "M1") { + pkt = this->codec_->get_button_request(BTN_M1); + } else if (preset == "M2") { + pkt = this->codec_->get_button_request(BTN_M2); + } else if (preset == "M3") { + pkt = this->codec_->get_button_request(BTN_M3); + } else { + ESP_LOGW(TAG, "Unsupported preset: %s", preset.c_str()); + return; + } + + auto status = this->write_bedjet_packet_(pkt); + if (status) { + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + } else { + this->force_refresh_ = true; + this->custom_preset = preset; + this->preset.reset(); + } + } + + if (call.get_fan_mode().has_value()) { + // Climate fan mode only supports low/med/high, but the BedJet supports 5-100% increments. + // We can still support a ClimateCall that requests low/med/high, and just translate it to a step increment here. + auto fan_mode = *call.get_fan_mode(); + BedjetPacket *pkt; + if (fan_mode == climate::CLIMATE_FAN_LOW) { + pkt = this->codec_->get_set_fan_speed_request(3 /* = 20% */); + } else if (fan_mode == climate::CLIMATE_FAN_MEDIUM) { + pkt = this->codec_->get_set_fan_speed_request(9 /* = 50% */); + } else if (fan_mode == climate::CLIMATE_FAN_HIGH) { + pkt = this->codec_->get_set_fan_speed_request(14 /* = 75% */); + } else { + ESP_LOGW(TAG, "[%s] Unsupported fan mode: %s", this->get_name().c_str(), + LOG_STR_ARG(climate_fan_mode_to_string(fan_mode))); + return; + } + + auto status = this->write_bedjet_packet_(pkt); + if (status) { + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + } else { + this->force_refresh_ = true; + } + } else if (call.get_custom_fan_mode().has_value()) { + auto fan_mode = *call.get_custom_fan_mode(); + auto fan_step = bedjet_fan_speed_to_step(fan_mode); + if (fan_step >= 0 && fan_step <= 19) { + ESP_LOGV(TAG, "[%s] Converted fan mode %s to bedjet fan step %d", this->get_name().c_str(), fan_mode.c_str(), + fan_step); + // The index should represent the fan_step index. + BedjetPacket *pkt = this->codec_->get_set_fan_speed_request(fan_step); + auto status = this->write_bedjet_packet_(pkt); + if (status) { + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + } else { + this->force_refresh_ = true; + } + } + } +} + +void Bedjet::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { + switch (event) { + case ESP_GATTC_DISCONNECT_EVT: { + ESP_LOGV(TAG, "Disconnected: reason=%d", param->disconnect.reason); + this->status_set_warning(); + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: { + auto *chr = this->parent_->get_characteristic(BEDJET_SERVICE_UUID, BEDJET_COMMAND_UUID); + if (chr == nullptr) { + ESP_LOGW(TAG, "[%s] No control service found at device, not a BedJet..?", this->get_name().c_str()); + break; + } + this->char_handle_cmd_ = chr->handle; + + chr = this->parent_->get_characteristic(BEDJET_SERVICE_UUID, BEDJET_STATUS_UUID); + if (chr == nullptr) { + ESP_LOGW(TAG, "[%s] No status service found at device, not a BedJet..?", this->get_name().c_str()); + break; + } + + this->char_handle_status_ = chr->handle; + // We also need to obtain the config descriptor for this handle. + // Otherwise once we set node_state=Established, the parent will flush all handles/descriptors, and we won't be + // able to look it up. + auto *descr = this->parent_->get_config_descriptor(this->char_handle_status_); + if (descr == nullptr) { + ESP_LOGW(TAG, "No config descriptor for status handle 0x%x. Will not be able to receive status notifications", + this->char_handle_status_); + } else if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 || + descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) { + ESP_LOGW(TAG, "Config descriptor 0x%x (uuid %s) is not a client config char uuid", this->char_handle_status_, + descr->uuid.to_string().c_str()); + } else { + this->config_descr_status_ = descr->handle; + } + + chr = this->parent_->get_characteristic(BEDJET_SERVICE_UUID, BEDJET_NAME_UUID); + if (chr != nullptr) { + this->char_handle_name_ = chr->handle; + auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_name_, + ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGI(TAG, "[%s] Unable to read name characteristic: %d", this->get_name().c_str(), status); + } + } + + ESP_LOGD(TAG, "Services complete: obtained char handles."); + this->node_state = espbt::ClientState::ESTABLISHED; + + this->set_notify_(true); + +#ifdef USE_TIME + if (this->time_id_.has_value()) { + this->send_local_time_(); + } +#endif + break; + } + case ESP_GATTC_WRITE_DESCR_EVT: { + if (param->write.status != ESP_GATT_OK) { + // ESP_GATT_INVALID_ATTR_LEN + ESP_LOGW(TAG, "Error writing descr at handle 0x%04d, status=%d", param->write.handle, param->write.status); + break; + } + // [16:44:44][V][bedjet:279]: [JOENJET] Register for notify event success: h=0x002a s=0 + // This might be the enable-notify descriptor? (or disable-notify) + ESP_LOGV(TAG, "[%s] Write to handle 0x%04x status=%d", this->get_name().c_str(), param->write.handle, + param->write.status); + break; + } + case ESP_GATTC_WRITE_CHAR_EVT: { + if (param->write.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "Error writing char at handle 0x%04d, status=%d", param->write.handle, param->write.status); + break; + } + if (param->write.handle == this->char_handle_cmd_) { + if (this->force_refresh_) { + // Command write was successful. Publish the pending state, hoping that notify will kick in. + this->publish_state(); + } + } + 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->char_handle_status_) { + // This is the additional packet that doesn't fit in the notify packet. + this->codec_->decode_extra(param->read.value, param->read.value_len); + } else if (param->read.handle == this->char_handle_name_) { + // The data should represent the name. + if (param->read.status == ESP_GATT_OK && param->read.value_len > 0) { + std::string bedjet_name(reinterpret_cast(param->read.value), param->read.value_len); + // this->set_name(bedjet_name); + ESP_LOGV(TAG, "[%s] Got BedJet name: '%s'", this->get_name().c_str(), bedjet_name.c_str()); + } + } + break; + } + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + // This event means that ESP received the request to enable notifications on the client side. But we also have to + // tell the server that we want it to send notifications. Normally BLEClient parent would handle this + // automatically, but as soon as we set our status to Established, the parent is going to purge all the + // service/char/descriptor handles, and then get_config_descriptor() won't work anymore. There's no way to disable + // the BLEClient parent behavior, so our only option is to write the handle anyway, and hope a double-write + // doesn't break anything. + + if (param->reg_for_notify.handle != this->char_handle_status_) { + ESP_LOGW(TAG, "[%s] Register for notify on unexpected handle 0x%04x, expecting 0x%04x", + this->get_name().c_str(), param->reg_for_notify.handle, this->char_handle_status_); + break; + } + + this->write_notify_config_descriptor_(true); + this->last_notify_ = 0; + this->force_refresh_ = true; + break; + } + case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: { + // This event is not handled by the parent BLEClient, so we need to do this either way. + if (param->unreg_for_notify.handle != this->char_handle_status_) { + ESP_LOGW(TAG, "[%s] Unregister for notify on unexpected handle 0x%04x, expecting 0x%04x", + this->get_name().c_str(), param->unreg_for_notify.handle, this->char_handle_status_); + break; + } + + this->write_notify_config_descriptor_(false); + this->last_notify_ = 0; + // Now we wait until the next update() poll to re-register notify... + break; + } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.handle != this->char_handle_status_) { + ESP_LOGW(TAG, "[%s] Unexpected notify handle, wanted %04X, got %04X", this->get_name().c_str(), + this->char_handle_status_, param->notify.handle); + break; + } + + // FIXME: notify events come in every ~200-300 ms, which is too fast to be helpful. So we + // throttle the updates to once every MIN_NOTIFY_THROTTLE (5 seconds). + // Another idea would be to keep notify off by default, and use update() as an opportunity to turn on + // notify to get enough data to update status, then turn off notify again. + + uint32_t now = millis(); + auto delta = now - this->last_notify_; + + if (this->last_notify_ == 0 || delta > MIN_NOTIFY_THROTTLE || this->force_refresh_) { + bool needs_extra = this->codec_->decode_notify(param->notify.value, param->notify.value_len); + this->last_notify_ = now; + + if (needs_extra) { + // this means the packet was partial, so read the status characteristic to get the second part. + auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id, + this->char_handle_status_, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGI(TAG, "[%s] Unable to read extended status packet", this->get_name().c_str()); + } + } + + if (this->force_refresh_) { + // If we requested an immediate update, do that now. + this->update(); + this->force_refresh_ = false; + } + } + break; + } + default: + ESP_LOGVV(TAG, "[%s] gattc unhandled event: enum=%d", this->get_name().c_str(), event); + break; + } +} + +/** Reimplementation of BLEClient.gattc_event_handler() for ESP_GATTC_REG_FOR_NOTIFY_EVT. + * + * This is a copy of ble_client's automatic handling of `ESP_GATTC_REG_FOR_NOTIFY_EVT`, in order + * to undo the same on unregister. It also allows us to maintain the config descriptor separately, + * since the parent BLEClient is going to purge all descriptors once we set our connection status + * to `Established`. + */ +uint8_t Bedjet::write_notify_config_descriptor_(bool enable) { + auto handle = this->config_descr_status_; + if (handle == 0) { + ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", this->char_handle_status_); + return -1; + } + + // NOTE: BLEClient uses `uint8_t*` of length 1, but BLE spec requires 16 bits. + uint8_t notify_en[] = {0, 0}; + notify_en[0] = enable; + auto status = + esp_ble_gattc_write_char_descr(this->parent_->gattc_if, this->parent_->conn_id, handle, sizeof(notify_en), + ¬ify_en[0], ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status); + return status; + } + ESP_LOGD(TAG, "[%s] wrote notify=%s to status config 0x%04x", this->get_name().c_str(), enable ? "true" : "false", + handle); + return ESP_GATT_OK; +} + +#ifdef USE_TIME +/** Attempts to sync the local time (via `time_id`) to the BedJet device. */ +void Bedjet::send_local_time_() { + if (this->node_state != espbt::ClientState::ESTABLISHED) { + ESP_LOGV(TAG, "[%s] Not connected, cannot send time.", this->get_name().c_str()); + return; + } + auto *time_id = *this->time_id_; + time::ESPTime now = time_id->now(); + if (now.is_valid()) { + uint8_t hour = now.hour; + uint8_t minute = now.minute; + BedjetPacket *pkt = this->codec_->get_set_time_request(hour, minute); + auto status = this->write_bedjet_packet_(pkt); + if (status) { + ESP_LOGW(TAG, "Failed setting BedJet clock: %d", status); + } else { + ESP_LOGD(TAG, "[%s] BedJet clock set to: %d:%02d", this->get_name().c_str(), hour, minute); + } + } +} + +/** Initializes time sync callbacks to support syncing current time to the BedJet. */ +void Bedjet::setup_time_() { + if (this->time_id_.has_value()) { + this->send_local_time_(); + auto *time_id = *this->time_id_; + time_id->add_on_time_sync_callback([this] { this->send_local_time_(); }); + time::ESPTime now = time_id->now(); + ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", now.hour, now.minute); + } else { + ESP_LOGI(TAG, "`time_id` is not configured: will not sync BedJet clock."); + } +} +#endif + +/** Writes one BedjetPacket to the BLE client on the BEDJET_COMMAND_UUID. */ +uint8_t Bedjet::write_bedjet_packet_(BedjetPacket *pkt) { + if (this->node_state != espbt::ClientState::ESTABLISHED) { + if (!this->parent_->enabled) { + ESP_LOGI(TAG, "[%s] Cannot write packet: Not connected, enabled=false", this->get_name().c_str()); + } else { + ESP_LOGW(TAG, "[%s] Cannot write packet: Not connected", this->get_name().c_str()); + } + return -1; + } + auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_cmd_, + pkt->data_length + 1, (uint8_t *) &pkt->command, ESP_GATT_WRITE_TYPE_NO_RSP, + ESP_GATT_AUTH_REQ_NONE); + return status; +} + +/** Configures the local ESP BLE client to register (`true`) or unregister (`false`) for status notifications. */ +uint8_t Bedjet::set_notify_(const bool enable) { + uint8_t status; + if (enable) { + status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, + this->char_handle_status_); + if (status) { + ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status); + } + } else { + status = esp_ble_gattc_unregister_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, + this->char_handle_status_); + if (status) { + ESP_LOGW(TAG, "[%s] esp_ble_gattc_unregister_for_notify failed, status=%d", this->get_name().c_str(), status); + } + } + ESP_LOGV(TAG, "[%s] set_notify: enable=%d; result=%d", this->get_name().c_str(), enable, status); + return status; +} + +/** Attempts to update the climate device from the last received BedjetStatusPacket. + * + * @return `true` if the status has been applied; `false` if there is nothing to apply. + */ +bool Bedjet::update_status_() { + if (!this->codec_->has_status()) + return false; + + BedjetStatusPacket status = *this->codec_->get_status_packet(); + + auto converted_temp = bedjet_temp_to_c(status.target_temp_step); + if (converted_temp > 0) + this->target_temperature = converted_temp; + converted_temp = bedjet_temp_to_c(status.ambient_temp_step); + if (converted_temp > 0) + this->current_temperature = converted_temp; + + const auto *fan_mode_name = bedjet_fan_step_to_fan_mode(status.fan_step); + if (fan_mode_name != nullptr) { + this->custom_fan_mode = *fan_mode_name; + } + + // TODO: Get biorhythm data to determine which preset (M1-3) is running, if any. + switch (status.mode) { + case MODE_WAIT: // Biorhythm "wait" step: device is idle + case MODE_STANDBY: + this->mode = climate::CLIMATE_MODE_OFF; + this->action = climate::CLIMATE_ACTION_IDLE; + this->fan_mode = climate::CLIMATE_FAN_OFF; + this->custom_preset.reset(); + this->preset.reset(); + break; + + case MODE_HEAT: + case MODE_EXTHT: + this->mode = climate::CLIMATE_MODE_HEAT; + this->action = climate::CLIMATE_ACTION_HEATING; + this->custom_preset.reset(); + this->preset.reset(); + break; + + case MODE_COOL: + this->mode = climate::CLIMATE_MODE_FAN_ONLY; + this->action = climate::CLIMATE_ACTION_COOLING; + this->custom_preset.reset(); + this->preset.reset(); + break; + + case MODE_DRY: + this->mode = climate::CLIMATE_MODE_DRY; + this->action = climate::CLIMATE_ACTION_DRYING; + this->custom_preset.reset(); + this->preset.reset(); + break; + + case MODE_TURBO: + this->preset = climate::CLIMATE_PRESET_BOOST; + this->custom_preset.reset(); + this->mode = climate::CLIMATE_MODE_HEAT; + this->action = climate::CLIMATE_ACTION_HEATING; + break; + + default: + ESP_LOGW(TAG, "[%s] Unexpected mode: 0x%02X", this->get_name().c_str(), status.mode); + break; + } + + if (this->is_valid_()) { + this->publish_state(); + this->codec_->clear_status(); + this->status_clear_warning(); + } + + return true; +} + +void Bedjet::update() { + ESP_LOGV(TAG, "[%s] update()", this->get_name().c_str()); + + if (this->node_state != espbt::ClientState::ESTABLISHED) { + if (!this->parent()->enabled) { + ESP_LOGD(TAG, "[%s] Not connected, because enabled=false", this->get_name().c_str()); + } else { + // Possibly still trying to connect. + ESP_LOGD(TAG, "[%s] Not connected, enabled=true", this->get_name().c_str()); + } + + return; + } + + auto result = this->update_status_(); + if (!result) { + uint32_t now = millis(); + uint32_t diff = now - this->last_notify_; + + if (this->last_notify_ == 0) { + // This means we're connected and haven't received a notification, so it likely means that the BedJet is off. + // However, it could also mean that it's running, but failing to send notifications. + // We can try to unregister for notifications now, and then re-register, hoping to clear it up... + // But how do we know for sure which state we're in, and how do we actually clear out the buggy state? + + ESP_LOGI(TAG, "[%s] Still waiting for first GATT notify event.", this->get_name().c_str()); + this->set_notify_(false); + } else if (diff > NOTIFY_WARN_THRESHOLD) { + ESP_LOGW(TAG, "[%s] Last GATT notify was %d seconds ago.", this->get_name().c_str(), diff / 1000); + } + + if (this->timeout_ > 0 && diff > this->timeout_ && this->parent()->enabled) { + ESP_LOGW(TAG, "[%s] Timed out after %d sec. Retrying...", this->get_name().c_str(), this->timeout_); + this->parent()->set_enabled(false); + this->parent()->set_enabled(true); + } + } +} + +} // namespace bedjet +} // namespace esphome + +#endif diff --git a/esphome/components/bedjet/bedjet.h b/esphome/components/bedjet/bedjet.h new file mode 100644 index 0000000000..b061d2b5ec --- /dev/null +++ b/esphome/components/bedjet/bedjet.h @@ -0,0 +1,121 @@ +#pragma once + +#include "esphome/components/ble_client/ble_client.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/climate/climate.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "bedjet_base.h" + +#ifdef USE_TIME +#include "esphome/components/time/real_time_clock.h" +#endif + +#ifdef USE_ESP32 + +#include + +namespace esphome { +namespace bedjet { + +namespace espbt = esphome::esp32_ble_tracker; + +static const espbt::ESPBTUUID BEDJET_SERVICE_UUID = espbt::ESPBTUUID::from_raw("00001000-bed0-0080-aa55-4265644a6574"); +static const espbt::ESPBTUUID BEDJET_STATUS_UUID = espbt::ESPBTUUID::from_raw("00002000-bed0-0080-aa55-4265644a6574"); +static const espbt::ESPBTUUID BEDJET_COMMAND_UUID = espbt::ESPBTUUID::from_raw("00002004-bed0-0080-aa55-4265644a6574"); +static const espbt::ESPBTUUID BEDJET_NAME_UUID = espbt::ESPBTUUID::from_raw("00002001-bed0-0080-aa55-4265644a6574"); + +class Bedjet : public climate::Climate, public esphome::ble_client::BLEClientNode, public PollingComponent { + public: + void setup() override; + void loop() override; + void update() override; + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + +#ifdef USE_TIME + void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; } +#endif + void set_status_timeout(uint32_t timeout) { this->timeout_ = timeout; } + + /** Attempts to check for and apply firmware updates. */ + void upgrade_firmware(); + + climate::ClimateTraits traits() override { + auto traits = climate::ClimateTraits(); + traits.set_supports_action(true); + traits.set_supports_current_temperature(true); + traits.set_supported_modes({ + climate::CLIMATE_MODE_OFF, + climate::CLIMATE_MODE_HEAT, + // climate::CLIMATE_MODE_TURBO // Not supported by Climate: see presets instead + climate::CLIMATE_MODE_FAN_ONLY, + climate::CLIMATE_MODE_DRY, + }); + + // It would be better if we had a slider for the fan modes. + traits.set_supported_custom_fan_modes(BEDJET_FAN_STEP_NAMES_SET); + traits.set_supported_presets({ + // If we support NONE, then have to decide what happens if the user switches to it (turn off?) + // climate::CLIMATE_PRESET_NONE, + // Climate doesn't have a "TURBO" mode, but we can use the BOOST preset instead. + climate::CLIMATE_PRESET_BOOST, + }); + traits.set_supported_custom_presets({ + // We could fetch biodata from bedjet and set these names that way. + // But then we have to invert the lookup in order to send the right preset. + // For now, we can leave them as M1-3 to match the remote buttons. + "M1", + "M2", + "M3", + }); + traits.set_visual_min_temperature(19.0); + traits.set_visual_max_temperature(43.0); + traits.set_visual_temperature_step(1.0); + return traits; + } + + protected: + void control(const climate::ClimateCall &call) override; + +#ifdef USE_TIME + void setup_time_(); + void send_local_time_(); + optional time_id_{}; +#endif + + uint32_t timeout_{DEFAULT_STATUS_TIMEOUT}; + + static const uint32_t MIN_NOTIFY_THROTTLE = 5000; + static const uint32_t NOTIFY_WARN_THRESHOLD = 300000; + static const uint32_t DEFAULT_STATUS_TIMEOUT = 900000; + + uint8_t set_notify_(bool enable); + uint8_t write_bedjet_packet_(BedjetPacket *pkt); + void reset_state_(); + bool update_status_(); + + bool is_valid_() { + // FIXME: find a better way to check this? + return !std::isnan(this->current_temperature) && !std::isnan(this->target_temperature) && + this->current_temperature > 1 && this->target_temperature > 1; + } + + uint32_t last_notify_ = 0; + bool force_refresh_ = false; + + std::unique_ptr codec_; + uint16_t char_handle_cmd_; + uint16_t char_handle_name_; + uint16_t char_handle_status_; + uint16_t config_descr_status_; + + uint8_t write_notify_config_descriptor_(bool enable); +}; + +} // namespace bedjet +} // namespace esphome + +#endif diff --git a/esphome/components/bedjet/bedjet_base.cpp b/esphome/components/bedjet/bedjet_base.cpp new file mode 100644 index 0000000000..99f1df96d3 --- /dev/null +++ b/esphome/components/bedjet/bedjet_base.cpp @@ -0,0 +1,123 @@ +#include "bedjet_base.h" +#include +#include + +namespace esphome { +namespace bedjet { + +/// Converts a BedJet temp step into degrees Fahrenheit. +float bedjet_temp_to_f(const uint8_t temp) { + // BedJet temp is "C*2"; to get F, multiply by 0.9 (half 1.8) and add 32. + return 0.9f * temp + 32.0f; +} + +/** Cleans up the packet before sending. */ +BedjetPacket *BedjetCodec::clean_packet_() { + // So far no commands require more than 2 bytes of data. + assert(this->packet_.data_length <= 2); + for (int i = this->packet_.data_length; i < 2; i++) { + this->packet_.data[i] = '\0'; + } + ESP_LOGV(TAG, "Created packet: %02X, %02X %02X", this->packet_.command, this->packet_.data[0], this->packet_.data[1]); + return &this->packet_; +} + +/** Returns a BedjetPacket that will initiate a BedjetButton press. */ +BedjetPacket *BedjetCodec::get_button_request(BedjetButton button) { + this->packet_.command = CMD_BUTTON; + this->packet_.data_length = 1; + this->packet_.data[0] = button; + return this->clean_packet_(); +} + +/** Returns a BedjetPacket that will set the device's target `temperature`. */ +BedjetPacket *BedjetCodec::get_set_target_temp_request(float temperature) { + this->packet_.command = CMD_SET_TEMP; + this->packet_.data_length = 1; + this->packet_.data[0] = temperature * 2; + return this->clean_packet_(); +} + +/** Returns a BedjetPacket that will set the device's target fan speed. */ +BedjetPacket *BedjetCodec::get_set_fan_speed_request(const uint8_t fan_step) { + this->packet_.command = CMD_SET_FAN; + this->packet_.data_length = 1; + this->packet_.data[0] = fan_step; + return this->clean_packet_(); +} + +/** Returns a BedjetPacket that will set the device's current time. */ +BedjetPacket *BedjetCodec::get_set_time_request(const uint8_t hour, const uint8_t minute) { + this->packet_.command = CMD_SET_TIME; + this->packet_.data_length = 2; + this->packet_.data[0] = hour; + this->packet_.data[1] = minute; + return this->clean_packet_(); +} + +/** Decodes the extra bytes that were received after being notified with a partial packet. */ +void BedjetCodec::decode_extra(const uint8_t *data, uint16_t length) { + ESP_LOGV(TAG, "Received extra: %d bytes: %d %d %d %d", length, data[1], data[2], data[3], data[4]); + uint8_t offset = this->last_buffer_size_; + if (offset > 0 && length + offset <= sizeof(BedjetStatusPacket)) { + memcpy(((uint8_t *) (&this->buf_)) + offset, data, length); + ESP_LOGV(TAG, + "Extra bytes: skip1=0x%08x, skip2=0x%04x, skip3=0x%02x; update phase=0x%02x, " + "flags=BedjetFlags ", + this->buf_._skip_1_, this->buf_._skip_2_, this->buf_._skip_3_, this->buf_.update_phase, + this->buf_.flags & 0x20 ? '1' : '0', this->buf_.flags & 0x10 ? '1' : '0', + this->buf_.flags & 0x04 ? '1' : '0', this->buf_.flags & 0x01 ? '1' : '0', + this->buf_.flags & ~(0x20 | 0x10 | 0x04 | 0x01)); + } else { + ESP_LOGI(TAG, "Could not determine where to append to, last offset=%d, max size=%u, new size would be %d", offset, + sizeof(BedjetStatusPacket), length + offset); + } +} + +/** Decodes the incoming status packet received on the BEDJET_STATUS_UUID. + * + * @return `true` if the packet was decoded and represents a "partial" packet; `false` otherwise. + */ +bool BedjetCodec::decode_notify(const uint8_t *data, uint16_t length) { + ESP_LOGV(TAG, "Received: %d bytes: %d %d %d %d", length, data[1], data[2], data[3], data[4]); + + if (data[1] == PACKET_FORMAT_V3_HOME && data[3] == PACKET_TYPE_STATUS) { + this->status_packet_.reset(); + + // Clear old buffer + memset(&this->buf_, 0, sizeof(BedjetStatusPacket)); + // Copy new data into buffer + memcpy(&this->buf_, data, length); + this->last_buffer_size_ = length; + + // TODO: validate the packet checksum? + if (this->buf_.mode >= 0 && this->buf_.mode < 7 && this->buf_.target_temp_step >= 38 && + this->buf_.target_temp_step <= 86 && this->buf_.actual_temp_step > 1 && this->buf_.actual_temp_step <= 100 && + this->buf_.ambient_temp_step > 1 && this->buf_.ambient_temp_step <= 100) { + // and save it for the update() loop + this->status_packet_ = this->buf_; + return this->buf_.is_partial == 1; + } else { + // TODO: log a warning if we detect that we connected to a non-V3 device. + ESP_LOGW(TAG, "Received potentially invalid packet (len %d):", length); + } + } else if (data[1] == PACKET_FORMAT_DEBUG || data[3] == PACKET_TYPE_DEBUG) { + // We don't actually know the packet format for this. Dump packets to log, in case a pattern presents itself. + ESP_LOGV(TAG, + "received DEBUG packet: set1=%01fF, set2=%01fF, air=%01fF; [7]=%d, [8]=%d, [9]=%d, [10]=%d, [11]=%d, " + "[12]=%d, [-1]=%d", + bedjet_temp_to_f(data[4]), bedjet_temp_to_f(data[5]), bedjet_temp_to_f(data[6]), data[7], data[8], data[9], + data[10], data[11], data[12], data[length - 1]); + + if (this->has_status()) { + this->status_packet_->ambient_temp_step = data[6]; + } + } else { + // TODO: log a warning if we detect that we connected to a non-V3 device. + } + + return false; +} + +} // namespace bedjet +} // namespace esphome diff --git a/esphome/components/bedjet/bedjet_base.h b/esphome/components/bedjet/bedjet_base.h new file mode 100644 index 0000000000..c63b70cb9a --- /dev/null +++ b/esphome/components/bedjet/bedjet_base.h @@ -0,0 +1,159 @@ +#pragma once + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include "bedjet_const.h" + +namespace esphome { +namespace bedjet { + +struct BedjetPacket { + uint8_t data_length; + BedjetCommand command; + uint8_t data[2]; +}; + +struct BedjetFlags { + /* uint8_t */ + int a_ : 1; // 0x80 + int b_ : 1; // 0x40 + int conn_test_passed : 1; ///< (0x20) Bit is set `1` if the last connection test passed. + int leds_enabled : 1; ///< (0x10) Bit is set `1` if the LEDs on the device are enabled. + int c_ : 1; // 0x08 + int units_setup : 1; ///< (0x04) Bit is set `1` if the device's units have been configured. + int d_ : 1; // 0x02 + int beeps_muted : 1; ///< (0x01) Bit is set `1` if the device's sound output is muted. +} __attribute__((packed)); + +enum BedjetPacketFormat : uint8_t { + PACKET_FORMAT_DEBUG = 0x05, // 5 + PACKET_FORMAT_V3_HOME = 0x56, // 86 +}; + +enum BedjetPacketType : uint8_t { + PACKET_TYPE_STATUS = 0x1, + PACKET_TYPE_DEBUG = 0x2, +}; + +/** The format of a BedJet V3 status packet. */ +struct BedjetStatusPacket { + // [0] + uint8_t is_partial : 8; ///< `1` indicates that this is a partial packet, and more data can be read directly from the + ///< characteristic. + BedjetPacketFormat packet_format : 8; ///< BedjetPacketFormat::PACKET_FORMAT_V3_HOME for BedJet V3 status packet + ///< format. BedjetPacketFormat::PACKET_FORMAT_DEBUG for debugging packets. + uint8_t + expecting_length : 8; ///< The expected total length of the status packet after merging the additional packet. + BedjetPacketType packet_type : 8; ///< Typically BedjetPacketType::PACKET_TYPE_STATUS for BedJet V3 status packet. + + // [4] + uint8_t time_remaining_hrs : 8; ///< Hours remaining in program runtime + uint8_t time_remaining_mins : 8; ///< Minutes remaining in program runtime + uint8_t time_remaining_secs : 8; ///< Seconds remaining in program runtime + + // [7] + uint8_t actual_temp_step : 8; ///< Actual temp of the air blown by the BedJet fan; value represents `2 * + ///< degrees_celsius`. See #bedjet_temp_to_c and #bedjet_temp_to_f + uint8_t target_temp_step : 8; ///< Target temp that the BedJet will try to heat to. See #actual_temp_step. + + // [9] + BedjetMode mode : 8; ///< BedJet operating mode. + + // [10] + uint8_t fan_step : 8; ///< BedJet fan speed; value is in the 0-19 range, representing 5% increments (5%-100%): `5 + 5 + ///< * fan_step` + uint8_t max_hrs : 8; ///< Max hours of mode runtime + uint8_t max_mins : 8; ///< Max minutes of mode runtime + uint8_t min_temp_step : 8; ///< Min temp allowed in mode. See #actual_temp_step. + uint8_t max_temp_step : 8; ///< Max temp allowed in mode. See #actual_temp_step. + + // [15-16] + uint16_t turbo_time : 16; ///< Time remaining in BedjetMode::MODE_TURBO. + + // [17] + uint8_t ambient_temp_step : 8; ///< Current ambient air temp. This is the coldest air the BedJet can blow. See + ///< #actual_temp_step. + uint8_t shutdown_reason : 8; ///< The reason for the last device shutdown. + + // [19-25]; the initial partial packet cuts off here after [19] + // Skip 7 bytes? + uint32_t _skip_1_ : 32; // Unknown 19-22 = 0x01810112 + + uint16_t _skip_2_ : 16; // Unknown 23-24 = 0x1310 + uint8_t _skip_3_ : 8; // Unknown 25 = 0x00 + + // [26] + // 0x18(24) = "Connection test has completed OK" + // 0x1a(26) = "Firmware update is not needed" + uint8_t update_phase : 8; ///< The current status/phase of a firmware update. + + // [27] + // FIXME: cannot nest packed struct of matching length here? + /* BedjetFlags */ uint8_t flags : 8; /// See BedjetFlags for the packed byte flags. + // [28-31]; 20+11 bytes + uint32_t _skip_4_ : 32; // Unknown + +} __attribute__((packed)); + +/** This class is responsible for encoding command packets and decoding status packets. + * + * Status Packets + * ============== + * The BedJet protocol depends on registering for notifications on the esphome::BedJet::BEDJET_SERVICE_UUID + * characteristic. If the BedJet is on, it will send rapid updates as notifications. If it is off, + * it generally will not notify of any status. + * + * As the BedJet V3's BedjetStatusPacket exceeds the buffer size allowed for BLE notification packets, + * the notification packet will contain `BedjetStatusPacket::is_partial == 1`. When that happens, an additional + * read of the esphome::BedJet::BEDJET_SERVICE_UUID characteristic will contain the second portion of the + * full status packet. + * + * Command Packets + * =============== + * This class supports encoding a number of BedjetPacket commands: + * - Button press + * This simulates a press of one of the BedjetButton values. + * - BedjetPacket#command = BedjetCommand::CMD_BUTTON + * - BedjetPacket#data [0] contains the BedjetButton value + * - Set target temp + * This sets the BedJet's target temp to a concrete temperature value. + * - BedjetPacket#command = BedjetCommand::CMD_SET_TEMP + * - BedjetPacket#data [0] contains the BedJet temp value; see BedjetStatusPacket#actual_temp_step + * - Set fan speed + * This sets the BedJet fan speed. + * - BedjetPacket#command = BedjetCommand::CMD_SET_FAN + * - BedjetPacket#data [0] contains the BedJet fan step in the range 0-19. + * - Set current time + * The BedJet needs to have its clock set properly in order to run the biorhythm programs, which might + * contain time-of-day based step rules. + * - BedjetPacket#command = BedjetCommand::CMD_SET_TIME + * - BedjetPacket#data [0] is hours, [1] is minutes + */ +class BedjetCodec { + public: + BedjetPacket *get_button_request(BedjetButton button); + BedjetPacket *get_set_target_temp_request(float temperature); + BedjetPacket *get_set_fan_speed_request(uint8_t fan_step); + BedjetPacket *get_set_time_request(uint8_t hour, uint8_t minute); + + bool decode_notify(const uint8_t *data, uint16_t length); + void decode_extra(const uint8_t *data, uint16_t length); + + inline bool has_status() { return this->status_packet_.has_value(); } + const optional &get_status_packet() const { return this->status_packet_; } + void clear_status() { this->status_packet_.reset(); } + + protected: + BedjetPacket *clean_packet_(); + + uint8_t last_buffer_size_ = 0; + + BedjetPacket packet_; + + optional status_packet_; + BedjetStatusPacket buf_; +}; + +} // namespace bedjet +} // namespace esphome diff --git a/esphome/components/bedjet/bedjet_const.h b/esphome/components/bedjet/bedjet_const.h new file mode 100644 index 0000000000..e6bfa45d3a --- /dev/null +++ b/esphome/components/bedjet/bedjet_const.h @@ -0,0 +1,78 @@ +#pragma once + +#include + +namespace esphome { +namespace bedjet { + +static const char *const TAG = "bedjet"; + +enum BedjetMode : uint8_t { + /// BedJet is Off + MODE_STANDBY = 0, + /// BedJet is in Heat mode (limited to 4 hours) + MODE_HEAT = 1, + /// BedJet is in Turbo mode (high heat, limited time) + MODE_TURBO = 2, + /// BedJet is in Extended Heat mode (limited to 10 hours) + MODE_EXTHT = 3, + /// BedJet is in Cool mode (actually "Fan only" mode) + MODE_COOL = 4, + /// BedJet is in Dry mode (high speed, no heat) + MODE_DRY = 5, + /// BedJet is in "wait" mode, a step during a biorhythm program + MODE_WAIT = 6, +}; + +enum BedjetButton : uint8_t { + /// Turn BedJet off + BTN_OFF = 0x1, + /// Enter Cool mode (fan only) + BTN_COOL = 0x2, + /// Enter Heat mode (limited to 4 hours) + BTN_HEAT = 0x3, + /// Enter Turbo mode (high heat, limited to 10 minutes) + BTN_TURBO = 0x4, + /// Enter Dry mode (high speed, no heat) + BTN_DRY = 0x5, + /// Enter Extended Heat mode (limited to 10 hours) + BTN_EXTHT = 0x6, + + /// Start the M1 biorhythm/preset program + BTN_M1 = 0x20, + /// Start the M2 biorhythm/preset program + BTN_M2 = 0x21, + /// Start the M3 biorhythm/preset program + BTN_M3 = 0x22, + + /* These are "MAGIC" buttons */ + + /// Turn debug mode on/off + MAGIC_DEBUG_ON = 0x40, + MAGIC_DEBUG_OFF = 0x41, + /// Perform a connection test. + MAGIC_CONNTEST = 0x42, + /// Request a firmware update. This will also restart the Bedjet. + MAGIC_UPDATE = 0x43, +}; + +enum BedjetCommand : uint8_t { + CMD_BUTTON = 0x1, + CMD_SET_TEMP = 0x3, + CMD_STATUS = 0x6, + CMD_SET_FAN = 0x7, + CMD_SET_TIME = 0x8, +}; + +#define BEDJET_FAN_STEP_NAMES_ \ + { \ + " 5%", " 10%", " 15%", " 20%", " 25%", " 30%", " 35%", " 40%", " 45%", " 50%", " 55%", " 60%", " 65%", " 70%", \ + " 75%", " 80%", " 85%", " 90%", " 95%", "100%" \ + } + +static const char *const BEDJET_FAN_STEP_NAMES[20] = BEDJET_FAN_STEP_NAMES_; +static const std::string BEDJET_FAN_STEP_NAME_STRINGS[20] = BEDJET_FAN_STEP_NAMES_; +static const std::set BEDJET_FAN_STEP_NAMES_SET BEDJET_FAN_STEP_NAMES_; + +} // namespace bedjet +} // namespace esphome diff --git a/esphome/components/bedjet/climate.py b/esphome/components/bedjet/climate.py new file mode 100644 index 0000000000..49353934f6 --- /dev/null +++ b/esphome/components/bedjet/climate.py @@ -0,0 +1,42 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate, ble_client, time +from esphome.const import ( + CONF_ID, + CONF_RECEIVE_TIMEOUT, + CONF_TIME_ID, +) + +CODEOWNERS = ["@jhansche"] +DEPENDENCIES = ["ble_client"] + +bedjet_ns = cg.esphome_ns.namespace("bedjet") +Bedjet = bedjet_ns.class_( + "Bedjet", climate.Climate, ble_client.BLEClientNode, cg.PollingComponent +) + +CONFIG_SCHEMA = ( + climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(Bedjet), + cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), + cv.Optional( + CONF_RECEIVE_TIMEOUT, default="0s" + ): cv.positive_time_period_milliseconds, + } + ) + .extend(ble_client.BLE_CLIENT_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 climate.register_climate(var, config) + await ble_client.register_ble_node(var, config) + if CONF_TIME_ID in config: + time_ = await cg.get_variable(config[CONF_TIME_ID]) + cg.add(var.set_time_id(time_)) + if CONF_RECEIVE_TIMEOUT in config: + cg.add(var.set_status_timeout(config[CONF_RECEIVE_TIMEOUT])) diff --git a/tests/test1.yaml b/tests/test1.yaml index 98a3ffcf4b..375499942b 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -291,6 +291,8 @@ ble_client: on_disconnect: then: - switch.turn_on: ble1_status + - mac_address: C4:4F:33:11:22:33 + id: my_bedjet_ble_client mcp23s08: - id: "mcp23s08_hub" cs_pin: GPIO12 @@ -1870,6 +1872,9 @@ climate: ble_client_id: ble_blah unit_of_measurement: c icon: mdi:stove + - platform: bedjet + name: My Bedjet + ble_client_id: my_bedjet_ble_client script: - id: climate_custom From 93b628d9a856461e570b9e195f8dbd4c04346cde Mon Sep 17 00:00:00 2001 From: Janez Troha <239513+dz0ny@users.noreply.github.com> Date: Thu, 14 Apr 2022 03:42:43 +0200 Subject: [PATCH 230/238] Allocate smaller amount of buffer for JSON (#3384) --- esphome/components/json/json_util.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 10179c9954..2bd8112255 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -23,13 +23,13 @@ std::string build_json(const json_build_t &f) { #ifdef USE_ESP8266 const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance) #elif defined(USE_ESP32) - const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL); + const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); #endif - const size_t request_size = std::min(free_heap - 2048, (size_t) 5120); + const size_t request_size = std::min(free_heap, (size_t) 512); DynamicJsonDocument json_document(request_size); - if (json_document.memoryPool().buffer() == nullptr) { + if (json_document.capacity() == 0) { ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, largest free heap block: %u bytes", request_size, free_heap); return "{}"; @@ -37,7 +37,7 @@ std::string build_json(const json_build_t &f) { JsonObject root = json_document.to(); f(root); json_document.shrinkToFit(); - + ESP_LOGV(TAG, "Size after shrink %u bytes", json_document.capacity()); std::string output; serializeJson(json_document, output); return output; @@ -51,13 +51,13 @@ void parse_json(const std::string &data, const json_parse_t &f) { #ifdef USE_ESP8266 const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance) #elif defined(USE_ESP32) - const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL); + const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); #endif bool pass = false; - size_t request_size = std::min(free_heap - 2048, (size_t)(data.size() * 1.5)); + size_t request_size = std::min(free_heap, (size_t)(data.size() * 1.5)); do { DynamicJsonDocument json_document(request_size); - if (json_document.memoryPool().buffer() == nullptr) { + if (json_document.capacity() == 0) { ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size, free_heap); return; From b605982f940f4ac831f179c7645a7dd3b034622a Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Mon, 18 Apr 2022 22:42:02 +0200 Subject: [PATCH 231/238] Fix power_delivered/produced_phase sensor deviceclass in DSMR (#3395) --- esphome/components/dsmr/sensor.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index bb4722655c..0b0439baa4 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -143,37 +143,37 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional("power_delivered_l1"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, - device_class=DEVICE_CLASS_CURRENT, + device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_delivered_l2"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, - device_class=DEVICE_CLASS_CURRENT, + device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_delivered_l3"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, - device_class=DEVICE_CLASS_CURRENT, + device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_returned_l1"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, - device_class=DEVICE_CLASS_CURRENT, + device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_returned_l2"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, - device_class=DEVICE_CLASS_CURRENT, + device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_returned_l3"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, - device_class=DEVICE_CLASS_CURRENT, + device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l1"): sensor.sensor_schema( From 2064abe16de0dc90b108839cce551af3af21e17a Mon Sep 17 00:00:00 2001 From: rnauber <7414650+rnauber@users.noreply.github.com> Date: Mon, 18 Apr 2022 22:43:34 +0200 Subject: [PATCH 232/238] Shelly Dimmer: Delete obsolete LICENSE.txt (#3394) --- esphome/components/shelly_dimmer/LICENSE.txt | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 esphome/components/shelly_dimmer/LICENSE.txt diff --git a/esphome/components/shelly_dimmer/LICENSE.txt b/esphome/components/shelly_dimmer/LICENSE.txt deleted file mode 100644 index 524fe0d514..0000000000 --- a/esphome/components/shelly_dimmer/LICENSE.txt +++ /dev/null @@ -1,2 +0,0 @@ -The firmware files for the STM microcontroller (shelly-dimmer-stm32_*.bin) are taken from -https://github.com/jamesturton/shelly-dimmer-stm32 and GPLv3 licensed. From 0767b92b62d8b54d2233f86bac2e23f8ab52cf94 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Apr 2022 06:56:09 +1200 Subject: [PATCH 233/238] Dont require {} for wifi ap with defaults (#3404) --- 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 20f43cb450..b56902df2f 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -126,6 +126,13 @@ WIFI_NETWORK_AP = WIFI_NETWORK_BASE.extend( } ) + +def wifi_network_ap(value): + if value is None: + value = {} + return WIFI_NETWORK_AP(value) + + WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend( { cv.Optional(CONF_BSSID): cv.mac_address, @@ -252,7 +259,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_PASSWORD): validate_password, cv.Optional(CONF_MANUAL_IP): STA_MANUAL_IP_SCHEMA, cv.Optional(CONF_EAP): EAP_AUTH_SCHEMA, - cv.Optional(CONF_AP): WIFI_NETWORK_AP, + cv.Optional(CONF_AP): wifi_network_ap, cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, cv.Optional( CONF_REBOOT_TIMEOUT, default="15min" From 988d3ea8baf755e69525bc2d96f94424ea5b4b08 Mon Sep 17 00:00:00 2001 From: parats15 <72889410+parats15@users.noreply.github.com> Date: Wed, 20 Apr 2022 02:46:55 +0200 Subject: [PATCH 234/238] Multi conf for Teleinfo component (#3401) --- esphome/components/teleinfo/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/teleinfo/__init__.py b/esphome/components/teleinfo/__init__.py index 33b748a031..e289e42c81 100644 --- a/esphome/components/teleinfo/__init__.py +++ b/esphome/components/teleinfo/__init__.py @@ -4,6 +4,7 @@ from esphome.components import uart from esphome.const import CONF_ID CODEOWNERS = ["@0hax"] +MULTI_CONF = True teleinfo_ns = cg.esphome_ns.namespace("teleinfo") TeleInfo = teleinfo_ns.class_("TeleInfo", cg.PollingComponent, uart.UARTDevice) From 9576d246eec5fa4a5d0b0d0b82261ad0aa539275 Mon Sep 17 00:00:00 2001 From: James Duke Date: Tue, 19 Apr 2022 17:50:24 -0700 Subject: [PATCH 235/238] Add support for Mopeka Pro+ Residential sensor (#3393) * Add support for Pro+ Residential sensor (enum) The Mopeka Pro+ Residential sensor is very similar to the Pro sensor, but includes a longer range antenna, and maybe hardware? The Pro+ identifies itself with 0x08 sensor type. * Add logic to support Pro+ Residential sensor * Fix formatting --- esphome/components/mopeka_pro_check/mopeka_pro_check.cpp | 3 ++- esphome/components/mopeka_pro_check/mopeka_pro_check.h | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp index bcfe0a80ce..fc57318a81 100644 --- a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp @@ -52,7 +52,8 @@ bool MopekaProCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) // Now parse the data - See Datasheet for definition - if (static_cast(manu_data.data[0]) != STANDARD_BOTTOM_UP) { + if (static_cast(manu_data.data[0]) != STANDARD_BOTTOM_UP && + static_cast(manu_data.data[0]) != PLUS_BOTTOM_UP) { ESP_LOGE(TAG, "Unsupported Sensor Type (0x%X)", manu_data.data[0]); return false; } diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.h b/esphome/components/mopeka_pro_check/mopeka_pro_check.h index 59d33f7763..dfdce9353e 100644 --- a/esphome/components/mopeka_pro_check/mopeka_pro_check.h +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.h @@ -12,7 +12,8 @@ namespace mopeka_pro_check { enum SensorType { STANDARD_BOTTOM_UP = 0x03, TOP_DOWN_AIR_ABOVE = 0x04, - BOTTOM_UP_WATER = 0x05 + BOTTOM_UP_WATER = 0x05, + PLUS_BOTTOM_UP = 0x08 // all other values are reserved }; From 7a778f3f330ca607f2772501eac82a043d0346d3 Mon Sep 17 00:00:00 2001 From: "I. Tomita" Date: Thu, 21 Apr 2022 01:11:25 +0300 Subject: [PATCH 236/238] Add support for BL0939 (Sonoff Dual R3 V2 powermeter) (#3300) --- CODEOWNERS | 1 + esphome/components/bl0939/__init__.py | 1 + esphome/components/bl0939/bl0939.cpp | 144 ++++++++++++++++++++++++++ esphome/components/bl0939/bl0939.h | 107 +++++++++++++++++++ esphome/components/bl0939/sensor.py | 123 ++++++++++++++++++++++ tests/test3.yaml | 24 +++++ 6 files changed, 400 insertions(+) create mode 100644 esphome/components/bl0939/__init__.py create mode 100644 esphome/components/bl0939/bl0939.cpp create mode 100644 esphome/components/bl0939/bl0939.h create mode 100644 esphome/components/bl0939/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index c9df669f03..7fd049f46e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -31,6 +31,7 @@ esphome/components/bang_bang/* @OttoWinter esphome/components/bedjet/* @jhansche esphome/components/bh1750/* @OttoWinter esphome/components/binary_sensor/* @esphome/core +esphome/components/bl0939/* @ziceva esphome/components/bl0940/* @tobias- esphome/components/ble_client/* @buxtronix esphome/components/bme680_bsec/* @trvrnrth diff --git a/esphome/components/bl0939/__init__.py b/esphome/components/bl0939/__init__.py new file mode 100644 index 0000000000..9bd4598dd2 --- /dev/null +++ b/esphome/components/bl0939/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@ziceva"] diff --git a/esphome/components/bl0939/bl0939.cpp b/esphome/components/bl0939/bl0939.cpp new file mode 100644 index 0000000000..61d7835a4b --- /dev/null +++ b/esphome/components/bl0939/bl0939.cpp @@ -0,0 +1,144 @@ +#include "bl0939.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace bl0939 { + +static const char *const TAG = "bl0939"; + +// https://www.belling.com.cn/media/file_object/bel_product/BL0939/datasheet/BL0939_V1.2_cn.pdf +// (unfortunatelly chinese, but the protocol can be understood with some translation tool) +static const uint8_t BL0939_READ_COMMAND = 0x55; // 0x5{A4,A3,A2,A1} +static const uint8_t BL0939_FULL_PACKET = 0xAA; +static const uint8_t BL0939_PACKET_HEADER = 0x55; + +static const uint8_t BL0939_WRITE_COMMAND = 0xA5; // 0xA{A4,A3,A2,A1} +static const uint8_t BL0939_REG_IA_FAST_RMS_CTRL = 0x10; +static const uint8_t BL0939_REG_IB_FAST_RMS_CTRL = 0x1E; +static const uint8_t BL0939_REG_MODE = 0x18; +static const uint8_t BL0939_REG_SOFT_RESET = 0x19; +static const uint8_t BL0939_REG_USR_WRPROT = 0x1A; +static const uint8_t BL0939_REG_TPS_CTRL = 0x1B; + +const uint8_t BL0939_INIT[6][6] = { + // Reset to default + {BL0939_WRITE_COMMAND, BL0939_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x33}, + // Enable User Operation Write + {BL0939_WRITE_COMMAND, BL0939_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xEB}, + // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS + {BL0939_WRITE_COMMAND, BL0939_REG_MODE, 0x00, 0x10, 0x00, 0x32}, + // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS + {BL0939_WRITE_COMMAND, BL0939_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xF9}, + // 0x181C = Half cycle, Fast RMS threshold 6172 + {BL0939_WRITE_COMMAND, BL0939_REG_IA_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x16}, + // 0x181C = Half cycle, Fast RMS threshold 6172 + {BL0939_WRITE_COMMAND, BL0939_REG_IB_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x08}}; + +void BL0939::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 BL0939::validate_checksum(const DataPacket *data) { + uint8_t checksum = BL0939_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, "BL0939 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum); + } + return checksum == data->checksum; +} + +void BL0939::update() { + this->flush(); + this->write_byte(BL0939_READ_COMMAND); + this->write_byte(BL0939_FULL_PACKET); +} + +void BL0939::setup() { + for (auto *i : BL0939_INIT) { + this->write_array(i, 6); + delay(1); + } + this->flush(); +} + +void BL0939::received_package_(const DataPacket *data) const { + // Bad header + if (data->frame_header != BL0939_PACKET_HEADER) { + ESP_LOGI("bl0939", "Invalid data. Header mismatch: %d", data->frame_header); + return; + } + + float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_; + float ia_rms = (float) to_uint32_t(data->ia_rms) / current_reference_; + float ib_rms = (float) to_uint32_t(data->ib_rms) / current_reference_; + float a_watt = (float) to_int32_t(data->a_watt) / power_reference_; + float b_watt = (float) to_int32_t(data->b_watt) / power_reference_; + int32_t cfa_cnt = to_int32_t(data->cfa_cnt); + int32_t cfb_cnt = to_int32_t(data->cfb_cnt); + float a_energy_consumption = (float) cfa_cnt / energy_reference_; + float b_energy_consumption = (float) cfb_cnt / energy_reference_; + float total_energy_consumption = a_energy_consumption + b_energy_consumption; + + if (voltage_sensor_ != nullptr) { + voltage_sensor_->publish_state(v_rms); + } + if (current_sensor_1_ != nullptr) { + current_sensor_1_->publish_state(ia_rms); + } + if (current_sensor_2_ != nullptr) { + current_sensor_2_->publish_state(ib_rms); + } + if (power_sensor_1_ != nullptr) { + power_sensor_1_->publish_state(a_watt); + } + if (power_sensor_2_ != nullptr) { + power_sensor_2_->publish_state(b_watt); + } + if (energy_sensor_1_ != nullptr) { + energy_sensor_1_->publish_state(a_energy_consumption); + } + if (energy_sensor_2_ != nullptr) { + energy_sensor_2_->publish_state(b_energy_consumption); + } + if (energy_sensor_sum_ != nullptr) { + energy_sensor_sum_->publish_state(total_energy_consumption); + } + + ESP_LOGV("bl0939", "BL0939: U %fV, I1 %fA, I2 %fA, P1 %fW, P2 %fW, CntA %d, CntB %d, ∫P1 %fkWh, ∫P2 %fkWh", v_rms, + ia_rms, ib_rms, a_watt, b_watt, cfa_cnt, cfb_cnt, a_energy_consumption, b_energy_consumption); +} + +void BL0939::dump_config() { // NOLINT(readability-function-cognitive-complexity) + ESP_LOGCONFIG(TAG, "BL0939:"); + LOG_SENSOR("", "Voltage", this->voltage_sensor_); + LOG_SENSOR("", "Current 1", this->current_sensor_1_); + LOG_SENSOR("", "Current 2", this->current_sensor_2_); + LOG_SENSOR("", "Power 1", this->power_sensor_1_); + LOG_SENSOR("", "Power 2", this->power_sensor_2_); + LOG_SENSOR("", "Energy 1", this->energy_sensor_1_); + LOG_SENSOR("", "Energy 2", this->energy_sensor_2_); + LOG_SENSOR("", "Energy sum", this->energy_sensor_sum_); +} + +uint32_t BL0939::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; } + +int32_t BL0939::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; } + +} // namespace bl0939 +} // namespace esphome diff --git a/esphome/components/bl0939/bl0939.h b/esphome/components/bl0939/bl0939.h new file mode 100644 index 0000000000..5221ae26e7 --- /dev/null +++ b/esphome/components/bl0939/bl0939.h @@ -0,0 +1,107 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace bl0939 { + +// https://datasheet.lcsc.com/lcsc/2108071830_BL-Shanghai-Belling-BL0939_C2841044.pdf +// (unfortunatelly chinese, but the formulas can be easily understood) +// Sonoff Dual R3 V2 has the exact same resistor values for the current shunts (RL=1miliOhm) +// and for the voltage divider (R1=0.51kOhm, R2=5*390kOhm) +// as in the manufacturer's reference circuit, so the same formulas were used here (Vref=1.218V) +static const float BL0939_IREF = 324004 * 1 / 1.218; +static const float BL0939_UREF = 79931 * 0.51 * 1000 / (1.218 * (5 * 390 + 0.51)); +static const float BL0939_PREF = 4046 * 1 * 0.51 * 1000 / (1.218 * 1.218 * (5 * 390 + 0.51)); +static const float BL0939_EREF = 3.6e6 * 4046 * 1 * 0.51 * 1000 / (1638.4 * 256 * 1.218 * 1.218 * (5 * 390 + 0.51)); + +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; // 0x55 according to docs + ube24_t ia_fast_rms; + ube24_t ia_rms; + ube24_t ib_rms; + ube24_t v_rms; + ube24_t ib_fast_rms; + sbe24_t a_watt; + sbe24_t b_watt; + sbe24_t cfa_cnt; + sbe24_t cfb_cnt; + ube16_t tps1; + uint8_t RESERVED1; // value of 0x00 + ube16_t tps2; + uint8_t RESERVED2; // value of 0x00 + uint8_t checksum; // checksum + }; +} __attribute__((packed)); + +class BL0939 : public PollingComponent, public uart::UARTDevice { + public: + void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } + void set_current_sensor_1(sensor::Sensor *current_sensor_1) { current_sensor_1_ = current_sensor_1; } + void set_current_sensor_2(sensor::Sensor *current_sensor_2) { current_sensor_2_ = current_sensor_2; } + void set_power_sensor_1(sensor::Sensor *power_sensor_1) { power_sensor_1_ = power_sensor_1; } + void set_power_sensor_2(sensor::Sensor *power_sensor_2) { power_sensor_2_ = power_sensor_2; } + void set_energy_sensor_1(sensor::Sensor *energy_sensor_1) { energy_sensor_1_ = energy_sensor_1; } + void set_energy_sensor_2(sensor::Sensor *energy_sensor_2) { energy_sensor_2_ = energy_sensor_2; } + void set_energy_sensor_sum(sensor::Sensor *energy_sensor_sum) { energy_sensor_sum_ = energy_sensor_sum; } + + void loop() override; + + void update() override; + void setup() override; + void dump_config() override; + + protected: + sensor::Sensor *voltage_sensor_; + sensor::Sensor *current_sensor_1_; + sensor::Sensor *current_sensor_2_; + // NB This may be negative as the circuits is seemingly able to measure + // power in both directions + sensor::Sensor *power_sensor_1_; + sensor::Sensor *power_sensor_2_; + sensor::Sensor *energy_sensor_1_; + sensor::Sensor *energy_sensor_2_; + sensor::Sensor *energy_sensor_sum_; + + // Divide by this to turn into Watt + float power_reference_ = BL0939_PREF; + // Divide by this to turn into Volt + float voltage_reference_ = BL0939_UREF; + // Divide by this to turn into Ampere + float current_reference_ = BL0939_IREF; + // Divide by this to turn into kWh + float energy_reference_ = BL0939_EREF; + + 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 bl0939 +} // namespace esphome diff --git a/esphome/components/bl0939/sensor.py b/esphome/components/bl0939/sensor.py new file mode 100644 index 0000000000..bcc72ad61a --- /dev/null +++ b/esphome/components/bl0939/sensor.py @@ -0,0 +1,123 @@ +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_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + UNIT_AMPERE, + UNIT_KILOWATT_HOURS, + UNIT_VOLT, + UNIT_WATT, +) + +DEPENDENCIES = ["uart"] + +CONF_CURRENT_1 = "current_1" +CONF_CURRENT_2 = "current_2" +CONF_ACTIVE_POWER_1 = "active_power_1" +CONF_ACTIVE_POWER_2 = "active_power_2" +CONF_ENERGY_1 = "energy_1" +CONF_ENERGY_2 = "energy_2" +CONF_ENERGY_TOTAL = "energy_total" + +bl0939_ns = cg.esphome_ns.namespace("bl0939") +BL0939 = bl0939_ns.class_("BL0939", cg.PollingComponent, uart.UARTDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(BL0939), + 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=0, + 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=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ENERGY_1): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + ), + cv.Optional(CONF_ENERGY_2): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + ), + cv.Optional(CONF_ENERGY_TOTAL): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + ), + } + ) + .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_1 in config: + conf = config[CONF_CURRENT_1] + sens = await sensor.new_sensor(conf) + cg.add(var.set_current_sensor_1(sens)) + if CONF_CURRENT_2 in config: + conf = config[CONF_CURRENT_2] + sens = await sensor.new_sensor(conf) + cg.add(var.set_current_sensor_2(sens)) + if CONF_ACTIVE_POWER_1 in config: + conf = config[CONF_ACTIVE_POWER_1] + sens = await sensor.new_sensor(conf) + cg.add(var.set_power_sensor_1(sens)) + if CONF_ACTIVE_POWER_2 in config: + conf = config[CONF_ACTIVE_POWER_2] + sens = await sensor.new_sensor(conf) + cg.add(var.set_power_sensor_2(sens)) + if CONF_ENERGY_1 in config: + conf = config[CONF_ENERGY_1] + sens = await sensor.new_sensor(conf) + cg.add(var.set_energy_sensor_1(sens)) + if CONF_ENERGY_2 in config: + conf = config[CONF_ENERGY_2] + sens = await sensor.new_sensor(conf) + cg.add(var.set_energy_sensor_2(sens)) + if CONF_ENERGY_TOTAL in config: + conf = config[CONF_ENERGY_TOTAL] + sens = await sensor.new_sensor(conf) + cg.add(var.set_energy_sensor_sum(sens)) diff --git a/tests/test3.yaml b/tests/test3.yaml index 58cb14740f..29a70d3cc3 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -256,6 +256,12 @@ uart: tx_pin: GPIO4 rx_pin: GPIO5 baud_rate: 38400 + - id: uart8 + tx_pin: GPIO4 + rx_pin: GPIO5 + baud_rate: 4800 + parity: NONE + stop_bits: 2 # Specifically added for testing debug with no options at all. debug: @@ -477,6 +483,24 @@ sensor: active_power_b: name: ADE7953 Active Power B id: ade7953_active_power_b + - platform: bl0939 + uart_id: uart8 + voltage: + name: 'BL0939 Voltage' + current_1: + name: 'BL0939 Current 1' + current_2: + name: 'BL0939 Current 2' + active_power_1: + name: 'BL0939 Active Power 1' + active_power_2: + name: 'BL0939 Active Power 2' + energy_1: + name: 'BL0939 Energy 1' + energy_2: + name: 'BL0939 Energy 2' + energy_total: + name: 'BL0939 Total energy' - platform: bl0940 uart_id: uart3 voltage: From 757b98748b7e6d13cf21ecf379870eb4efe94fa4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 22 Apr 2022 17:08:01 +1200 Subject: [PATCH 237/238] Add "esphome rename" command (#3403) * Add "esphome rename" command * Only open file once * Update esphome/__main__.py Co-authored-by: Paulus Schoutsen * Add final return * Use match.group consistently * Validate name characters * Add whitespace to regex so it is only replacing exact match * Validate yaml config file after manipulation Co-authored-by: Paulus Schoutsen --- esphome/__main__.py | 101 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/esphome/__main__.py b/esphome/__main__.py index 85cf4ede85..00770d6f05 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -2,6 +2,7 @@ import argparse import functools import logging import os +import re import sys from datetime import datetime @@ -9,15 +10,18 @@ from esphome import const, writer, yaml_util import esphome.codegen as cg from esphome.config import iter_components, read_config, strip_default_ids from esphome.const import ( + ALLOWED_NAME_CHARS, CONF_BAUD_RATE, CONF_BROKER, CONF_DEASSERT_RTS_DTR, CONF_LOGGER, + CONF_NAME, CONF_OTA, CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS, + CONF_SUBSTITUTIONS, SECRETS_FILES, ) from esphome.core import CORE, EsphomeError, coroutine @@ -481,6 +485,96 @@ def command_idedata(args, config): return 0 +def command_rename(args, config): + for c in args.name: + if c not in ALLOWED_NAME_CHARS: + print( + color( + Fore.BOLD_RED, + f"'{c}' is an invalid character for names. Valid characters are: " + f"{ALLOWED_NAME_CHARS} (lowercase, no spaces)", + ) + ) + return 1 + with open(CORE.config_path, mode="r+", encoding="utf-8") as raw_file: + raw_contents = raw_file.read() + yaml = yaml_util.load_yaml(CORE.config_path) + if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]: + print( + color( + Fore.BOLD_RED, "Complex YAML files cannot be automatically renamed." + ) + ) + return 1 + old_name = yaml[CONF_ESPHOME][CONF_NAME] + match = re.match(r"^\$\{?([a-zA-Z0-9_]+)\}?$", old_name) + if match is None: + new_raw = re.sub( + rf"name:\s+[\"']?{old_name}[\"']?", + f'name: "{args.name}"', + raw_contents, + ) + else: + old_name = yaml[CONF_SUBSTITUTIONS][match.group(1)] + if ( + len( + re.findall( + rf"^\s+{match.group(1)}:\s+[\"']?{old_name}[\"']?", + raw_contents, + flags=re.MULTILINE, + ) + ) + > 1 + ): + print(color(Fore.BOLD_RED, "Too many matches in YAML to safely rename")) + return 1 + + new_raw = re.sub( + rf"^(\s+{match.group(1)}):\s+[\"']?{old_name}[\"']?", + f'\\1: "{args.name}"', + raw_contents, + flags=re.MULTILINE, + ) + + raw_file.seek(0) + raw_file.write(new_raw) + raw_file.flush() + + print(f"Updating {color(Fore.CYAN, CORE.config_path)}") + print() + + rc = run_external_process("esphome", "config", CORE.config_path) + if rc != 0: + raw_file.seek(0) + raw_file.write(raw_contents) + print(color(Fore.BOLD_RED, "Rename failed. Reverting changes.")) + return 1 + + cli_args = [ + "run", + CORE.config_path, + "--no-logs", + "--device", + CORE.address, + ] + + if args.dashboard: + cli_args.insert(0, "--dashboard") + + try: + rc = run_external_process("esphome", *cli_args) + except KeyboardInterrupt: + rc = 1 + if rc != 0: + raw_file.seek(0) + raw_file.write(raw_contents) + return 1 + + print(color(Fore.BOLD_GREEN, "SUCCESS")) + print() + return 0 + + PRE_CONFIG_ACTIONS = { "wizard": command_wizard, "version": command_version, @@ -499,6 +593,7 @@ POST_CONFIG_ACTIONS = { "mqtt-fingerprint": command_mqtt_fingerprint, "clean": command_clean, "idedata": command_idedata, + "rename": command_rename, } @@ -681,6 +776,12 @@ def parse_args(argv): "configuration", help="Your YAML configuration file(s).", nargs=1 ) + parser_rename = subparsers.add_parser("rename") + parser_rename.add_argument( + "configuration", help="Your YAML configuration file.", nargs=1 + ) + parser_rename.add_argument("name", help="The new name for the device.", type=str) + # Keep backward compatibility with the old command line format of # esphome . # From 6fe22a7e629edf9243ba822d77a75f4587e6afd2 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 25 Apr 2022 23:50:36 +0200 Subject: [PATCH 238/238] SPS30: Add fan action (#3410) * Add fan action to SPS30 * add codeowner --- CODEOWNERS | 1 + esphome/components/sps30/automation.h | 21 +++++++++++++++++++ esphome/components/sps30/sensor.py | 27 ++++++++++++++++++++++++ esphome/components/sps30/sps30.cpp | 30 ++++++++++++++++++++++++++- esphome/components/sps30/sps30.h | 5 ++++- 5 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 esphome/components/sps30/automation.h diff --git a/CODEOWNERS b/CODEOWNERS index 7fd049f46e..e2a356360a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -183,6 +183,7 @@ esphome/components/sm2135/* @BoukeHaarsma23 esphome/components/socket/* @esphome/core esphome/components/sonoff_d1/* @anatoly-savchenkov esphome/components/spi/* @esphome/core +esphome/components/sps30/* @martgras esphome/components/ssd1322_base/* @kbx81 esphome/components/ssd1322_spi/* @kbx81 esphome/components/ssd1325_base/* @kbx81 diff --git a/esphome/components/sps30/automation.h b/esphome/components/sps30/automation.h new file mode 100644 index 0000000000..443aafb575 --- /dev/null +++ b/esphome/components/sps30/automation.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "sps30.h" + +namespace esphome { +namespace sps30 { + +template class StartFanAction : public Action { + public: + explicit StartFanAction(SPS30Component *sps30) : sps30_(sps30) {} + + void play(Ts... x) override { this->sps30_->start_fan_cleaning(); } + + protected: + SPS30Component *sps30_; +}; + +} // namespace sps30 +} // namespace esphome diff --git a/esphome/components/sps30/sensor.py b/esphome/components/sps30/sensor.py index 89cb25c24f..ff8d5a3594 100644 --- a/esphome/components/sps30/sensor.py +++ b/esphome/components/sps30/sensor.py @@ -1,6 +1,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor, sensirion_common +from esphome import automation +from esphome.automation import maybe_simple_id from esphome.const import ( CONF_ID, CONF_PM_1_0, @@ -25,6 +27,7 @@ from esphome.const import ( ICON_RULER, ) +CODEOWNERS = ["@martgras"] DEPENDENCIES = ["i2c"] AUTO_LOAD = ["sensirion_common"] @@ -33,6 +36,11 @@ SPS30Component = sps30_ns.class_( "SPS30Component", cg.PollingComponent, sensirion_common.SensirionI2CDevice ) +# Actions +StartFanAction = sps30_ns.class_("StartFanAction", automation.Action) + +CONF_AUTO_CLEANING_INTERVAL = "auto_cleaning_interval" + CONFIG_SCHEMA = ( cv.Schema( { @@ -100,6 +108,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_AUTO_CLEANING_INTERVAL): cv.update_interval, } ) .extend(cv.polling_component_schema("60s")) @@ -151,3 +160,21 @@ async def to_code(config): if CONF_PM_SIZE in config: sens = await sensor.new_sensor(config[CONF_PM_SIZE]) cg.add(var.set_pm_size_sensor(sens)) + + if CONF_AUTO_CLEANING_INTERVAL in config: + cg.add(var.set_auto_cleaning_interval(config[CONF_AUTO_CLEANING_INTERVAL])) + + +SPS30_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(SPS30Component), + } +) + + +@automation.register_action( + "sps30.start_fan_autoclean", StartFanAction, SPS30_ACTION_SCHEMA +) +async def sps30_fan_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) diff --git a/esphome/components/sps30/sps30.cpp b/esphome/components/sps30/sps30.cpp index 2885125a8a..cdcd4a6a54 100644 --- a/esphome/components/sps30/sps30.cpp +++ b/esphome/components/sps30/sps30.cpp @@ -1,5 +1,6 @@ -#include "sps30.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" +#include "sps30.h" namespace esphome { namespace sps30 { @@ -44,6 +45,22 @@ 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_); + + bool result; + if (this->fan_interval_.has_value()) { + // override default value + result = write_command(SPS30_CMD_SET_AUTOMATIC_CLEANING_INTERVAL_SECONDS, this->fan_interval_.value()); + } else { + result = write_command(SPS30_CMD_SET_AUTOMATIC_CLEANING_INTERVAL_SECONDS); + } + if (result) { + delay(20); + uint16_t secs[2]; + if (this->read_data(secs, 2)) { + fan_interval_ = secs[0] << 16 | secs[1]; + } + } + this->status_clear_warning(); this->skipped_data_read_cycles_ = 0; this->start_continuous_measurement_(); @@ -206,5 +223,16 @@ bool SPS30Component::start_continuous_measurement_() { return true; } +bool SPS30Component::start_fan_cleaning() { + if (!write_command(SPS30_CMD_START_FAN_CLEANING)) { + this->status_set_warning(); + ESP_LOGE(TAG, "write error start fan (%d)", this->last_error_); + return false; + } else { + ESP_LOGD(TAG, "Fan auto clean started"); + } + return true; +} + } // namespace sps30 } // namespace esphome diff --git a/esphome/components/sps30/sps30.h b/esphome/components/sps30/sps30.h index 9a93df8597..cf2e7a7d4f 100644 --- a/esphome/components/sps30/sps30.h +++ b/esphome/components/sps30/sps30.h @@ -22,12 +22,14 @@ class SPS30Component : public PollingComponent, public sensirion_common::Sensiri void set_pmc_10_0_sensor(sensor::Sensor *pmc_10_0) { pmc_10_0_sensor_ = pmc_10_0; } void set_pm_size_sensor(sensor::Sensor *pm_size) { pm_size_sensor_ = pm_size; } - + void set_auto_cleaning_interval(uint32_t auto_cleaning_interval) { fan_interval_ = auto_cleaning_interval; } void setup() override; void update() override; void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } + bool start_fan_cleaning(); + protected: char serial_number_[17] = {0}; /// Terminating NULL character uint16_t raw_firmware_version_; @@ -54,6 +56,7 @@ class SPS30Component : public PollingComponent, public sensirion_common::Sensiri sensor::Sensor *pmc_4_0_sensor_{nullptr}; sensor::Sensor *pmc_10_0_sensor_{nullptr}; sensor::Sensor *pm_size_sensor_{nullptr}; + optional fan_interval_; }; } // namespace sps30