From 04ba53c870344ef23d2eb1a19b7573310485770b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 11 Nov 2021 10:10:05 +1300 Subject: [PATCH 001/111] Bump version to 2021.12.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index ff8510b40e..99cb53fe4b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.11.0-dev" +__version__ = "2021.12.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 4395d6156daa3c283a55a4f16950455598c32120 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 11 Nov 2021 11:24:48 +1300 Subject: [PATCH 002/111] Fix template number initial value being NaN (#2692) --- esphome/components/template/number/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/template/number/__init__.py b/esphome/components/template/number/__init__.py index 887f6b15ad..3dec7066d3 100644 --- a/esphome/components/template/number/__init__.py +++ b/esphome/components/template/number/__init__.py @@ -35,6 +35,9 @@ def validate(config): raise cv.Invalid("initial_value cannot be used with lambda") if CONF_RESTORE_VALUE in config: raise cv.Invalid("restore_value cannot be used with lambda") + elif CONF_INITIAL_VALUE not in config: + config[CONF_INITIAL_VALUE] = config[CONF_MIN_VALUE] + if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config: raise cv.Invalid( "Either optimistic mode must be enabled, or set_action must be set, to handle the number being set." @@ -80,8 +83,7 @@ async def to_code(config): else: cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) - if CONF_INITIAL_VALUE in config: - cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) + cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) if CONF_RESTORE_VALUE in config: cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE])) From abf3708cc2b80f69d061800e7aa6c85293440499 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Wed, 10 Nov 2021 23:28:45 +0100 Subject: [PATCH 003/111] [remote_transmitter] accurate pulse timing for ESP8266 (#2476) --- .../remote_transmitter/remote_transmitter.h | 3 + .../remote_transmitter_esp8266.cpp | 78 ++++++++++--------- 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index 733ac5e50d..a4235e875f 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -32,6 +32,9 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, void mark_(uint32_t on_time, uint32_t off_time, uint32_t usec); void space_(uint32_t usec); + + void await_target_time_(); + uint32_t target_time_; #endif #ifdef USE_ESP32 diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp index 74e62d4e3b..39752cac5b 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp @@ -33,56 +33,64 @@ void RemoteTransmitterComponent::calculate_on_off_time_(uint32_t carrier_frequen *off_time_period = period - *on_time_period; } +void RemoteTransmitterComponent::await_target_time_() { + const uint32_t current_time = micros(); + if (this->target_time_ == 0) + this->target_time_ = current_time; + else if (this->target_time_ > current_time) + delayMicroseconds(this->target_time_ - current_time); +} + void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint32_t usec) { - if (this->carrier_duty_percent_ == 100 || (on_time == 0 && off_time == 0)) { - this->pin_->digital_write(true); - delayMicroseconds(usec); - this->pin_->digital_write(false); - return; - } - - const uint32_t start_time = micros(); - uint32_t current_time = start_time; - - while (current_time - start_time < usec) { - const uint32_t elapsed = current_time - start_time; - this->pin_->digital_write(true); - - delayMicroseconds(std::min(on_time, usec - elapsed)); - this->pin_->digital_write(false); - if (elapsed + on_time >= usec) - return; - - delayMicroseconds(std::min(usec - elapsed - on_time, off_time)); - - current_time = micros(); + this->await_target_time_(); + this->pin_->digital_write(true); + + const uint32_t target = this->target_time_ + usec; + if (this->carrier_duty_percent_ < 100 && (on_time > 0 || off_time > 0)) { + while (true) { // Modulate with carrier frequency + this->target_time_ += on_time; + if (this->target_time_ >= target) + break; + this->await_target_time_(); + this->pin_->digital_write(false); + + this->target_time_ += off_time; + if (this->target_time_ >= target) + break; + this->await_target_time_(); + this->pin_->digital_write(true); + } } + this->target_time_ = target; } + void RemoteTransmitterComponent::space_(uint32_t usec) { + this->await_target_time_(); this->pin_->digital_write(false); - delayMicroseconds(usec); + this->target_time_ += usec; } + void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { ESP_LOGD(TAG, "Sending remote code..."); uint32_t on_time, off_time; this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time); + this->target_time_ = 0; for (uint32_t i = 0; i < send_times; i++) { - { - InterruptLock lock; - for (int32_t item : this->temp_.get_data()) { - if (item > 0) { - const auto length = uint32_t(item); - this->mark_(on_time, off_time, length); - } else { - const auto length = uint32_t(-item); - this->space_(length); - } - App.feed_wdt(); + for (int32_t item : this->temp_.get_data()) { + if (item > 0) { + const auto length = uint32_t(item); + this->mark_(on_time, off_time, length); + } else { + const auto length = uint32_t(-item); + this->space_(length); } + App.feed_wdt(); } + this->await_target_time_(); // wait for duration of last pulse + this->pin_->digital_write(false); if (i + 1 < send_times) - delayMicroseconds(send_wait); + this->target_time_ += send_wait; } } From e99af991ecc63c942b64dfba17ad1d70261f415e Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 10 Nov 2021 23:34:17 +0100 Subject: [PATCH 004/111] Uart debugging support (#2478) Co-authored-by: Maurice Makaay Co-authored-by: Maurice Makaay Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/uart/__init__.py | 69 +++++++ esphome/components/uart/uart.h | 6 +- esphome/components/uart/uart_component.h | 21 ++ .../uart/uart_component_esp32_arduino.cpp | 12 +- .../uart/uart_component_esp8266.cpp | 9 +- .../uart/uart_component_esp_idf.cpp | 12 +- esphome/components/uart/uart_debugger.cpp | 193 ++++++++++++++++++ esphome/components/uart/uart_debugger.h | 101 +++++++++ esphome/const.py | 7 + esphome/core/defines.h | 1 + tests/test1.yaml | 12 ++ 11 files changed, 430 insertions(+), 13 deletions(-) create mode 100644 esphome/components/uart/uart_debugger.cpp create mode 100644 esphome/components/uart/uart_debugger.h diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 35af3eedf7..53209dfc7b 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -14,6 +14,16 @@ from esphome.const import ( CONF_DATA, CONF_RX_BUFFER_SIZE, CONF_INVERT, + CONF_TRIGGER_ID, + CONF_SEQUENCE, + CONF_TIMEOUT, + CONF_DEBUG, + CONF_DIRECTION, + CONF_AFTER, + CONF_BYTES, + CONF_DELIMITER, + CONF_DUMMY_RECEIVER, + CONF_DUMMY_RECEIVER_ID, ) from esphome.core import CORE @@ -31,6 +41,8 @@ ESP8266UartComponent = uart_ns.class_( UARTDevice = uart_ns.class_("UARTDevice") UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action) +UARTDebugger = uart_ns.class_("UARTDebugger", cg.Component, automation.Action) +UARTDummyReceiver = uart_ns.class_("UARTDummyReceiver", cg.Component) MULTI_CONF = True @@ -75,6 +87,34 @@ CONF_STOP_BITS = "stop_bits" CONF_DATA_BITS = "data_bits" CONF_PARITY = "parity" +UARTDirection = uart_ns.enum("UARTDirection") +UART_DIRECTIONS = { + "RX": UARTDirection.UART_DIRECTION_RX, + "TX": UARTDirection.UART_DIRECTION_TX, + "BOTH": UARTDirection.UART_DIRECTION_BOTH, +} + +DEBUG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UARTDebugger), + cv.Optional(CONF_DIRECTION, default="BOTH"): cv.enum( + UART_DIRECTIONS, upper=True + ), + cv.Optional(CONF_AFTER): cv.Schema( + { + cv.Optional(CONF_BYTES, default=256): cv.validate_bytes, + cv.Optional( + CONF_TIMEOUT, default="100ms" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_DELIMITER): cv.templatable(validate_raw_data), + } + ), + cv.Required(CONF_SEQUENCE): automation.validate_automation(), + cv.Optional(CONF_DUMMY_RECEIVER, default=False): cv.boolean, + cv.GenerateID(CONF_DUMMY_RECEIVER_ID): cv.declare_id(UARTDummyReceiver), + } +) + CONFIG_SCHEMA = cv.All( cv.Schema( { @@ -91,12 +131,38 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_INVERT): cv.invalid( "This option has been removed. Please instead use invert in the tx/rx pin schemas." ), + cv.Optional(CONF_DEBUG): DEBUG_SCHEMA, } ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN), ) +async def debug_to_code(config, parent): + trigger = cg.new_Pvariable(config[CONF_TRIGGER_ID], parent) + await cg.register_component(trigger, config) + for action in config[CONF_SEQUENCE]: + await automation.build_automation( + trigger, + [(UARTDirection, "direction"), (cg.std_vector.template(cg.uint8), "bytes")], + action, + ) + cg.add(trigger.set_direction(config[CONF_DIRECTION])) + after = config[CONF_AFTER] + cg.add(trigger.set_after_bytes(after[CONF_BYTES])) + cg.add(trigger.set_after_timeout(after[CONF_TIMEOUT])) + if CONF_DELIMITER in after: + data = after[CONF_DELIMITER] + if isinstance(data, bytes): + data = list(data) + for byte in after[CONF_DELIMITER]: + cg.add(trigger.add_delimiter_byte(byte)) + if config[CONF_DUMMY_RECEIVER]: + dummy = cg.new_Pvariable(config[CONF_DUMMY_RECEIVER_ID], parent) + await cg.register_component(dummy, {}) + cg.add_define("USE_UART_DEBUGGER") + + async def to_code(config): cg.add_global(uart_ns.using) var = cg.new_Pvariable(config[CONF_ID]) @@ -115,6 +181,9 @@ async def to_code(config): cg.add(var.set_data_bits(config[CONF_DATA_BITS])) cg.add(var.set_parity(config[CONF_PARITY])) + if CONF_DEBUG in config: + await debug_to_code(config[CONF_DEBUG], var) + # A schema to use for all UART devices, all UART integrations must extend this! UART_DEVICE_SCHEMA = cv.Schema( diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index c368f9ed6b..d41dbe26e6 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -45,17 +45,17 @@ class UARTDevice { // Compat APIs int read() { uint8_t data; - if (!read_byte(&data)) + if (!this->read_byte(&data)) return -1; return data; } size_t write(uint8_t data) { - write_byte(data); + this->write_byte(data); return 1; } int peek() { uint8_t data; - if (!peek_byte(&data)) + if (!this->peek_byte(&data)) return -1; return data; } diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h index de85cd2ca3..73694d3db7 100644 --- a/esphome/components/uart/uart_component.h +++ b/esphome/components/uart/uart_component.h @@ -2,9 +2,13 @@ #include #include +#include "esphome/core/defines.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" +#ifdef USE_UART_DEBUGGER +#include "esphome/core/automation.h" +#endif namespace esphome { namespace uart { @@ -15,6 +19,14 @@ enum UARTParityOptions { UART_CONFIG_PARITY_ODD, }; +#ifdef USE_UART_DEBUGGER +enum UARTDirection { + UART_DIRECTION_RX, + UART_DIRECTION_TX, + UART_DIRECTION_BOTH, +}; +#endif + const LogString *parity_to_str(UARTParityOptions parity); class UARTComponent { @@ -50,6 +62,12 @@ class UARTComponent { void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; } uint32_t get_baud_rate() const { return baud_rate_; } +#ifdef USE_UART_DEBUGGER + void add_debug_callback(std::function &&callback) { + this->debug_callback_.add(std::move(callback)); + } +#endif + protected: virtual void check_logger_conflict() = 0; bool check_read_timeout_(size_t len = 1); @@ -61,6 +79,9 @@ class UARTComponent { uint8_t stop_bits_; uint8_t data_bits_; UARTParityOptions parity_; +#ifdef USE_UART_DEBUGGER + CallbackManager debug_callback_{}; +#endif }; } // namespace uart diff --git a/esphome/components/uart/uart_component_esp32_arduino.cpp b/esphome/components/uart/uart_component_esp32_arduino.cpp index 1b1ce382f2..95cdde4a43 100644 --- a/esphome/components/uart/uart_component_esp32_arduino.cpp +++ b/esphome/components/uart/uart_component_esp32_arduino.cpp @@ -117,26 +117,32 @@ void ESP32ArduinoUARTComponent::dump_config() { void ESP32ArduinoUARTComponent::write_array(const uint8_t *data, size_t len) { this->hw_serial_->write(data, len); +#ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { - ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); + this->debug_callback_.call(UART_DIRECTION_TX, data[i]); } +#endif } + bool ESP32ArduinoUARTComponent::peek_byte(uint8_t *data) { if (!this->check_read_timeout_()) return false; *data = this->hw_serial_->peek(); return true; } + bool ESP32ArduinoUARTComponent::read_array(uint8_t *data, size_t len) { if (!this->check_read_timeout_(len)) return false; this->hw_serial_->readBytes(data, len); +#ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { - ESP_LOGVV(TAG, " Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); + this->debug_callback_.call(UART_DIRECTION_RX, data[i]); } - +#endif return true; } + int ESP32ArduinoUARTComponent::available() { return this->hw_serial_->available(); } void ESP32ArduinoUARTComponent::flush() { ESP_LOGVV(TAG, " Flushing..."); diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index 973306cde2..c367de05bb 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -130,9 +130,11 @@ void ESP8266UartComponent::write_array(const uint8_t *data, size_t len) { for (size_t i = 0; i < len; i++) this->sw_serial_->write_byte(data[i]); } +#ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { - ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); + this->debug_callback_.call(UART_DIRECTION_TX, data[i]); } +#endif } bool ESP8266UartComponent::peek_byte(uint8_t *data) { if (!this->check_read_timeout_()) @@ -153,10 +155,11 @@ bool ESP8266UartComponent::read_array(uint8_t *data, size_t len) { for (size_t i = 0; i < len; i++) data[i] = this->sw_serial_->read_byte(); } +#ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { - ESP_LOGVV(TAG, " Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); + this->debug_callback_.call(UART_DIRECTION_RX, data[i]); } - +#endif return true; } int ESP8266UartComponent::available() { diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 1cccd5821e..4d6a6af0fc 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -130,10 +130,13 @@ void IDFUARTComponent::write_array(const uint8_t *data, size_t len) { xSemaphoreTake(this->lock_, portMAX_DELAY); uart_write_bytes(this->uart_num_, data, len); xSemaphoreGive(this->lock_); +#ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { - ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); + this->debug_callback_.call(UART_DIRECTION_TX, data[i]); } +#endif } + bool IDFUARTComponent::peek_byte(uint8_t *data) { if (!this->check_read_timeout_()) return false; @@ -152,6 +155,7 @@ bool IDFUARTComponent::peek_byte(uint8_t *data) { xSemaphoreGive(this->lock_); return true; } + bool IDFUARTComponent::read_array(uint8_t *data, size_t len) { size_t length_to_read = len; if (!this->check_read_timeout_(len)) @@ -165,12 +169,12 @@ bool IDFUARTComponent::read_array(uint8_t *data, size_t len) { } if (length_to_read > 0) uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_RATE_MS); - xSemaphoreGive(this->lock_); +#ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { - ESP_LOGVV(TAG, " Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); + this->debug_callback_.call(UART_DIRECTION_RX, data[i]); } - +#endif return true; } diff --git a/esphome/components/uart/uart_debugger.cpp b/esphome/components/uart/uart_debugger.cpp new file mode 100644 index 0000000000..9a535656a2 --- /dev/null +++ b/esphome/components/uart/uart_debugger.cpp @@ -0,0 +1,193 @@ +#include "esphome/core/defines.h" +#ifdef USE_UART_DEBUGGER + +#include +#include "uart_debugger.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace uart { + +static const char *const TAG = "uart_debug"; + +UARTDebugger::UARTDebugger(UARTComponent *parent) { + parent->add_debug_callback([this](UARTDirection direction, uint8_t byte) { + if (!this->is_my_direction_(direction) || this->is_recursive_()) { + return; + } + this->trigger_after_direction_change_(direction); + this->store_byte_(direction, byte); + this->trigger_after_delimiter_(byte); + this->trigger_after_bytes_(); + }); +} + +void UARTDebugger::loop() { this->trigger_after_timeout_(); } + +bool UARTDebugger::is_my_direction_(UARTDirection direction) { + return this->for_direction_ == UART_DIRECTION_BOTH || this->for_direction_ == direction; +} + +bool UARTDebugger::is_recursive_() { return this->is_triggering_; } + +void UARTDebugger::trigger_after_direction_change_(UARTDirection direction) { + if (this->has_buffered_bytes_() && this->for_direction_ == UART_DIRECTION_BOTH && + this->last_direction_ != direction) { + this->fire_trigger_(); + } +} + +void UARTDebugger::store_byte_(UARTDirection direction, uint8_t byte) { + this->bytes_.push_back(byte); + this->last_direction_ = direction; + this->last_time_ = millis(); +} + +void UARTDebugger::trigger_after_delimiter_(uint8_t byte) { + if (this->after_delimiter_.empty() || !this->has_buffered_bytes_()) { + return; + } + if (this->after_delimiter_[this->after_delimiter_pos_] != byte) { + this->after_delimiter_pos_ = 0; + return; + } + this->after_delimiter_pos_++; + if (this->after_delimiter_pos_ == this->after_delimiter_.size()) { + this->fire_trigger_(); + this->after_delimiter_pos_ = 0; + } +} + +void UARTDebugger::trigger_after_bytes_() { + if (this->has_buffered_bytes_() && this->after_bytes_ > 0 && this->bytes_.size() >= this->after_bytes_) { + this->fire_trigger_(); + } +} + +void UARTDebugger::trigger_after_timeout_() { + if (this->has_buffered_bytes_() && this->after_timeout_ > 0 && millis() - this->last_time_ >= this->after_timeout_) { + this->fire_trigger_(); + } +} + +bool UARTDebugger::has_buffered_bytes_() { return !this->bytes_.empty(); } + +void UARTDebugger::fire_trigger_() { + this->is_triggering_ = true; + trigger(this->last_direction_, this->bytes_); + this->bytes_.clear(); + this->is_triggering_ = false; +} + +void UARTDummyReceiver::loop() { + // Reading up to a limited number of bytes, to make sure that this loop() + // won't lock up the system on a continuous incoming stream of bytes. + uint8_t data; + int count = 50; + while (this->available() && count--) { + this->read_byte(&data); + } +} + +void UARTDebug::log_hex(UARTDirection direction, std::vector bytes, uint8_t separator) { + std::string res; + if (direction == UART_DIRECTION_RX) { + res += "<<< "; + } else { + res += ">>> "; + } + size_t len = bytes.size(); + char buf[5]; + for (size_t i = 0; i < len; i++) { + if (i > 0) { + res += separator; + } + sprintf(buf, "%02X", bytes[i]); + res += buf; + } + ESP_LOGD(TAG, "%s", res.c_str()); +} + +void UARTDebug::log_string(UARTDirection direction, std::vector bytes) { + std::string res; + if (direction == UART_DIRECTION_RX) { + res += "<<< \""; + } else { + res += ">>> \""; + } + size_t len = bytes.size(); + char buf[5]; + for (size_t i = 0; i < len; i++) { + if (bytes[i] == 7) { + res += "\\a"; + } else if (bytes[i] == 8) { + res += "\\b"; + } else if (bytes[i] == 9) { + res += "\\t"; + } else if (bytes[i] == 10) { + res += "\\n"; + } else if (bytes[i] == 11) { + res += "\\v"; + } else if (bytes[i] == 12) { + res += "\\f"; + } else if (bytes[i] == 13) { + res += "\\r"; + } else if (bytes[i] == 27) { + res += "\\e"; + } else if (bytes[i] == 34) { + res += "\\\""; + } else if (bytes[i] == 39) { + res += "\\'"; + } else if (bytes[i] == 92) { + res += "\\\\"; + } else if (bytes[i] < 32 || bytes[i] > 127) { + sprintf(buf, "\\x%02X", bytes[i]); + res += buf; + } else { + res += bytes[i]; + } + } + res += '"'; + ESP_LOGD(TAG, "%s", res.c_str()); +} + +void UARTDebug::log_int(UARTDirection direction, std::vector bytes, uint8_t separator) { + std::string res; + size_t len = bytes.size(); + if (direction == UART_DIRECTION_RX) { + res += "<<< "; + } else { + res += ">>> "; + } + for (size_t i = 0; i < len; i++) { + if (i > 0) { + res += separator; + } + res += to_string(bytes[i]); + } + ESP_LOGD(TAG, "%s", res.c_str()); +} + +void UARTDebug::log_binary(UARTDirection direction, std::vector bytes, uint8_t separator) { + std::string res; + size_t len = bytes.size(); + if (direction == UART_DIRECTION_RX) { + res += "<<< "; + } else { + res += ">>> "; + } + char buf[20]; + for (size_t i = 0; i < len; i++) { + if (i > 0) { + res += separator; + } + sprintf(buf, "0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(bytes[i]), bytes[i]); + res += buf; + } + ESP_LOGD(TAG, "%s", res.c_str()); +} + +} // namespace uart +} // namespace esphome +#endif diff --git a/esphome/components/uart/uart_debugger.h b/esphome/components/uart/uart_debugger.h new file mode 100644 index 0000000000..6e84bbe450 --- /dev/null +++ b/esphome/components/uart/uart_debugger.h @@ -0,0 +1,101 @@ +#pragma once +#include "esphome/core/defines.h" +#ifdef USE_UART_DEBUGGER + +#include +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "uart.h" +#include "uart_component.h" + +namespace esphome { +namespace uart { + +/// The UARTDebugger class adds debugging support to a UART bus. +/// +/// It accumulates bytes that travel over the UART bus and triggers one or +/// more actions that can log the data at an appropriate time. What +/// 'appropriate time' means exactly, is determined by a number of +/// configurable constraints. E.g. when a given number of bytes is gathered +/// and/or when no more data has been seen for a given time interval. +class UARTDebugger : public Component, public Trigger> { + public: + explicit UARTDebugger(UARTComponent *parent); + void loop() override; + + /// Set the direction in which to inspect the bytes: incoming, outgoing + /// or both. When debugging in both directions, logging will be triggered + /// when the direction of the data stream changes. + void set_direction(UARTDirection direction) { this->for_direction_ = direction; } + + /// Set the maximum number of bytes to accumulate. When the number of bytes + /// is reached, logging will be triggered. + void set_after_bytes(size_t size) { this->after_bytes_ = size; } + + /// Set a timeout for the data stream. When no new bytes are seen during + /// this timeout, logging will be triggered. + void set_after_timeout(uint32_t timeout) { this->after_timeout_ = timeout; } + + /// Add a delimiter byte. This can be called multiple times to setup a + /// multi-byte delimiter (a typical example would be '\r\n'). + /// When the constructued byte sequence is found in the data stream, + /// logging will be triggered. + void add_delimiter_byte(uint8_t byte) { this->after_delimiter_.push_back(byte); } + + protected: + UARTDirection for_direction_; + UARTDirection last_direction_{}; + std::vector bytes_{}; + size_t after_bytes_; + uint32_t after_timeout_; + uint32_t last_time_{}; + std::vector after_delimiter_{}; + size_t after_delimiter_pos_{}; + bool is_triggering_{false}; + + bool is_my_direction_(UARTDirection direction); + bool is_recursive_(); + void store_byte_(UARTDirection direction, uint8_t byte); + void trigger_after_direction_change_(UARTDirection direction); + void trigger_after_delimiter_(uint8_t byte); + void trigger_after_bytes_(); + void trigger_after_timeout_(); + bool has_buffered_bytes_(); + void fire_trigger_(); +}; + +/// This UARTDevice is used by the serial debugger to read data from a +/// serial interface when the 'dummy_receiver' option is enabled. +/// The data are not stored, nor processed. This is most useful when the +/// debugger is used to reverse engineer a serial protocol, for which no +/// specific UARTDevice implementation exists (yet), but for which the +/// incoming bytes must be read to drive the debugger. +class UARTDummyReceiver : public Component, public UARTDevice { + public: + UARTDummyReceiver(UARTComponent *parent) : UARTDevice(parent) {} + void loop() override; +}; + +/// This class contains some static methods, that can be used to easily +/// create a logging action for the debugger. +class UARTDebug { + public: + /// Log the bytes as hex values, separated by the provided separator + /// character. + static void log_hex(UARTDirection direction, std::vector bytes, uint8_t separator); + + /// Log the bytes as string values, escaping unprintable characters. + static void log_string(UARTDirection direction, std::vector bytes); + + /// Log the bytes as integer values, separated by the provided separator + /// character. + static void log_int(UARTDirection direction, std::vector bytes, uint8_t separator); + + /// Log the bytes as ' ()' values, separated by the provided + /// separator. + static void log_binary(UARTDirection direction, std::vector bytes, uint8_t separator); +}; + +} // namespace uart +} // namespace esphome +#endif diff --git a/esphome/const.py b/esphome/const.py index 99cb53fe4b..9951747ee2 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -34,6 +34,7 @@ ARDUINO_VERSION_ESP8266 = { SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"} HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"} + CONF_ABOVE = "above" CONF_ACCELERATION = "acceleration" CONF_ACCELERATION_X = "acceleration_x" @@ -47,6 +48,7 @@ CONF_ACTIVE_POWER = "active_power" CONF_ADDRESS = "address" CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id" CONF_ADVANCED = "advanced" +CONF_AFTER = "after" CONF_ALPHA = "alpha" CONF_ALTITUDE = "altitude" CONF_AND = "and" @@ -93,6 +95,7 @@ CONF_BUFFER_SIZE = "buffer_size" CONF_BUILD_PATH = "build_path" CONF_BUS_VOLTAGE = "bus_voltage" CONF_BUSY_PIN = "busy_pin" +CONF_BYTES = "bytes" CONF_CALCULATED_LUX = "calculated_lux" CONF_CALIBRATE_LINEAR = "calibrate_linear" CONF_CALIBRATION = "calibration" @@ -164,6 +167,7 @@ CONF_DAYS_OF_WEEK = "days_of_week" CONF_DC_PIN = "dc_pin" CONF_DEASSERT_RTS_DTR = "deassert_rts_dtr" CONF_DEBOUNCE = "debounce" +CONF_DEBUG = "debug" CONF_DECAY_MODE = "decay_mode" CONF_DECELERATION = "deceleration" CONF_DEFAULT_MODE = "default_mode" @@ -171,6 +175,7 @@ CONF_DEFAULT_TARGET_TEMPERATURE_HIGH = "default_target_temperature_high" CONF_DEFAULT_TARGET_TEMPERATURE_LOW = "default_target_temperature_low" CONF_DEFAULT_TRANSITION_LENGTH = "default_transition_length" CONF_DELAY = "delay" +CONF_DELIMITER = "delimiter" CONF_DELTA = "delta" CONF_DEVICE = "device" CONF_DEVICE_CLASS = "device_class" @@ -192,6 +197,8 @@ CONF_DNS2 = "dns2" CONF_DOMAIN = "domain" CONF_DRY_ACTION = "dry_action" CONF_DRY_MODE = "dry_mode" +CONF_DUMMY_RECEIVER = "dummy_receiver" +CONF_DUMMY_RECEIVER_ID = "dummy_receiver_id" CONF_DUMP = "dump" CONF_DURATION = "duration" CONF_EAP = "eap" diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 94fac73906..e679fe1cef 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -37,6 +37,7 @@ #define USE_SWITCH #define USE_TEXT_SENSOR #define USE_TIME +#define USE_UART_DEBUGGER #define USE_WEBSERVER #define USE_WIFI diff --git a/tests/test1.yaml b/tests/test1.yaml index dee7493bf2..04d928e1b8 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -193,6 +193,18 @@ uart: data_bits: 8 stop_bits: 1 rx_buffer_size: 512 + debug: + dummy_receiver: true + direction: both + after: + bytes: 50 + timeout: 500ms + delimiter: "\r\n" + sequence: + - lambda: UARTDebug::log_hex(direction, bytes, ':'); + - lambda: UARTDebug::log_string(direction, bytes); + - lambda: UARTDebug::log_int(direction, bytes, ','); + - lambda: UARTDebug::log_binary(direction, bytes, ';'); - id: adalight_uart tx_pin: GPIO25 From bb9793d5b7b7238074ce2fdc91a4f9ecee728d7d Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 23:53:25 +0100 Subject: [PATCH 005/111] Enable addressable light power supply based on raw values (#2690) --- esphome/components/light/addressable_light.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index 2b2c0ca7e4..8302239d6a 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -87,7 +87,7 @@ class AddressableLight : public LightOutput, public Component { void mark_shown_() { #ifdef USE_POWER_SUPPLY for (const auto &c : *this) { - if (c.get().is_on()) { + if (c.get_red_raw() > 0 || c.get_green_raw() > 0 || c.get_blue_raw() > 0 || c.get_white_raw() > 0) { this->power_.request(); return; } From f11220da3a24d2ff1ca5d83d5f26674d8033049f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 11 Nov 2021 15:15:37 +1300 Subject: [PATCH 006/111] Remove my.ha links from improv (#2695) --- .../components/esp32_improv/esp32_improv_component.cpp | 10 ++++++++-- .../improv_serial/improv_serial_component.cpp | 3 +-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index faa9ab7df6..22bebdfe98 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -11,6 +11,7 @@ namespace esphome { namespace esp32_improv { static const char *const TAG = "esp32_improv.component"; +static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome"; ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; } @@ -124,8 +125,13 @@ void ESP32ImprovComponent::loop() { this->cancel_timeout("wifi-connect-timeout"); this->set_state_(improv::STATE_PROVISIONED); - std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome"; - std::vector data = improv::build_rpc_response(improv::WIFI_SETTINGS, {url}); + std::vector urls = {ESPHOME_MY_LINK}; +#ifdef USE_WEBSERVER + auto ip = wifi::global_wifi_component->wifi_sta_ip(); + std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT); + urls.push_back(webserver_url); +#endif + std::vector data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls); this->send_response_(data); this->set_timeout("end-service", 1000, [this] { this->service_->stop(); diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index a12f1bd83b..abbb76ab11 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -92,8 +92,7 @@ void ImprovSerialComponent::loop() { } std::vector ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) { - std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome"; - std::vector urls = {url}; + std::vector urls; #ifdef USE_WEBSERVER auto ip = wifi::global_wifi_component->wifi_sta_ip(); std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT); From a6873c1520eaf21e85ad455fb15049c5761b32eb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 11 Nov 2021 22:56:35 +1300 Subject: [PATCH 007/111] Only allow prometheus when using arduino (#2697) --- esphome/components/prometheus/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/prometheus/__init__.py b/esphome/components/prometheus/__init__.py index f5f166d085..45345f06e8 100644 --- a/esphome/components/prometheus/__init__.py +++ b/esphome/components/prometheus/__init__.py @@ -15,7 +15,8 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( web_server_base.WebServerBase ), - } + }, + cv.only_with_arduino, ).extend(cv.COMPONENT_SCHEMA) From 0372e12b81db6a78d7397a1d6ab274dbce7ee3e1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 11 Nov 2021 22:56:54 +1300 Subject: [PATCH 008/111] Defines tidy (#2696) * Move webserver defines inside arduino block * Move esp8266 flash define * Move prometheus define --- esphome/core/defines.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index e679fe1cef..8dcae9fa31 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -19,7 +19,6 @@ #define USE_CLIMATE #define USE_COVER #define USE_DEEP_SLEEP -#define USE_ESP8266_PREFERENCES_FLASH #define USE_FAN #define USE_GRAPH #define USE_HOMEASSISTANT_TIME @@ -30,7 +29,6 @@ #define USE_OTA_PASSWORD #define USE_OTA_STATE_CALLBACK #define USE_POWER_SUPPLY -#define USE_PROMETHEUS #define USE_SELECT #define USE_SENSOR #define USE_STATUS_LED @@ -38,18 +36,18 @@ #define USE_TEXT_SENSOR #define USE_TIME #define USE_UART_DEBUGGER -#define USE_WEBSERVER #define USE_WIFI -#define WEBSERVER_PORT 80 // NOLINT - // Arduino-specific feature flags #ifdef USE_ARDUINO #define USE_CAPTIVE_PORTAL #define USE_JSON #define USE_NEXTION_TFT_UPLOAD #define USE_MQTT +#define USE_PROMETHEUS +#define USE_WEBSERVER #define USE_WIFI_WPA2_EAP +#define WEBSERVER_PORT 80 // NOLINT #endif // ESP32-specific feature flags @@ -68,6 +66,7 @@ // ESP8266-specific feature flags #ifdef USE_ESP8266 #define USE_ADC_SENSOR_VCC +#define USE_ESP8266_PREFERENCES_FLASH #define USE_HTTP_REQUEST_ESP8266_HTTPS #define USE_SOCKET_IMPL_LWIP_TCP #endif From 7bb7456a8b034ce0f04aef4994db341b646af7b7 Mon Sep 17 00:00:00 2001 From: lcavalli Date: Fri, 12 Nov 2021 01:17:10 +0100 Subject: [PATCH 009/111] Update device classes for binary sensors (#2703) --- esphome/components/binary_sensor/__init__.py | 6 ++++-- esphome/const.py | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index faafcddd06..3f11e18e45 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -44,10 +44,11 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_PRESENCE, DEVICE_CLASS_PROBLEM, + DEVICE_CLASS_RUNNING, DEVICE_CLASS_SAFETY, DEVICE_CLASS_SMOKE, DEVICE_CLASS_SOUND, - DEVICE_CLASS_UPDATE, + DEVICE_CLASS_TAMPER, DEVICE_CLASS_VIBRATION, DEVICE_CLASS_WINDOW, ) @@ -76,10 +77,11 @@ DEVICE_CLASSES = [ DEVICE_CLASS_POWER, DEVICE_CLASS_PRESENCE, DEVICE_CLASS_PROBLEM, + DEVICE_CLASS_RUNNING, DEVICE_CLASS_SAFETY, DEVICE_CLASS_SMOKE, DEVICE_CLASS_SOUND, - DEVICE_CLASS_UPDATE, + DEVICE_CLASS_TAMPER, DEVICE_CLASS_VIBRATION, DEVICE_CLASS_WINDOW, ] diff --git a/esphome/const.py b/esphome/const.py index 9951747ee2..017feb0268 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -878,10 +878,11 @@ DEVICE_CLASS_OPENING = "opening" DEVICE_CLASS_PLUG = "plug" DEVICE_CLASS_PRESENCE = "presence" DEVICE_CLASS_PROBLEM = "problem" +DEVICE_CLASS_RUNNING = "running" DEVICE_CLASS_SAFETY = "safety" DEVICE_CLASS_SMOKE = "smoke" DEVICE_CLASS_SOUND = "sound" -DEVICE_CLASS_UPDATE = "update" +DEVICE_CLASS_TAMPER = "tamper" DEVICE_CLASS_VIBRATION = "vibration" DEVICE_CLASS_WINDOW = "window" # device classes of both binary_sensor and sensor component From 2e0c89409d487eb4e382d307e6e2fe836aec8ee1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 13 Nov 2021 21:22:32 +1300 Subject: [PATCH 010/111] Bump ESPAsyncWebServer to 2.1.0 (#2686) --- esphome/components/web_server_base/__init__.py | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index dc1a2bc2f0..14fb033a56 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -28,4 +28,4 @@ async def to_code(config): cg.add_library("FS", None) cg.add_library("Update", None) # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json - cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.0.1") + cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.1.0") diff --git a/platformio.ini b/platformio.ini index 0dd32268e0..5e89afe8e6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -41,7 +41,7 @@ lib_deps = ${common.lib_deps} ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt ottowinter/ArduinoJson-esphomelib@5.13.3 ; json - esphome/ESPAsyncWebServer-esphome@2.0.1 ; web_server_base + esphome/ESPAsyncWebServer-esphome@2.1.0 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 From 582567696ebb5e4711a062325238a643c5aa5176 Mon Sep 17 00:00:00 2001 From: NeoAcheron <12508986+NeoAcheron@users.noreply.github.com> Date: Sat, 13 Nov 2021 15:14:23 +0100 Subject: [PATCH 011/111] pmsx003: add support for PMS5003S device (#2710) --- esphome/components/pmsx003/pmsx003.cpp | 14 ++++++++++---- esphome/components/pmsx003/pmsx003.h | 1 + esphome/components/pmsx003/sensor.py | 10 ++++++---- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/esphome/components/pmsx003/pmsx003.cpp b/esphome/components/pmsx003/pmsx003.cpp index 0474d6ffd0..5de94699f0 100644 --- a/esphome/components/pmsx003/pmsx003.cpp +++ b/esphome/components/pmsx003/pmsx003.cpp @@ -96,6 +96,7 @@ optional PMSX003Component::check_byte_() { length_matches = payload_length == 28 || payload_length == 20; break; case PMSX003_TYPE_5003T: + case PMSX003_TYPE_5003S: length_matches = payload_length == 28; break; case PMSX003_TYPE_5003ST: @@ -133,20 +134,25 @@ optional PMSX003Component::check_byte_() { void PMSX003Component::parse_data_() { switch (this->type_) { case PMSX003_TYPE_5003ST: { - uint16_t formaldehyde = this->get_16_bit_uint_(28); float temperature = this->get_16_bit_uint_(30) / 10.0f; float humidity = this->get_16_bit_uint_(32) / 10.0f; - ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%% Formaldehyde: %u µg/m^3", temperature, humidity, - formaldehyde); + ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%%", temperature, humidity); if (this->temperature_sensor_ != nullptr) this->temperature_sensor_->publish_state(temperature); if (this->humidity_sensor_ != nullptr) this->humidity_sensor_->publish_state(humidity); + // The rest of the PMS5003ST matches the PMS5003S, continue on + } + case PMSX003_TYPE_5003S: { + uint16_t formaldehyde = this->get_16_bit_uint_(28); + + ESP_LOGD(TAG, "Got Formaldehyde: %u µg/m^3", formaldehyde); + if (this->formaldehyde_sensor_ != nullptr) this->formaldehyde_sensor_->publish_state(formaldehyde); - // The rest of the PMS5003ST matches the PMS5003, continue on + // The rest of the PMS5003S matches the PMS5003, continue on } case PMSX003_TYPE_X003: { uint16_t pm_1_0_std_concentration = this->get_16_bit_uint_(4); diff --git a/esphome/components/pmsx003/pmsx003.h b/esphome/components/pmsx003/pmsx003.h index a5adecb534..fd6364c70c 100644 --- a/esphome/components/pmsx003/pmsx003.h +++ b/esphome/components/pmsx003/pmsx003.h @@ -11,6 +11,7 @@ enum PMSX003Type { PMSX003_TYPE_X003 = 0, PMSX003_TYPE_5003T, PMSX003_TYPE_5003ST, + PMSX003_TYPE_5003S, }; class PMSX003Component : public uart::UARTDevice, public Component { diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index 350117a235..56a91d22fc 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -42,21 +42,23 @@ PMSX003Sensor = pmsx003_ns.class_("PMSX003Sensor", sensor.Sensor) TYPE_PMSX003 = "PMSX003" TYPE_PMS5003T = "PMS5003T" TYPE_PMS5003ST = "PMS5003ST" +TYPE_PMS5003S = "PMS5003S" PMSX003Type = pmsx003_ns.enum("PMSX003Type") PMSX003_TYPES = { TYPE_PMSX003: PMSX003Type.PMSX003_TYPE_X003, TYPE_PMS5003T: PMSX003Type.PMSX003_TYPE_5003T, TYPE_PMS5003ST: PMSX003Type.PMSX003_TYPE_5003ST, + TYPE_PMS5003S: PMSX003Type.PMSX003_TYPE_5003S, } SENSORS_TO_TYPE = { - CONF_PM_1_0: [TYPE_PMSX003, TYPE_PMS5003ST], - CONF_PM_2_5: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST], - CONF_PM_10_0: [TYPE_PMSX003, TYPE_PMS5003ST], + CONF_PM_1_0: [TYPE_PMSX003, TYPE_PMS5003ST, TYPE_PMS5003S], + CONF_PM_2_5: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], + CONF_PM_10_0: [TYPE_PMSX003, TYPE_PMS5003ST, TYPE_PMS5003S], CONF_TEMPERATURE: [TYPE_PMS5003T, TYPE_PMS5003ST], CONF_HUMIDITY: [TYPE_PMS5003T, TYPE_PMS5003ST], - CONF_FORMALDEHYDE: [TYPE_PMS5003ST], + CONF_FORMALDEHYDE: [TYPE_PMS5003ST, TYPE_PMS5003S], } From aae63a7ff3b64054860384a461314dea31a22e80 Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Sat, 13 Nov 2021 18:42:15 +0400 Subject: [PATCH 012/111] Add climate on_state trigger (#2707) --- esphome/components/climate/__init__.py | 12 ++++++++++++ esphome/components/climate/automation.h | 7 +++++++ tests/test1.yaml | 2 ++ 3 files changed, 21 insertions(+) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 7ff769e5cb..b0f9146012 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -20,6 +20,7 @@ from esphome.const import ( CONF_MODE, CONF_MODE_COMMAND_TOPIC, CONF_MODE_STATE_TOPIC, + CONF_ON_STATE, CONF_PRESET, CONF_SWING_MODE, CONF_SWING_MODE_COMMAND_TOPIC, @@ -34,6 +35,7 @@ from esphome.const import ( CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC, CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC, CONF_TEMPERATURE_STEP, + CONF_TRIGGER_ID, CONF_VISUAL, CONF_MQTT_ID, ) @@ -101,6 +103,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True) # Actions ControlAction = climate_ns.class_("ControlAction", automation.Action) +StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template()) CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { @@ -161,6 +164,11 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA). cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All( cv.requires_component("mqtt"), cv.publish_topic ), + cv.Optional(CONF_ON_STATE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), + } + ), } ) @@ -256,6 +264,10 @@ async def setup_climate_core_(var, config): ) ) + for conf in config.get(CONF_ON_STATE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + async def register_climate(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/components/climate/automation.h b/esphome/components/climate/automation.h index 49a87027f2..3145358dab 100644 --- a/esphome/components/climate/automation.h +++ b/esphome/components/climate/automation.h @@ -42,5 +42,12 @@ template class ControlAction : public Action { Climate *climate_; }; +class StateTrigger : public Trigger<> { + public: + StateTrigger(Climate *climate) { + climate->add_on_state_callback([this]() { this->trigger(); }); + } +}; + } // namespace climate } // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index 04d928e1b8..f10922a7b9 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1707,6 +1707,8 @@ climate: min_temperature: 18 max_temperature: 30 - platform: midea + on_state: + logger.log: "State changed!" id: midea_unit uart_id: uart0 name: Midea Climate From f643a46bbf764c138875190393ec437b8ba78c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Bia=C5=82ek?= Date: Sun, 14 Nov 2021 14:59:34 +0100 Subject: [PATCH 013/111] Allow setting custom command_topic for Select and Number components (#2714) --- esphome/components/number/__init__.py | 2 +- esphome/components/select/__init__.py | 2 +- tests/test1.yaml | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index bb3427e4bd..1da25caafe 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -41,7 +41,7 @@ NumberInRangeCondition = number_ns.class_( icon = cv.icon -NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent), cv.GenerateID(): cv.declare_id(Number), diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index 8ea159d657..c15036e9f9 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -30,7 +30,7 @@ SelectSetAction = select_ns.class_("SelectSetAction", automation.Action) icon = cv.icon -SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent), cv.GenerateID(): cv.declare_id(Select), diff --git a/tests/test1.yaml b/tests/test1.yaml index f10922a7b9..263754dc4f 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2514,3 +2514,23 @@ teleinfo: uart_id: uart0 update_interval: 60s historical_mode: true + +number: + - platform: template + id: test_number + state_topic: livingroom/custom_state_topic + command_topic: livingroom/custom_command_topic + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + state_topic: livingroom/custom_state_topic + command_topic: livingroom/custom_command_topic + options: + - one + - two + optimistic: true From 4eaa6afa4d43299aedd277d7508b6c3ca012a0cf Mon Sep 17 00:00:00 2001 From: Clifford Roche Date: Sun, 14 Nov 2021 10:11:21 -0500 Subject: [PATCH 014/111] Add greeyac protocol to IR Climate / HeatpumpIR (#2694) --- esphome/components/heatpumpir/climate.py | 5 ++++- esphome/components/heatpumpir/heatpumpir.cpp | 17 +++++++++++++++-- esphome/components/heatpumpir/heatpumpir.h | 1 + .../components/heatpumpir/ir_sender_esphome.h | 4 ++-- platformio.ini | 4 +++- 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py index 36e56aa5da..592e03f959 100644 --- a/esphome/components/heatpumpir/climate.py +++ b/esphome/components/heatpumpir/climate.py @@ -30,6 +30,7 @@ PROTOCOLS = { "gree": Protocol.PROTOCOL_GREE, "greeya": Protocol.PROTOCOL_GREEYAA, "greeyan": Protocol.PROTOCOL_GREEYAN, + "greeyac": Protocol.PROTOCOL_GREEYAC, "hisense_aud": Protocol.PROTOCOL_HISENSE_AUD, "hitachi": Protocol.PROTOCOL_HITACHI, "hyundai": Protocol.PROTOCOL_HYUNDAI, @@ -111,4 +112,6 @@ def to_code(config): cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE])) cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE])) - cg.add_library("tonia/HeatpumpIR", "1.0.15") + # PIO isn't updating releases, so referencing the release tag directly. See: + # https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd + cg.add_library("", "", "https://github.com/ToniA/arduino-heatpumpir.git#1.0.18") diff --git a/esphome/components/heatpumpir/heatpumpir.cpp b/esphome/components/heatpumpir/heatpumpir.cpp index 8d9fc962c0..ad3731b955 100644 --- a/esphome/components/heatpumpir/heatpumpir.cpp +++ b/esphome/components/heatpumpir/heatpumpir.cpp @@ -25,6 +25,7 @@ const std::map> PROTOCOL_CONSTRUCTOR_MAP {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }}, // NOLINT {PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }}, // NOLINT {PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT + {PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }}, // NOLINT {PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT {PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT {PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT @@ -61,6 +62,19 @@ void HeatpumpIRClimate::setup() { } this->heatpump_ir_ = protocol_constructor->second(); climate_ir::ClimateIR::setup(); + if (this->sensor_) { + this->sensor_->add_on_state_callback([this](float state) { + this->current_temperature = state; + + IRSenderESPHome esp_sender(this->transmitter_); + this->heatpump_ir_->send(esp_sender, uint8_t(lround(this->current_temperature + 0.5))); + + // current temperature changed, publish state + this->publish_state(); + }); + this->current_temperature = this->sensor_->state; + } else + this->current_temperature = NAN; } void HeatpumpIRClimate::transmit_state() { @@ -171,8 +185,7 @@ void HeatpumpIRClimate::transmit_state() { temperature_cmd = (uint8_t) clamp(this->target_temperature, this->min_temperature_, this->max_temperature_); - IRSenderESPHome esp_sender(0, this->transmitter_); - + IRSenderESPHome esp_sender(this->transmitter_); heatpump_ir_->send(esp_sender, power_mode_cmd, operating_mode_cmd, fan_speed_cmd, temperature_cmd, swing_v_cmd, swing_h_cmd); } diff --git a/esphome/components/heatpumpir/heatpumpir.h b/esphome/components/heatpumpir/heatpumpir.h index e2d2b45dc4..18d9b5040f 100644 --- a/esphome/components/heatpumpir/heatpumpir.h +++ b/esphome/components/heatpumpir/heatpumpir.h @@ -25,6 +25,7 @@ enum Protocol { PROTOCOL_GREE, PROTOCOL_GREEYAA, PROTOCOL_GREEYAN, + PROTOCOL_GREEYAC, PROTOCOL_HISENSE_AUD, PROTOCOL_HITACHI, PROTOCOL_HYUNDAI, diff --git a/esphome/components/heatpumpir/ir_sender_esphome.h b/esphome/components/heatpumpir/ir_sender_esphome.h index 24e8ba9883..7546d990ea 100644 --- a/esphome/components/heatpumpir/ir_sender_esphome.h +++ b/esphome/components/heatpumpir/ir_sender_esphome.h @@ -11,8 +11,8 @@ namespace heatpumpir { class IRSenderESPHome : public IRSender { public: - IRSenderESPHome(uint8_t pin, remote_transmitter::RemoteTransmitterComponent *transmitter) - : IRSender(pin), transmit_(transmitter->transmit()){}; + IRSenderESPHome(remote_transmitter::RemoteTransmitterComponent *transmitter) + : IRSender(0), transmit_(transmitter->transmit()){}; void setFrequency(int frequency) override; // NOLINT(readability-identifier-naming) void space(int space_length) override; void mark(int mark_length) override; diff --git a/platformio.ini b/platformio.ini index 5e89afe8e6..2ac6d0bfc8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -49,7 +49,9 @@ lib_deps = glmnet/Dsmr@0.5 ; dsmr rweather/Crypto@0.2.0 ; dsmr dudanov/MideaUART@1.1.8 ; midea - tonia/HeatpumpIR@1.0.15 ; heatpumpir + ; PIO isn't update releases correctly, see: + ; https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd + https://github.com/ToniA/arduino-heatpumpir.git#1.0.18 ; heatpumpir build_flags = ${common.build_flags} -DUSE_ARDUINO From 108b8e6705a6f5080fa2cdc408437d2b4d3c525a Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sun, 14 Nov 2021 16:17:13 +0100 Subject: [PATCH 015/111] Fix rom/rtc.h deprecation compile warning for debug component (#2520) --- esphome/components/debug/debug_component.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index b856733121..40eb20fa6e 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -4,20 +4,23 @@ #include "esphome/core/defines.h" #include "esphome/core/version.h" +#ifdef USE_ESP_IDF +#include +#include +#endif + #ifdef USE_ESP32 +#if ESP_IDF_VERSION_MAJOR >= 4 +#include +#else #include -#include +#endif #endif #ifdef USE_ARDUINO #include #endif -#ifdef USE_ESP_IDF -#include -#include -#endif - namespace esphome { namespace debug { From 66cebfc992120212bb6032f3cd6f8ce6eff5f80a Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 14 Nov 2021 20:05:11 +0100 Subject: [PATCH 016/111] Restore InterruptLock on wifi-less ESP8266 (#2712) --- esphome/core/helpers.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index daca3ffd32..27608a84c1 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -9,6 +9,7 @@ #ifdef USE_WIFI #include #endif +#include #include #elif defined(USE_ESP32_FRAMEWORK_ARDUINO) #include @@ -430,13 +431,8 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green } #ifdef USE_ESP8266 -#ifdef USE_WIFI IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } -#else -IRAM_ATTR InterruptLock::InterruptLock() {} -IRAM_ATTR InterruptLock::~InterruptLock() {} -#endif #endif #ifdef USE_ESP32 IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } From 14299bb2cc9af365d0b45080c0eb31d1a2edc11a Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 14 Nov 2021 20:07:58 +0100 Subject: [PATCH 017/111] Drop unused constants from const.py (#2718) --- esphome/const.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/esphome/const.py b/esphome/const.py index 017feb0268..fb241f04b5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -8,29 +8,7 @@ PLATFORM_ESP32 = "esp32" PLATFORM_ESP8266 = "esp8266" TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266] -TARGET_FRAMEWORKS = ["arduino", "esp-idf"] -# See also https://github.com/platformio/platform-espressif8266/releases -ARDUINO_VERSION_ESP8266 = { - "dev": "https://github.com/platformio/platform-espressif8266.git", - "3.0.1": "platformio/espressif8266@3.1.0", - "3.0.0": "platformio/espressif8266@3.0.0", - "2.7.4": "platformio/espressif8266@2.6.2", - "2.7.3": "platformio/espressif8266@2.6.1", - "2.7.2": "platformio/espressif8266@2.6.0", - "2.7.1": "platformio/espressif8266@2.5.3", - "2.7.0": "platformio/espressif8266@2.5.0", - "2.6.3": "platformio/espressif8266@2.4.0", - "2.6.2": "platformio/espressif8266@2.3.1", - "2.6.1": "platformio/espressif8266@2.3.0", - "2.5.2": "platformio/espressif8266@2.2.3", - "2.5.1": "platformio/espressif8266@2.1.1", - "2.5.0": "platformio/espressif8266@2.0.4", - "2.4.2": "platformio/espressif8266@1.8.0", - "2.4.1": "platformio/espressif8266@1.7.3", - "2.4.0": "platformio/espressif8266@1.6.0", - "2.3.0": "platformio/espressif8266@1.5.0", -} SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"} HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"} From 6a7440f7d325cbb9a53c48df3df89df00ed55e52 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 14 Nov 2021 21:45:25 +0100 Subject: [PATCH 018/111] Feed WDT between doing ESP32 touchpad measurements (#2720) --- esphome/components/esp32_touch/esp32_touch.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index cb72820900..85f4058eee 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -1,6 +1,7 @@ #ifdef USE_ESP32 #include "esp32_touch.h" +#include "esphome/core/application.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" @@ -125,6 +126,8 @@ void ESP32TouchComponent::loop() { if (should_print) { ESP_LOGD(TAG, "Touch Pad '%s' (T%u): %u", child->get_name().c_str(), child->get_touch_pad(), value); } + + App.feed_wdt(); } if (should_print) { From 04740fbcbb4ea7ccff9ebbe89a801bda90f46168 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 14 Nov 2021 22:04:43 +0100 Subject: [PATCH 019/111] Install test requirements in lint Docker image (#2719) --- docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 0d30bb0267..62a64c851d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -147,9 +147,9 @@ RUN \ /var/{cache,log}/* \ /var/lib/apt/lists/* -COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / +COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini / RUN \ - pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ + pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \ && /platformio_install_deps.py /platformio.ini VOLUME ["/esphome"] From d99c5ed8901279def13815d9f2b22f9f8a203a9f Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Mon, 15 Nov 2021 01:40:35 +0400 Subject: [PATCH 020/111] RemoteTransmitter fix. Bug from version 2021.10. Some changes. (#2706) --- .../midea/{adapter.cpp => ac_adapter.cpp} | 4 +- .../midea/{adapter.h => ac_adapter.h} | 6 +- .../midea/{automations.h => ac_automations.h} | 2 + esphome/components/midea/air_conditioner.cpp | 13 +-- esphome/components/midea/air_conditioner.h | 21 +++- esphome/components/midea/appliance_base.h | 109 ++++++++++-------- esphome/components/midea/climate.py | 20 ++-- .../midea/{midea_ir.h => ir_transmitter.h} | 15 +++ 8 files changed, 122 insertions(+), 68 deletions(-) rename esphome/components/midea/{adapter.cpp => ac_adapter.cpp} (98%) rename esphome/components/midea/{adapter.h => ac_adapter.h} (95%) rename esphome/components/midea/{automations.h => ac_automations.h} (97%) rename esphome/components/midea/{midea_ir.h => ir_transmitter.h} (73%) diff --git a/esphome/components/midea/adapter.cpp b/esphome/components/midea/ac_adapter.cpp similarity index 98% rename from esphome/components/midea/adapter.cpp rename to esphome/components/midea/ac_adapter.cpp index a3f19dbda8..2837713c35 100644 --- a/esphome/components/midea/adapter.cpp +++ b/esphome/components/midea/ac_adapter.cpp @@ -1,10 +1,11 @@ #ifdef USE_ARDUINO #include "esphome/core/log.h" -#include "adapter.h" +#include "ac_adapter.h" namespace esphome { namespace midea { +namespace ac { const char *const Constants::TAG = "midea"; const std::string Constants::FREEZE_PROTECTION = "freeze protection"; @@ -171,6 +172,7 @@ void Converters::to_climate_traits(ClimateTraits &traits, const dudanov::midea:: traits.add_supported_custom_preset(Constants::FREEZE_PROTECTION); } +} // namespace ac } // namespace midea } // namespace esphome diff --git a/esphome/components/midea/adapter.h b/esphome/components/midea/ac_adapter.h similarity index 95% rename from esphome/components/midea/adapter.h rename to esphome/components/midea/ac_adapter.h index 2497cbbe5b..c17894ae31 100644 --- a/esphome/components/midea/adapter.h +++ b/esphome/components/midea/ac_adapter.h @@ -2,12 +2,15 @@ #ifdef USE_ARDUINO +// MideaUART #include + #include "esphome/components/climate/climate_traits.h" -#include "appliance_base.h" +#include "air_conditioner.h" namespace esphome { namespace midea { +namespace ac { using MideaMode = dudanov::midea::ac::Mode; using MideaSwingMode = dudanov::midea::ac::SwingMode; @@ -41,6 +44,7 @@ class Converters { static void to_climate_traits(ClimateTraits &traits, const dudanov::midea::ac::Capabilities &capabilities); }; +} // namespace ac } // namespace midea } // namespace esphome diff --git a/esphome/components/midea/automations.h b/esphome/components/midea/ac_automations.h similarity index 97% rename from esphome/components/midea/automations.h rename to esphome/components/midea/ac_automations.h index 5b638286ac..d4ed2e7168 100644 --- a/esphome/components/midea/automations.h +++ b/esphome/components/midea/ac_automations.h @@ -7,6 +7,7 @@ namespace esphome { namespace midea { +namespace ac { template class MideaActionBase : public Action { public: @@ -55,6 +56,7 @@ template class PowerOffAction : public MideaActionBase { void play(Ts... x) override { this->parent_->do_power_off(); } }; +} // namespace ac } // namespace midea } // namespace esphome diff --git a/esphome/components/midea/air_conditioner.cpp b/esphome/components/midea/air_conditioner.cpp index 103b852936..dd48f640a2 100644 --- a/esphome/components/midea/air_conditioner.cpp +++ b/esphome/components/midea/air_conditioner.cpp @@ -2,13 +2,11 @@ #include "esphome/core/log.h" #include "air_conditioner.h" -#include "adapter.h" -#ifdef USE_REMOTE_TRANSMITTER -#include "midea_ir.h" -#endif +#include "ac_adapter.h" namespace esphome { namespace midea { +namespace ac { static void set_sensor(Sensor *sensor, float value) { if (sensor != nullptr && (!sensor->has_state() || sensor->get_raw_state() != value)) @@ -122,7 +120,7 @@ void AirConditioner::dump_config() { void AirConditioner::do_follow_me(float temperature, bool beeper) { #ifdef USE_REMOTE_TRANSMITTER IrFollowMeData data(static_cast(lroundf(temperature)), beeper); - this->transmit_ir(data); + this->transmitter_.transmit(data); #else ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); #endif @@ -131,7 +129,7 @@ void AirConditioner::do_follow_me(float temperature, bool beeper) { void AirConditioner::do_swing_step() { #ifdef USE_REMOTE_TRANSMITTER IrSpecialData data(0x01); - this->transmit_ir(data); + this->transmitter_.transmit(data); #else ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); #endif @@ -143,13 +141,14 @@ void AirConditioner::do_display_toggle() { } else { #ifdef USE_REMOTE_TRANSMITTER IrSpecialData data(0x08); - this->transmit_ir(data); + this->transmitter_.transmit(data); #else ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); #endif } } +} // namespace ac } // namespace midea } // namespace esphome diff --git a/esphome/components/midea/air_conditioner.h b/esphome/components/midea/air_conditioner.h index 8dfb9dcb3d..a6023b78bb 100644 --- a/esphome/components/midea/air_conditioner.h +++ b/esphome/components/midea/air_conditioner.h @@ -2,17 +2,25 @@ #ifdef USE_ARDUINO +// MideaUART #include + #include "appliance_base.h" #include "esphome/components/sensor/sensor.h" namespace esphome { namespace midea { +namespace ac { using sensor::Sensor; using climate::ClimateCall; +using climate::ClimatePreset; +using climate::ClimateTraits; +using climate::ClimateMode; +using climate::ClimateSwingMode; +using climate::ClimateFanMode; -class AirConditioner : public ApplianceBase { +class AirConditioner : public ApplianceBase, public climate::Climate { public: void dump_config() override; void set_outdoor_temperature_sensor(Sensor *sensor) { this->outdoor_sensor_ = sensor; } @@ -31,15 +39,26 @@ class AirConditioner : public ApplianceBase void do_beeper_off() { this->set_beeper_feedback(false); } void do_power_on() { this->base_.setPowerState(true); } void do_power_off() { this->base_.setPowerState(false); } + void set_supported_modes(const std::set &modes) { this->supported_modes_ = modes; } + void set_supported_swing_modes(const std::set &modes) { this->supported_swing_modes_ = modes; } + void set_supported_presets(const std::set &presets) { this->supported_presets_ = presets; } + void set_custom_presets(const std::set &presets) { this->supported_custom_presets_ = presets; } + void set_custom_fan_modes(const std::set &modes) { this->supported_custom_fan_modes_ = modes; } protected: void control(const ClimateCall &call) override; ClimateTraits traits() override; + std::set supported_modes_{}; + std::set supported_swing_modes_{}; + std::set supported_presets_{}; + std::set supported_custom_presets_{}; + std::set supported_custom_fan_modes_{}; Sensor *outdoor_sensor_{nullptr}; Sensor *humidity_sensor_{nullptr}; Sensor *power_sensor_{nullptr}; }; +} // namespace ac } // namespace midea } // namespace esphome diff --git a/esphome/components/midea/appliance_base.h b/esphome/components/midea/appliance_base.h index 88a722e389..060cbd996b 100644 --- a/esphome/components/midea/appliance_base.h +++ b/esphome/components/midea/appliance_base.h @@ -2,84 +2,97 @@ #ifdef USE_ARDUINO +// MideaUART +#include +#include + +// Include global defines +#include "esphome/core/defines.h" + #include "esphome/core/component.h" #include "esphome/core/log.h" #include "esphome/components/uart/uart.h" #include "esphome/components/climate/climate.h" -#ifdef USE_REMOTE_TRANSMITTER -#include "esphome/components/remote_base/midea_protocol.h" -#include "esphome/components/remote_transmitter/remote_transmitter.h" -#endif -#include -#include +#include "ir_transmitter.h" namespace esphome { namespace midea { -using climate::ClimatePreset; -using climate::ClimateTraits; -using climate::ClimateMode; -using climate::ClimateSwingMode; -using climate::ClimateFanMode; +/* Stream from UART component */ +class UARTStream : public Stream { + public: + void set_uart(uart::UARTComponent *uart) { this->uart_ = uart; } -template -class ApplianceBase : public Component, public uart::UARTDevice, public climate::Climate, public Stream { + /* Stream interface implementation */ + + int available() override { return this->uart_->available(); } + int read() override { + uint8_t data; + this->uart_->read_byte(&data); + return data; + } + int peek() override { + uint8_t data; + this->uart_->peek_byte(&data); + return data; + } + size_t write(uint8_t data) override { + this->uart_->write_byte(data); + return 1; + } + size_t write(const uint8_t *data, size_t size) override { + this->uart_->write_array(data, size); + return size; + } + void flush() override { this->uart_->flush(); } + + protected: + uart::UARTComponent *uart_; +}; + +template class ApplianceBase : public Component { static_assert(std::is_base_of::value, "T must derive from dudanov::midea::ApplianceBase class"); public: ApplianceBase() { - this->base_.setStream(this); + this->base_.setStream(&this->stream_); this->base_.addOnStateCallback(std::bind(&ApplianceBase::on_status_change, this)); dudanov::midea::ApplianceBase::setLogger( [](int level, const char *tag, int line, const String &format, va_list args) { esp_log_vprintf_(level, tag, line, format.c_str(), args); }); } - bool can_proceed() override { - return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS; - } - float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; } - void setup() override { this->base_.setup(); } - void loop() override { this->base_.loop(); } + +#ifdef USE_REMOTE_TRANSMITTER + void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_.set_transmitter(transmitter); } +#endif + + /* UART communication */ + + void set_uart_parent(uart::UARTComponent *parent) { this->stream_.set_uart(parent); } void set_period(uint32_t ms) { this->base_.setPeriod(ms); } void set_response_timeout(uint32_t ms) { this->base_.setTimeout(ms); } void set_request_attempts(uint32_t attempts) { this->base_.setNumAttempts(attempts); } + + /* Component methods */ + + void setup() override { this->base_.setup(); } + void loop() override { this->base_.loop(); } + float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; } + bool can_proceed() override { + return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS; + } + void set_beeper_feedback(bool state) { this->base_.setBeeper(state); } void set_autoconf(bool value) { this->base_.setAutoconf(value); } - void set_supported_modes(const std::set &modes) { this->supported_modes_ = modes; } - void set_supported_swing_modes(const std::set &modes) { this->supported_swing_modes_ = modes; } - void set_supported_presets(const std::set &presets) { this->supported_presets_ = presets; } - void set_custom_presets(const std::set &presets) { this->supported_custom_presets_ = presets; } - void set_custom_fan_modes(const std::set &modes) { this->supported_custom_fan_modes_ = modes; } virtual void on_status_change() = 0; -#ifdef USE_REMOTE_TRANSMITTER - void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) { - this->transmitter_ = transmitter; - } - void transmit_ir(remote_base::MideaData &data) { - data.finalize(); - auto transmit = this->transmitter_->transmit(); - remote_base::MideaProtocol().encode(transmit.get_data(), data); - transmit.perform(); - } -#endif - - int available() override { return uart::UARTDevice::available(); } - int read() override { return uart::UARTDevice::read(); } - int peek() override { return uart::UARTDevice::peek(); } - void flush() override { uart::UARTDevice::flush(); } - size_t write(uint8_t data) override { return uart::UARTDevice::write(data); } protected: T base_; - std::set supported_modes_{}; - std::set supported_swing_modes_{}; - std::set supported_presets_{}; - std::set supported_custom_presets_{}; - std::set supported_custom_fan_modes_{}; + UARTStream stream_; #ifdef USE_REMOTE_TRANSMITTER - remote_transmitter::RemoteTransmitterComponent *transmitter_{nullptr}; + IrTransmitter transmitter_; #endif }; diff --git a/esphome/components/midea/climate.py b/esphome/components/midea/climate.py index 08e82025b6..46c0019efa 100644 --- a/esphome/components/midea/climate.py +++ b/esphome/components/midea/climate.py @@ -40,9 +40,9 @@ AUTO_LOAD = ["sensor"] CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" CONF_POWER_USAGE = "power_usage" CONF_HUMIDITY_SETPOINT = "humidity_setpoint" -midea_ns = cg.esphome_ns.namespace("midea") -AirConditioner = midea_ns.class_("AirConditioner", climate.Climate, cg.Component) -Capabilities = midea_ns.namespace("Constants") +midea_ac_ns = cg.esphome_ns.namespace("midea").namespace("ac") +AirConditioner = midea_ac_ns.class_("AirConditioner", climate.Climate, cg.Component) +Capabilities = midea_ac_ns.namespace("Constants") def templatize(value): @@ -156,13 +156,13 @@ CONFIG_SCHEMA = cv.All( ) # Actions -FollowMeAction = midea_ns.class_("FollowMeAction", automation.Action) -DisplayToggleAction = midea_ns.class_("DisplayToggleAction", automation.Action) -SwingStepAction = midea_ns.class_("SwingStepAction", automation.Action) -BeeperOnAction = midea_ns.class_("BeeperOnAction", automation.Action) -BeeperOffAction = midea_ns.class_("BeeperOffAction", automation.Action) -PowerOnAction = midea_ns.class_("PowerOnAction", automation.Action) -PowerOffAction = midea_ns.class_("PowerOffAction", automation.Action) +FollowMeAction = midea_ac_ns.class_("FollowMeAction", automation.Action) +DisplayToggleAction = midea_ac_ns.class_("DisplayToggleAction", automation.Action) +SwingStepAction = midea_ac_ns.class_("SwingStepAction", automation.Action) +BeeperOnAction = midea_ac_ns.class_("BeeperOnAction", automation.Action) +BeeperOffAction = midea_ac_ns.class_("BeeperOffAction", automation.Action) +PowerOnAction = midea_ac_ns.class_("PowerOnAction", automation.Action) +PowerOffAction = midea_ac_ns.class_("PowerOffAction", automation.Action) MIDEA_ACTION_BASE_SCHEMA = cv.Schema( { diff --git a/esphome/components/midea/midea_ir.h b/esphome/components/midea/ir_transmitter.h similarity index 73% rename from esphome/components/midea/midea_ir.h rename to esphome/components/midea/ir_transmitter.h index abd4324bcc..34a9f8498e 100644 --- a/esphome/components/midea/midea_ir.h +++ b/esphome/components/midea/ir_transmitter.h @@ -7,6 +7,7 @@ namespace esphome { namespace midea { +using remote_base::RemoteTransmitterBase; using IrData = remote_base::MideaData; class IrFollowMeData : public IrData { @@ -38,6 +39,20 @@ class IrSpecialData : public IrData { IrSpecialData(uint8_t code) : IrData({MIDEA_TYPE_SPECIAL, code, 0xFF, 0xFF, 0xFF}) {} }; +class IrTransmitter { + public: + void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; } + void transmit(IrData &data) { + data.finalize(); + auto transmit = this->transmitter_->transmit(); + remote_base::MideaProtocol().encode(transmit.get_data(), data); + transmit.perform(); + } + + protected: + RemoteTransmitterBase *transmitter_{nullptr}; +}; + } // namespace midea } // namespace esphome From 7333123ba4e108f9e03b021af418abb6877330ea Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:59:48 +1300 Subject: [PATCH 021/111] Fix indentation of write_lambda for modbus_controller number (#2722) --- .../modbus_controller/number/__init__.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py index afb69f8798..4de0ffbcea 100644 --- a/esphome/components/modbus_controller/number/__init__.py +++ b/esphome/components/modbus_controller/number/__init__.py @@ -129,14 +129,14 @@ async def to_code(config): return_type=cg.optional.template(float), ) cg.add(var.set_template(template_)) - if CONF_WRITE_LAMBDA in config: - template_ = await cg.process_lambda( - config[CONF_WRITE_LAMBDA], - [ - (ModbusNumber.operator("ptr"), "item"), - (cg.float_, "x"), - (cg.std_vector.template(cg.uint16).operator("ref"), "payload"), - ], - return_type=cg.optional.template(float), - ) - cg.add(var.set_write_template(template_)) + if CONF_WRITE_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_WRITE_LAMBDA], + [ + (ModbusNumber.operator("ptr"), "item"), + (cg.float_, "x"), + (cg.std_vector.template(cg.uint16).operator("ref"), "payload"), + ], + return_type=cg.optional.template(float), + ) + cg.add(var.set_write_template(template_)) From 0b193eee432cd9c11dc0ac42d1429b9c48966db2 Mon Sep 17 00:00:00 2001 From: Alexandre-Jacques St-Jacques Date: Sun, 14 Nov 2021 17:58:22 -0500 Subject: [PATCH 022/111] Remove unnecessary duplicate touch_pad_filter_start (#2724) --- esphome/components/esp32_touch/esp32_touch.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 85f4058eee..b225ae1a8a 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -94,7 +94,6 @@ void ESP32TouchComponent::dump_config() { if (this->iir_filter_enabled_()) { ESP_LOGCONFIG(TAG, " IIR Filter: %ums", this->iir_filter_); - touch_pad_filter_start(this->iir_filter_); } else { ESP_LOGCONFIG(TAG, " IIR Filter DISABLED"); } From 5404163be08ec98b95970de204105ab424d21aaf Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 15 Nov 2021 15:48:16 +0100 Subject: [PATCH 023/111] Clean-up MAC address helpers (#2713) --- esphome/core/helpers.cpp | 28 +++++++++------------------- esphome/core/helpers.h | 7 +++---- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 27608a84c1..3e614eb515 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -6,11 +6,10 @@ #include #if defined(USE_ESP8266) -#ifdef USE_WIFI -#include -#endif -#include #include +#include +// for xt_rsil()/xt_wsr_ps() +#include #elif defined(USE_ESP32_FRAMEWORK_ARDUINO) #include #elif defined(USE_ESP_IDF) @@ -31,8 +30,8 @@ namespace esphome { static const char *const TAG = "helpers"; void get_mac_address_raw(uint8_t *mac) { -#ifdef USE_ESP32 -#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC +#if defined(USE_ESP32) +#if defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC) // On some devices, the MAC address that is burnt into EFuse does not // match the CRC that goes along with it. For those devices, this // work-around reads and uses the MAC address as-is from EFuse, @@ -41,30 +40,21 @@ void get_mac_address_raw(uint8_t *mac) { #else esp_efuse_mac_get_default(mac); #endif -#endif -#if (defined USE_ESP8266 && defined USE_WIFI) - WiFi.macAddress(mac); +#elif defined(USE_ESP8266) + wifi_get_macaddr(STATION_IF, mac); #endif } std::string get_mac_address() { - char tmp[20]; uint8_t mac[6]; get_mac_address_raw(mac); -#ifdef USE_WIFI - sprintf(tmp, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); -#else - return ""; -#endif - return std::string(tmp); + return str_sprintf("%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } std::string get_mac_address_pretty() { - char tmp[20]; uint8_t mac[6]; get_mac_address_raw(mac); - sprintf(tmp, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - return std::string(tmp); + return str_sprintf("%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } #ifdef USE_ESP32 diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index c67ad8eea3..120642c62e 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -25,14 +25,13 @@ namespace esphome { -/// Read the raw MAC address into the provided byte array (6 bytes). +/// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes). void get_mac_address_raw(uint8_t *mac); -/// Get the MAC address as a string, using lower case hex notation. -/// This can be used as way to identify this ESP. +/// Get the device MAC address as a string, in lowercase hex notation. std::string get_mac_address(); -/// Get the MAC address as a string, using colon-separated upper case hex notation. +/// Get the device MAC address as a string, in colon-separated uppercase hex notation. std::string get_mac_address_pretty(); #ifdef USE_ESP32 From 515519bc8745a627dd02cf33c53933d5d4e4eeb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Bia=C5=82ek?= Date: Mon, 15 Nov 2021 15:49:18 +0100 Subject: [PATCH 024/111] Provide an option to select MQTT unique_id generator (#2701) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen --- esphome/components/mqtt/__init__.py | 23 ++++++++++++++++++++-- esphome/components/mqtt/mqtt_client.cpp | 4 +++- esphome/components/mqtt/mqtt_client.h | 11 ++++++++++- esphome/components/mqtt/mqtt_component.cpp | 14 ++++++++++--- esphome/const.py | 1 + tests/test1.yaml | 1 + 6 files changed, 47 insertions(+), 7 deletions(-) diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 0f7d246473..73af0bad90 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -14,6 +14,7 @@ from esphome.const import ( CONF_DISCOVERY, CONF_DISCOVERY_PREFIX, CONF_DISCOVERY_RETAIN, + CONF_DISCOVERY_UNIQUE_ID_GENERATOR, CONF_ID, CONF_KEEPALIVE, CONF_LEVEL, @@ -95,6 +96,12 @@ MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent) MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent) MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent) +MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator") +MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = { + "legacy": MQTTDiscoveryUniqueIdGenerator.MQTT_LEGACY_UNIQUE_ID_GENERATOR, + "mac": MQTTDiscoveryUniqueIdGenerator.MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR, +} + def validate_config(value): # Populate default fields @@ -153,6 +160,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_DISCOVERY_PREFIX, default="homeassistant" ): cv.publish_topic, + cv.Optional(CONF_DISCOVERY_UNIQUE_ID_GENERATOR, default="legacy"): cv.enum( + MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS + ), cv.Optional(CONF_USE_ABBREVIATIONS, default=True): cv.boolean, cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA, cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA, @@ -231,13 +241,22 @@ async def to_code(config): discovery = config[CONF_DISCOVERY] discovery_retain = config[CONF_DISCOVERY_RETAIN] discovery_prefix = config[CONF_DISCOVERY_PREFIX] + discovery_unique_id_generator = config[CONF_DISCOVERY_UNIQUE_ID_GENERATOR] if not discovery: cg.add(var.disable_discovery()) elif discovery == "CLEAN": - cg.add(var.set_discovery_info(discovery_prefix, discovery_retain, True)) + cg.add( + var.set_discovery_info( + discovery_prefix, discovery_unique_id_generator, discovery_retain, True + ) + ) elif CONF_DISCOVERY_RETAIN in config or CONF_DISCOVERY_PREFIX in config: - cg.add(var.set_discovery_info(discovery_prefix, discovery_retain)) + cg.add( + var.set_discovery_info( + discovery_prefix, discovery_unique_id_generator, discovery_retain + ) + ) cg.add(var.set_topic_prefix(config[CONF_TOPIC_PREFIX])) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 040b0001fe..43c49e9f7f 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -535,8 +535,10 @@ void MQTTClientComponent::set_birth_message(MQTTMessage &&message) { void MQTTClientComponent::set_shutdown_message(MQTTMessage &&message) { this->shutdown_message_ = std::move(message); } -void MQTTClientComponent::set_discovery_info(std::string &&prefix, bool retain, bool clean) { +void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, + bool retain, bool clean) { this->discovery_info_.prefix = std::move(prefix); + this->discovery_info_.unique_id_generator = unique_id_generator; this->discovery_info_.retain = retain; this->discovery_info_.clean = clean; } diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index fa689eaa04..d6194da794 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -55,6 +55,12 @@ struct Availability { std::string payload_not_available; }; +/// available discovery unique_id generators +enum MQTTDiscoveryUniqueIdGenerator { + MQTT_LEGACY_UNIQUE_ID_GENERATOR = 0, + MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR, +}; + /** Internal struct for MQTT Home Assistant discovery * * See MQTT Discovery. @@ -63,6 +69,7 @@ struct MQTTDiscoveryInfo { std::string prefix; ///< The Home Assistant discovery prefix. Empty means disabled. bool retain; ///< Whether to retain discovery messages. bool clean; + MQTTDiscoveryUniqueIdGenerator unique_id_generator; }; enum MQTTClientState { @@ -98,9 +105,11 @@ class MQTTClientComponent : public Component { * * See MQTT Discovery. * @param prefix The Home Assistant discovery prefix. + * @param unique_id_generator Controls how UniqueId is generated. * @param retain Whether to retain discovery messages. */ - void set_discovery_info(std::string &&prefix, bool retain, bool clean = false); + void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, bool retain, + bool clean = false); /// Get Home Assistant discovery info. const MQTTDiscoveryInfo &get_discovery_info() const; /// Globally disable Home Assistant discovery. diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index e3ae4dea50..bf9f5e34b8 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -114,9 +114,17 @@ bool MQTTComponent::send_discovery_() { if (!unique_id.empty()) { root[MQTT_UNIQUE_ID] = unique_id; } else { - // default to almost-unique ID. It's a hack but the only way to get that - // gorgeous device registry view. - root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_(); + const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info(); + if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) { + char friendly_name_hash[9]; + sprintf(friendly_name_hash, "%08x", fnv1_hash(this->friendly_name())); + friendly_name_hash[8] = 0; // ensure the hash-string ends with null + root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash; + } else { + // default to almost-unique ID. It's a hack but the only way to get that + // gorgeous device registry view. + root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_(); + } } JsonObject &device_info = root.createNestedObject(MQTT_DEVICE); diff --git a/esphome/const.py b/esphome/const.py index fb241f04b5..9a778edfeb 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -167,6 +167,7 @@ CONF_DISABLED_BY_DEFAULT = "disabled_by_default" CONF_DISCOVERY = "discovery" CONF_DISCOVERY_PREFIX = "discovery_prefix" CONF_DISCOVERY_RETAIN = "discovery_retain" +CONF_DISCOVERY_UNIQUE_ID_GENERATOR = "discovery_unique_id_generator" CONF_DISTANCE = "distance" CONF_DITHER = "dither" CONF_DIV_RATIO = "div_ratio" diff --git a/tests/test1.yaml b/tests/test1.yaml index 263754dc4f..18c6610b08 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -98,6 +98,7 @@ mqtt: discovery: True discovery_retain: False discovery_prefix: discovery + discovery_unique_id_generator: legacy topic_prefix: helloworld log_topic: topic: helloworld/hi From b386284180f33c0446fbb5033a99fe8e85d2fdd9 Mon Sep 17 00:00:00 2001 From: cvwillegen Date: Mon, 15 Nov 2021 20:06:55 +0100 Subject: [PATCH 025/111] Ignore secrets.yaml on command line (#2715) --- esphome/__main__.py | 16 ++++++++-------- esphome/const.py | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index c2a6dd343f..7c7c22dd1f 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -18,6 +18,7 @@ from esphome.const import ( CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS, + SECRETS_FILES, ) from esphome.core import CORE, EsphomeError, coroutine from esphome.helpers import indent @@ -200,8 +201,7 @@ def upload_using_esptool(config, port): firmware_offset = "0x10000" if CORE.is_esp32 else "0x0" flash_images = [ platformio_api.FlashImage( - path=idedata.firmware_bin_path, - offset=firmware_offset, + path=idedata.firmware_bin_path, offset=firmware_offset ), *idedata.extra_flash_images, ] @@ -607,10 +607,7 @@ def parse_args(argv): "wizard", help="A helpful setup wizard that will guide you through setting up ESPHome.", ) - parser_wizard.add_argument( - "configuration", - help="Your YAML configuration file.", - ) + parser_wizard.add_argument("configuration", help="Your YAML configuration file.") parser_fingerprint = subparsers.add_parser( "mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker." @@ -632,8 +629,7 @@ def parse_args(argv): "dashboard", help="Create a simple web server for a dashboard." ) parser_dashboard.add_argument( - "configuration", - help="Your YAML configuration file directory.", + "configuration", help="Your YAML configuration file directory." ) parser_dashboard.add_argument( "--port", @@ -789,6 +785,10 @@ def run_esphome(argv): return 1 for conf_path in args.configuration: + if any(os.path.basename(conf_path) == x for x in SECRETS_FILES): + _LOGGER.warning("Skipping secrets file %s", conf_path) + continue + CORE.config_path = conf_path CORE.dashboard = args.dashboard diff --git a/esphome/const.py b/esphome/const.py index 9a778edfeb..2a7942a2b4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -11,6 +11,7 @@ TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266] SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"} HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"} +SECRETS_FILES = {"secrets.yaml", "secrets.yml"} CONF_ABOVE = "above" From 0809673ba9fa9d3a024653522d5b9aa8aea470b2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 16 Nov 2021 09:53:52 +1300 Subject: [PATCH 026/111] Add zeroconf as a direct dependency and lock the version (#2729) --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 6e1fe56057..c4b211283d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,7 @@ esptool==3.2 click==8.0.3 esphome-dashboard==20211021.1 aioesphomeapi==10.2.0 +zeroconf==0.36.13 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 9e4fa5dcf1b7f8526fe40f68cbbec3e1155dfa18 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 16 Nov 2021 11:02:45 +1300 Subject: [PATCH 027/111] Improv serial/checksum changes (#2731) Co-authored-by: Paulus Schoutsen --- esphome/components/improv/improv.cpp | 54 ++++++++++--------- esphome/components/improv/improv.h | 9 ++-- .../improv_serial/improv_serial_component.cpp | 35 ++++++++---- 3 files changed, 61 insertions(+), 37 deletions(-) diff --git a/esphome/components/improv/improv.cpp b/esphome/components/improv/improv.cpp index 94068bc626..759962b51a 100644 --- a/esphome/components/improv/improv.cpp +++ b/esphome/components/improv/improv.cpp @@ -2,30 +2,32 @@ namespace improv { -ImprovCommand parse_improv_data(const std::vector &data) { - return parse_improv_data(data.data(), data.size()); +ImprovCommand parse_improv_data(const std::vector &data, bool check_checksum) { + return parse_improv_data(data.data(), data.size(), check_checksum); } -ImprovCommand parse_improv_data(const uint8_t *data, size_t length) { +ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum) { ImprovCommand improv_command; Command command = (Command) data[0]; uint8_t data_length = data[1]; - if (data_length != length - 3) { + if (data_length != length - 2 - check_checksum) { improv_command.command = UNKNOWN; return improv_command; } - uint8_t checksum = data[length - 1]; + if (check_checksum) { + uint8_t checksum = data[length - 1]; - uint32_t calculated_checksum = 0; - for (uint8_t i = 0; i < length - 1; i++) { - calculated_checksum += data[i]; - } + uint32_t calculated_checksum = 0; + for (uint8_t i = 0; i < length - 1; i++) { + calculated_checksum += data[i]; + } - if ((uint8_t) calculated_checksum != checksum) { - improv_command.command = BAD_CHECKSUM; - return improv_command; + if ((uint8_t) calculated_checksum != checksum) { + improv_command.command = BAD_CHECKSUM; + return improv_command; + } } if (command == WIFI_SETTINGS) { @@ -46,7 +48,7 @@ ImprovCommand parse_improv_data(const uint8_t *data, size_t length) { return improv_command; } -std::vector build_rpc_response(Command command, const std::vector &datum) { +std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum) { std::vector out; uint32_t length = 0; out.push_back(command); @@ -58,17 +60,19 @@ std::vector build_rpc_response(Command command, const std::vector build_rpc_response(Command command, const std::vector &datum) { +#ifdef ARDUINO +std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum) { std::vector out; uint32_t length = 0; out.push_back(command); @@ -80,14 +84,16 @@ std::vector build_rpc_response(Command command, const std::vector &data); -ImprovCommand parse_improv_data(const uint8_t *data, size_t length); +ImprovCommand parse_improv_data(const std::vector &data, bool check_checksum = true); +ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum = true); -std::vector build_rpc_response(Command command, const std::vector &datum); +std::vector build_rpc_response(Command command, const std::vector &datum, + bool add_checksum = true); #ifdef ARDUINO -std::vector build_rpc_response(Command command, const std::vector &datum); +std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum = true); #endif // ARDUINO } // namespace improv diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index abbb76ab11..a9a7467125 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -98,13 +98,13 @@ std::vector ImprovSerialComponent::build_rpc_settings_response_(improv: std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT); urls.push_back(webserver_url); #endif - std::vector data = improv::build_rpc_response(command, urls); + std::vector data = improv::build_rpc_response(command, urls, false); return data; } std::vector ImprovSerialComponent::build_version_info_() { std::vector infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()}; - std::vector data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos); + std::vector data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false); return data; }; @@ -140,22 +140,33 @@ bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) { if (at < 8 + data_len) return true; - if (at == 8 + data_len) { + if (at == 8 + data_len) + return true; + + if (at == 8 + data_len + 1) { + uint8_t checksum = 0x00; + for (uint8_t i = 0; i < at; i++) + checksum += raw[i]; + + if (checksum != byte) { + ESP_LOGW(TAG, "Error decoding Improv payload"); + this->set_error_(improv::ERROR_INVALID_RPC); + return false; + } + if (type == TYPE_RPC) { this->set_error_(improv::ERROR_NONE); - auto command = improv::parse_improv_data(&raw[9], data_len); + auto command = improv::parse_improv_data(&raw[9], data_len, false); return this->parse_improv_payload_(command); } } - return true; + + // If we got here then the command coming is is improv, but not an RPC command + return false; } bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) { switch (command.command) { - case improv::BAD_CHECKSUM: - ESP_LOGW(TAG, "Error decoding Improv payload"); - this->set_error_(improv::ERROR_INVALID_RPC); - return false; case improv::WIFI_SETTINGS: { wifi::WiFiAP sta{}; sta.set_ssid(command.ssid); @@ -232,6 +243,12 @@ void ImprovSerialComponent::send_response_(std::vector &response) { data[7] = TYPE_RPC_RESPONSE; data[8] = response.size(); data.insert(data.end(), response.begin(), response.end()); + + uint8_t checksum = 0x00; + for (uint8_t d : data) + checksum += d; + data.push_back(checksum); + this->write_data_(data); } From f1954df57337fe4ed08cbd9a59d4bd156278113f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 16 Nov 2021 12:47:06 +1300 Subject: [PATCH 028/111] Fix zeroconf time comparisons (#2733) Co-authored-by: J. Nick Koston --- esphome/zeroconf.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index a19fc143ec..1fbdf7e93f 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -13,8 +13,9 @@ from zeroconf import ( RecordUpdateListener, Zeroconf, ServiceBrowser, + ServiceStateChange, + current_time_millis, ) -from zeroconf._services import ServiceStateChange _CLASS_IN = 1 _FLAGS_QR_QUERY = 0x0000 # query @@ -88,7 +89,7 @@ class DashboardStatus(threading.Thread): entries = self.zc.cache.entries_with_name(key) if not entries: return False - now = time.time() * 1000 + now = current_time_millis() return any( (entry.created + DashboardStatus.OFFLINE_AFTER) >= now for entry in entries @@ -99,7 +100,7 @@ class DashboardStatus(threading.Thread): self.on_update( {key: self.host_status(host) for key, host in self.key_to_host.items()} ) - now = time.time() * 1000 + now = current_time_millis() for host in self.query_hosts: entries = self.zc.cache.entries_with_name(host) if not entries or all( From b35f5097848b94e14f45816ddfd8036057dcb770 Mon Sep 17 00:00:00 2001 From: Jan Harkes Date: Tue, 16 Nov 2021 03:16:43 -0500 Subject: [PATCH 029/111] Allow for subsecond sampling of hmc5883l (#2735) --- esphome/components/hmc5883l/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/hmc5883l/sensor.py b/esphome/components/hmc5883l/sensor.py index 73e7472dcf..9d8701079e 100644 --- a/esphome/components/hmc5883l/sensor.py +++ b/esphome/components/hmc5883l/sensor.py @@ -114,8 +114,8 @@ CONFIG_SCHEMA = ( def auto_data_rate(config): - interval_sec = config[CONF_UPDATE_INTERVAL].seconds - interval_hz = 1.0 / interval_sec + interval_msec = config[CONF_UPDATE_INTERVAL].total_milliseconds + interval_hz = 1000.0 / interval_msec for datarate in sorted(HMC5883LDatarates.keys()): if float(datarate) >= interval_hz: return HMC5883LDatarates[datarate] From 8ece639987339a7082fdcea68a4d766906e786d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=2E=20=C3=81rkosi=20R=C3=B3bert?= Date: Tue, 16 Nov 2021 11:28:12 +0100 Subject: [PATCH 030/111] Change log level from DEBUG to INFO for sniffing services (#2736) Sniffing for codes only happens if the user deliberately asked for it with the related service through HA - to find out the codes present in the air. The resulted data shouldn't be printed out only in debug mode, as this is information required to be known on demand for later use, not actually a debug info. Changing log level from DEBUG to INFO for sniffing services has two benefits: - no need to run firmware with DEBUG enabled for occasional sniffing with devices in production (no need to flash back and forth with different log levels set just for this reason) - if the user still wants DEBUG enabled, sniffed data appears in different color, it's easier to find between the lines. --- esphome/components/rf_bridge/rf_bridge.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/rf_bridge/rf_bridge.cpp b/esphome/components/rf_bridge/rf_bridge.cpp index a4259e5aa2..d8c8047496 100644 --- a/esphome/components/rf_bridge/rf_bridge.cpp +++ b/esphome/components/rf_bridge/rf_bridge.cpp @@ -52,7 +52,7 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) { if (action == RF_CODE_LEARN_OK) ESP_LOGD(TAG, "Learning success"); - ESP_LOGD(TAG, "Received RFBridge Code: sync=0x%04X low=0x%04X high=0x%04X code=0x%06X", data.sync, data.low, + ESP_LOGI(TAG, "Received RFBridge Code: sync=0x%04X low=0x%04X high=0x%04X code=0x%06X", data.sync, data.low, data.high, data.code); this->data_callback_.call(data); break; @@ -73,7 +73,7 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) { data.code += next_byte; } - ESP_LOGD(TAG, "Received RFBridge Advanced Code: length=0x%02X protocol=0x%02X code=0x%s", data.length, + ESP_LOGI(TAG, "Received RFBridge Advanced Code: length=0x%02X protocol=0x%02X code=0x%s", data.length, data.protocol, data.code.c_str()); this->advanced_data_callback_.call(data); break; @@ -97,7 +97,7 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) { str += " "; } } - ESP_LOGD(TAG, "Received RFBridge Bucket: %s", str.c_str()); + ESP_LOGI(TAG, "Received RFBridge Bucket: %s", str.c_str()); break; } default: @@ -186,7 +186,7 @@ void RFBridgeComponent::dump_config() { } void RFBridgeComponent::start_advanced_sniffing() { - ESP_LOGD(TAG, "Advanced Sniffing on"); + ESP_LOGI(TAG, "Advanced Sniffing on"); this->write(RF_CODE_START); this->write(RF_CODE_SNIFFING_ON); this->write(RF_CODE_STOP); @@ -194,7 +194,7 @@ void RFBridgeComponent::start_advanced_sniffing() { } void RFBridgeComponent::stop_advanced_sniffing() { - ESP_LOGD(TAG, "Advanced Sniffing off"); + ESP_LOGI(TAG, "Advanced Sniffing off"); this->write(RF_CODE_START); this->write(RF_CODE_SNIFFING_OFF); this->write(RF_CODE_STOP); @@ -202,7 +202,7 @@ void RFBridgeComponent::stop_advanced_sniffing() { } void RFBridgeComponent::start_bucket_sniffing() { - ESP_LOGD(TAG, "Raw Bucket Sniffing on"); + ESP_LOGI(TAG, "Raw Bucket Sniffing on"); this->write(RF_CODE_START); this->write(RF_CODE_RFIN_BUCKET); this->write(RF_CODE_STOP); From f565ff5def4620afe0ced65499310275d81d86aa Mon Sep 17 00:00:00 2001 From: Ryan Hoffman Date: Tue, 16 Nov 2021 12:53:36 -0500 Subject: [PATCH 031/111] Use as_reversed_hex_array in ble_sensor to fix UUID parsing (#2737) #1627 renamed as_hex_array to as_reversed_hex_array but forgot to rename these users. --- esphome/components/ble_client/sensor/__init__.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/esphome/components/ble_client/sensor/__init__.py b/esphome/components/ble_client/sensor/__init__.py index efe4bf0e9a..4aa6a92ba5 100644 --- a/esphome/components/ble_client/sensor/__init__.py +++ b/esphome/components/ble_client/sensor/__init__.py @@ -67,7 +67,7 @@ async def to_code(config): var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) ) elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): - uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) + uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) cg.add(var.set_service_uuid128(uuid128)) if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): @@ -87,7 +87,9 @@ async def to_code(config): elif len(config[CONF_CHARACTERISTIC_UUID]) == len( esp32_ble_tracker.bt_uuid128_format ): - uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_CHARACTERISTIC_UUID]) + uuid128 = esp32_ble_tracker.as_reversed_hex_array( + config[CONF_CHARACTERISTIC_UUID] + ) cg.add(var.set_char_uuid128(uuid128)) if CONF_DESCRIPTOR_UUID in config: @@ -108,7 +110,9 @@ async def to_code(config): elif len(config[CONF_DESCRIPTOR_UUID]) == len( esp32_ble_tracker.bt_uuid128_format ): - uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_DESCRIPTOR_UUID]) + uuid128 = esp32_ble_tracker.as_reversed_hex_array( + config[CONF_DESCRIPTOR_UUID] + ) cg.add(var.set_descr_uuid128(uuid128)) if CONF_LAMBDA in config: From 57bdc2b88540402f57dd2ef7774b162191f5b95e Mon Sep 17 00:00:00 2001 From: Ryan Hoffman Date: Tue, 16 Nov 2021 13:30:42 -0500 Subject: [PATCH 032/111] Add ble_client binary_output (#2200) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/ble_client/ble_client.cpp | 9 +++ esphome/components/ble_client/ble_client.h | 2 +- .../components/ble_client/output/__init__.py | 67 +++++++++++++++++ .../ble_client/output/ble_binary_output.cpp | 71 +++++++++++++++++++ .../ble_client/output/ble_binary_output.h | 39 ++++++++++ 5 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 esphome/components/ble_client/output/__init__.py create mode 100644 esphome/components/ble_client/output/ble_binary_output.cpp create mode 100644 esphome/components/ble_client/output/ble_binary_output.h diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index e6cdb0c23d..407f1a1d17 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -388,6 +388,15 @@ BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) { return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid)); } +void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) { + auto client = this->service->client; + auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val, + ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status); + } +} + } // namespace ble_client } // namespace esphome diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index 23123914e8..5680b69f72 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -59,7 +59,7 @@ class BLECharacteristic { void parse_descriptors(); BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); BLEDescriptor *get_descriptor(uint16_t uuid); - + void write_value(uint8_t *new_val, int16_t new_val_size); BLEService *service; }; diff --git a/esphome/components/ble_client/output/__init__.py b/esphome/components/ble_client/output/__init__.py new file mode 100644 index 0000000000..fe5835ca82 --- /dev/null +++ b/esphome/components/ble_client/output/__init__.py @@ -0,0 +1,67 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output, ble_client, esp32_ble_tracker +from esphome.const import CONF_ID, CONF_SERVICE_UUID +from .. import ble_client_ns + + +DEPENDENCIES = ["ble_client"] + +CONF_CHARACTERISTIC_UUID = "characteristic_uuid" + +BLEBinaryOutput = ble_client_ns.class_( + "BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component +) + +CONFIG_SCHEMA = cv.All( + output.BINARY_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(BLEBinaryOutput), + cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, + cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(ble_client.BLE_CLIENT_SCHEMA) +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): + cg.add( + var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) + ) + elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format): + cg.add( + var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) + ) + elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): + uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) + cg.add(var.set_service_uuid128(uuid128)) + + if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): + cg.add( + var.set_char_uuid16( + esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID]) + ) + ) + elif len(config[CONF_CHARACTERISTIC_UUID]) == len( + esp32_ble_tracker.bt_uuid32_format + ): + cg.add( + var.set_char_uuid32( + esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID]) + ) + ) + elif len(config[CONF_CHARACTERISTIC_UUID]) == len( + esp32_ble_tracker.bt_uuid128_format + ): + uuid128 = esp32_ble_tracker.as_reversed_hex_array( + config[CONF_CHARACTERISTIC_UUID] + ) + cg.add(var.set_char_uuid128(uuid128)) + + yield output.register_output(var, config) + yield ble_client.register_ble_node(var, config) + yield cg.register_component(var, config) diff --git a/esphome/components/ble_client/output/ble_binary_output.cpp b/esphome/components/ble_client/output/ble_binary_output.cpp new file mode 100644 index 0000000000..ff3711e842 --- /dev/null +++ b/esphome/components/ble_client/output/ble_binary_output.cpp @@ -0,0 +1,71 @@ +#include "ble_binary_output.h" +#include "esphome/core/log.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#ifdef USE_ESP32 +namespace esphome { +namespace ble_client { + +static const char *const TAG = "ble_binary_output"; + +void BLEBinaryOutput::dump_config() { + ESP_LOGCONFIG(TAG, "BLE Binary Output:"); + ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent_->address_str().c_str()); + ESP_LOGCONFIG(TAG, " Service UUID : %s", this->service_uuid_.to_string().c_str()); + ESP_LOGCONFIG(TAG, " Characteristic UUID: %s", this->char_uuid_.to_string().c_str()); + LOG_BINARY_OUTPUT(this); +} + +void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) { + switch (event) { + case ESP_GATTC_OPEN_EVT: + this->client_state_ = espbt::ClientState::ESTABLISHED; + ESP_LOGW(TAG, "[%s] Connected successfully!", this->char_uuid_.to_string().c_str()); + break; + case ESP_GATTC_DISCONNECT_EVT: + ESP_LOGW(TAG, "[%s] Disconnected", this->char_uuid_.to_string().c_str()); + this->client_state_ = espbt::ClientState::IDLE; + break; + case ESP_GATTC_WRITE_CHAR_EVT: { + if (param->write.status == 0) { + break; + } + + auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); + if (chr == nullptr) { + ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str()); + break; + } + if (param->write.handle == chr->handle) { + ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status); + } + break; + } + default: + break; + } +} + +void BLEBinaryOutput::write_state(bool state) { + if (this->client_state_ != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.", + this->char_uuid_.to_string().c_str()); + return; + } + + auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); + if (chr == nullptr) { + ESP_LOGW(TAG, "[%s] Characteristic not found. State update can not be written.", + this->char_uuid_.to_string().c_str()); + return; + } + + uint8_t state_as_uint = (uint8_t) state; + ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint); + chr->write_value(&state_as_uint, sizeof(state_as_uint)); +} + +} // namespace ble_client +} // namespace esphome +#endif diff --git a/esphome/components/ble_client/output/ble_binary_output.h b/esphome/components/ble_client/output/ble_binary_output.h new file mode 100644 index 0000000000..e1d62a267b --- /dev/null +++ b/esphome/components/ble_client/output/ble_binary_output.h @@ -0,0 +1,39 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/ble_client/ble_client.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/output/binary_output.h" + +#ifdef USE_ESP32 +#include +namespace esphome { +namespace ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, public Component { + public: + void dump_config() override; + void loop() override {} + float get_setup_priority() const override { return setup_priority::DATA; } + void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } + void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } + void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } + void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } + void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } + void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + + protected: + void write_state(bool state) override; + espbt::ESPBTUUID service_uuid_; + espbt::ESPBTUUID char_uuid_; + espbt::ClientState client_state_; +}; + +} // namespace ble_client +} // namespace esphome + +#endif From df68403b6d7fbde9677015189dc657ebff056adb Mon Sep 17 00:00:00 2001 From: rotarykite Date: Wed, 17 Nov 2021 02:57:03 +0800 Subject: [PATCH 033/111] Fix senseair component uart read timeout (#2658) Co-authored-by: DAVe3283 Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Chua Jun Chieh --- esphome/components/senseair/senseair.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/esphome/components/senseair/senseair.cpp b/esphome/components/senseair/senseair.cpp index 610892dd9e..50b9e01f17 100644 --- a/esphome/components/senseair/senseair.cpp +++ b/esphome/components/senseair/senseair.cpp @@ -141,12 +141,16 @@ void SenseAirComponent::abc_get_period() { } bool SenseAirComponent::senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length) { + // Verify we have somewhere to store the response + if (response == nullptr) { + return false; + } + // Write wake up byte required by some S8 sensor models + this->write_byte(0); this->flush(); + delay(5); this->write_array(command, SENSEAIR_REQUEST_LENGTH); - if (response == nullptr) - return true; - bool ret = this->read_array(response, response_length); this->flush(); return ret; From dbcfa7b5990d7b9f0e2b70b9d807b16e8e6a8f64 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 17 Nov 2021 16:22:38 +1300 Subject: [PATCH 034/111] Remove duplicated const data in esp8266 boards (#2740) --- esphome/components/esp8266/boards.py | 58 ---------------------------- 1 file changed, 58 deletions(-) diff --git a/esphome/components/esp8266/boards.py b/esphome/components/esp8266/boards.py index c49aae4ffa..410e934615 100644 --- a/esphome/components/esp8266/boards.py +++ b/esphome/components/esp8266/boards.py @@ -206,61 +206,3 @@ ESP8266_BOARD_PINS = { "wio_node": {"LED": 2, "GROVE": 15, "D0": 3, "D1": 5, "BUTTON": 0}, "xinabox_cw01": {"SDA": 2, "SCL": 14, "LED": 5, "LED_RED": 12, "LED_GREEN": 13}, } - -FLASH_SIZE_1_MB = 2 ** 20 -FLASH_SIZE_512_KB = FLASH_SIZE_1_MB // 2 -FLASH_SIZE_2_MB = 2 * FLASH_SIZE_1_MB -FLASH_SIZE_4_MB = 4 * FLASH_SIZE_1_MB -FLASH_SIZE_16_MB = 16 * FLASH_SIZE_1_MB - -ESP8266_FLASH_SIZES = { - "d1": FLASH_SIZE_4_MB, - "d1_mini": FLASH_SIZE_4_MB, - "d1_mini_lite": FLASH_SIZE_1_MB, - "d1_mini_pro": FLASH_SIZE_16_MB, - "esp01": FLASH_SIZE_512_KB, - "esp01_1m": FLASH_SIZE_1_MB, - "esp07": FLASH_SIZE_4_MB, - "esp12e": FLASH_SIZE_4_MB, - "esp210": FLASH_SIZE_4_MB, - "esp8285": FLASH_SIZE_1_MB, - "esp_wroom_02": FLASH_SIZE_2_MB, - "espduino": FLASH_SIZE_4_MB, - "espectro": FLASH_SIZE_4_MB, - "espino": FLASH_SIZE_4_MB, - "espinotee": FLASH_SIZE_4_MB, - "espmxdevkit": FLASH_SIZE_1_MB, - "espresso_lite_v1": FLASH_SIZE_4_MB, - "espresso_lite_v2": FLASH_SIZE_4_MB, - "gen4iod": FLASH_SIZE_512_KB, - "heltec_wifi_kit_8": FLASH_SIZE_4_MB, - "huzzah": FLASH_SIZE_4_MB, - "inventone": FLASH_SIZE_4_MB, - "modwifi": FLASH_SIZE_2_MB, - "nodemcu": FLASH_SIZE_4_MB, - "nodemcuv2": FLASH_SIZE_4_MB, - "oak": FLASH_SIZE_4_MB, - "phoenix_v1": FLASH_SIZE_4_MB, - "phoenix_v2": FLASH_SIZE_4_MB, - "sonoff_basic": FLASH_SIZE_1_MB, - "sonoff_s20": FLASH_SIZE_1_MB, - "sonoff_sv": FLASH_SIZE_1_MB, - "sonoff_th": FLASH_SIZE_1_MB, - "sparkfunBlynk": FLASH_SIZE_4_MB, - "thing": FLASH_SIZE_512_KB, - "thingdev": FLASH_SIZE_512_KB, - "wifi_slot": FLASH_SIZE_1_MB, - "wifiduino": FLASH_SIZE_4_MB, - "wifinfo": FLASH_SIZE_1_MB, - "wio_link": FLASH_SIZE_4_MB, - "wio_node": FLASH_SIZE_4_MB, - "xinabox_cw01": FLASH_SIZE_4_MB, -} - -ESP8266_LD_SCRIPTS = { - FLASH_SIZE_512_KB: ("eagle.flash.512k0.ld", "eagle.flash.512k.ld"), - FLASH_SIZE_1_MB: ("eagle.flash.1m0.ld", "eagle.flash.1m.ld"), - FLASH_SIZE_2_MB: ("eagle.flash.2m.ld", "eagle.flash.2m.ld"), - FLASH_SIZE_4_MB: ("eagle.flash.4m.ld", "eagle.flash.4m.ld"), - FLASH_SIZE_16_MB: ("eagle.flash.16m.ld", "eagle.flash.16m14m.ld"), -} From 0469e19f54e10a5f55e18db19f7686fc6474b04c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 17 Nov 2021 09:52:40 +0100 Subject: [PATCH 035/111] Fix HM3301 AQI index calculator (#2739) --- esphome/components/hm3301/aqi_calculator.h | 2 +- esphome/components/hm3301/caqi_calculator.h | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/esphome/components/hm3301/aqi_calculator.h b/esphome/components/hm3301/aqi_calculator.h index 1410eac72b..a3839b643c 100644 --- a/esphome/components/hm3301/aqi_calculator.h +++ b/esphome/components/hm3301/aqi_calculator.h @@ -33,7 +33,7 @@ class AQICalculator : public AbstractAQICalculator { int conc_lo = array[grid_index][0]; int conc_hi = array[grid_index][1]; - return ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo; + return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo; } int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { diff --git a/esphome/components/hm3301/caqi_calculator.h b/esphome/components/hm3301/caqi_calculator.h index 51158454d0..a7f5460e0a 100644 --- a/esphome/components/hm3301/caqi_calculator.h +++ b/esphome/components/hm3301/caqi_calculator.h @@ -37,9 +37,7 @@ class CAQICalculator : public AbstractAQICalculator { int conc_lo = array[grid_index][0]; int conc_hi = array[grid_index][1]; - int aqi = ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo; - - return aqi; + return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo; } int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { From 6c1ef398bb7e981a6cd281175c89f702b21f19a0 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 17 Nov 2021 11:28:31 +0100 Subject: [PATCH 036/111] Re-instate device class update for binary sensors (#2743) --- esphome/components/binary_sensor/__init__.py | 2 ++ esphome/const.py | 1 + 2 files changed, 3 insertions(+) diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 3f11e18e45..1eab76d54e 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -49,6 +49,7 @@ from esphome.const import ( DEVICE_CLASS_SMOKE, DEVICE_CLASS_SOUND, DEVICE_CLASS_TAMPER, + DEVICE_CLASS_UPDATE, DEVICE_CLASS_VIBRATION, DEVICE_CLASS_WINDOW, ) @@ -82,6 +83,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_SMOKE, DEVICE_CLASS_SOUND, DEVICE_CLASS_TAMPER, + DEVICE_CLASS_UPDATE, DEVICE_CLASS_VIBRATION, DEVICE_CLASS_WINDOW, ] diff --git a/esphome/const.py b/esphome/const.py index 2a7942a2b4..f7beee8245 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -863,6 +863,7 @@ DEVICE_CLASS_SAFETY = "safety" DEVICE_CLASS_SMOKE = "smoke" DEVICE_CLASS_SOUND = "sound" DEVICE_CLASS_TAMPER = "tamper" +DEVICE_CLASS_UPDATE = "update" DEVICE_CLASS_VIBRATION = "vibration" DEVICE_CLASS_WINDOW = "window" # device classes of both binary_sensor and sensor component From df6730be55a19b5a3e1d8974beb4740fdc29e8fc Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 18 Nov 2021 06:23:17 +1300 Subject: [PATCH 037/111] Move to use improv lib from platformio (#2741) --- CODEOWNERS | 1 - esphome/components/esp32_improv/__init__.py | 3 +- .../esp32_improv/esp32_improv_component.h | 5 +- esphome/components/improv/__init__.py | 1 - esphome/components/improv/improv.cpp | 99 ------------------- esphome/components/improv/improv.h | 63 ------------ esphome/components/improv_serial/__init__.py | 2 +- .../improv_serial/improv_serial_component.h | 3 +- platformio.ini | 1 + 9 files changed, 9 insertions(+), 169 deletions(-) delete mode 100644 esphome/components/improv/__init__.py delete mode 100644 esphome/components/improv/improv.cpp delete mode 100644 esphome/components/improv/improv.h diff --git a/CODEOWNERS b/CODEOWNERS index 18b4564280..80c5dc34c1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -72,7 +72,6 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/homeassistant/* @OttoWinter esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/i2c/* @esphome/core -esphome/components/improv/* @jesserockz esphome/components/improv_serial/* @esphome/core esphome/components/inkbird_ibsth1_mini/* @fkirill esphome/components/inkplate6/* @jesserockz diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 0b0214c63e..80c53f7c2a 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -4,7 +4,7 @@ from esphome.components import binary_sensor, output, esp32_ble_server from esphome.const import CONF_ID -AUTO_LOAD = ["binary_sensor", "output", "improv", "esp32_ble_server"] +AUTO_LOAD = ["binary_sensor", "output", "esp32_ble_server"] CODEOWNERS = ["@jesserockz"] CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] DEPENDENCIES = ["wifi", "esp32"] @@ -56,6 +56,7 @@ async def to_code(config): cg.add(ble_server.register_service_component(var)) cg.add_define("USE_IMPROV") + cg.add_library("esphome/Improv", "1.0.0") cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION])) cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION])) diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index 3a5d150fbe..45639f2f63 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -1,9 +1,8 @@ #pragma once #include "esphome/components/binary_sensor/binary_sensor.h" -#include "esphome/components/esp32_ble_server/ble_server.h" #include "esphome/components/esp32_ble_server/ble_characteristic.h" -#include "esphome/components/improv/improv.h" +#include "esphome/components/esp32_ble_server/ble_server.h" #include "esphome/components/output/binary_output.h" #include "esphome/components/wifi/wifi_component.h" #include "esphome/core/component.h" @@ -12,6 +11,8 @@ #ifdef USE_ESP32 +#include + namespace esphome { namespace esp32_improv { diff --git a/esphome/components/improv/__init__.py b/esphome/components/improv/__init__.py deleted file mode 100644 index b1de57df8f..0000000000 --- a/esphome/components/improv/__init__.py +++ /dev/null @@ -1 +0,0 @@ -CODEOWNERS = ["@jesserockz"] diff --git a/esphome/components/improv/improv.cpp b/esphome/components/improv/improv.cpp deleted file mode 100644 index 759962b51a..0000000000 --- a/esphome/components/improv/improv.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "improv.h" - -namespace improv { - -ImprovCommand parse_improv_data(const std::vector &data, bool check_checksum) { - return parse_improv_data(data.data(), data.size(), check_checksum); -} - -ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum) { - ImprovCommand improv_command; - Command command = (Command) data[0]; - uint8_t data_length = data[1]; - - if (data_length != length - 2 - check_checksum) { - improv_command.command = UNKNOWN; - return improv_command; - } - - if (check_checksum) { - uint8_t checksum = data[length - 1]; - - uint32_t calculated_checksum = 0; - for (uint8_t i = 0; i < length - 1; i++) { - calculated_checksum += data[i]; - } - - if ((uint8_t) calculated_checksum != checksum) { - improv_command.command = BAD_CHECKSUM; - return improv_command; - } - } - - if (command == WIFI_SETTINGS) { - uint8_t ssid_length = data[2]; - uint8_t ssid_start = 3; - size_t ssid_end = ssid_start + ssid_length; - - uint8_t pass_length = data[ssid_end]; - size_t pass_start = ssid_end + 1; - size_t pass_end = pass_start + pass_length; - - std::string ssid(data + ssid_start, data + ssid_end); - std::string password(data + pass_start, data + pass_end); - return {.command = command, .ssid = ssid, .password = password}; - } - - improv_command.command = command; - return improv_command; -} - -std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum) { - std::vector out; - uint32_t length = 0; - out.push_back(command); - for (auto str : datum) { - uint8_t len = str.length(); - length += len; - out.push_back(len); - out.insert(out.end(), str.begin(), str.end()); - } - out.insert(out.begin() + 1, length); - - if (add_checksum) { - uint32_t calculated_checksum = 0; - - for (uint8_t byte : out) { - calculated_checksum += byte; - } - out.push_back(calculated_checksum); - } - return out; -} - -#ifdef ARDUINO -std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum) { - std::vector out; - uint32_t length = 0; - out.push_back(command); - for (auto str : datum) { - uint8_t len = str.length(); - length += len; - out.push_back(len); - out.insert(out.end(), str.begin(), str.end()); - } - out.insert(out.begin() + 1, length); - - if (add_checksum) { - uint32_t calculated_checksum = 0; - - for (uint8_t byte : out) { - calculated_checksum += byte; - } - out.push_back(calculated_checksum); - } - return out; -} -#endif // ARDUINO - -} // namespace improv diff --git a/esphome/components/improv/improv.h b/esphome/components/improv/improv.h deleted file mode 100644 index 9d1886ddaf..0000000000 --- a/esphome/components/improv/improv.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#ifdef ARDUINO -#include "WString.h" -#endif // ARDUINO - -#include -#include -#include - -namespace improv { - -static const char *const SERVICE_UUID = "00467768-6228-2272-4663-277478268000"; -static const char *const STATUS_UUID = "00467768-6228-2272-4663-277478268001"; -static const char *const ERROR_UUID = "00467768-6228-2272-4663-277478268002"; -static const char *const RPC_COMMAND_UUID = "00467768-6228-2272-4663-277478268003"; -static const char *const RPC_RESULT_UUID = "00467768-6228-2272-4663-277478268004"; -static const char *const CAPABILITIES_UUID = "00467768-6228-2272-4663-277478268005"; - -enum Error : uint8_t { - ERROR_NONE = 0x00, - ERROR_INVALID_RPC = 0x01, - ERROR_UNKNOWN_RPC = 0x02, - ERROR_UNABLE_TO_CONNECT = 0x03, - ERROR_NOT_AUTHORIZED = 0x04, - ERROR_UNKNOWN = 0xFF, -}; - -enum State : uint8_t { - STATE_STOPPED = 0x00, - STATE_AWAITING_AUTHORIZATION = 0x01, - STATE_AUTHORIZED = 0x02, - STATE_PROVISIONING = 0x03, - STATE_PROVISIONED = 0x04, -}; - -enum Command : uint8_t { - UNKNOWN = 0x00, - WIFI_SETTINGS = 0x01, - IDENTIFY = 0x02, - GET_CURRENT_STATE = 0x02, - GET_DEVICE_INFO = 0x03, - BAD_CHECKSUM = 0xFF, -}; - -static const uint8_t CAPABILITY_IDENTIFY = 0x01; - -struct ImprovCommand { - Command command; - std::string ssid; - std::string password; -}; - -ImprovCommand parse_improv_data(const std::vector &data, bool check_checksum = true); -ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum = true); - -std::vector build_rpc_response(Command command, const std::vector &datum, - bool add_checksum = true); -#ifdef ARDUINO -std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum = true); -#endif // ARDUINO - -} // namespace improv diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py index b1cdc2d93e..ed7c382a2f 100644 --- a/esphome/components/improv_serial/__init__.py +++ b/esphome/components/improv_serial/__init__.py @@ -5,7 +5,6 @@ import esphome.final_validate as fv CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["logger", "wifi"] -AUTO_LOAD = ["improv"] improv_serial_ns = cg.esphome_ns.namespace("improv_serial") @@ -31,3 +30,4 @@ FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + cg.add_library("esphome/Improv", "1.0.0") diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h index 539674e2d3..304afdaf75 100644 --- a/esphome/components/improv_serial/improv_serial_component.h +++ b/esphome/components/improv_serial/improv_serial_component.h @@ -1,11 +1,12 @@ #pragma once -#include "esphome/components/improv/improv.h" #include "esphome/components/wifi/wifi_component.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/helpers.h" +#include + #ifdef USE_ARDUINO #include #endif diff --git a/platformio.ini b/platformio.ini index 2ac6d0bfc8..b49438b097 100644 --- a/platformio.ini +++ b/platformio.ini @@ -28,6 +28,7 @@ build_flags = lib_deps = esphome/noise-c@0.1.4 ; api makuna/NeoPixelBus@2.6.9 ; neopixelbus + esphome/Improv@1.0.0 ; improv_serial / esp32_improv build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = From dee5d639e2771606906b0bf94b94f0f3331a6132 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 17 Nov 2021 18:24:02 +0100 Subject: [PATCH 038/111] Add max_telegram_length option to dsmr (#2674) Co-authored-by: Maurice Makaay Co-authored-by: Oxan van Leeuwen --- esphome/components/dsmr/__init__.py | 3 ++ esphome/components/dsmr/dsmr.cpp | 63 +++++++++++++++++------------ esphome/components/dsmr/dsmr.h | 10 ++++- tests/test3.yaml | 1 + 4 files changed, 50 insertions(+), 27 deletions(-) diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index dd6e6051aa..1cfc21a4ac 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -15,6 +15,7 @@ CONF_DSMR_ID = "dsmr_id" CONF_DECRYPTION_KEY = "decryption_key" CONF_CRC_CHECK = "crc_check" CONF_GAS_MBUS_ID = "gas_mbus_id" +CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length" # Hack to prevent compile error due to ambiguity with lib namespace dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr") @@ -46,6 +47,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DECRYPTION_KEY): _validate_key, cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean, cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, + cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_, } ).extend(uart.UART_DEVICE_SCHEMA), cv.only_with_arduino, @@ -55,6 +57,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): uart_component = await cg.get_variable(config[CONF_UART_ID]) var = cg.new_Pvariable(config[CONF_ID], uart_component, config[CONF_CRC_CHECK]) + cg.add(var.set_max_telegram_length(config[CONF_MAX_TELEGRAM_LENGTH])) if CONF_DECRYPTION_KEY in config: cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY])) await cg.register_component(var, config) diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index ea852e626e..631b18a1f4 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -12,11 +12,15 @@ namespace dsmr { static const char *const TAG = "dsmr"; +void Dsmr::setup() { + telegram_ = new char[max_telegram_len_]; // NOLINT +} + void Dsmr::loop() { - if (this->decryption_key_.empty()) - this->receive_telegram_(); + if (decryption_key_.empty()) + receive_telegram_(); else - this->receive_encrypted_(); + receive_encrypted_(); } bool Dsmr::available_within_timeout_() { @@ -51,10 +55,10 @@ void Dsmr::receive_telegram_() { continue; // Check for buffer overflow. - if (telegram_len_ >= MAX_TELEGRAM_LENGTH) { + if (telegram_len_ >= max_telegram_len_) { header_found_ = false; footer_found_ = false; - ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH); + ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", max_telegram_len_); return; } @@ -86,9 +90,7 @@ void Dsmr::receive_telegram_() { } void Dsmr::receive_encrypted_() { - // Encrypted buffer - uint8_t buffer[MAX_TELEGRAM_LENGTH]; - size_t buffer_length = 0; + encrypted_telegram_len_ = 0; size_t packet_size = 0; while (true) { @@ -114,39 +116,39 @@ void Dsmr::receive_encrypted_() { } // Check for buffer overflow. - if (buffer_length >= MAX_TELEGRAM_LENGTH) { + if (encrypted_telegram_len_ >= max_telegram_len_) { header_found_ = false; - ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH); + ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", max_telegram_len_); return; } - buffer[buffer_length++] = c; + encrypted_telegram_[encrypted_telegram_len_++] = c; - if (packet_size == 0 && buffer_length > 20) { + if (packet_size == 0 && encrypted_telegram_len_ > 20) { // Complete header + data bytes - packet_size = 13 + (buffer[11] << 8 | buffer[12]); + packet_size = 13 + (encrypted_telegram_[11] << 8 | encrypted_telegram_[12]); ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size); } - if (buffer_length == packet_size && packet_size > 0) { + if (encrypted_telegram_len_ == packet_size && packet_size > 0) { ESP_LOGV(TAG, "End of encrypted telegram found"); GCM *gcmaes128{new GCM()}; - gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize()); + gcmaes128->setKey(decryption_key_.data(), gcmaes128->keySize()); // the iv is 8 bytes of the system title + 4 bytes frame counter // system title is at byte 2 and frame counter at byte 15 for (int i = 10; i < 14; i++) - buffer[i] = buffer[i + 4]; + encrypted_telegram_[i] = encrypted_telegram_[i + 4]; constexpr uint16_t iv_size{12}; - gcmaes128->setIV(&buffer[2], iv_size); - gcmaes128->decrypt(reinterpret_cast(this->telegram_), + gcmaes128->setIV(&encrypted_telegram_[2], iv_size); + gcmaes128->decrypt(reinterpret_cast(telegram_), // the ciphertext start at byte 18 - &buffer[18], + &encrypted_telegram_[18], // cipher size - buffer_length - 17); + encrypted_telegram_len_ - 17); delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory) - telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_)); + telegram_len_ = strnlen(telegram_, max_telegram_len_); ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_); - ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); + ESP_LOGVV(TAG, "Decrypted telegram: %s", telegram_); parse_telegram(); @@ -162,7 +164,7 @@ bool Dsmr::parse_telegram() { ESP_LOGV(TAG, "Trying to parse telegram"); ::dsmr::ParseResult res = ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false, - this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. + crc_check_); // Parse telegram according to data definition. Ignore unknown values. if (res.err) { // Parsing error, show it auto err_str = res.fullError(telegram_, telegram_ + telegram_len_); @@ -177,6 +179,7 @@ bool Dsmr::parse_telegram() { void Dsmr::dump_config() { ESP_LOGCONFIG(TAG, "DSMR:"); + ESP_LOGCONFIG(TAG, " Max telegram length: %d", max_telegram_len_); #define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_); DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, ) @@ -188,7 +191,11 @@ void Dsmr::dump_config() { void Dsmr::set_decryption_key(const std::string &decryption_key) { if (decryption_key.length() == 0) { ESP_LOGI(TAG, "Disabling decryption"); - this->decryption_key_.clear(); + decryption_key_.clear(); + if (encrypted_telegram_ != nullptr) { + delete[] encrypted_telegram_; + encrypted_telegram_ = nullptr; + } return; } @@ -196,7 +203,7 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { ESP_LOGE(TAG, "Error, decryption key must be 32 character long"); return; } - this->decryption_key_.clear(); + decryption_key_.clear(); ESP_LOGI(TAG, "Decryption key is set"); // Verbose level prints decryption key @@ -207,8 +214,14 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { strncpy(temp, &(decryption_key.c_str()[i * 2]), 2); decryption_key_.push_back(std::strtoul(temp, nullptr, 16)); } + + if (encrypted_telegram_ == nullptr) { + encrypted_telegram_ = new uint8_t[max_telegram_len_]; // NOLINT + } } +void Dsmr::set_max_telegram_length(size_t length) { max_telegram_len_ = length; } + } // namespace dsmr } // namespace esphome diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index ca2c0f0877..5943e4d47f 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -16,7 +16,6 @@ namespace esphome { namespace dsmr { -static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500; static constexpr uint32_t READ_TIMEOUT_MS = 200; using namespace ::dsmr::fields; @@ -52,6 +51,8 @@ class Dsmr : public Component, public uart::UARTDevice { public: Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {} + void setup() override; + void loop() override; bool parse_telegram(); @@ -72,6 +73,8 @@ class Dsmr : public Component, public uart::UARTDevice { void set_decryption_key(const std::string &decryption_key); + void set_max_telegram_length(size_t length); + // Sensor setters #define DSMR_SET_SENSOR(s) \ void set_##s(sensor::Sensor *sensor) { s_##s##_ = sensor; } @@ -97,8 +100,11 @@ class Dsmr : public Component, public uart::UARTDevice { bool available_within_timeout_(); // Telegram buffer - char telegram_[MAX_TELEGRAM_LENGTH]; + size_t max_telegram_len_; + char *telegram_{nullptr}; int telegram_len_{0}; + uint8_t *encrypted_telegram_{nullptr}; + int encrypted_telegram_len_{0}; // Serial parser bool header_found_{false}; diff --git a/tests/test3.yaml b/tests/test3.yaml index cf80c06aa8..0b9297a0b8 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1308,6 +1308,7 @@ fingerprint_grow: dsmr: decryption_key: 00112233445566778899aabbccddeeff uart_id: uart6 + max_telegram_length: 1000 daly_bms: update_interval: 20s From 06994c0dfc45270cb34327e61f758d7c55b7dded Mon Sep 17 00:00:00 2001 From: spattinson Date: Wed, 17 Nov 2021 17:28:36 +0000 Subject: [PATCH 039/111] Change LUT for ttgo t5 2.13inch to improve partial refresh (#2475) --- esphome/components/waveshare_epaper/waveshare_epaper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 92fa289cfa..ee3fb2fe47 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -1183,7 +1183,7 @@ static const uint8_t PART_UPDATE_LUT_TTGO_DKE[LUT_SIZE_TTGO_DKE_PART] = { 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, From 6f9439e1bc60807d7fe7d8f2d009b8fadda4c02c Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Wed, 17 Nov 2021 21:35:50 +0400 Subject: [PATCH 040/111] Fix byte order in NEC protocol implementation (#2534) --- esphome/components/remote_base/nec_protocol.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/remote_base/nec_protocol.cpp b/esphome/components/remote_base/nec_protocol.cpp index 79a30903a4..47b4d676dd 100644 --- a/esphome/components/remote_base/nec_protocol.cpp +++ b/esphome/components/remote_base/nec_protocol.cpp @@ -17,14 +17,14 @@ void NECProtocol::encode(RemoteTransmitData *dst, const NECData &data) { dst->set_carrier_frequency(38000); dst->item(HEADER_HIGH_US, HEADER_LOW_US); - for (uint32_t mask = 1UL << 15; mask; mask >>= 1) { + for (uint16_t mask = 1; mask; mask <<= 1) { if (data.address & mask) dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); else dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); } - for (uint32_t mask = 1UL << 15; mask; mask >>= 1) { + for (uint16_t mask = 1; mask; mask <<= 1) { if (data.command & mask) dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); else @@ -41,7 +41,7 @@ optional NECProtocol::decode(RemoteReceiveData src) { if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) return {}; - for (uint32_t mask = 1UL << 15; mask != 0; mask >>= 1) { + for (uint16_t mask = 1; mask; mask <<= 1) { if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { data.address |= mask; } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { @@ -51,7 +51,7 @@ optional NECProtocol::decode(RemoteReceiveData src) { } } - for (uint32_t mask = 1UL << 15; mask != 0; mask >>= 1) { + for (uint16_t mask = 1; mask; mask <<= 1) { if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { data.command |= mask; } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { From 8267f01ccdcb4d9564d262f3ec0a02fb0650c107 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Wed, 17 Nov 2021 20:03:46 +0100 Subject: [PATCH 041/111] Remove arduino dependency from hm3301 (#2745) --- .../hm3301/abstract_aqi_calculator.h | 3 --- esphome/components/hm3301/aqi_calculator.h | 4 ---- .../components/hm3301/aqi_calculator_factory.h | 4 ---- esphome/components/hm3301/caqi_calculator.h | 4 ---- esphome/components/hm3301/hm3301.cpp | 13 +++---------- esphome/components/hm3301/hm3301.h | 18 ++++++++---------- esphome/components/hm3301/sensor.py | 4 ---- platformio.ini | 1 - 8 files changed, 11 insertions(+), 40 deletions(-) diff --git a/esphome/components/hm3301/abstract_aqi_calculator.h b/esphome/components/hm3301/abstract_aqi_calculator.h index fb41b921d9..42d900a262 100644 --- a/esphome/components/hm3301/abstract_aqi_calculator.h +++ b/esphome/components/hm3301/abstract_aqi_calculator.h @@ -1,6 +1,5 @@ #pragma once -#ifdef USE_ARDUINO #include namespace esphome { @@ -13,5 +12,3 @@ class AbstractAQICalculator { } // namespace hm3301 } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/aqi_calculator.h b/esphome/components/hm3301/aqi_calculator.h index a3839b643c..08d1dc2921 100644 --- a/esphome/components/hm3301/aqi_calculator.h +++ b/esphome/components/hm3301/aqi_calculator.h @@ -1,7 +1,5 @@ #pragma once -#ifdef USE_ARDUINO - #include "abstract_aqi_calculator.h" namespace esphome { @@ -48,5 +46,3 @@ class AQICalculator : public AbstractAQICalculator { } // namespace hm3301 } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/aqi_calculator_factory.h b/esphome/components/hm3301/aqi_calculator_factory.h index 3c6f9709b6..55608b6e51 100644 --- a/esphome/components/hm3301/aqi_calculator_factory.h +++ b/esphome/components/hm3301/aqi_calculator_factory.h @@ -1,7 +1,5 @@ #pragma once -#ifdef USE_ARDUINO - #include "caqi_calculator.h" #include "aqi_calculator.h" @@ -29,5 +27,3 @@ class AQICalculatorFactory { } // namespace hm3301 } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/caqi_calculator.h b/esphome/components/hm3301/caqi_calculator.h index a7f5460e0a..1ec61f2416 100644 --- a/esphome/components/hm3301/caqi_calculator.h +++ b/esphome/components/hm3301/caqi_calculator.h @@ -1,7 +1,5 @@ #pragma once -#ifdef USE_ARDUINO - #include "esphome/core/log.h" #include "abstract_aqi_calculator.h" @@ -52,5 +50,3 @@ class CAQICalculator : public AbstractAQICalculator { } // namespace hm3301 } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/hm3301.cpp b/esphome/components/hm3301/hm3301.cpp index 759157f330..a2bef2a01d 100644 --- a/esphome/components/hm3301/hm3301.cpp +++ b/esphome/components/hm3301/hm3301.cpp @@ -1,5 +1,3 @@ -#ifdef USE_ARDUINO - #include "esphome/core/log.h" #include "hm3301.h" @@ -14,9 +12,8 @@ static const uint8_t PM_10_0_VALUE_INDEX = 7; void HM3301Component::setup() { ESP_LOGCONFIG(TAG, "Setting up HM3301..."); - hm3301_ = make_unique(); - error_code_ = hm3301_->init(); - if (error_code_ != NO_ERROR) { + if (i2c::ERROR_OK != this->write(&SELECT_COMM_CMD, 1)) { + error_code_ = ERROR_COMM; this->mark_failed(); return; } @@ -38,7 +35,7 @@ void HM3301Component::dump_config() { float HM3301Component::get_setup_priority() const { return setup_priority::DATA; } void HM3301Component::update() { - if (!this->read_sensor_value_(data_buffer_)) { + if (this->read(data_buffer_, 29) != i2c::ERROR_OK) { ESP_LOGW(TAG, "Read result failed"); this->status_set_warning(); return; @@ -87,8 +84,6 @@ void HM3301Component::update() { this->status_clear_warning(); } -bool HM3301Component::read_sensor_value_(uint8_t *data) { return !hm3301_->read_sensor_value(data, 29); } - bool HM3301Component::validate_checksum_(const uint8_t *data) { uint8_t sum = 0; for (int i = 0; i < 28; i++) { @@ -104,5 +99,3 @@ uint16_t HM3301Component::get_sensor_value_(const uint8_t *data, uint8_t i) { } // namespace hm3301 } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/hm3301.h b/esphome/components/hm3301/hm3301.h index 61bbf7e4ab..e13ffa466e 100644 --- a/esphome/components/hm3301/hm3301.h +++ b/esphome/components/hm3301/hm3301.h @@ -1,17 +1,15 @@ #pragma once -#ifdef USE_ARDUINO - #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" #include "aqi_calculator_factory.h" -#include - namespace esphome { namespace hm3301 { +static const uint8_t SELECT_COMM_CMD = 0X88; + class HM3301Component : public PollingComponent, public i2c::I2CDevice { public: HM3301Component() = default; @@ -29,9 +27,12 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { void update() override; protected: - std::unique_ptr hm3301_; - - HM330XErrorCode error_code_{NO_ERROR}; + enum { + NO_ERROR = 0, + ERROR_PARAM = -1, + ERROR_COMM = -2, + ERROR_OTHERS = -128, + } error_code_{NO_ERROR}; uint8_t data_buffer_[30]; @@ -43,12 +44,9 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { AQICalculatorType aqi_calc_type_; AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory(); - bool read_sensor_value_(uint8_t *); bool validate_checksum_(const uint8_t *); uint16_t get_sensor_value_(const uint8_t *, uint8_t); }; } // namespace hm3301 } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py index 976a0488e1..8e9ee4c6fb 100644 --- a/esphome/components/hm3301/sensor.py +++ b/esphome/components/hm3301/sensor.py @@ -84,7 +84,6 @@ CONFIG_SCHEMA = cv.All( .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x40)), _validate, - cv.only_with_arduino, ) @@ -109,6 +108,3 @@ async def to_code(config): sens = await sensor.new_sensor(config[CONF_AQI]) cg.add(var.set_aqi_sensor(sens)) cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE])) - - # https://platformio.org/lib/show/6306/Grove%20-%20Laser%20PM2.5%20Sensor%20HM3301 - cg.add_library("seeed-studio/Grove - Laser PM2.5 Sensor HM3301", "1.0.3") diff --git a/platformio.ini b/platformio.ini index b49438b097..0f80d6d8d3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -46,7 +46,6 @@ lib_deps = fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 - seeed-studio/Grove - Laser PM2.5 Sensor HM3301@1.0.3 ; hm3301 glmnet/Dsmr@0.5 ; dsmr rweather/Crypto@0.2.0 ; dsmr dudanov/MideaUART@1.1.8 ; midea From 448e1690aac902cca7d614b2902514fa59e8d6ab Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Wed, 17 Nov 2021 23:59:40 +0100 Subject: [PATCH 042/111] Add retry handler (#2721) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen --- esphome/core/component.cpp | 13 ++++++++ esphome/core/component.h | 34 +++++++++++++++++++- esphome/core/scheduler.cpp | 63 ++++++++++++++++++++++++++++++-------- esphome/core/scheduler.h | 27 ++++++++++++++-- 4 files changed, 122 insertions(+), 15 deletions(-) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 5692194a91..f97818cfb1 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -55,6 +55,15 @@ bool Component::cancel_interval(const std::string &name) { // NOLINT return App.scheduler.cancel_interval(this, name); } +void Component::set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, + std::function &&f, float backoff_increase_factor) { // NOLINT + App.scheduler.set_retry(this, name, initial_wait_time, max_attempts, std::move(f), backoff_increase_factor); +} + +bool Component::cancel_retry(const std::string &name) { // NOLINT + return App.scheduler.cancel_retry(this, name); +} + void Component::set_timeout(const std::string &name, uint32_t timeout, std::function &&f) { // NOLINT return App.scheduler.set_timeout(this, name, timeout, std::move(f)); } @@ -120,6 +129,10 @@ void Component::set_timeout(uint32_t timeout, std::function &&f) { // N void Component::set_interval(uint32_t interval, std::function &&f) { // NOLINT App.scheduler.set_interval(this, "", interval, std::move(f)); } +void Component::set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function &&f, + float backoff_increase_factor) { // NOLINT + App.scheduler.set_retry(this, "", initial_wait_time, max_attempts, std::move(f), backoff_increase_factor); +} bool Component::is_failed() { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; } bool Component::can_proceed() { return true; } bool Component::status_has_warning() { return this->component_state_ & STATUS_LED_WARNING; } diff --git a/esphome/core/component.h b/esphome/core/component.h index a1afc17c2c..c3a4ac3782 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -61,6 +61,8 @@ extern const uint32_t STATUS_LED_OK; extern const uint32_t STATUS_LED_WARNING; extern const uint32_t STATUS_LED_ERROR; +enum RetryResult { DONE, RETRY }; + class Component { public: /** Where the component's initialization should happen. @@ -180,7 +182,35 @@ class Component { */ bool cancel_interval(const std::string &name); // NOLINT - void set_timeout(uint32_t timeout, std::function &&f); // NOLINT + /** Set an retry function with a unique name. Empty name means no cancelling possible. + * + * This will call f. If f returns RetryResult::RETRY f is called again after initial_wait_time ms. + * f should return RetryResult::DONE if no repeat is required. The initial wait time will be increased + * by backoff_increase_factor for each iteration. Default is doubling the time between iterations + * Can be cancelled via cancel_retry(). + * + * IMPORTANT: Do not rely on this having correct timing. This is only called from + * loop() and therefore can be significantly delayed. + * + * @param name The identifier for this retry function. + * @param initial_wait_time The time in ms before f is called again + * @param max_attempts The maximum number of retries + * @param f The function (or lambda) that should be called + * @param backoff_increase_factor time between retries is increased by this factor on every retry + * @see cancel_retry() + */ + void set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT + std::function &&f, float backoff_increase_factor = 1.0f); // NOLINT + + void set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function &&f, // NOLINT + float backoff_increase_factor = 1.0f); // NOLINT + + /** Cancel a retry function. + * + * @param name The identifier for this retry function. + * @return Whether a retry function was deleted. + */ + bool cancel_retry(const std::string &name); // NOLINT /** Set a timeout function with a unique name. * @@ -198,6 +228,8 @@ class Component { */ void set_timeout(const std::string &name, uint32_t timeout, std::function &&f); // NOLINT + void set_timeout(uint32_t timeout, std::function &&f); // NOLINT + /** Cancel a timeout function. * * @param name The identifier for this timeout function. diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index a6d3e0307e..3fe07f94b5 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -32,7 +32,7 @@ void HOT Scheduler::set_timeout(Component *component, const std::string &name, u item->timeout = timeout; item->last_execution = now; item->last_execution_major = this->millis_major_; - item->f = std::move(func); + item->void_callback = std::move(func); item->remove = false; this->push_(std::move(item)); } @@ -65,13 +65,47 @@ void HOT Scheduler::set_interval(Component *component, const std::string &name, item->last_execution_major = this->millis_major_; if (item->last_execution > now) item->last_execution_major--; - item->f = std::move(func); + item->void_callback = std::move(func); item->remove = false; this->push_(std::move(item)); } bool HOT Scheduler::cancel_interval(Component *component, const std::string &name) { return this->cancel_item_(component, name, SchedulerItem::INTERVAL); } + +void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, + uint8_t max_attempts, std::function &&func, + float backoff_increase_factor) { + const uint32_t now = this->millis_(); + + if (!name.empty()) + this->cancel_retry(component, name); + + if (initial_wait_time == SCHEDULER_DONT_RUN) + return; + + ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%u,max_attempts=%u, backoff_factor=%0.1f)", name.c_str(), + initial_wait_time, max_attempts, backoff_increase_factor); + + auto item = make_unique(); + item->component = component; + item->name = name; + item->type = SchedulerItem::RETRY; + item->interval = initial_wait_time; + item->retry_countdown = max_attempts; + item->backoff_multiplier = backoff_increase_factor; + item->last_execution = now - initial_wait_time; + item->last_execution_major = this->millis_major_; + if (item->last_execution > now) + item->last_execution_major--; + item->retry_callback = std::move(func); + item->remove = false; + this->push_(std::move(item)); +} +bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) { + return this->cancel_item_(component, name, SchedulerItem::RETRY); +} + optional HOT Scheduler::next_schedule_in() { if (this->empty_()) return {}; @@ -95,10 +129,9 @@ void IRAM_ATTR HOT Scheduler::call() { ESP_LOGVV(TAG, "Items: count=%u, now=%u", this->items_.size(), now); while (!this->empty_()) { auto item = std::move(this->items_[0]); - const char *type = item->type == SchedulerItem::INTERVAL ? "interval" : "timeout"; - ESP_LOGVV(TAG, " %s '%s' interval=%u last_execution=%u (%u) next=%u (%u)", type, item->name.c_str(), - item->interval, item->last_execution, item->last_execution_major, item->next_execution(), - item->next_execution_major()); + ESP_LOGVV(TAG, " %s '%s' interval=%u last_execution=%u (%u) next=%u (%u)", item->get_type_str(), + item->name.c_str(), item->interval, item->last_execution, item->last_execution_major, + item->next_execution(), item->next_execution_major()); this->pop_raw_(); old_items.push_back(std::move(item)); @@ -129,6 +162,7 @@ void IRAM_ATTR HOT Scheduler::call() { } while (!this->empty_()) { + RetryResult retry_result = RETRY; // use scoping to indicate visibility of `item` variable { // Don't copy-by value yet @@ -147,17 +181,19 @@ void IRAM_ATTR HOT Scheduler::call() { } #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE - const char *type = item->type == SchedulerItem::INTERVAL ? "interval" : "timeout"; - ESP_LOGVV(TAG, "Running %s '%s' with interval=%u last_execution=%u (now=%u)", type, item->name.c_str(), - item->interval, item->last_execution, now); + ESP_LOGVV(TAG, "Running %s '%s' with interval=%u last_execution=%u (now=%u)", item->get_type_str(), + item->name.c_str(), item->interval, item->last_execution, now); #endif - // Warning: During f(), a lot of stuff can happen, including: + // Warning: During callback(), a lot of stuff can happen, including: // - timeouts/intervals get added, potentially invalidating vector pointers // - timeouts/intervals get cancelled { WarnIfComponentBlockingGuard guard{item->component}; - item->f(); + if (item->type == SchedulerItem::RETRY) + retry_result = item->retry_callback(); + else + item->void_callback(); } } @@ -175,13 +211,16 @@ void IRAM_ATTR HOT Scheduler::call() { continue; } - if (item->type == SchedulerItem::INTERVAL) { + if (item->type == SchedulerItem::INTERVAL || + (item->type == SchedulerItem::RETRY && (--item->retry_countdown > 0 && retry_result != RetryResult::DONE))) { if (item->interval != 0) { const uint32_t before = item->last_execution; const uint32_t amount = (now - item->last_execution) / item->interval; item->last_execution += amount * item->interval; if (item->last_execution < before) item->last_execution_major++; + if (item->type == SchedulerItem::RETRY) + item->interval *= item->backoff_multiplier; } this->push_(std::move(item)); } diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index d1839cb4a7..dc96d58329 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -15,6 +15,10 @@ class Scheduler { void set_interval(Component *component, const std::string &name, uint32_t interval, std::function &&func); bool cancel_interval(Component *component, const std::string &name); + void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, + std::function &&func, float backoff_increase_factor = 1.0f); + bool cancel_retry(Component *component, const std::string &name); + optional next_schedule_in(); void call(); @@ -25,13 +29,20 @@ class Scheduler { struct SchedulerItem { Component *component; std::string name; - enum Type { TIMEOUT, INTERVAL } type; + enum Type { TIMEOUT, INTERVAL, RETRY } type; union { uint32_t interval; uint32_t timeout; }; uint32_t last_execution; - std::function f; + // Ideally this should be a union or std::variant + // but unions don't work with object like std::function + // union CallBack_{ + std::function void_callback; + std::function retry_callback; + // }; + uint8_t retry_countdown{3}; + float backoff_multiplier{1.0f}; bool remove; uint8_t last_execution_major; @@ -45,6 +56,18 @@ class Scheduler { } static bool cmp(const std::unique_ptr &a, const std::unique_ptr &b); + const char *get_type_str() { + switch (this->type) { + case SchedulerItem::INTERVAL: + return "interval"; + case SchedulerItem::RETRY: + return "retry"; + case SchedulerItem::TIMEOUT: + return "timeout"; + default: + return ""; + } + } }; uint32_t millis_(); From 9e1c3e8f01c82f8ee5c227ff0c3b6d8359bf9c3b Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Thu, 18 Nov 2021 22:41:26 +0100 Subject: [PATCH 043/111] Allow UART debug configuration with no after: definition (#2753) --- esphome/components/uart/__init__.py | 10 +++++++--- tests/test2.yaml | 6 ++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 53209dfc7b..159b08d2d9 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -94,17 +94,21 @@ UART_DIRECTIONS = { "BOTH": UARTDirection.UART_DIRECTION_BOTH, } +AFTER_DEFAULTS = {CONF_BYTES: 256, CONF_TIMEOUT: "100ms"} + DEBUG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UARTDebugger), cv.Optional(CONF_DIRECTION, default="BOTH"): cv.enum( UART_DIRECTIONS, upper=True ), - cv.Optional(CONF_AFTER): cv.Schema( + cv.Optional(CONF_AFTER, default=AFTER_DEFAULTS): cv.Schema( { - cv.Optional(CONF_BYTES, default=256): cv.validate_bytes, cv.Optional( - CONF_TIMEOUT, default="100ms" + CONF_BYTES, default=AFTER_DEFAULTS[CONF_BYTES] + ): cv.validate_bytes, + cv.Optional( + CONF_TIMEOUT, default=AFTER_DEFAULTS[CONF_TIMEOUT] ): cv.positive_time_period_milliseconds, cv.Optional(CONF_DELIMITER): cv.templatable(validate_raw_data), } diff --git a/tests/test2.yaml b/tests/test2.yaml index f90e522b1e..3afef9501d 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -39,6 +39,12 @@ uart: tx_pin: GPIO22 rx_pin: GPIO23 baud_rate: 115200 + # Specifically added for testing debug with no after: definition. + debug: + dummy_receiver: false + direction: rx + sequence: + - lambda: UARTDebug::log_hex(direction, bytes, ':'); ota: safe_mode: True From e5cb5756aa1feb7ccb47dfc20d9039b0985b09cb Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Thu, 18 Nov 2021 22:20:32 +0000 Subject: [PATCH 044/111] Fix frame scaling for animated gifs (#2750) --- esphome/components/animation/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index 3f03e5c185..1780bdf72e 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -60,6 +60,10 @@ async def to_code(config): image.seek(frameIndex) frame = image.convert("L", dither=Image.NONE) pixels = list(frame.getdata()) + if len(pixels) != height * width: + raise core.EsphomeError( + f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}" + ) for pix in pixels: data[pos] = pix pos += 1 @@ -69,8 +73,14 @@ async def to_code(config): pos = 0 for frameIndex in range(frames): image.seek(frameIndex) + if CONF_RESIZE in config: + image.thumbnail(config[CONF_RESIZE]) frame = image.convert("RGB") pixels = list(frame.getdata()) + if len(pixels) != height * width: + raise core.EsphomeError( + f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}" + ) for pix in pixels: data[pos] = pix[0] pos += 1 From 61ec16cdfc2ffeb69a7bbdc06c67f88ea79793fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Mon, 22 Nov 2021 00:09:11 +0100 Subject: [PATCH 045/111] esp32_camera_web_server: Improve support for MotionEye (#2777) --- .../camera_web_server.cpp | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.cpp b/esphome/components/esp32_camera_web_server/camera_web_server.cpp index c9a684c7e5..653a274bf4 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.cpp +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp @@ -21,12 +21,19 @@ static const char *const TAG = "esp32_camera_web_server"; #define CONTENT_TYPE "image/jpeg" #define CONTENT_LENGTH "Content-Length" -static const char *const STREAM_HEADER = - "HTTP/1.1 200\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY - "\r\n"; -static const char *const STREAM_500 = "HTTP/1.1 500\r\nContent-Type: text/plain\r\n\r\nNo frames send.\r\n"; -static const char *const STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; +static const char *const STREAM_HEADER = "HTTP/1.0 200 OK\r\n" + "Access-Control-Allow-Origin: *\r\n" + "Connection: close\r\n" + "Content-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY "\r\n" + "\r\n" + "--" PART_BOUNDARY "\r\n"; +static const char *const STREAM_ERROR = "Content-Type: text/plain\r\n" + "\r\n" + "No frames send.\r\n" + "--" PART_BOUNDARY "\r\n"; static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n"; +static const char *const STREAM_BOUNDARY = "\r\n" + "--" PART_BOUNDARY "\r\n"; CameraWebServer::CameraWebServer() {} @@ -45,6 +52,7 @@ void CameraWebServer::setup() { config.ctrl_port = this->port_; config.max_open_sockets = 1; config.backlog_conn = 2; + config.lru_purge_enable = true; if (httpd_start(&this->httpd_, &config) != ESP_OK) { mark_failed(); @@ -172,9 +180,6 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) { ESP_LOGW(TAG, "STREAM: failed to acquire frame"); res = ESP_FAIL; } - if (res == ESP_OK) { - res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY)); - } if (res == ESP_OK) { size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length()); res = httpd_send_all(req, part_buf, hlen); @@ -182,6 +187,9 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) { if (res == ESP_OK) { res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length()); } + if (res == ESP_OK) { + res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY)); + } if (res == ESP_OK) { frames++; int64_t frame_time = millis() - last_frame; @@ -193,7 +201,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) { } if (!frames) { - res = httpd_send_all(req, STREAM_500, strlen(STREAM_500)); + res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR)); } ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames); From 1424091ee51a31d75558f8e4a029ea80edb4d415 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Sun, 21 Nov 2021 15:11:36 -0800 Subject: [PATCH 046/111] Remove floating point ops from the ISR (#2751) Co-authored-by: Samuel Sieb --- esphome/components/zyaura/zyaura.cpp | 40 +++++++++++++++++----------- esphome/components/zyaura/zyaura.h | 8 +++--- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/esphome/components/zyaura/zyaura.cpp b/esphome/components/zyaura/zyaura.cpp index 11643a5c23..621439aa0c 100644 --- a/esphome/components/zyaura/zyaura.cpp +++ b/esphome/components/zyaura/zyaura.cpp @@ -57,38 +57,46 @@ void IRAM_ATTR ZaSensorStore::interrupt(ZaSensorStore *arg) { void IRAM_ATTR ZaSensorStore::set_data_(ZaMessage *message) { switch (message->type) { case HUMIDITY: - this->humidity = (message->value > 10000) ? NAN : (message->value / 100.0f); + this->humidity = message->value; break; - case TEMPERATURE: - this->temperature = (message->value > 5970) ? NAN : (message->value / 16.0f - 273.15f); + this->temperature = message->value; break; - case CO2: - this->co2 = (message->value > 10000) ? NAN : message->value; - break; - - default: + this->co2 = message->value; break; } } -bool ZyAuraSensor::publish_state_(sensor::Sensor *sensor, float *value) { - // Sensor doesn't added to configuration +bool ZyAuraSensor::publish_state_(ZaDataType data_type, sensor::Sensor *sensor, uint16_t *data_value) { + // Sensor wasn't added to configuration if (sensor == nullptr) { return true; } - sensor->publish_state(*value); + float value = NAN; + switch (data_type) { + case HUMIDITY: + value = (*data_value > 10000) ? NAN : (*data_value / 100.0f); + break; + case TEMPERATURE: + value = (*data_value > 5970) ? NAN : (*data_value / 16.0f - 273.15f); + break; + case CO2: + value = (*data_value > 10000) ? NAN : *data_value; + break; + } + + sensor->publish_state(value); // Sensor reported wrong value - if (std::isnan(*value)) { + if (std::isnan(value)) { ESP_LOGW(TAG, "Sensor reported invalid data. Is the update interval too small?"); this->status_set_warning(); return false; } - *value = NAN; + *data_value = -1; return true; } @@ -104,9 +112,9 @@ void ZyAuraSensor::dump_config() { } void ZyAuraSensor::update() { - bool co2_result = this->publish_state_(this->co2_sensor_, &this->store_.co2); - bool temperature_result = this->publish_state_(this->temperature_sensor_, &this->store_.temperature); - bool humidity_result = this->publish_state_(this->humidity_sensor_, &this->store_.humidity); + bool co2_result = this->publish_state_(CO2, this->co2_sensor_, &this->store_.co2); + bool temperature_result = this->publish_state_(TEMPERATURE, this->temperature_sensor_, &this->store_.temperature); + bool humidity_result = this->publish_state_(HUMIDITY, this->humidity_sensor_, &this->store_.humidity); if (co2_result && temperature_result && humidity_result) { this->status_clear_warning(); diff --git a/esphome/components/zyaura/zyaura.h b/esphome/components/zyaura/zyaura.h index 2b9e3fbb35..85c31ec75a 100644 --- a/esphome/components/zyaura/zyaura.h +++ b/esphome/components/zyaura/zyaura.h @@ -42,9 +42,9 @@ class ZaDataProcessor { class ZaSensorStore { public: - float co2 = NAN; - float temperature = NAN; - float humidity = NAN; + uint16_t co2 = -1; + uint16_t temperature = -1; + uint16_t humidity = -1; void setup(InternalGPIOPin *pin_clock, InternalGPIOPin *pin_data); static void interrupt(ZaSensorStore *arg); @@ -79,7 +79,7 @@ class ZyAuraSensor : public PollingComponent { sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; - bool publish_state_(sensor::Sensor *sensor, float *value); + bool publish_state_(ZaDataType data_type, sensor::Sensor *sensor, uint16_t *data_value); }; } // namespace zyaura From 897277992b3bf6608c1622780de5c5f209ca3102 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 23 Nov 2021 08:30:49 +0100 Subject: [PATCH 047/111] Introduce str_snprintf helper function (#2780) --- esphome/core/helpers.cpp | 18 ++++++++++++++++-- esphome/core/helpers.h | 5 ++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 3e614eb515..37d9cdc24b 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -48,13 +48,13 @@ void get_mac_address_raw(uint8_t *mac) { std::string get_mac_address() { uint8_t mac[6]; get_mac_address_raw(mac); - return str_sprintf("%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return str_snprintf("%02x%02x%02x%02x%02x%02x", 12, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } std::string get_mac_address_pretty() { uint8_t mac[6]; get_mac_address_raw(mac); - return str_sprintf("%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } #ifdef USE_ESP32 @@ -325,6 +325,20 @@ bool str_startswith(const std::string &full, const std::string &start) { return bool str_endswith(const std::string &full, const std::string &ending) { return full.rfind(ending) == (full.size() - ending.size()); } +std::string str_snprintf(const char *fmt, size_t length, ...) { + std::string str; + va_list args; + + str.resize(length); + va_start(args, length); + size_t out_length = vsnprintf(&str[0], length + 1, fmt, args); + va_end(args); + + if (out_length < length) + str.resize(out_length); + + return str; +} std::string str_sprintf(const char *fmt, ...) { std::string str; va_list args; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 120642c62e..4ce1f08074 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -57,7 +57,10 @@ bool str_equals_case_insensitive(const std::string &a, const std::string &b); bool str_startswith(const std::string &full, const std::string &start); bool str_endswith(const std::string &full, const std::string &ending); -/// sprintf-like function returning std::string instead of writing to char array. +/// snprintf-like function returning std::string with a given maximum length. +std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t length, ...); + +/// sprintf-like function returning std::string. std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...); class HighFrequencyLoopRequester { From 3e5331a263fcf585c88c359cdb0105ea3db79897 Mon Sep 17 00:00:00 2001 From: cvwillegen Date: Tue, 23 Nov 2021 09:20:20 +0100 Subject: [PATCH 048/111] Prettier date time display after time sync (#2778) --- esphome/components/sntp/sntp_component.cpp | 2 +- esphome/components/time/real_time_clock.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 96be0e9709..21fcb96842 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -71,7 +71,7 @@ void SNTPComponent::loop() { if (!time.is_valid()) return; - ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour, + ESP_LOGD(TAG, "Synchronized time: %04d-%02d-%02d %02d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour, time.minute, time.second); this->time_sync_callback_.call(); this->has_time_ = true; diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 064e6f899c..6f6739d293 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -35,7 +35,7 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) { } auto time = this->now(); - ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%d:%d", time.year, time.month, time.day_of_month, time.hour, + ESP_LOGD(TAG, "Synchronized time: %04d-%02d-%02d %02d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour, time.minute, time.second); this->time_sync_callback_.call(); From 07b882c80194020182c41afd91f4dc2486b4f8cb Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 23 Nov 2021 08:20:36 +0000 Subject: [PATCH 049/111] Fix distorted gif frames when resizing (#2774) --- esphome/components/animation/__init__.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index 1780bdf72e..7c9ff07f97 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -44,8 +44,9 @@ async def to_code(config): width, height = image.size frames = image.n_frames if CONF_RESIZE in config: - image.thumbnail(config[CONF_RESIZE]) - width, height = image.size + new_width_max, new_height_max = config[CONF_RESIZE] + ratio = min(new_width_max / width, new_height_max / height) + width, height = int(width * ratio), int(height * ratio) else: if width > 500 or height > 500: _LOGGER.warning( @@ -59,10 +60,12 @@ async def to_code(config): for frameIndex in range(frames): image.seek(frameIndex) frame = image.convert("L", dither=Image.NONE) + if CONF_RESIZE in config: + frame = frame.resize([width, height]) pixels = list(frame.getdata()) if len(pixels) != height * width: raise core.EsphomeError( - f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}" + f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})" ) for pix in pixels: data[pos] = pix @@ -73,13 +76,13 @@ async def to_code(config): pos = 0 for frameIndex in range(frames): image.seek(frameIndex) - if CONF_RESIZE in config: - image.thumbnail(config[CONF_RESIZE]) frame = image.convert("RGB") + if CONF_RESIZE in config: + frame = frame.resize([width, height]) pixels = list(frame.getdata()) if len(pixels) != height * width: raise core.EsphomeError( - f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}" + f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})" ) for pix in pixels: data[pos] = pix[0] @@ -95,6 +98,8 @@ async def to_code(config): for frameIndex in range(frames): image.seek(frameIndex) frame = image.convert("1", dither=Image.NONE) + if CONF_RESIZE in config: + frame = frame.resize([width, height]) for y in range(height): for x in range(width): if frame.getpixel((x, y)): From 710096b1c6035d71c7ed4912e8940fdb83ead3ae Mon Sep 17 00:00:00 2001 From: Andreas Hergert <36455093+andreashergert1984@users.noreply.github.com> Date: Tue, 23 Nov 2021 09:20:55 +0100 Subject: [PATCH 050/111] Fixed wrong setup of tc9548a (#2766) --- esphome/components/tca9548a/tca9548a.cpp | 2 +- esphome/components/tca9548a/tca9548a.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/tca9548a/tca9548a.cpp b/esphome/components/tca9548a/tca9548a.cpp index e902eb5ed4..f3f8685287 100644 --- a/esphome/components/tca9548a/tca9548a.cpp +++ b/esphome/components/tca9548a/tca9548a.cpp @@ -22,7 +22,7 @@ i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffer void TCA9548AComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up TCA9548A..."); uint8_t status = 0; - if (this->read_register(0x00, &status, 1) != i2c::ERROR_OK) { + if (this->read(&status, 1) != i2c::ERROR_OK) { ESP_LOGI(TAG, "TCA9548A failed"); this->mark_failed(); return; diff --git a/esphome/components/tca9548a/tca9548a.h b/esphome/components/tca9548a/tca9548a.h index 314346d317..39d07c2eb4 100644 --- a/esphome/components/tca9548a/tca9548a.h +++ b/esphome/components/tca9548a/tca9548a.h @@ -24,6 +24,7 @@ class TCA9548AComponent : public Component, public i2c::I2CDevice { public: void setup() override; void dump_config() override; + float get_setup_priority() const override { return setup_priority::IO; } void update(); i2c::ErrorCode switch_to_channel(uint8_t channel); From 05fe5db030a05d9bc2877927494d8cd6b30b2bdd Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Tue, 23 Nov 2021 21:21:14 +1300 Subject: [PATCH 051/111] Relax the icon validator to allow non-mdi icons (#2764) --- esphome/config_validation.py | 6 ++++-- tests/unit_tests/test_config_validation.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 2bb45487fa..8df74ba861 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -296,9 +296,11 @@ def icon(value): value = string_strict(value) if not value: return value - if value.startswith("mdi:"): + if re.match("^[\\w\\-]+:[\\w\\-]+$", value): return value - raise Invalid('Icons should start with prefix "mdi:"') + raise Invalid( + 'Icons must match the format "[icon pack]:[icon]", e.g. "mdi:home-assistant"' + ) def boolean(value): diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index 16cfb16e94..9e9af52d00 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -66,7 +66,7 @@ def test_string_string__invalid(value): config_validation.string_strict(value) -@given(builds(lambda v: "mdi:" + v, text())) +@given(builds(lambda v: "mdi:" + v, text(alphabet=string.ascii_letters + string.digits + "-_", min_size=1, max_size=20))) @example("") def test_icon__valid(value): actual = config_validation.icon(value) @@ -75,7 +75,7 @@ def test_icon__valid(value): def test_icon__invalid(): - with pytest.raises(Invalid, match="Icons should start with prefix"): + with pytest.raises(Invalid, match="Icons must match the format "): config_validation.icon("foo") From 335e69e6cd887ead7c22d3d76ddd9391dedef6cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Nov 2021 09:24:28 +0100 Subject: [PATCH 052/111] Bump black from 21.10b0 to 21.11b1 (#2760) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 03879c5d0e..cc3fcfb2ec 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==2.11.1 flake8==4.0.1 -black==21.10b0 +black==21.11b1 pexpect==4.8.0 pre-commit From 598f5b241f3f1b4746559ea82178f8da058031cf Mon Sep 17 00:00:00 2001 From: krunkel Date: Tue, 23 Nov 2021 09:26:16 +0100 Subject: [PATCH 053/111] Remove unnecessary write in AHT10 update (#2675) --- esphome/components/aht10/aht10.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp index e5e04ac181..78f98cb14f 100644 --- a/esphome/components/aht10/aht10.cpp +++ b/esphome/components/aht10/aht10.cpp @@ -73,13 +73,6 @@ void AHT10Component::update() { bool success = false; for (int i = 0; i < AHT10_ATTEMPTS; ++i) { ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis()); - delayMicroseconds(4); - - uint8_t reg = 0; - if (this->write(®, 1) != i2c::ERROR_OK) { - ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); - continue; - } delay(delay_ms); if (this->read(data, 6) != i2c::ERROR_OK) { ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); From 15cd602e8b196b5a0948a8abb5e65a16d530ecf9 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Tue, 23 Nov 2021 09:34:10 +0100 Subject: [PATCH 054/111] Add support for P1 Data Request pin control (#2676) --- esphome/components/dsmr/__init__.py | 17 ++- esphome/components/dsmr/dsmr.cpp | 210 +++++++++++++++++++--------- esphome/components/dsmr/dsmr.h | 14 +- tests/test3.yaml | 2 + 4 files changed, 174 insertions(+), 69 deletions(-) diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index 1cfc21a4ac..06b022c513 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome import pins from esphome.components import uart from esphome.const import ( CONF_ID, @@ -11,11 +12,13 @@ CODEOWNERS = ["@glmnet", "@zuidwijk"] DEPENDENCIES = ["uart"] AUTO_LOAD = ["sensor", "text_sensor"] -CONF_DSMR_ID = "dsmr_id" -CONF_DECRYPTION_KEY = "decryption_key" CONF_CRC_CHECK = "crc_check" +CONF_DECRYPTION_KEY = "decryption_key" +CONF_DSMR_ID = "dsmr_id" CONF_GAS_MBUS_ID = "gas_mbus_id" CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length" +CONF_REQUEST_INTERVAL = "request_interval" +CONF_REQUEST_PIN = "request_pin" # Hack to prevent compile error due to ambiguity with lib namespace dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr") @@ -48,6 +51,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean, cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_, + cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_REQUEST_INTERVAL): cv.positive_time_period_milliseconds, } ).extend(uart.UART_DEVICE_SCHEMA), cv.only_with_arduino, @@ -62,6 +67,14 @@ async def to_code(config): cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY])) await cg.register_component(var, config) + if CONF_REQUEST_PIN in config: + request_pin = await cg.gpio_pin_expression(config[CONF_REQUEST_PIN]) + cg.add(var.set_request_pin(request_pin)) + if CONF_REQUEST_INTERVAL in config: + cg.add( + var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds) + ) + cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID]) # DSMR Parser diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 631b18a1f4..03e418662a 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -13,147 +13,217 @@ namespace dsmr { static const char *const TAG = "dsmr"; void Dsmr::setup() { - telegram_ = new char[max_telegram_len_]; // NOLINT + this->telegram_ = new char[this->max_telegram_len_]; // NOLINT + if (this->request_pin_ != nullptr) { + this->request_pin_->setup(); + } } void Dsmr::loop() { - if (decryption_key_.empty()) - receive_telegram_(); - else - receive_encrypted_(); + if (this->ready_to_request_data_()) { + if (this->decryption_key_.empty()) { + this->receive_telegram_(); + } else { + this->receive_encrypted_(); + } + } +} + +bool Dsmr::ready_to_request_data_() { + // When using a request pin, then wait for the next request interval. + if (this->request_pin_ != nullptr) { + if (!this->requesting_data_ && this->request_interval_reached_()) { + this->start_requesting_data_(); + } + } + // Otherwise, sink serial data until next request interval. + else { + if (this->request_interval_reached_()) { + this->start_requesting_data_(); + } + if (!this->requesting_data_) { + while (this->available()) { + this->read(); + } + } + } + return this->requesting_data_; +} + +bool Dsmr::request_interval_reached_() { + if (this->last_request_time_ == 0) { + return true; + } + return millis() - this->last_request_time_ > this->request_interval_; } bool Dsmr::available_within_timeout_() { uint8_t tries = READ_TIMEOUT_MS / 5; while (tries--) { delay(5); - if (available()) { + if (this->available()) { return true; } } return false; } +void Dsmr::start_requesting_data_() { + if (!this->requesting_data_) { + if (this->request_pin_ != nullptr) { + ESP_LOGV(TAG, "Start requesting data from P1 port"); + this->request_pin_->digital_write(true); + } else { + ESP_LOGV(TAG, "Start reading data from P1 port"); + } + this->requesting_data_ = true; + this->last_request_time_ = millis(); + } +} + +void Dsmr::stop_requesting_data_() { + if (this->requesting_data_) { + if (this->request_pin_ != nullptr) { + ESP_LOGV(TAG, "Stop requesting data from P1 port"); + this->request_pin_->digital_write(false); + } else { + ESP_LOGV(TAG, "Stop reading data from P1 port"); + } + while (this->available()) { + this->read(); + } + this->requesting_data_ = false; + } +} + void Dsmr::receive_telegram_() { while (true) { - if (!available()) { - if (!header_found_ || !available_within_timeout_()) { + if (!this->available()) { + if (!this->header_found_ || !this->available_within_timeout_()) { return; } } - const char c = read(); + const char c = this->read(); // Find a new telegram header, i.e. forward slash. if (c == '/') { ESP_LOGV(TAG, "Header of telegram found"); - header_found_ = true; - footer_found_ = false; - telegram_len_ = 0; + this->header_found_ = true; + this->footer_found_ = false; + this->telegram_len_ = 0; } - if (!header_found_) + if (!this->header_found_) continue; // Check for buffer overflow. - if (telegram_len_ >= max_telegram_len_) { - header_found_ = false; - footer_found_ = false; - ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", max_telegram_len_); + if (this->telegram_len_ >= this->max_telegram_len_) { + this->header_found_ = false; + this->footer_found_ = false; + ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_); return; } // Some v2.2 or v3 meters will send a new value which starts with '(' - // in a new line while the value belongs to the previous ObisId. For - // proper parsing remove these new line characters - while (c == '(' && (telegram_[telegram_len_ - 1] == '\n' || telegram_[telegram_len_ - 1] == '\r')) - telegram_len_--; + // in a new line, while the value belongs to the previous ObisId. For + // proper parsing, remove these new line characters. + if (c == '(') { + while (true) { + auto previous_char = this->telegram_[this->telegram_len_ - 1]; + if (previous_char == '\n' || previous_char == '\r') { + this->telegram_len_--; + } else { + break; + } + } + } // Store the byte in the buffer. - telegram_[telegram_len_] = c; - telegram_len_++; + this->telegram_[this->telegram_len_] = c; + this->telegram_len_++; // Check for a footer, i.e. exlamation mark, followed by a hex checksum. if (c == '!') { ESP_LOGV(TAG, "Footer of telegram found"); - footer_found_ = true; + this->footer_found_ = true; continue; } // Check for the end of the hex checksum, i.e. a newline. - if (footer_found_ && c == '\n') { + if (this->footer_found_ && c == '\n') { // Parse the telegram and publish sensor values. - parse_telegram(); + this->parse_telegram(); - header_found_ = false; + this->header_found_ = false; return; } } } void Dsmr::receive_encrypted_() { - encrypted_telegram_len_ = 0; + this->encrypted_telegram_len_ = 0; size_t packet_size = 0; while (true) { - if (!available()) { - if (!header_found_) { + if (!this->available()) { + if (!this->header_found_) { return; } - if (!available_within_timeout_()) { + if (!this->available_within_timeout_()) { ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram"); return; } } - const char c = read(); + const char c = this->read(); // Find a new telegram start byte. - if (!header_found_) { + if (!this->header_found_) { if ((uint8_t) c != 0xDB) { continue; } ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); - header_found_ = true; + this->header_found_ = true; } // Check for buffer overflow. - if (encrypted_telegram_len_ >= max_telegram_len_) { - header_found_ = false; - ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", max_telegram_len_); + if (this->encrypted_telegram_len_ >= this->max_telegram_len_) { + this->header_found_ = false; + ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_); return; } - encrypted_telegram_[encrypted_telegram_len_++] = c; + this->encrypted_telegram_[this->encrypted_telegram_len_++] = c; - if (packet_size == 0 && encrypted_telegram_len_ > 20) { + if (packet_size == 0 && this->encrypted_telegram_len_ > 20) { // Complete header + data bytes - packet_size = 13 + (encrypted_telegram_[11] << 8 | encrypted_telegram_[12]); + packet_size = 13 + (this->encrypted_telegram_[11] << 8 | this->encrypted_telegram_[12]); ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size); } - if (encrypted_telegram_len_ == packet_size && packet_size > 0) { + if (this->encrypted_telegram_len_ == packet_size && packet_size > 0) { ESP_LOGV(TAG, "End of encrypted telegram found"); GCM *gcmaes128{new GCM()}; - gcmaes128->setKey(decryption_key_.data(), gcmaes128->keySize()); + gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize()); // the iv is 8 bytes of the system title + 4 bytes frame counter // system title is at byte 2 and frame counter at byte 15 for (int i = 10; i < 14; i++) - encrypted_telegram_[i] = encrypted_telegram_[i + 4]; + this->encrypted_telegram_[i] = this->encrypted_telegram_[i + 4]; constexpr uint16_t iv_size{12}; - gcmaes128->setIV(&encrypted_telegram_[2], iv_size); - gcmaes128->decrypt(reinterpret_cast(telegram_), + gcmaes128->setIV(&this->encrypted_telegram_[2], iv_size); + gcmaes128->decrypt(reinterpret_cast(this->telegram_), // the ciphertext start at byte 18 - &encrypted_telegram_[18], + &this->encrypted_telegram_[18], // cipher size - encrypted_telegram_len_ - 17); + this->encrypted_telegram_len_ - 17); delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory) - telegram_len_ = strnlen(telegram_, max_telegram_len_); - ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_); - ESP_LOGVV(TAG, "Decrypted telegram: %s", telegram_); + this->telegram_len_ = strnlen(this->telegram_, this->max_telegram_len_); + ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->telegram_len_); + ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); - parse_telegram(); + this->parse_telegram(); - header_found_ = false; - telegram_len_ = 0; + this->header_found_ = false; + this->telegram_len_ = 0; return; } } @@ -162,24 +232,32 @@ void Dsmr::receive_encrypted_() { bool Dsmr::parse_telegram() { MyData data; ESP_LOGV(TAG, "Trying to parse telegram"); + this->stop_requesting_data_(); ::dsmr::ParseResult res = - ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false, - crc_check_); // Parse telegram according to data definition. Ignore unknown values. + ::dsmr::P1Parser::parse(&data, this->telegram_, this->telegram_len_, false, + this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. if (res.err) { // Parsing error, show it - auto err_str = res.fullError(telegram_, telegram_ + telegram_len_); + auto err_str = res.fullError(this->telegram_, this->telegram_ + this->telegram_len_); ESP_LOGE(TAG, "%s", err_str.c_str()); return false; } else { this->status_clear_warning(); - publish_sensors(data); + this->publish_sensors(data); return true; } } void Dsmr::dump_config() { ESP_LOGCONFIG(TAG, "DSMR:"); - ESP_LOGCONFIG(TAG, " Max telegram length: %d", max_telegram_len_); + ESP_LOGCONFIG(TAG, " Max telegram length: %d", this->max_telegram_len_); + + if (this->request_pin_ != nullptr) { + LOG_PIN(" Request Pin: ", this->request_pin_); + } + if (this->request_interval_ > 0) { + ESP_LOGCONFIG(TAG, " Request Interval: %.1fs", this->request_interval_ / 1e3f); + } #define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_); DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, ) @@ -191,10 +269,10 @@ void Dsmr::dump_config() { void Dsmr::set_decryption_key(const std::string &decryption_key) { if (decryption_key.length() == 0) { ESP_LOGI(TAG, "Disabling decryption"); - decryption_key_.clear(); - if (encrypted_telegram_ != nullptr) { - delete[] encrypted_telegram_; - encrypted_telegram_ = nullptr; + this->decryption_key_.clear(); + if (this->encrypted_telegram_ != nullptr) { + delete[] this->encrypted_telegram_; + this->encrypted_telegram_ = nullptr; } return; } @@ -203,7 +281,7 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { ESP_LOGE(TAG, "Error, decryption key must be 32 character long"); return; } - decryption_key_.clear(); + this->decryption_key_.clear(); ESP_LOGI(TAG, "Decryption key is set"); // Verbose level prints decryption key @@ -212,11 +290,11 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { char temp[3] = {0}; for (int i = 0; i < 16; i++) { strncpy(temp, &(decryption_key.c_str()[i * 2]), 2); - decryption_key_.push_back(std::strtoul(temp, nullptr, 16)); + this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16)); } - if (encrypted_telegram_ == nullptr) { - encrypted_telegram_ = new uint8_t[max_telegram_len_]; // NOLINT + if (this->encrypted_telegram_ == nullptr) { + this->encrypted_telegram_ = new uint8_t[this->max_telegram_len_]; // NOLINT } } diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index 5943e4d47f..0430eb93ed 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -52,7 +52,6 @@ class Dsmr : public Component, public uart::UARTDevice { Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {} void setup() override; - void loop() override; bool parse_telegram(); @@ -75,6 +74,9 @@ class Dsmr : public Component, public uart::UARTDevice { void set_max_telegram_length(size_t length); + void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; } + void set_request_interval(uint32_t interval) { this->request_interval_ = interval; } + // Sensor setters #define DSMR_SET_SENSOR(s) \ void set_##s(sensor::Sensor *sensor) { s_##s##_ = sensor; } @@ -99,6 +101,16 @@ class Dsmr : public Component, public uart::UARTDevice { /// lost in the process. bool available_within_timeout_(); + // Data request + GPIOPin *request_pin_{nullptr}; + uint32_t request_interval_{0}; + uint32_t last_request_time_{0}; + bool requesting_data_{false}; + bool ready_to_request_data_(); + bool request_interval_reached_(); + void start_requesting_data_(); + void stop_requesting_data_(); + // Telegram buffer size_t max_telegram_len_; char *telegram_{nullptr}; diff --git a/tests/test3.yaml b/tests/test3.yaml index 0b9297a0b8..27a0c148d9 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1309,6 +1309,8 @@ dsmr: decryption_key: 00112233445566778899aabbccddeeff uart_id: uart6 max_telegram_length: 1000 + request_pin: D5 + request_interval: 20s daly_bms: update_interval: 20s From 7e8012c1a02b2e9c10f9a76ac36f786d8ba5a24a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 25 Nov 2021 07:59:32 +1300 Subject: [PATCH 055/111] Allow specifying the dashboard bind address (#2787) --- esphome/__main__.py | 6 ++++++ esphome/dashboard/dashboard.py | 7 ++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 7c7c22dd1f..2f06a71b5f 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -637,6 +637,12 @@ def parse_args(argv): type=int, default=6052, ) + parser_dashboard.add_argument( + "--address", + help="The address to bind to.", + type=str, + default="0.0.0.0", + ) parser_dashboard.add_argument( "--username", help="The optional username to require for authentication.", diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 11571ec889..c98047d9e5 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -970,16 +970,17 @@ def start_web_server(args): server.add_socket(socket) else: _LOGGER.info( - "Starting dashboard web server on http://0.0.0.0:%s and configuration dir %s...", + "Starting dashboard web server on http://%s:%s and configuration dir %s...", + args.address, args.port, settings.config_dir, ) - app.listen(args.port) + app.listen(args.port, args.address) if args.open_ui: import webbrowser - webbrowser.open(f"localhost:{args.port}") + webbrowser.open(f"http://{args.address}:{args.port}") if settings.status_use_ping: status_thread = PingStatusThread() From 4b1d73791d4a13bd1486083a60b0940bea266c63 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Wed, 24 Nov 2021 20:06:08 +0100 Subject: [PATCH 056/111] remove LEDC_HIGH_SPEED_MODE for C3, S2, S3 (#2791) --- esphome/components/ledc/ledc_output.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index 21a747e34d..13ad32ab70 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -15,6 +15,18 @@ namespace ledc { static const char *const TAG = "ledc.output"; +#ifdef USE_ESP_IDF +#if SOC_LEDC_SUPPORT_HS_MODE +// Only ESP32 has LEDC_HIGH_SPEED_MODE +inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; } +#else +// S2, C3, S3 only support LEDC_LOW_SPEED_MODE +// See +// https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html#functionality-overview +inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; } +#endif +#endif + float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); } float ledc_min_frequency_for_bit_depth(uint8_t bit_depth) { const float max_div_num = ((1 << 20) - 1) / 256.0f; @@ -48,7 +60,7 @@ void LEDCOutput::write_state(float state) { ledcWrite(this->channel_, duty); #endif #ifdef USE_ESP_IDF - auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; + auto speed_mode = get_speed_mode(channel_); auto chan_num = static_cast(channel_ % 8); ledc_set_duty(speed_mode, chan_num, duty); ledc_update_duty(speed_mode, chan_num); @@ -63,7 +75,7 @@ void LEDCOutput::setup() { ledcAttachPin(this->pin_->get_pin(), this->channel_); #endif #ifdef USE_ESP_IDF - auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; + auto speed_mode = get_speed_mode(channel_); auto timer_num = static_cast((channel_ % 8) / 2); auto chan_num = static_cast(channel_ % 8); @@ -114,7 +126,7 @@ void LEDCOutput::update_frequency(float frequency) { ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!"); return; } - auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; + auto speed_mode = get_speed_mode(channel_); auto timer_num = static_cast((channel_ % 8) / 2); ledc_timer_config_t timer_conf{}; From 290da8df2ded1cc82268d2a22e24a163e2a27881 Mon Sep 17 00:00:00 2001 From: rsumner Date: Wed, 24 Nov 2021 16:22:51 -0600 Subject: [PATCH 057/111] Fix LEDC resolution calculation on ESP32-C3/S2/S3 (#2794) Co-authored-by: Oxan van Leeuwen Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/ledc/ledc_output.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index 13ad32ab70..a56dccfd72 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -16,6 +16,7 @@ namespace ledc { static const char *const TAG = "ledc.output"; #ifdef USE_ESP_IDF +static const int MAX_RES_BITS = LEDC_TIMER_BIT_MAX - 1; #if SOC_LEDC_SUPPORT_HS_MODE // Only ESP32 has LEDC_HIGH_SPEED_MODE inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; } @@ -25,19 +26,26 @@ inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_H // https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html#functionality-overview inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; } #endif +#else +static const int MAX_RES_BITS = 20; #endif float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); } -float ledc_min_frequency_for_bit_depth(uint8_t bit_depth) { - const float max_div_num = ((1 << 20) - 1) / 256.0f; + +float ledc_min_frequency_for_bit_depth(uint8_t bit_depth, bool low_frequency) { + const float max_div_num = ((1 << MAX_RES_BITS) - 1) / (low_frequency ? 32.0f : 256.0f); return 80e6f / (max_div_num * float(1 << bit_depth)); } + optional ledc_bit_depth_for_frequency(float frequency) { - for (int i = 20; i >= 1; i--) { - const float min_frequency = ledc_min_frequency_for_bit_depth(i); + ESP_LOGD(TAG, "Calculating resolution bit-depth for frequency %f", frequency); + for (int i = MAX_RES_BITS; i >= 1; i--) { + const float min_frequency = ledc_min_frequency_for_bit_depth(i, (frequency < 100)); const float max_frequency = ledc_max_frequency_for_bit_depth(i); - if (min_frequency <= frequency && frequency <= max_frequency) + if (min_frequency <= frequency && frequency <= max_frequency) { + ESP_LOGD(TAG, "Resolution calculated as %d", i); return i; + } } return {}; } @@ -80,6 +88,10 @@ void LEDCOutput::setup() { auto chan_num = static_cast(channel_ % 8); bit_depth_ = *ledc_bit_depth_for_frequency(frequency_); + if (bit_depth_ < 1) { + ESP_LOGW(TAG, "Frequency %f can't be achieved with any bit depth", frequency_); + this->status_set_warning(); + } ledc_timer_config_t timer_conf{}; timer_conf.speed_mode = speed_mode; From ccfa1e23f0211f5421bef22b328ec3fb5b4204ee Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Wed, 24 Nov 2021 23:28:19 +0100 Subject: [PATCH 058/111] Add support for sdp8xx (#2779) --- esphome/components/sdp3x/sdp3x.cpp | 106 ++++++++++++++++++----------- esphome/components/sdp3x/sdp3x.h | 6 +- esphome/components/sdp3x/sensor.py | 12 ++++ 3 files changed, 83 insertions(+), 41 deletions(-) diff --git a/esphome/components/sdp3x/sdp3x.cpp b/esphome/components/sdp3x/sdp3x.cpp index b0d8bcc6c4..107ed2902f 100644 --- a/esphome/components/sdp3x/sdp3x.cpp +++ b/esphome/components/sdp3x/sdp3x.cpp @@ -11,6 +11,7 @@ static const uint8_t SDP3X_SOFT_RESET[2] = {0x00, 0x06}; static const uint8_t SDP3X_READ_ID1[2] = {0x36, 0x7C}; static const uint8_t SDP3X_READ_ID2[2] = {0xE1, 0x02}; static const uint8_t SDP3X_START_DP_AVG[2] = {0x36, 0x15}; +static const uint8_t SDP3X_START_MASS_FLOW_AVG[2] = {0x36, 0x03}; static const uint8_t SDP3X_STOP_MEAS[2] = {0x3F, 0xF9}; void SDP3XComponent::update() { this->read_pressure_(); } @@ -26,46 +27,69 @@ void SDP3XComponent::setup() { ESP_LOGW(TAG, "Soft Reset SDP3X failed!"); // This sometimes fails for no good reason } - delayMicroseconds(20000); + this->set_timeout(20, [this] { + if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Read ID1 SDP3X failed!"); + this->mark_failed(); + return; + } + if (this->write(SDP3X_READ_ID2, 2) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Read ID2 SDP3X failed!"); + this->mark_failed(); + return; + } - if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) { - ESP_LOGE(TAG, "Read ID1 SDP3X failed!"); - this->mark_failed(); - return; - } - if (this->write(SDP3X_READ_ID2, 2) != i2c::ERROR_OK) { - ESP_LOGE(TAG, "Read ID2 SDP3X failed!"); - this->mark_failed(); - return; - } + uint8_t data[18]; + if (this->read(data, 18) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Read ID SDP3X failed!"); + this->mark_failed(); + return; + } + if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]))) { + ESP_LOGE(TAG, "CRC ID SDP3X failed!"); + this->mark_failed(); + return; + } - uint8_t data[18]; - if (this->read(data, 18) != i2c::ERROR_OK) { - ESP_LOGE(TAG, "Read ID SDP3X failed!"); - this->mark_failed(); - return; - } + // SDP8xx + // ref: + // https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/8_Differential_Pressure/Datasheets/Sensirion_Differential_Pressure_Datasheet_SDP8xx_Digital.pdf + if (data[2] == 0x02) { + switch (data[3]) { + case 0x01: // SDP800-500Pa + ESP_LOGCONFIG(TAG, "Sensor is SDP800-500Pa"); + break; + case 0x0A: // SDP810-500Pa + ESP_LOGCONFIG(TAG, "Sensor is SDP810-500Pa"); + break; + case 0x04: // SDP801-500Pa + ESP_LOGCONFIG(TAG, "Sensor is SDP801-500Pa"); + break; + case 0x0D: // SDP811-500Pa + ESP_LOGCONFIG(TAG, "Sensor is SDP811-500Pa"); + break; + case 0x02: // SDP800-125Pa + ESP_LOGCONFIG(TAG, "Sensor is SDP800-125Pa"); + break; + case 0x0B: // SDP810-125Pa + ESP_LOGCONFIG(TAG, "Sensor is SDP810-125Pa"); + break; + } + } else if (data[2] == 0x01) { + if (data[3] == 0x01) { + ESP_LOGCONFIG(TAG, "Sensor is SDP31-500Pa"); + } else if (data[3] == 0x02) { + ESP_LOGCONFIG(TAG, "Sensor is SDP32-125Pa"); + } + } - if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]))) { - ESP_LOGE(TAG, "CRC ID SDP3X failed!"); - this->mark_failed(); - return; - } - - if (data[3] == 0x01) { - ESP_LOGCONFIG(TAG, "SDP3X is SDP31"); - pressure_scale_factor_ = 60.0f * 100.0f; // Scale factors converted to hPa per count - } else if (data[3] == 0x02) { - ESP_LOGCONFIG(TAG, "SDP3X is SDP32"); - pressure_scale_factor_ = 240.0f * 100.0f; - } - - if (this->write(SDP3X_START_DP_AVG, 2) != i2c::ERROR_OK) { - ESP_LOGE(TAG, "Start Measurements SDP3X failed!"); - this->mark_failed(); - return; - } - ESP_LOGCONFIG(TAG, "SDP3X started!"); + if (this->write(measurement_mode_ == DP_AVG ? SDP3X_START_DP_AVG : SDP3X_START_MASS_FLOW_AVG, 2) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Start Measurements SDP3X failed!"); + this->mark_failed(); + return; + } + ESP_LOGCONFIG(TAG, "SDP3X started!"); + }); } void SDP3XComponent::dump_config() { LOG_SENSOR(" ", "SDP3X", this); @@ -91,8 +115,12 @@ void SDP3XComponent::read_pressure_() { } int16_t pressure_raw = encode_uint16(data[0], data[1]); - float pressure = pressure_raw / pressure_scale_factor_; - ESP_LOGV(TAG, "Got raw pressure=%d, scale factor =%.3f ", pressure_raw, pressure_scale_factor_); + int16_t temperature_raw = encode_uint16(data[3], data[4]); + int16_t scale_factor_raw = encode_uint16(data[6], data[7]); + // scale factor is in Pa - convert to hPa + float pressure = pressure_raw / (scale_factor_raw * 100.0f); + ESP_LOGV(TAG, "Got raw pressure=%d, raw scale factor =%d, raw temperature=%d ", pressure_raw, scale_factor_raw, + temperature_raw); ESP_LOGD(TAG, "Got Pressure=%.3f hPa", pressure); this->publish_state(pressure); diff --git a/esphome/components/sdp3x/sdp3x.h b/esphome/components/sdp3x/sdp3x.h index 51c9973c61..0e74d0883d 100644 --- a/esphome/components/sdp3x/sdp3x.h +++ b/esphome/components/sdp3x/sdp3x.h @@ -7,6 +7,8 @@ namespace esphome { namespace sdp3x { +enum MeasurementMode { MASS_FLOW_AVG, DP_AVG }; + class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor { public: /// Schedule temperature+pressure readings. @@ -16,14 +18,14 @@ class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public se void dump_config() override; float get_setup_priority() const override; + void set_measurement_mode(MeasurementMode mode) { measurement_mode_ = mode; } protected: /// Internal method to read the pressure from the component after it has been scheduled. void read_pressure_(); bool check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum); - - float pressure_scale_factor_ = 0.0f; // hPa per count + MeasurementMode measurement_mode_; }; } // namespace sdp3x diff --git a/esphome/components/sdp3x/sensor.py b/esphome/components/sdp3x/sensor.py index 08d7250f6e..45f5cc4d9a 100644 --- a/esphome/components/sdp3x/sensor.py +++ b/esphome/components/sdp3x/sensor.py @@ -14,6 +14,14 @@ CODEOWNERS = ["@Azimath"] sdp3x_ns = cg.esphome_ns.namespace("sdp3x") SDP3XComponent = sdp3x_ns.class_("SDP3XComponent", cg.PollingComponent, i2c.I2CDevice) + +MeasurementMode = sdp3x_ns.enum("MeasurementMode") +MEASUREMENT_MODE = { + "mass_flow": MeasurementMode.MASS_FLOW_AVG, + "differential_pressure": MeasurementMode.DP_AVG, +} +CONF_MEASUREMENT_MODE = "measurement_mode" + CONFIG_SCHEMA = ( sensor.sensor_schema( unit_of_measurement=UNIT_HECTOPASCAL, @@ -24,6 +32,9 @@ CONFIG_SCHEMA = ( .extend( { cv.GenerateID(): cv.declare_id(SDP3XComponent), + cv.Optional( + CONF_MEASUREMENT_MODE, default="differential_pressure" + ): cv.enum(MEASUREMENT_MODE), } ) .extend(cv.polling_component_schema("60s")) @@ -36,3 +47,4 @@ async def to_code(config): await cg.register_component(var, config) await i2c.register_i2c_device(var, config) await sensor.register_sensor(var, config) + cg.add(var.set_measurement_mode(config[CONF_MEASUREMENT_MODE])) From ceb9b1d1ff0b989dc196eeb624963d6e6c644427 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 24 Nov 2021 23:51:56 +0100 Subject: [PATCH 059/111] Allow empty UART debug: option, logging in hex format by default (#2771) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Maurice Makaay --- esphome/components/uart/__init__.py | 29 ++++++++++++++++++++++++++--- tests/test3.yaml | 2 ++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 159b08d2d9..61b54044d7 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -3,6 +3,7 @@ from typing import Optional import esphome.codegen as cg import esphome.config_validation as cv import esphome.final_validate as fv +from esphome.yaml_util import make_data_base from esphome import pins, automation from esphome.const import ( CONF_BAUD_RATE, @@ -24,6 +25,7 @@ from esphome.const import ( CONF_DELIMITER, CONF_DUMMY_RECEIVER, CONF_DUMMY_RECEIVER_ID, + CONF_LAMBDA, ) from esphome.core import CORE @@ -94,7 +96,26 @@ UART_DIRECTIONS = { "BOTH": UARTDirection.UART_DIRECTION_BOTH, } -AFTER_DEFAULTS = {CONF_BYTES: 256, CONF_TIMEOUT: "100ms"} +# The reason for having CONF_BYTES at 150 by default: +# +# The log message buffer size is 512 bytes by default. About 35 bytes are +# used for the log prefix. That leaves us with 477 bytes for logging data. +# The default log output is hex, which uses 3 characters per represented +# byte (2 hex chars + 1 separator). That means that 477 / 3 = 159 bytes +# can be represented in a single log line. Using 150, because people love +# round numbers. +AFTER_DEFAULTS = {CONF_BYTES: 150, CONF_TIMEOUT: "100ms"} + +# By default, log in hex format when no specific sequence is provided. +DEFAULT_DEBUG_OUTPUT = "UARTDebug::log_hex(direction, bytes, ':');" +DEFAULT_SEQUENCE = [{CONF_LAMBDA: make_data_base(DEFAULT_DEBUG_OUTPUT)}] + + +def maybe_empty_debug(value): + if value is None: + value = {} + return DEBUG_SCHEMA(value) + DEBUG_SCHEMA = cv.Schema( { @@ -113,7 +134,9 @@ DEBUG_SCHEMA = cv.Schema( cv.Optional(CONF_DELIMITER): cv.templatable(validate_raw_data), } ), - cv.Required(CONF_SEQUENCE): automation.validate_automation(), + cv.Optional( + CONF_SEQUENCE, default=DEFAULT_SEQUENCE + ): automation.validate_automation(), cv.Optional(CONF_DUMMY_RECEIVER, default=False): cv.boolean, cv.GenerateID(CONF_DUMMY_RECEIVER_ID): cv.declare_id(UARTDummyReceiver), } @@ -135,7 +158,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_INVERT): cv.invalid( "This option has been removed. Please instead use invert in the tx/rx pin schemas." ), - cv.Optional(CONF_DEBUG): DEBUG_SCHEMA, + cv.Optional(CONF_DEBUG): maybe_empty_debug, } ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN), diff --git a/tests/test3.yaml b/tests/test3.yaml index 27a0c148d9..61c7a3dcad 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -254,6 +254,8 @@ uart: tx_pin: GPIO4 rx_pin: GPIO5 baud_rate: 38400 + # Specifically added for testing debug with no options at all. + debug: modbus: uart_id: uart1 From 2aea27d2726d06599290bc7afa3288700edfcd3d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Nov 2021 20:34:11 +0100 Subject: [PATCH 060/111] Bump pylint from 2.11.1 to 2.12.1 (#2798) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index cc3fcfb2ec..ad98ec2a4f 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.11.1 +pylint==2.12.1 flake8==4.0.1 black==21.11b1 pexpect==4.8.0 From 3637be251e594769080ff6cb683d4afa3221c3ff Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 25 Nov 2021 21:00:49 +0100 Subject: [PATCH 061/111] Fix parsing numbers from null-terminated buffers (#2755) --- esphome/components/anova/anova_base.cpp | 23 ++++++++++++----------- esphome/components/anova/anova_base.h | 1 - esphome/components/ezo/ezo.cpp | 4 ++-- esphome/core/helpers.h | 22 ++++++++++++---------- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp index d55404089e..dcef75e483 100644 --- a/esphome/components/anova/anova_base.cpp +++ b/esphome/components/anova/anova_base.cpp @@ -73,51 +73,52 @@ AnovaPacket *AnovaCodec::get_stop_request() { } void AnovaCodec::decode(const uint8_t *data, uint16_t length) { - memset(this->buf_, 0, 32); - strncpy(this->buf_, (char *) data, length); + char buf[32]; + memset(buf, 0, sizeof(buf)); + strncpy(buf, (char *) data, std::min(length, sizeof(buf) - 1)); this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false; switch (this->current_query_) { case READ_DEVICE_STATUS: { - if (!strncmp(this->buf_, "stopped", 7)) { + if (!strncmp(buf, "stopped", 7)) { this->has_running_ = true; this->running_ = false; } - if (!strncmp(this->buf_, "running", 7)) { + if (!strncmp(buf, "running", 7)) { this->has_running_ = true; this->running_ = true; } break; } case START: { - if (!strncmp(this->buf_, "start", 5)) { + if (!strncmp(buf, "start", 5)) { this->has_running_ = true; this->running_ = true; } break; } case STOP: { - if (!strncmp(this->buf_, "stop", 4)) { + if (!strncmp(buf, "stop", 4)) { this->has_running_ = true; this->running_ = false; } break; } case READ_TARGET_TEMPERATURE: { - this->target_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f); + this->target_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f); if (this->fahrenheit_) this->target_temp_ = ftoc(this->target_temp_); this->has_target_temp_ = true; break; } case SET_TARGET_TEMPERATURE: { - this->target_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f); + this->target_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f); if (this->fahrenheit_) this->target_temp_ = ftoc(this->target_temp_); this->has_target_temp_ = true; break; } case READ_CURRENT_TEMPERATURE: { - this->current_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f); + this->current_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f); if (this->fahrenheit_) this->current_temp_ = ftoc(this->current_temp_); this->has_current_temp_ = true; @@ -125,8 +126,8 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) { } case SET_UNIT: case READ_UNIT: { - this->unit_ = this->buf_[0]; - this->fahrenheit_ = this->buf_[0] == 'f'; + this->unit_ = buf[0]; + this->fahrenheit_ = buf[0] == 'f'; this->has_unit_ = true; break; } diff --git a/esphome/components/anova/anova_base.h b/esphome/components/anova/anova_base.h index 7c1383512d..b831157849 100644 --- a/esphome/components/anova/anova_base.h +++ b/esphome/components/anova/anova_base.h @@ -70,7 +70,6 @@ class AnovaCodec { bool has_current_temp_; bool has_unit_; bool has_running_; - char buf_[32]; bool fahrenheit_; CurrentQuery current_query_; diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index 7f7a41fb41..426f2807c1 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -32,7 +32,7 @@ void EZOSensor::update() { } void EZOSensor::loop() { - uint8_t buf[20]; + uint8_t buf[21]; if (!(this->state_ & EZO_STATE_WAIT)) { if (this->state_ & EZO_STATE_SEND_TEMP) { int len = sprintf((char *) buf, "T,%0.3f", this->tempcomp_); @@ -74,7 +74,7 @@ void EZOSensor::loop() { if (buf[0] != 1) return; - float val = parse_number((char *) &buf[1], sizeof(buf) - 1).value_or(0); + float val = parse_number((char *) &buf[1], sizeof(buf) - 2).value_or(0); this->publish_state(val); } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 4ce1f08074..1faf1ac3aa 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -364,45 +364,47 @@ std::string str_sanitize(const std::string &str); /// @name Parsing & formatting ///@{ -/// Parse a unsigned decimal number. +/// Parse an unsigned decimal number (requires null-terminated string). template::value && std::is_unsigned::value), int> = 0> optional parse_number(const char *str, size_t len) { char *end = nullptr; unsigned long value = ::strtoul(str, &end, 10); // NOLINT(google-runtime-int) - if (end == nullptr || end != str + len || value > std::numeric_limits::max()) + if (end == str || *end != '\0' || value > std::numeric_limits::max()) return {}; return value; } +/// Parse an unsigned decimal number. template::value && std::is_unsigned::value), int> = 0> optional parse_number(const std::string &str) { - return parse_number(str.c_str(), str.length()); + return parse_number(str.c_str(), str.length() + 1); } -/// Parse a signed decimal number. +/// Parse a signed decimal number (requires null-terminated string). template::value && std::is_signed::value), int> = 0> optional parse_number(const char *str, size_t len) { char *end = nullptr; signed long value = ::strtol(str, &end, 10); // NOLINT(google-runtime-int) - if (end == nullptr || end != str + len || value < std::numeric_limits::min() || - value > std::numeric_limits::max()) + if (end == str || *end != '\0' || value < std::numeric_limits::min() || value > std::numeric_limits::max()) return {}; return value; } +/// Parse a signed decimal number. template::value && std::is_signed::value), int> = 0> optional parse_number(const std::string &str) { - return parse_number(str.c_str(), str.length()); + return parse_number(str.c_str(), str.length() + 1); } -/// Parse a decimal floating-point number. +/// Parse a decimal floating-point number (requires null-terminated string). template::value), int> = 0> optional parse_number(const char *str, size_t len) { char *end = nullptr; float value = ::strtof(str, &end); - if (end == nullptr || end != str + len || value == HUGE_VALF) + if (end == str || *end != '\0' || value == HUGE_VALF) return {}; return value; } +/// Parse a decimal floating-point number. template::value), int> = 0> optional parse_number(const std::string &str) { - return parse_number(str.c_str(), str.length()); + return parse_number(str.c_str(), str.length() + 1); } ///@} From 2a78c2970da865bcea82df7f6f8e04afab47d34e Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 25 Nov 2021 21:27:34 +0100 Subject: [PATCH 062/111] Fix CI cache key for test3.yaml compile (#2757) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 02b64d2bf5..b3e06a3367 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: - id: test file: tests/test3.yaml name: Test tests/test3.yaml - pio_cache_key: test1 + pio_cache_key: test3 - id: test file: tests/test4.yaml name: Test tests/test4.yaml From 4e448b21ff2eaa9f158889f658d2269ee8f8dce0 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 25 Nov 2021 21:27:53 +0100 Subject: [PATCH 063/111] Drop obsolete comment from CI workflow file (#2758) --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3e06a3367..d723424326 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,3 @@ -# THESE JOBS ARE COPIED IN release.yml and release-dev.yml -# PLEASE ALSO UPDATE THOSE FILES WHEN CHANGING LINES HERE name: CI on: From d50bdf619f38ec86e4a026a96a657e04050ac3e1 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 25 Nov 2021 21:29:10 +0100 Subject: [PATCH 064/111] Cache virtualenv instead of pip cache between CI runs (#2759) --- .github/workflows/ci.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d723424326..3daabecc9f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,18 +78,23 @@ jobs: with: python-version: '3.7' - - name: Cache pip modules + - name: Cache virtualenv uses: actions/cache@v2 with: - path: ~/.cache/pip - key: pip-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} + path: .venv + key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} restore-keys: | - pip-${{ steps.python.outputs.python-version }}- + venv-${{ steps.python.outputs.python-version }}- - - name: Set up python environment + - name: Set up virtualenv run: | - pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt - pip3 install -e . + python -m venv .venv + source .venv/bin/activate + pip install -U pip + pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt + pip install -e . + echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH + echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV # Use per check platformio cache because checks use different parts - name: Cache platformio From b5f660398c509ca73dae1f44a821b591646e9f52 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 25 Nov 2021 21:35:33 +0100 Subject: [PATCH 065/111] Add map filter for text sensors (#2761) --- esphome/components/text_sensor/__init__.py | 38 ++++++++++++---------- esphome/components/text_sensor/filter.cpp | 6 ++++ esphome/components/text_sensor/filter.h | 11 +++++++ tests/test2.yaml | 10 ++++++ 4 files changed, 48 insertions(+), 17 deletions(-) diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index a070e6f86d..e0fc6af19c 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -49,6 +49,7 @@ ToLowerFilter = text_sensor_ns.class_("ToLowerFilter", Filter) AppendFilter = text_sensor_ns.class_("AppendFilter", Filter) PrependFilter = text_sensor_ns.class_("PrependFilter", Filter) SubstituteFilter = text_sensor_ns.class_("SubstituteFilter", Filter) +MapFilter = text_sensor_ns.class_("MapFilter", Filter) @FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda) @@ -79,26 +80,21 @@ async def prepend_filter_to_code(config, filter_id): return cg.new_Pvariable(filter_id, config) -def validate_substitute(value): - if isinstance(value, dict): - return cv.Schema( - { - cv.Required(CONF_FROM): cv.string, - cv.Required(CONF_TO): cv.string, - } - )(value) - value = cv.string(value) - if "->" not in value: - raise cv.Invalid("Substitute mapping must contain '->'") - a, b = value.split("->", 1) - a, b = a.strip(), b.strip() - return validate_substitute({CONF_FROM: cv.string(a), CONF_TO: cv.string(b)}) +def validate_mapping(value): + if not isinstance(value, dict): + value = cv.string(value) + if "->" not in value: + raise cv.Invalid("Mapping must contain '->'") + a, b = value.split("->", 1) + value = {CONF_FROM: a.strip(), CONF_TO: b.strip()} + + return cv.Schema( + {cv.Required(CONF_FROM): cv.string, cv.Required(CONF_TO): cv.string} + )(value) @FILTER_REGISTRY.register( - "substitute", - SubstituteFilter, - cv.All(cv.ensure_list(validate_substitute), cv.Length(min=2)), + "substitute", SubstituteFilter, cv.ensure_list(validate_mapping) ) async def substitute_filter_to_code(config, filter_id): from_strings = [conf[CONF_FROM] for conf in config] @@ -106,6 +102,14 @@ async def substitute_filter_to_code(config, filter_id): return cg.new_Pvariable(filter_id, from_strings, to_strings) +@FILTER_REGISTRY.register("map", MapFilter, cv.ensure_list(validate_mapping)) +async def map_filter_to_code(config, filter_id): + map_ = cg.std_ns.class_("map").template(cg.std_string, cg.std_string) + return cg.new_Pvariable( + filter_id, map_([(item[CONF_FROM], item[CONF_TO]) for item in config]) + ) + + icon = cv.icon diff --git a/esphome/components/text_sensor/filter.cpp b/esphome/components/text_sensor/filter.cpp index 14df6238ff..a692378e8b 100644 --- a/esphome/components/text_sensor/filter.cpp +++ b/esphome/components/text_sensor/filter.cpp @@ -70,5 +70,11 @@ optional SubstituteFilter::new_value(std::string value) { return value; } +// Map +optional MapFilter::new_value(std::string value) { + auto item = mappings_.find(value); + return item == mappings_.end() ? value : item->second; +} + } // namespace text_sensor } // namespace esphome diff --git a/esphome/components/text_sensor/filter.h b/esphome/components/text_sensor/filter.h index 6a1d9ab04e..38f35e6172 100644 --- a/esphome/components/text_sensor/filter.h +++ b/esphome/components/text_sensor/filter.h @@ -4,6 +4,7 @@ #include "esphome/core/helpers.h" #include #include +#include namespace esphome { namespace text_sensor { @@ -108,5 +109,15 @@ class SubstituteFilter : public Filter { std::vector to_strings_; }; +/// A filter that maps values from one set to another +class MapFilter : public Filter { + public: + MapFilter(std::map mappings) : mappings_(std::move(mappings)) {} + optional new_value(std::string value) override; + + protected: + std::map mappings_; +}; + } // namespace text_sensor } // namespace esphome diff --git a/tests/test2.yaml b/tests/test2.yaml index 3afef9501d..50743dc643 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -458,6 +458,16 @@ text_sensor: name: 'Template Text Sensor' lambda: |- return {"Hello World"}; + filters: + - to_upper: + - to_lower: + - append: "xyz" + - prepend: "abcd" + - substitute: + - Hello -> Goodbye + - map: + - red -> green + - lambda: return {"1234"}; - platform: homeassistant entity_id: sensor.hello_world2 id: ha_hello_world2 From 5e631bc6ba6ded3fa61df88327b4a74d9d691f36 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 25 Nov 2021 21:36:42 +0100 Subject: [PATCH 066/111] Only match GCC warnings from ESPHome source files in CI (#2756) --- .github/workflows/matchers/gcc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/matchers/gcc.json b/.github/workflows/matchers/gcc.json index 899239f816..a00d9c33f4 100644 --- a/.github/workflows/matchers/gcc.json +++ b/.github/workflows/matchers/gcc.json @@ -5,7 +5,7 @@ "severity": "error", "pattern": [ { - "regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "regexp": "^src/(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", "file": 1, "line": 2, "column": 3, From 9681dfb45891f891789990f525b6212cd0961552 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 25 Nov 2021 21:37:27 +0100 Subject: [PATCH 067/111] Correct constant for dynamic I2S bus in NeoPixelBus (#2797) --- esphome/components/neopixelbus/_methods.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/neopixelbus/_methods.py b/esphome/components/neopixelbus/_methods.py index b03544f246..4e3c3ca778 100644 --- a/esphome/components/neopixelbus/_methods.py +++ b/esphome/components/neopixelbus/_methods.py @@ -88,8 +88,8 @@ def _esp32_i2s_default_bus(): def _validate_esp32_i2s_bus(value): - if isinstance(value, str) and value.lower() == CHANNEL_DYNAMIC: - value = CHANNEL_DYNAMIC + if isinstance(value, str) and value.lower() == BUS_DYNAMIC: + value = BUS_DYNAMIC else: value = cv.int_(value) variant_buses = { From 00965fe19e857a83cb7f500da640c0c80694e79e Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 25 Nov 2021 21:54:11 +0100 Subject: [PATCH 068/111] Consistently format errors in CI scripts (#2762) --- .github/workflows/matchers/ci-custom.json | 2 +- .github/workflows/matchers/lint-python.json | 4 +-- requirements_test.txt | 1 - script/ci-custom.py | 16 +++++----- script/clang-format | 16 ++++------ script/clang-tidy | 33 +++++++++------------ script/helpers.py | 19 +++++++----- script/lint-python | 21 ++++++++----- 8 files changed, 57 insertions(+), 55 deletions(-) diff --git a/.github/workflows/matchers/ci-custom.json b/.github/workflows/matchers/ci-custom.json index 4e1eafff5e..9888dbb440 100644 --- a/.github/workflows/matchers/ci-custom.json +++ b/.github/workflows/matchers/ci-custom.json @@ -4,7 +4,7 @@ "owner": "ci-custom", "pattern": [ { - "regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$", + "regexp": "^(.*):(\\d+):(\\d+):\\s+(.*)$", "file": 1, "line": 2, "column": 3, diff --git a/.github/workflows/matchers/lint-python.json b/.github/workflows/matchers/lint-python.json index decbe36c4a..e88f74f6f6 100644 --- a/.github/workflows/matchers/lint-python.json +++ b/.github/workflows/matchers/lint-python.json @@ -5,7 +5,7 @@ "severity": "error", "pattern": [ { - "regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$", + "regexp": "^(.*):(\\d+): ([EFCDNW]\\d{3}.*)$", "file": 1, "line": 2, "message": 3 @@ -17,7 +17,7 @@ "severity": "error", "pattern": [ { - "regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$", + "regexp": "^(.*):(\\d+): (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$", "file": 1, "line": 2, "message": 3 diff --git a/requirements_test.txt b/requirements_test.txt index ad98ec2a4f..b916e8bb1b 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,7 +1,6 @@ pylint==2.12.1 flake8==4.0.1 black==21.11b1 -pexpect==4.8.0 pre-commit # Unit tests diff --git a/script/ci-custom.py b/script/ci-custom.py index 89550afd3d..3f01fb81bf 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 -from helpers import git_ls_files, filter_changed +from helpers import styled, print_error_for_file, git_ls_files, filter_changed +import argparse import codecs import collections +import colorama import fnmatch +import functools import os.path import re -import subprocess import sys import time -import functools -import argparse sys.path.append(os.path.dirname(__file__)) @@ -30,6 +30,8 @@ def find_all(a_str, sub): column += len(sub) +colorama.init() + parser = argparse.ArgumentParser() parser.add_argument( "files", nargs="*", default=[], help="files to be processed (regex on path)" @@ -657,10 +659,8 @@ for fname in files: run_checks(LINT_POST_CHECKS, "POST") for f, errs in sorted(errors.items()): - print(f"\033[0;32m************* File \033[1;32m{f}\033[0m") - for lineno, col, msg in errs: - print(f"ERROR {f}:{lineno}:{col} - {msg}") - print() + err_str = (f"{styled(colorama.Style.BRIGHT, f'{f}:{lineno}:{col}:')} {msg}\n" for lineno, col, msg in errs) + print_error_for_file(f, "\n".join(err_str)) if args.print_slowest: lint_times = [] diff --git a/script/clang-format b/script/clang-format index d6588f1ccb..515df4c027 100755 --- a/script/clang-format +++ b/script/clang-format @@ -1,6 +1,9 @@ #!/usr/bin/env python3 +from helpers import print_error_for_file, get_output, git_ls_files, filter_changed import argparse +import click +import colorama import multiprocessing import os import queue @@ -9,11 +12,6 @@ import subprocess import sys import threading -import click - -sys.path.append(os.path.dirname(__file__)) -from helpers import get_output, git_ls_files, filter_changed - def run_format(args, queue, lock, failed_files): """Takes filenames out of queue and runs clang-format on them.""" @@ -29,11 +27,7 @@ def run_format(args, queue, lock, failed_files): proc = subprocess.run(invocation, capture_output=True, encoding='utf-8') if proc.returncode != 0: with lock: - print() - print("\033[0;32m************* File \033[1;32m{}\033[0m".format(path)) - print(proc.stdout) - print(proc.stderr) - print() + print_error_for_file(path, proc.stderr) failed_files.append(path) queue.task_done() @@ -43,6 +37,8 @@ def progress_bar_show(value): def main(): + colorama.init() + parser = argparse.ArgumentParser() parser.add_argument('-j', '--jobs', type=int, default=multiprocessing.cpu_count(), diff --git a/script/clang-tidy b/script/clang-tidy index ad5fdfeb04..7450084634 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -1,7 +1,10 @@ #!/usr/bin/env python3 +from helpers import print_error_for_file, get_output, filter_grep, \ + build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata, basepath import argparse -import json +import click +import colorama import multiprocessing import os import queue @@ -12,13 +15,6 @@ import sys import tempfile import threading -import click -import pexpect - -sys.path.append(os.path.dirname(__file__)) -from helpers import shlex_quote, get_output, filter_grep, \ - build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata, basepath - def clang_options(idedata): cmd = [ @@ -87,23 +83,20 @@ def run_tidy(args, options, tmpdir, queue, lock, failed_files): invocation.append(name) if args.quiet: - invocation.append('-quiet') + invocation.append('--quiet') + + if sys.stdout.isatty(): + invocation.append('--use-color') - invocation.append(os.path.abspath(path)) invocation.append(f"--header-filter={os.path.abspath(basepath)}/.*") + invocation.append(os.path.abspath(path)) invocation.append('--') invocation.extend(options) - invocation_s = ' '.join(shlex_quote(x) for x in invocation) - # Use pexpect for a pseudy-TTY with colored output - output, rc = pexpect.run(invocation_s, withexitstatus=True, encoding='utf-8', - timeout=15 * 60) - if rc != 0: + proc = subprocess.run(invocation, capture_output=True, encoding='utf-8') + if proc.returncode != 0: with lock: - print() - print("\033[0;32m************* File \033[1;32m{}\033[0m".format(path)) - print(output) - print() + print_error_for_file(path, proc.stdout) failed_files.append(path) queue.task_done() @@ -119,6 +112,8 @@ def split_list(a, n): def main(): + colorama.init() + parser = argparse.ArgumentParser() parser.add_argument('-j', '--jobs', type=int, default=multiprocessing.cpu_count(), diff --git a/script/helpers.py b/script/helpers.py index 430d8a8e7f..abf970b8a2 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -1,4 +1,4 @@ -import codecs +import colorama import os.path import re import subprocess @@ -11,13 +11,18 @@ temp_folder = os.path.join(root_path, ".temp") temp_header_file = os.path.join(temp_folder, "all-include.cpp") -def shlex_quote(s): - if not s: - return "''" - if re.search(r"[^\w@%+=:,./-]", s) is None: - return s +def styled(color, msg, reset=True): + prefix = ''.join(color) if isinstance(color, tuple) else color + suffix = colorama.Style.RESET_ALL if reset else '' + return prefix + msg + suffix - return "'" + s.replace("'", "'\"'\"'") + "'" + +def print_error_for_file(file, body): + print(styled(colorama.Fore.GREEN, "### File ") + styled((colorama.Fore.GREEN, colorama.Style.BRIGHT), file)) + print() + if body is not None: + print(body) + print() def build_all_include(): diff --git a/script/lint-python b/script/lint-python index 41885b9672..8ee038a661 100755 --- a/script/lint-python +++ b/script/lint-python @@ -1,15 +1,13 @@ #!/usr/bin/env python3 from __future__ import print_function -from helpers import get_output, get_err, git_ls_files, filter_changed - +from helpers import styled, print_error_for_file, get_output, get_err, git_ls_files, filter_changed import argparse +import colorama import os import re import sys -sys.path.append(os.path.dirname(__file__)) - curfile = None @@ -17,14 +15,18 @@ def print_error(file, lineno, msg): global curfile if curfile != file: - print() - print("\033[0;32m************* File \033[1;32m{}\033[0m".format(file)) + print_error_for_file(file, None) curfile = file - print("{}:{} - {}".format(file, lineno, msg)) + if lineno is not None: + print(f"{styled(colorama.Style.BRIGHT, f'{file}:{lineno}:')} {msg}") + else: + print(f"{styled(colorama.Style.BRIGHT, f'{file}:')} {msg}") def main(): + colorama.init() + parser = argparse.ArgumentParser() parser.add_argument( "files", nargs="*", default=[], help="files to be processed (regex on path)" @@ -56,6 +58,7 @@ def main(): cmd = ["black", "--verbose", "--check"] + files print("Running black...") + print() log = get_err(*cmd) for line in log.splitlines(): WOULD_REFORMAT = "would reformat" @@ -65,7 +68,9 @@ def main(): errors += 1 cmd = ["flake8"] + files + print() print("Running flake8...") + print() log = get_output(*cmd) for line in log.splitlines(): line = line.split(":", 4) @@ -78,7 +83,9 @@ def main(): errors += 1 cmd = ["pylint", "-f", "parseable", "--persistent=n"] + files + print() print("Running pylint...") + print() log = get_output(*cmd) for line in log.splitlines(): line = line.split(":", 3) From 2347e043a9dcc5e265328a2d63ffdf140e0281de Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 26 Nov 2021 10:02:39 +1300 Subject: [PATCH 069/111] Cancel previous workflows for PRs and branches (#2800) --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3daabecc9f..93a29874f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,10 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: ci: name: ${{ matrix.name }} From e7827a69976a48aff3f9306eba5ae28f9ec81259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Panella?= Date: Thu, 25 Nov 2021 15:35:36 -0600 Subject: [PATCH 070/111] total_daily_energy: allow to disable restore mode (#2795) --- esphome/components/total_daily_energy/sensor.py | 3 +++ .../total_daily_energy/total_daily_energy.cpp | 12 ++++++------ .../total_daily_energy/total_daily_energy.h | 2 ++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index 6a8a416b81..0c20ccd27c 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -4,6 +4,7 @@ from esphome.components import sensor, time from esphome.const import ( CONF_ICON, CONF_ID, + CONF_RESTORE, CONF_TIME_ID, DEVICE_CLASS_ENERGY, CONF_METHOD, @@ -36,6 +37,7 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(TotalDailyEnergy), cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), + cv.Optional(CONF_RESTORE, default=True): cv.boolean, cv.Optional( CONF_MIN_SAVE_INTERVAL, default="0s" ): cv.positive_time_period_milliseconds, @@ -70,5 +72,6 @@ async def to_code(config): cg.add(var.set_parent(sens)) time_ = await cg.get_variable(config[CONF_TIME_ID]) cg.add(var.set_time(time_)) + cg.add(var.set_restore(config[CONF_RESTORE])) cg.add(var.set_min_save_interval(config[CONF_MIN_SAVE_INTERVAL])) cg.add(var.set_method(config[CONF_METHOD])) diff --git a/esphome/components/total_daily_energy/total_daily_energy.cpp b/esphome/components/total_daily_energy/total_daily_energy.cpp index 178dc7cbe0..3746301715 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.cpp +++ b/esphome/components/total_daily_energy/total_daily_energy.cpp @@ -7,14 +7,14 @@ namespace total_daily_energy { static const char *const TAG = "total_daily_energy"; void TotalDailyEnergy::setup() { - this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); + float initial_value = 0; - float recovered; - if (this->pref_.load(&recovered)) { - this->publish_state_and_save(recovered); - } else { - this->publish_state_and_save(0); + if (this->restore_) { + this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); + this->pref_.load(&initial_value); } + this->publish_state_and_save(initial_value); + this->last_update_ = millis(); this->last_save_ = this->last_update_; diff --git a/esphome/components/total_daily_energy/total_daily_energy.h b/esphome/components/total_daily_energy/total_daily_energy.h index fedceafbd3..498f65891e 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.h +++ b/esphome/components/total_daily_energy/total_daily_energy.h @@ -17,6 +17,7 @@ enum TotalDailyEnergyMethod { class TotalDailyEnergy : public sensor::Sensor, public Component { public: + void set_restore(bool restore) { restore_ = restore; } void set_min_save_interval(uint32_t min_interval) { this->min_save_interval_ = min_interval; } void set_time(time::RealTimeClock *time) { time_ = time; } void set_parent(Sensor *parent) { parent_ = parent; } @@ -41,6 +42,7 @@ class TotalDailyEnergy : public sensor::Sensor, public Component { uint32_t last_update_{0}; uint32_t last_save_{0}; uint32_t min_save_interval_{0}; + bool restore_; float total_energy_{0.0f}; float last_power_state_{0.0f}; }; From 17a37b1de929ccdd9485269a934da3801380da8c Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Fri, 26 Nov 2021 00:48:52 +0100 Subject: [PATCH 071/111] Modbus_controller: Add custom command. (#2680) --- .../components/modbus_controller/__init__.py | 111 ++++++++++++++++-- .../binary_sensor/__init__.py | 49 +++----- esphome/components/modbus_controller/const.py | 1 + .../modbus_controller/modbus_controller.cpp | 59 +++++++--- .../modbus_controller/modbus_controller.h | 33 ++++-- .../modbus_controller/number/__init__.py | 64 ++++------ .../modbus_controller/output/__init__.py | 33 ++---- .../modbus_controller/sensor/__init__.py | 55 +++------ .../modbus_controller/sensor/modbus_sensor.h | 1 + .../modbus_controller/switch/__init__.py | 59 +++++----- .../switch/modbus_switch.cpp | 56 ++++++--- .../modbus_controller/switch/modbus_switch.h | 5 + .../modbus_controller/text_sensor/__init__.py | 50 +++----- 13 files changed, 315 insertions(+), 261 deletions(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 8499cec561..825b91280e 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -1,10 +1,20 @@ +import binascii import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import modbus -from esphome.const import CONF_ID, CONF_ADDRESS +from esphome.const import CONF_ADDRESS, CONF_ID, CONF_NAME, CONF_LAMBDA, CONF_OFFSET from esphome.cpp_helpers import logging from .const import ( + CONF_BITMASK, + CONF_BYTE_OFFSET, CONF_COMMAND_THROTTLE, + CONF_CUSTOM_COMMAND, + CONF_FORCE_NEW_RANGE, + CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_COUNT, + CONF_REGISTER_TYPE, + CONF_SKIP_UPDATES, + CONF_VALUE_TYPE, ) CODEOWNERS = ["@martgras"] @@ -37,6 +47,7 @@ MODBUS_FUNCTION_CODE = { ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType") ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType") MODBUS_REGISTER_TYPE = { + "custom": ModbusRegisterType.CUSTOM, "coil": ModbusRegisterType.COIL, "discrete_input": ModbusRegisterType.DISCRETE_INPUT, "holding": ModbusRegisterType.HOLDING, @@ -95,6 +106,96 @@ CONFIG_SCHEMA = cv.All( ) +ModbusItemBaseSchema = cv.Schema( + { + cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), + cv.Optional(CONF_ADDRESS): cv.positive_int, + cv.Optional(CONF_CUSTOM_COMMAND): cv.ensure_list(cv.hex_uint8_t), + cv.Exclusive( + CONF_OFFSET, + "offset", + f"{CONF_OFFSET} and {CONF_BYTE_OFFSET} can't be used together", + ): cv.positive_int, + cv.Exclusive( + CONF_BYTE_OFFSET, + "offset", + f"{CONF_OFFSET} and {CONF_BYTE_OFFSET} can't be used together", + ): cv.positive_int, + cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t, + cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, + cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + }, +) + + +def validate_modbus_register(config): + if CONF_CUSTOM_COMMAND not in config and CONF_ADDRESS not in config: + raise cv.Invalid( + f" {CONF_ADDRESS} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used" + ) + if CONF_CUSTOM_COMMAND in config and CONF_REGISTER_TYPE in config: + raise cv.Invalid( + f"can't use '{CONF_REGISTER_TYPE}:' together with '{CONF_CUSTOM_COMMAND}:'", + ) + + if CONF_CUSTOM_COMMAND not in config and CONF_REGISTER_TYPE not in config: + raise cv.Invalid( + f" {CONF_REGISTER_TYPE} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used" + ) + return config + + +def modbus_calc_properties(config): + byte_offset = 0 + reg_count = 0 + if CONF_OFFSET in config: + byte_offset = config[CONF_OFFSET] + # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET + if CONF_BYTE_OFFSET in config: + byte_offset = config[CONF_BYTE_OFFSET] + if CONF_REGISTER_COUNT in config: + reg_count = config[CONF_REGISTER_COUNT] + if CONF_VALUE_TYPE in config: + value_type = config[CONF_VALUE_TYPE] + if reg_count == 0: + reg_count = TYPE_REGISTER_MAP[value_type] + if CONF_CUSTOM_COMMAND in config: + if CONF_ADDRESS not in config: + # generate a unique modbus address using the hash of the name + # CONF_NAME set even if only CONF_ID is used. + # a modbus register address is required to add the item to sensormap + value = config[CONF_NAME] + if isinstance(value, str): + value = value.encode() + config[CONF_ADDRESS] = binascii.crc_hqx(value, 0) + config[CONF_REGISTER_TYPE] = ModbusRegisterType.CUSTOM + config[CONF_FORCE_NEW_RANGE] = True + return byte_offset, reg_count + + +async def add_modbus_base_properties( + var, config, sensor_type, lamdba_param_type=cg.float_, lamdba_return_type=float +): + if CONF_CUSTOM_COMMAND in config: + cg.add(var.set_custom_data(config[CONF_CUSTOM_COMMAND])) + + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], + [ + (sensor_type.operator("ptr"), "item"), + (lamdba_param_type, "x"), + ( + cg.std_vector.template(cg.uint8).operator("const").operator("ref"), + "data", + ), + ], + return_type=cg.optional.template(lamdba_return_type), + ) + cg.add(var.set_template(template_)) + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID], config[CONF_COMMAND_THROTTLE]) cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE])) @@ -119,11 +220,3 @@ def function_code_to_register(function_code): "write_multiple_registers": ModbusRegisterType.HOLDING, } return FUNCTION_CODE_TYPE_MAP[function_code] - - -def find_by_value(dict, find_value): - for (key, value) in MODBUS_REGISTER_TYPE.items(): - print(find_value, value) - if find_value == value: - return key - return "not found" diff --git a/esphome/components/modbus_controller/binary_sensor/__init__.py b/esphome/components/modbus_controller/binary_sensor/__init__.py index d46ff71f2d..99d56fed67 100644 --- a/esphome/components/modbus_controller/binary_sensor/__init__.py +++ b/esphome/components/modbus_controller/binary_sensor/__init__.py @@ -2,16 +2,18 @@ from esphome.components import binary_sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OFFSET +from esphome.const import CONF_ADDRESS, CONF_ID from .. import ( - SensorItem, + add_modbus_base_properties, modbus_controller_ns, - ModbusController, + modbus_calc_properties, + validate_modbus_register, + ModbusItemBaseSchema, + SensorItem, MODBUS_REGISTER_TYPE, ) from ..const import ( CONF_BITMASK, - CONF_BYTE_OFFSET, CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, CONF_REGISTER_TYPE, @@ -27,30 +29,20 @@ ModbusBinarySensor = modbus_controller_ns.class_( ) CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.BINARY_SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA) + .extend(ModbusItemBaseSchema) + .extend( { cv.GenerateID(): cv.declare_id(ModbusBinarySensor), - cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), - cv.Required(CONF_ADDRESS): cv.positive_int, - cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), - cv.Optional(CONF_OFFSET, default=0): cv.positive_int, - cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, - cv.Optional(CONF_BITMASK, default=0x1): cv.hex_uint32_t, - cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, - cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, - cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), } - ).extend(cv.COMPONENT_SCHEMA), + ), + validate_modbus_register, ) async def to_code(config): - byte_offset = 0 - if CONF_OFFSET in config: - byte_offset = config[CONF_OFFSET] - # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET - if CONF_BYTE_OFFSET in config: - byte_offset = config[CONF_BYTE_OFFSET] + byte_offset, _ = modbus_calc_properties(config) var = cg.new_Pvariable( config[CONF_ID], config[CONF_REGISTER_TYPE], @@ -65,17 +57,4 @@ async def to_code(config): paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) cg.add(paren.add_sensor_item(var)) - if CONF_LAMBDA in config: - template_ = await cg.process_lambda( - config[CONF_LAMBDA], - [ - (ModbusBinarySensor.operator("ptr"), "item"), - (cg.float_, "x"), - ( - cg.std_vector.template(cg.uint8).operator("const").operator("ref"), - "data", - ), - ], - return_type=cg.optional.template(bool), - ) - cg.add(var.set_template(template_)) + await add_modbus_base_properties(var, config, ModbusBinarySensor, cg.float_, bool) diff --git a/esphome/components/modbus_controller/const.py b/esphome/components/modbus_controller/const.py index 3cd114e673..8d1676dd38 100644 --- a/esphome/components/modbus_controller/const.py +++ b/esphome/components/modbus_controller/const.py @@ -1,6 +1,7 @@ CONF_BITMASK = "bitmask" CONF_BYTE_OFFSET = "byte_offset" CONF_COMMAND_THROTTLE = "command_throttle" +CONF_CUSTOM_COMMAND = "custom_command" CONF_FORCE_NEW_RANGE = "force_new_range" CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id" CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode" diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 70b5bf8eae..8b96c20691 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -28,7 +28,10 @@ bool ModbusController::send_next_command_() { command->register_address, command->register_count); command->send(); this->last_command_timestamp_ = millis(); - if (!command->on_data_func) { // No handler remove from queue directly after sending + // remove from queue if no handler is defined or command was sent too often + if (!command->on_data_func || command->send_countdown < 1) { + ESP_LOGD(TAG, "Modbus command to device=%d register=0x%02X countdown=%d removed from queue after send", + this->address_, command->register_address, command->send_countdown); command_queue_.pop_front(); } } @@ -69,24 +72,30 @@ void ModbusController::on_modbus_error(uint8_t function_code, uint8_t exception_ } } -void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address, - const std::vector &data) { - ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address); - +std::map::iterator ModbusController::find_register_(ModbusRegisterType register_type, + uint16_t start_address) { auto vec_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) { return (r.start_address == start_address && r.register_type == register_type); }); if (vec_it == register_ranges_.end()) { - ESP_LOGE(TAG, "Handle incoming data : No matching range for sensor found - start_address : 0x%X", start_address); - return; - } - auto map_it = sensormap_.find(vec_it->first_sensorkey); - if (map_it == sensormap_.end()) { - ESP_LOGE(TAG, "Handle incoming data : No sensor found in at start_address : 0x%X (0x%llX)", start_address, - vec_it->first_sensorkey); - return; + ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address); + } else { + auto map_it = sensormap_.find(vec_it->first_sensorkey); + if (map_it == sensormap_.end()) { + ESP_LOGE(TAG, "No sensor found in at start_address : 0x%X (0x%llX)", start_address, vec_it->first_sensorkey); + } else { + return sensormap_.find(vec_it->first_sensorkey); + } } + // not found + return std::end(sensormap_); +} +void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data) { + ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address); + + auto map_it = find_register_(register_type, start_address); // loop through all sensors with the same start address while (map_it != sensormap_.end() && map_it->second->start_address == start_address) { if (map_it->second->register_type == register_type) { @@ -116,9 +125,23 @@ void ModbusController::update_range_(RegisterRange &r) { ESP_LOGV(TAG, "Range : %X Size: %x (%d) skip: %d", r.start_address, r.register_count, (int) r.register_type, r.skip_updates_counter); if (r.skip_updates_counter == 0) { - ModbusCommandItem command_item = - ModbusCommandItem::create_read_command(this, r.register_type, r.start_address, r.register_count); - queue_command(command_item); + // if a custom command is used the user supplied custom_data is only available in the SensorItem. + if (r.register_type == ModbusRegisterType::CUSTOM) { + auto it = this->find_register_(r.register_type, r.start_address); + if (it != sensormap_.end()) { + auto command_item = ModbusCommandItem::create_custom_command( + this, it->second->custom_data, + [this](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { + this->on_register_data(ModbusRegisterType::CUSTOM, start_address, data); + }); + command_item.register_address = it->second->start_address; + command_item.register_count = it->second->register_count; + command_item.function_code = ModbusFunctionCode::CUSTOM; + queue_command(command_item); + } + } else { + queue_command(ModbusCommandItem::create_read_command(this, r.register_type, r.start_address, r.register_count)); + } r.skip_updates_counter = r.skip_updates; // reset counter to config value } else { r.skip_updates_counter--; @@ -422,6 +445,7 @@ bool ModbusCommandItem::send() { modbusdevice->send_raw(this->payload); } ESP_LOGV(TAG, "Command sent %d 0x%X %d", uint8_t(this->function_code), this->register_address, this->register_count); + send_countdown--; return true; } @@ -549,6 +573,9 @@ float payload_to_float(const std::vector &data, SensorValueType sensor_ ESP_LOGD(TAG, "FP32_R = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value); result = raw_to_float.float_value; } break; + case SensorValueType::RAW: + result = NAN; + break; default: break; } diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 222ebbd020..39c0d8026f 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -247,18 +247,11 @@ float payload_to_float(const std::vector &data, SensorValueType sensor_ class ModbusController; -struct SensorItem { - ModbusRegisterType register_type; - SensorValueType sensor_value_type; - uint16_t start_address; - uint32_t bitmask; - uint8_t offset; - uint8_t register_count; - uint8_t skip_updates; - bool force_new_range{false}; - +class SensorItem { + public: virtual void parse_and_publish(const std::vector &data) = 0; + void set_custom_data(const std::vector &data) { custom_data = data; } uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); } size_t virtual get_register_size() const { if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) @@ -266,10 +259,22 @@ struct SensorItem { else return register_count * 2; } + + ModbusRegisterType register_type; + SensorValueType sensor_value_type; + uint16_t start_address; + uint32_t bitmask; + uint8_t offset; + uint8_t register_count; + uint8_t skip_updates; + std::vector custom_data{}; + bool force_new_range{false}; }; -struct ModbusCommandItem { +class ModbusCommandItem { + public: static const size_t MAX_PAYLOAD_BYTES = 240; + static const uint8_t MAX_SEND_REPEATS = 5; ModbusController *modbusdevice; uint16_t register_address; uint16_t register_count; @@ -279,7 +284,9 @@ struct ModbusCommandItem { on_data_func; std::vector payload = {}; bool send(); - + // wrong commands (esp. custom commands) can block the send queue + // limit the number of repeats + uint8_t send_countdown{MAX_SEND_REPEATS}; /// factory methods /** Create modbus read command * Function code 02-04 @@ -392,6 +399,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { protected: /// parse sensormap_ and create range of sequential addresses size_t create_register_ranges_(); + // find register in sensormap. Returns iterator with all registers having the same start address + std::map::iterator find_register_(ModbusRegisterType register_type, uint16_t start_address); /// submit the read command for the address range to the send queue void update_range_(RegisterRange &r); /// parse incoming modbus data diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py index 4de0ffbcea..3c5db9b9c8 100644 --- a/esphome/components/modbus_controller/number/__init__.py +++ b/esphome/components/modbus_controller/number/__init__.py @@ -4,29 +4,26 @@ from esphome.components import number from esphome.const import ( CONF_ADDRESS, CONF_ID, - CONF_LAMBDA, CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_MULTIPLY, - CONF_OFFSET, CONF_STEP, ) from .. import ( + add_modbus_base_properties, modbus_controller_ns, - ModbusController, - SENSOR_VALUE_TYPE, + modbus_calc_properties, + ModbusItemBaseSchema, SensorItem, - TYPE_REGISTER_MAP, + SENSOR_VALUE_TYPE, ) - from ..const import ( CONF_BITMASK, - CONF_BYTE_OFFSET, + CONF_CUSTOM_COMMAND, CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, - CONF_REGISTER_COUNT, CONF_SKIP_UPDATES, CONF_VALUE_TYPE, CONF_WRITE_LAMBDA, @@ -51,22 +48,21 @@ def validate_min_max(config): return config +def validate_modbus_number(config): + if CONF_CUSTOM_COMMAND not in config and CONF_ADDRESS not in config: + raise cv.Invalid( + f" {CONF_ADDRESS} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used" + ) + return config + + CONFIG_SCHEMA = cv.All( - number.NUMBER_SCHEMA.extend( + number.NUMBER_SCHEMA.extend(ModbusItemBaseSchema) + .extend( { cv.GenerateID(): cv.declare_id(ModbusNumber), - cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), - cv.Required(CONF_ADDRESS): cv.positive_int, - cv.Optional(CONF_OFFSET, default=0): cv.positive_int, - cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, - cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t, cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), - cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, - cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, - cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, - cv.Optional(CONF_LAMBDA): cv.returning_lambda, cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, - cv.GenerateID(): cv.declare_id(ModbusNumber), # 24 bits are the maximum value for fp32 before precison is lost # 0x00FFFFFF = 16777215 cv.Optional(CONF_MAX_VALUE, default=16777215.0): cv.float_, @@ -74,22 +70,15 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_STEP, default=1): cv.positive_float, cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, } - ).extend(cv.polling_component_schema("60s")), + ) + .extend(cv.polling_component_schema("60s")), validate_min_max, + validate_modbus_number, ) async def to_code(config): - byte_offset = 0 - if CONF_OFFSET in config: - byte_offset = config[CONF_OFFSET] - # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET - if CONF_BYTE_OFFSET in config: - byte_offset = config[CONF_BYTE_OFFSET] - value_type = config[CONF_VALUE_TYPE] - reg_count = config[CONF_REGISTER_COUNT] - if reg_count == 0: - reg_count = TYPE_REGISTER_MAP[value_type] + byte_offset, reg_count = modbus_calc_properties(config) var = cg.new_Pvariable( config[CONF_ID], config[CONF_ADDRESS], @@ -115,20 +104,7 @@ async def to_code(config): cg.add(var.set_parent(parent)) cg.add(parent.add_sensor_item(var)) - if CONF_LAMBDA in config: - template_ = await cg.process_lambda( - config[CONF_LAMBDA], - [ - (ModbusNumber.operator("ptr"), "item"), - (cg.float_, "x"), - ( - cg.std_vector.template(cg.uint8).operator("const").operator("ref"), - "data", - ), - ], - return_type=cg.optional.template(float), - ) - cg.add(var.set_template(template_)) + await add_modbus_base_properties(var, config, ModbusNumber) if CONF_WRITE_LAMBDA in config: template_ = await cg.process_lambda( config[CONF_WRITE_LAMBDA], diff --git a/esphome/components/modbus_controller/output/__init__.py b/esphome/components/modbus_controller/output/__init__.py index 4aca4db64f..eacd96579f 100644 --- a/esphome/components/modbus_controller/output/__init__.py +++ b/esphome/components/modbus_controller/output/__init__.py @@ -6,24 +6,21 @@ from esphome.const import ( CONF_ADDRESS, CONF_ID, CONF_MULTIPLY, - CONF_OFFSET, ) from .. import ( - SensorItem, modbus_controller_ns, - ModbusController, - TYPE_REGISTER_MAP, + modbus_calc_properties, + validate_modbus_register, + ModbusItemBaseSchema, + SensorItem, ) from ..const import ( - CONF_BYTE_OFFSET, CONF_MODBUS_CONTROLLER_ID, - CONF_REGISTER_COUNT, CONF_VALUE_TYPE, CONF_WRITE_LAMBDA, ) -from ..sensor import SENSOR_VALUE_TYPE DEPENDENCIES = ["modbus_controller"] CODEOWNERS = ["@martgras"] @@ -34,38 +31,24 @@ ModbusOutput = modbus_controller_ns.class_( ) CONFIG_SCHEMA = cv.All( - output.FLOAT_OUTPUT_SCHEMA.extend( + output.FLOAT_OUTPUT_SCHEMA.extend(ModbusItemBaseSchema).extend( { - cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), cv.GenerateID(): cv.declare_id(ModbusOutput), - cv.Required(CONF_ADDRESS): cv.positive_int, - cv.Optional(CONF_OFFSET, default=0): cv.positive_int, - cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, - cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), - cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, } ), + validate_modbus_register, ) async def to_code(config): - byte_offset = 0 - if CONF_OFFSET in config: - byte_offset = config[CONF_OFFSET] - # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET - if CONF_BYTE_OFFSET in config: - byte_offset = config[CONF_BYTE_OFFSET] - value_type = config[CONF_VALUE_TYPE] - reg_count = config[CONF_REGISTER_COUNT] - if reg_count == 0: - reg_count = TYPE_REGISTER_MAP[value_type] + byte_offset, reg_count = modbus_calc_properties(config) var = cg.new_Pvariable( config[CONF_ID], config[CONF_ADDRESS], byte_offset, - value_type, + config[CONF_VALUE_TYPE], reg_count, ) await output.register_output(var, config) diff --git a/esphome/components/modbus_controller/sensor/__init__.py b/esphome/components/modbus_controller/sensor/__init__.py index 82acfe120b..da7b8928b4 100644 --- a/esphome/components/modbus_controller/sensor/__init__.py +++ b/esphome/components/modbus_controller/sensor/__init__.py @@ -2,18 +2,19 @@ from esphome.components import sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET +from esphome.const import CONF_ID, CONF_ADDRESS from .. import ( - SensorItem, + add_modbus_base_properties, modbus_controller_ns, - ModbusController, + modbus_calc_properties, + validate_modbus_register, + ModbusItemBaseSchema, + SensorItem, MODBUS_REGISTER_TYPE, SENSOR_VALUE_TYPE, - TYPE_REGISTER_MAP, ) from ..const import ( CONF_BITMASK, - CONF_BYTE_OFFSET, CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, CONF_REGISTER_COUNT, @@ -31,43 +32,30 @@ ModbusSensor = modbus_controller_ns.class_( ) CONFIG_SCHEMA = cv.All( - sensor.SENSOR_SCHEMA.extend( + sensor.SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA) + .extend(ModbusItemBaseSchema) + .extend( { cv.GenerateID(): cv.declare_id(ModbusSensor), - cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), - cv.Required(CONF_ADDRESS): cv.positive_int, - cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), - cv.Optional(CONF_OFFSET, default=0): cv.positive_int, - cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, - cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t, + cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, - cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, - cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, - cv.Optional(CONF_LAMBDA): cv.returning_lambda, } - ).extend(cv.COMPONENT_SCHEMA), + ), + validate_modbus_register, ) async def to_code(config): - byte_offset = 0 - if CONF_OFFSET in config: - byte_offset = config[CONF_OFFSET] - # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET - if CONF_BYTE_OFFSET in config: - byte_offset = config[CONF_BYTE_OFFSET] + byte_offset, reg_count = modbus_calc_properties(config) value_type = config[CONF_VALUE_TYPE] - reg_count = config[CONF_REGISTER_COUNT] - if reg_count == 0: - reg_count = TYPE_REGISTER_MAP[value_type] var = cg.new_Pvariable( config[CONF_ID], config[CONF_REGISTER_TYPE], config[CONF_ADDRESS], byte_offset, config[CONF_BITMASK], - config[CONF_VALUE_TYPE], + value_type, reg_count, config[CONF_SKIP_UPDATES], config[CONF_FORCE_NEW_RANGE], @@ -77,17 +65,4 @@ async def to_code(config): paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) cg.add(paren.add_sensor_item(var)) - if CONF_LAMBDA in config: - template_ = await cg.process_lambda( - config[CONF_LAMBDA], - [ - (ModbusSensor.operator("ptr"), "item"), - (cg.float_, "x"), - ( - cg.std_vector.template(cg.uint8).operator("const").operator("ref"), - "data", - ), - ], - return_type=cg.optional.template(float), - ) - cg.add(var.set_template(template_)) + await add_modbus_base_properties(var, config, ModbusSensor) diff --git a/esphome/components/modbus_controller/sensor/modbus_sensor.h b/esphome/components/modbus_controller/sensor/modbus_sensor.h index 4f48c2a4dd..37ea9d0dd0 100644 --- a/esphome/components/modbus_controller/sensor/modbus_sensor.h +++ b/esphome/components/modbus_controller/sensor/modbus_sensor.h @@ -25,6 +25,7 @@ class ModbusSensor : public Component, public sensor::Sensor, public SensorItem void parse_and_publish(const std::vector &data) override; void dump_config() override; using transform_func_t = std::function(ModbusSensor *, float, const std::vector &)>; + void set_template(transform_func_t &&f) { this->transform_func_ = f; } protected: diff --git a/esphome/components/modbus_controller/switch/__init__.py b/esphome/components/modbus_controller/switch/__init__.py index e03b0d37be..df11b268ac 100644 --- a/esphome/components/modbus_controller/switch/__init__.py +++ b/esphome/components/modbus_controller/switch/__init__.py @@ -3,21 +3,25 @@ import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET +from esphome.const import CONF_ID, CONF_ADDRESS from .. import ( - MODBUS_REGISTER_TYPE, - SensorItem, + add_modbus_base_properties, modbus_controller_ns, - ModbusController, + modbus_calc_properties, + validate_modbus_register, + ModbusItemBaseSchema, + SensorItem, + MODBUS_REGISTER_TYPE, ) from ..const import ( CONF_BITMASK, - CONF_BYTE_OFFSET, CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, CONF_REGISTER_TYPE, + CONF_WRITE_LAMBDA, ) +CONF_USE_WRITE_MULTIPLE = "use_write_multiple" DEPENDENCIES = ["modbus_controller"] CODEOWNERS = ["@martgras"] @@ -26,31 +30,23 @@ ModbusSwitch = modbus_controller_ns.class_( "ModbusSwitch", cg.Component, switch.Switch, SensorItem ) - CONFIG_SCHEMA = cv.All( - switch.SWITCH_SCHEMA.extend( + switch.SWITCH_SCHEMA.extend(cv.COMPONENT_SCHEMA) + .extend(ModbusItemBaseSchema) + .extend( { cv.GenerateID(): cv.declare_id(ModbusSwitch), - cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), - cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), - cv.Required(CONF_ADDRESS): cv.positive_int, - cv.Optional(CONF_OFFSET, default=0): cv.positive_int, - cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, - cv.Optional(CONF_BITMASK, default=0x1): cv.hex_uint32_t, - cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, - cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), + cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, + cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, } - ).extend(cv.COMPONENT_SCHEMA), + ), + validate_modbus_register, ) async def to_code(config): - byte_offset = 0 - if CONF_OFFSET in config: - byte_offset = config[CONF_OFFSET] - # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET - if CONF_BYTE_OFFSET in config: - byte_offset = config[CONF_BYTE_OFFSET] + byte_offset, _ = modbus_calc_properties(config) var = cg.new_Pvariable( config[CONF_ID], config[CONF_REGISTER_TYPE], @@ -63,19 +59,18 @@ async def to_code(config): await switch.register_switch(var, config) paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) - cg.add(paren.add_sensor_item(var)) cg.add(var.set_parent(paren)) - if CONF_LAMBDA in config: - publish_template_ = await cg.process_lambda( - config[CONF_LAMBDA], + cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE])) + cg.add(paren.add_sensor_item(var)) + if CONF_WRITE_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_WRITE_LAMBDA], [ (ModbusSwitch.operator("ptr"), "item"), - (bool, "x"), - ( - cg.std_vector.template(cg.uint8).operator("const").operator("ref"), - "data", - ), + (cg.bool_, "x"), + (cg.std_vector.template(cg.uint8).operator("ref"), "payload"), ], return_type=cg.optional.template(bool), ) - cg.add(var.set_template(publish_template_)) + cg.add(var.set_write_template(template_)) + await add_modbus_base_properties(var, config, ModbusSwitch, bool, bool) diff --git a/esphome/components/modbus_controller/switch/modbus_switch.cpp b/esphome/components/modbus_controller/switch/modbus_switch.cpp index ce9557e6c4..c7c3c419d4 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.cpp +++ b/esphome/components/modbus_controller/switch/modbus_switch.cpp @@ -45,22 +45,50 @@ void ModbusSwitch::parse_and_publish(const std::vector &data) { void ModbusSwitch::write_state(bool state) { // This will be called every time the user requests a state change. ModbusCommandItem cmd; - ESP_LOGV(TAG, "write_state '%s': new value = %s type = %d address = %X offset = %x", this->get_name().c_str(), - ONOFF(state), (int) this->register_type, this->start_address, this->offset); - switch (this->register_type) { - case ModbusRegisterType::COIL: + std::vector data; + // Is there are lambda configured? + if (this->write_transform_func_.has_value()) { + // data is passed by reference + // the lambda can fill the empty vector directly + // in that case the return value is ignored + auto val = (*this->write_transform_func_)(this, state, data); + if (val.has_value()) { + ESP_LOGV(TAG, "Value overwritten by lambda"); + state = val.value(); + } else { + ESP_LOGV(TAG, "Communication handled by lambda - exiting control"); + return; + } + } + if (!data.empty()) { + ESP_LOGV(TAG, "Modbus Switch write raw: %s", hexencode(data).c_str()); + cmd = ModbusCommandItem::create_custom_command( + this->parent_, data, + [this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { + this->parent_->on_write_register_response(cmd.register_type, this->start_address, data); + }); + } else { + ESP_LOGV(TAG, "write_state '%s': new value = %s type = %d address = %X offset = %x", this->get_name().c_str(), + ONOFF(state), (int) this->register_type, this->start_address, this->offset); + if (this->register_type == ModbusRegisterType::COIL) { // offset for coil and discrete inputs is the coil/register number not bytes - cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state); - break; - case ModbusRegisterType::DISCRETE_INPUT: - cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, state); - break; - - default: + if (this->use_write_multiple_) { + std::vector states{state}; + cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states); + } else { + cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state); + } + } else { // since offset is in bytes and a register is 16 bits we get the start by adding offset/2 - cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, - state ? 0xFFFF & this->bitmask : 0); - break; + if (this->use_write_multiple_) { + std::vector bool_states(1, state ? (0xFFFF & this->bitmask) : 0); + cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, 1, + bool_states); + } else { + cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, + state ? 0xFFFF & this->bitmask : 0u); + } + } } this->parent_->queue_command(cmd); publish_state(state); diff --git a/esphome/components/modbus_controller/switch/modbus_switch.h b/esphome/components/modbus_controller/switch/modbus_switch.h index a38668fabb..5ac2af01a1 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.h +++ b/esphome/components/modbus_controller/switch/modbus_switch.h @@ -33,11 +33,16 @@ class ModbusSwitch : public Component, public switch_::Switch, public SensorItem void set_parent(ModbusController *parent) { this->parent_ = parent; } using transform_func_t = std::function(ModbusSwitch *, bool, const std::vector &)>; + using write_transform_func_t = std::function(ModbusSwitch *, bool, std::vector &)>; void set_template(transform_func_t &&f) { this->publish_transform_func_ = f; } + void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } protected: ModbusController *parent_; + bool use_write_multiple_; optional publish_transform_func_{nullopt}; + optional write_transform_func_{nullopt}; }; } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/text_sensor/__init__.py b/esphome/components/modbus_controller/text_sensor/__init__.py index 2c02c86795..5cc85af5bc 100644 --- a/esphome/components/modbus_controller/text_sensor/__init__.py +++ b/esphome/components/modbus_controller/text_sensor/__init__.py @@ -3,15 +3,17 @@ import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET +from esphome.const import CONF_ADDRESS, CONF_ID from .. import ( - SensorItem, + add_modbus_base_properties, modbus_controller_ns, - ModbusController, + modbus_calc_properties, + validate_modbus_register, + ModbusItemBaseSchema, + SensorItem, MODBUS_REGISTER_TYPE, ) from ..const import ( - CONF_BYTE_OFFSET, CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, CONF_REGISTER_COUNT, @@ -38,32 +40,23 @@ RAW_ENCODING = { } CONFIG_SCHEMA = cv.All( - text_sensor.TEXT_SENSOR_SCHEMA.extend( + text_sensor.TEXT_SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA) + .extend(ModbusItemBaseSchema) + .extend( { cv.GenerateID(): cv.declare_id(ModbusTextSensor), - cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), - cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), - cv.Required(CONF_ADDRESS): cv.positive_int, - cv.Optional(CONF_OFFSET, default=0): cv.positive_int, - cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, + cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, cv.Optional(CONF_RESPONSE_SIZE, default=2): cv.positive_int, cv.Optional(CONF_RAW_ENCODE, default="NONE"): cv.enum(RAW_ENCODING), - cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, - cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, - cv.Optional(CONF_LAMBDA): cv.returning_lambda, } - ).extend(cv.COMPONENT_SCHEMA), + ), + validate_modbus_register, ) async def to_code(config): - byte_offset = 0 - if CONF_OFFSET in config: - byte_offset = config[CONF_OFFSET] - # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET - if CONF_BYTE_OFFSET in config: - byte_offset = config[CONF_BYTE_OFFSET] + byte_offset, reg_count = modbus_calc_properties(config) response_size = config[CONF_RESPONSE_SIZE] reg_count = config[CONF_REGISTER_COUNT] if reg_count == 0: @@ -85,17 +78,6 @@ async def to_code(config): paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) cg.add(paren.add_sensor_item(var)) - if CONF_LAMBDA in config: - template_ = await cg.process_lambda( - config[CONF_LAMBDA], - [ - (ModbusTextSensor.operator("ptr"), "item"), - (cg.std_string.operator("const").operator("ref"), "x"), - ( - cg.std_vector.template(cg.uint8).operator("const").operator("ref"), - "data", - ), - ], - return_type=cg.optional.template(cg.std_string), - ) - cg.add(var.set_template(template_)) + await add_modbus_base_properties( + var, config, ModbusTextSensor, cg.std_string, cg.std_string + ) From 5946c379256a3b652eec9334f1401d5ceb96d955 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Fri, 26 Nov 2021 09:16:39 +0100 Subject: [PATCH 072/111] Fix usage of deprecated climate method in anova (#2801) --- esphome/components/anova/anova.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/anova/anova.h b/esphome/components/anova/anova.h index 2e6910f326..4f8f0d0ee2 100644 --- a/esphome/components/anova/anova.h +++ b/esphome/components/anova/anova.h @@ -30,7 +30,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode climate::ClimateTraits traits() override { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_heat_mode(true); + traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::ClimateMode::CLIMATE_MODE_HEAT}); traits.set_visual_min_temperature(25.0); traits.set_visual_max_temperature(100.0); traits.set_visual_temperature_step(0.1); From 671d68bc2ceecb74756894cd634d1d3cf2653d6e Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Fri, 26 Nov 2021 21:25:58 +0100 Subject: [PATCH 073/111] Add missing nvs_flash_init() to ESP32 preferences code (#2805) Co-authored-by: Maurice Makaay --- esphome/components/esp32/preferences.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index 96b7e7809e..8c2b67a942 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -76,6 +76,7 @@ class ESP32Preferences : public ESPPreferences { uint32_t current_offset = 0; void open() { + nvs_flash_init(); esp_err_t err = nvs_open("esphome", NVS_READWRITE, &nvs_handle); if (err == 0) return; From 7a564b222d8aa7a3ab1a29d10693eea0c85bf0b5 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 28 Nov 2021 19:59:30 +0100 Subject: [PATCH 074/111] Make clang-tidy suggest stdint.h int types (#2820) --- .clang-tidy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.clang-tidy b/.clang-tidy index 79276f81c3..1c7e65b762 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -101,6 +101,8 @@ CheckOptions: value: '10' - key: google-readability-namespace-comments.SpacesBeforeComments value: '2' + - key: google-runtime-int.TypeSuffix + value: '_t' - key: modernize-loop-convert.MaxCopySize value: '16' - key: modernize-loop-convert.MinConfidence From 10a2a7e0fc8e957010a32a3842b51547134300eb Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 28 Nov 2021 20:00:29 +0100 Subject: [PATCH 075/111] Fix parsing numbers in Anova (#2816) --- esphome/components/anova/anova_base.cpp | 6 +++--- esphome/core/helpers.cpp | 5 +++++ esphome/core/helpers.h | 6 ++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp index dcef75e483..cb877bef35 100644 --- a/esphome/components/anova/anova_base.cpp +++ b/esphome/components/anova/anova_base.cpp @@ -104,21 +104,21 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) { break; } case READ_TARGET_TEMPERATURE: { - this->target_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f); + this->target_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f); if (this->fahrenheit_) this->target_temp_ = ftoc(this->target_temp_); this->has_target_temp_ = true; break; } case SET_TARGET_TEMPERATURE: { - this->target_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f); + this->target_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f); if (this->fahrenheit_) this->target_temp_ = ftoc(this->target_temp_); this->has_target_temp_ = true; break; } case READ_CURRENT_TEMPERATURE: { - this->current_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f); + this->current_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f); if (this->fahrenheit_) this->current_temp_ = ftoc(this->current_temp_); this->has_current_temp_ = true; diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 37d9cdc24b..60849fcae7 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -448,6 +448,11 @@ IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } std::string str_truncate(const std::string &str, size_t length) { return str.length() > length ? str.substr(0, length) : str; } +std::string str_until(const char *str, char ch) { + char *pos = strchr(str, ch); + return pos == nullptr ? std::string(str) : std::string(str, pos - str); +} +std::string str_until(const std::string &str, char ch) { return str.substr(0, str.find(ch)); } std::string str_snake_case(const std::string &str) { std::string result; result.resize(str.length()); diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 1faf1ac3aa..63aa4123ae 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -353,6 +353,12 @@ template::value, int> = 0> constexpr /// Truncate a string to a specific length. std::string str_truncate(const std::string &str, size_t length); +/// Extract the part of the string until either the first occurence of the specified character, or the end (requires str +/// to be null-terminated). +std::string str_until(const char *str, char ch); +/// Extract the part of the string until either the first occurence of the specified character, or the end. +std::string str_until(const std::string &str, char ch); + /// Convert the string to snake case (lowercase with underscores). std::string str_snake_case(const std::string &str); From 2b504068569f599b0297536f7c7e287368c83f8d Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 28 Nov 2021 20:02:10 +0100 Subject: [PATCH 076/111] Fix parsing of multiple values in EZO sensor (#2814) Co-authored-by: Lydia Sevelt --- esphome/components/ezo/ezo.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index 426f2807c1..ca6f121dbb 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -74,6 +74,11 @@ void EZOSensor::loop() { if (buf[0] != 1) return; + // some sensors return multiple comma-separated values, terminate string after first one + for (int i = 1; i < sizeof(buf) - 1; i++) + if (buf[i] == ',') + buf[i] = '\0'; + float val = parse_number((char *) &buf[1], sizeof(buf) - 2).value_or(0); this->publish_state(val); } From 7a5c3aa7edbac0e448977710ec958c5302eb840c Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Sun, 28 Nov 2021 20:06:53 +0100 Subject: [PATCH 077/111] Fix compilation error for WPA enterprise in ESP-IDF (#2815) --- esphome/components/wifi/wifi_component_esp_idf.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 7f71b7078c..1d346c0a8e 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -375,8 +375,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_password failed! %d", err); } } - esp_wpa2_config_t wpa2_config = WPA2_CONFIG_INIT_DEFAULT(); - err = esp_wifi_sta_wpa2_ent_enable(&wpa2_config); + err = esp_wifi_sta_wpa2_ent_enable(); if (err != ESP_OK) { ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_enable failed! %d", err); } From 10f830c3efcddeb0a40348f100db1ef0344b4971 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sun, 28 Nov 2021 19:12:40 +0000 Subject: [PATCH 078/111] Correct bitmask for third color (blue) scaling. (#2817) --- esphome/components/display/display_color_utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/display/display_color_utils.h b/esphome/components/display/display_color_utils.h index 8fc3b0adb9..202de912de 100644 --- a/esphome/components/display/display_color_utils.h +++ b/esphome/components/display/display_color_utils.h @@ -42,7 +42,7 @@ class ColorUtil { ? esp_scale(((colorcode >> third_bits) & ((1 << second_bits) - 1)), ((1 << second_bits) - 1)) : esp_scale(((colorcode >> 8) & 0xFF), ((1 << second_bits) - 1)); - third_color = (right_bit_aligned ? esp_scale(((colorcode >> 0) & 0xFF), ((1 << third_bits) - 1)) + third_color = (right_bit_aligned ? esp_scale(((colorcode >> 0) & ((1 << third_bits) - 1)), ((1 << third_bits) - 1)) : esp_scale(((colorcode >> 0) & 0xFF), (1 << third_bits) - 1)); Color color_return; From 7afcb0fb043945b1172cfdb45d33657d923100cf Mon Sep 17 00:00:00 2001 From: Conclusio Date: Sun, 28 Nov 2021 20:13:42 +0100 Subject: [PATCH 079/111] Add delay to improve stability (#2793) --- esphome/components/scd30/scd30.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index e6d6ec1c1a..272ee75e30 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -200,6 +200,7 @@ bool SCD30Component::is_data_ready_() { if (!this->write_command_(SCD30_CMD_GET_DATA_READY_STATUS)) { return false; } + delay(4); uint16_t is_data_ready; if (!this->read_data_(&is_data_ready, 1)) { return false; From cae283dc8678789ae70b11fdab7ee9fca19964c8 Mon Sep 17 00:00:00 2001 From: anatoly-savchenkov <48646998+anatoly-savchenkov@users.noreply.github.com> Date: Sun, 28 Nov 2021 22:31:15 +0300 Subject: [PATCH 080/111] Fixed data type inside fast_random_8() routine (#2818) --- esphome/core/helpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 60849fcae7..6678eddbff 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -98,7 +98,7 @@ uint16_t fast_random_16() { return (rand32 & 0xFFFF) + (rand32 >> 16); } uint8_t fast_random_8() { - uint8_t rand32 = fast_random_32(); + uint32_t rand32 = fast_random_32(); return (rand32 & 0xFF) + ((rand32 >> 8) & 0xFF); } From adf48246a9b73bd3abe78f9e41d00ea6f3f0b202 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Mon, 29 Nov 2021 16:40:53 +0100 Subject: [PATCH 081/111] Improve DSMR read timeout handling (#2699) --- esphome/components/dsmr/__init__.py | 14 +- esphome/components/dsmr/dsmr.cpp | 181 ++++++++++++---------- esphome/components/dsmr/dsmr.h | 30 ++-- esphome/components/uart/uart_component.h | 1 + esphome/components/uart/uart_debugger.cpp | 9 ++ tests/test3.yaml | 1 + 6 files changed, 136 insertions(+), 100 deletions(-) diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index 06b022c513..7a7681082e 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -5,6 +5,7 @@ from esphome.components import uart from esphome.const import ( CONF_ID, CONF_UART_ID, + CONF_RECEIVE_TIMEOUT, ) CODEOWNERS = ["@glmnet", "@zuidwijk"] @@ -52,7 +53,12 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_, cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_REQUEST_INTERVAL): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_REQUEST_INTERVAL, default="0ms" + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_RECEIVE_TIMEOUT, default="200ms" + ): cv.positive_time_period_milliseconds, } ).extend(uart.UART_DEVICE_SCHEMA), cv.only_with_arduino, @@ -70,10 +76,8 @@ async def to_code(config): if CONF_REQUEST_PIN in config: request_pin = await cg.gpio_pin_expression(config[CONF_REQUEST_PIN]) cg.add(var.set_request_pin(request_pin)) - if CONF_REQUEST_INTERVAL in config: - cg.add( - var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds) - ) + cg.add(var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds)) + cg.add(var.set_receive_timeout(config[CONF_RECEIVE_TIMEOUT].total_milliseconds)) cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID]) diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 03e418662a..7b339e5fe0 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -24,7 +24,7 @@ void Dsmr::loop() { if (this->decryption_key_.empty()) { this->receive_telegram_(); } else { - this->receive_encrypted_(); + this->receive_encrypted_telegram_(); } } } @@ -57,14 +57,42 @@ bool Dsmr::request_interval_reached_() { return millis() - this->last_request_time_ > this->request_interval_; } +bool Dsmr::receive_timeout_reached_() { return millis() - this->last_read_time_ > this->receive_timeout_; } + bool Dsmr::available_within_timeout_() { - uint8_t tries = READ_TIMEOUT_MS / 5; - while (tries--) { - delay(5); - if (this->available()) { - return true; + // Data are available for reading on the UART bus? + // Then we can start reading right away. + if (this->available()) { + this->last_read_time_ = millis(); + return true; + } + // When we're not in the process of reading a telegram, then there is + // no need to actively wait for new data to come in. + if (!header_found_) { + return false; + } + // A telegram is being read. The smart meter might not deliver a telegram + // in one go, but instead send it in chunks with small pauses in between. + // When the UART RX buffer cannot hold a full telegram, then make sure + // that the UART read buffer does not overflow while other components + // perform their work in their loop. Do this by not returning control to + // the main loop, until the read timeout is reached. + if (this->parent_->get_rx_buffer_size() < this->max_telegram_len_) { + while (!this->receive_timeout_reached_()) { + delay(5); + if (this->available()) { + this->last_read_time_ = millis(); + return true; + } } } + // No new data has come in during the read timeout? Then stop reading the + // telegram and start waiting for the next one to arrive. + if (this->receive_timeout_reached_()) { + ESP_LOGW(TAG, "Timeout while reading data for telegram"); + this->reset_telegram_(); + } + return false; } @@ -96,30 +124,31 @@ void Dsmr::stop_requesting_data_() { } } -void Dsmr::receive_telegram_() { - while (true) { - if (!this->available()) { - if (!this->header_found_ || !this->available_within_timeout_()) { - return; - } - } +void Dsmr::reset_telegram_() { + this->header_found_ = false; + this->footer_found_ = false; + this->bytes_read_ = 0; + this->crypt_bytes_read_ = 0; + this->crypt_telegram_len_ = 0; + this->last_read_time_ = 0; +} +void Dsmr::receive_telegram_() { + while (this->available_within_timeout_()) { const char c = this->read(); // Find a new telegram header, i.e. forward slash. if (c == '/') { ESP_LOGV(TAG, "Header of telegram found"); + this->reset_telegram_(); this->header_found_ = true; - this->footer_found_ = false; - this->telegram_len_ = 0; } if (!this->header_found_) continue; // Check for buffer overflow. - if (this->telegram_len_ >= this->max_telegram_len_) { - this->header_found_ = false; - this->footer_found_ = false; + if (this->bytes_read_ >= this->max_telegram_len_) { + this->reset_telegram_(); ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_); return; } @@ -129,9 +158,9 @@ void Dsmr::receive_telegram_() { // proper parsing, remove these new line characters. if (c == '(') { while (true) { - auto previous_char = this->telegram_[this->telegram_len_ - 1]; + auto previous_char = this->telegram_[this->bytes_read_ - 1]; if (previous_char == '\n' || previous_char == '\r') { - this->telegram_len_--; + this->bytes_read_--; } else { break; } @@ -139,8 +168,8 @@ void Dsmr::receive_telegram_() { } // Store the byte in the buffer. - this->telegram_[this->telegram_len_] = c; - this->telegram_len_++; + this->telegram_[this->bytes_read_] = c; + this->bytes_read_++; // Check for a footer, i.e. exlamation mark, followed by a hex checksum. if (c == '!') { @@ -152,28 +181,14 @@ void Dsmr::receive_telegram_() { if (this->footer_found_ && c == '\n') { // Parse the telegram and publish sensor values. this->parse_telegram(); - - this->header_found_ = false; + this->reset_telegram_(); return; } } } -void Dsmr::receive_encrypted_() { - this->encrypted_telegram_len_ = 0; - size_t packet_size = 0; - - while (true) { - if (!this->available()) { - if (!this->header_found_) { - return; - } - if (!this->available_within_timeout_()) { - ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram"); - return; - } - } - +void Dsmr::receive_encrypted_telegram_() { + while (this->available_within_timeout_()) { const char c = this->read(); // Find a new telegram start byte. @@ -182,50 +197,58 @@ void Dsmr::receive_encrypted_() { continue; } ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); + this->reset_telegram_(); this->header_found_ = true; } // Check for buffer overflow. - if (this->encrypted_telegram_len_ >= this->max_telegram_len_) { - this->header_found_ = false; + if (this->crypt_bytes_read_ >= this->max_telegram_len_) { + this->reset_telegram_(); ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_); return; } - this->encrypted_telegram_[this->encrypted_telegram_len_++] = c; + // Store the byte in the buffer. + this->crypt_telegram_[this->crypt_bytes_read_] = c; + this->crypt_bytes_read_++; - if (packet_size == 0 && this->encrypted_telegram_len_ > 20) { + // Read the length of the incoming encrypted telegram. + if (this->crypt_telegram_len_ == 0 && this->crypt_bytes_read_ > 20) { // Complete header + data bytes - packet_size = 13 + (this->encrypted_telegram_[11] << 8 | this->encrypted_telegram_[12]); - ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size); + this->crypt_telegram_len_ = 13 + (this->crypt_telegram_[11] << 8 | this->crypt_telegram_[12]); + ESP_LOGV(TAG, "Encrypted telegram length: %d bytes", this->crypt_telegram_len_); } - if (this->encrypted_telegram_len_ == packet_size && packet_size > 0) { - ESP_LOGV(TAG, "End of encrypted telegram found"); - GCM *gcmaes128{new GCM()}; - gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize()); - // the iv is 8 bytes of the system title + 4 bytes frame counter - // system title is at byte 2 and frame counter at byte 15 - for (int i = 10; i < 14; i++) - this->encrypted_telegram_[i] = this->encrypted_telegram_[i + 4]; - constexpr uint16_t iv_size{12}; - gcmaes128->setIV(&this->encrypted_telegram_[2], iv_size); - gcmaes128->decrypt(reinterpret_cast(this->telegram_), - // the ciphertext start at byte 18 - &this->encrypted_telegram_[18], - // cipher size - this->encrypted_telegram_len_ - 17); - delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory) - this->telegram_len_ = strnlen(this->telegram_, this->max_telegram_len_); - ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->telegram_len_); - ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); - - this->parse_telegram(); - - this->header_found_ = false; - this->telegram_len_ = 0; - return; + // Check for the end of the encrypted telegram. + if (this->crypt_telegram_len_ == 0 || this->crypt_bytes_read_ != this->crypt_telegram_len_) { + continue; } + ESP_LOGV(TAG, "End of encrypted telegram found"); + + // Decrypt the encrypted telegram. + GCM *gcmaes128{new GCM()}; + gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize()); + // the iv is 8 bytes of the system title + 4 bytes frame counter + // system title is at byte 2 and frame counter at byte 15 + for (int i = 10; i < 14; i++) + this->crypt_telegram_[i] = this->crypt_telegram_[i + 4]; + constexpr uint16_t iv_size{12}; + gcmaes128->setIV(&this->crypt_telegram_[2], iv_size); + gcmaes128->decrypt(reinterpret_cast(this->telegram_), + // the ciphertext start at byte 18 + &this->crypt_telegram_[18], + // cipher size + this->crypt_bytes_read_ - 17); + delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory) + + this->bytes_read_ = strnlen(this->telegram_, this->max_telegram_len_); + ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->bytes_read_); + ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); + + // Parse the decrypted telegram and publish sensor values. + this->parse_telegram(); + this->reset_telegram_(); + return; } } @@ -234,11 +257,11 @@ bool Dsmr::parse_telegram() { ESP_LOGV(TAG, "Trying to parse telegram"); this->stop_requesting_data_(); ::dsmr::ParseResult res = - ::dsmr::P1Parser::parse(&data, this->telegram_, this->telegram_len_, false, + ::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false, this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. if (res.err) { // Parsing error, show it - auto err_str = res.fullError(this->telegram_, this->telegram_ + this->telegram_len_); + auto err_str = res.fullError(this->telegram_, this->telegram_ + this->bytes_read_); ESP_LOGE(TAG, "%s", err_str.c_str()); return false; } else { @@ -251,7 +274,7 @@ bool Dsmr::parse_telegram() { void Dsmr::dump_config() { ESP_LOGCONFIG(TAG, "DSMR:"); ESP_LOGCONFIG(TAG, " Max telegram length: %d", this->max_telegram_len_); - + ESP_LOGCONFIG(TAG, " Receive timeout: %.1fs", this->receive_timeout_ / 1e3f); if (this->request_pin_ != nullptr) { LOG_PIN(" Request Pin: ", this->request_pin_); } @@ -270,9 +293,9 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { if (decryption_key.length() == 0) { ESP_LOGI(TAG, "Disabling decryption"); this->decryption_key_.clear(); - if (this->encrypted_telegram_ != nullptr) { - delete[] this->encrypted_telegram_; - this->encrypted_telegram_ = nullptr; + if (this->crypt_telegram_ != nullptr) { + delete[] this->crypt_telegram_; + this->crypt_telegram_ = nullptr; } return; } @@ -293,13 +316,11 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16)); } - if (this->encrypted_telegram_ == nullptr) { - this->encrypted_telegram_ = new uint8_t[this->max_telegram_len_]; // NOLINT + if (this->crypt_telegram_ == nullptr) { + this->crypt_telegram_ = new uint8_t[this->max_telegram_len_]; // NOLINT } } -void Dsmr::set_max_telegram_length(size_t length) { max_telegram_len_ = length; } - } // namespace dsmr } // namespace esphome diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index 0430eb93ed..db0bf95ca1 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -16,8 +16,6 @@ namespace esphome { namespace dsmr { -static constexpr uint32_t READ_TIMEOUT_MS = 200; - using namespace ::dsmr::fields; // DSMR_**_LIST generated by ESPHome and written in esphome/core/defines @@ -71,11 +69,10 @@ class Dsmr : public Component, public uart::UARTDevice { void dump_config() override; void set_decryption_key(const std::string &decryption_key); - - void set_max_telegram_length(size_t length); - + void set_max_telegram_length(size_t length) { this->max_telegram_len_ = length; } void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; } void set_request_interval(uint32_t interval) { this->request_interval_ = interval; } + void set_receive_timeout(uint32_t timeout) { this->receive_timeout_ = timeout; } // Sensor setters #define DSMR_SET_SENSOR(s) \ @@ -88,7 +85,8 @@ class Dsmr : public Component, public uart::UARTDevice { protected: void receive_telegram_(); - void receive_encrypted_(); + void receive_encrypted_telegram_(); + void reset_telegram_(); /// Wait for UART data to become available within the read timeout. /// @@ -101,24 +99,26 @@ class Dsmr : public Component, public uart::UARTDevice { /// lost in the process. bool available_within_timeout_(); - // Data request + // Request telegram + uint32_t request_interval_; + bool request_interval_reached_(); GPIOPin *request_pin_{nullptr}; - uint32_t request_interval_{0}; uint32_t last_request_time_{0}; bool requesting_data_{false}; bool ready_to_request_data_(); - bool request_interval_reached_(); void start_requesting_data_(); void stop_requesting_data_(); - // Telegram buffer + // Read telegram + uint32_t receive_timeout_; + bool receive_timeout_reached_(); size_t max_telegram_len_; char *telegram_{nullptr}; - int telegram_len_{0}; - uint8_t *encrypted_telegram_{nullptr}; - int encrypted_telegram_len_{0}; - - // Serial parser + int bytes_read_{0}; + uint8_t *crypt_telegram_{nullptr}; + size_t crypt_telegram_len_{0}; + int crypt_bytes_read_{0}; + uint32_t last_read_time_{0}; bool header_found_{false}; bool footer_found_{false}; diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h index 73694d3db7..42702cf5b8 100644 --- a/esphome/components/uart/uart_component.h +++ b/esphome/components/uart/uart_component.h @@ -52,6 +52,7 @@ class UARTComponent { void set_tx_pin(InternalGPIOPin *tx_pin) { this->tx_pin_ = tx_pin; } void set_rx_pin(InternalGPIOPin *rx_pin) { this->rx_pin_ = rx_pin; } void set_rx_buffer_size(size_t rx_buffer_size) { this->rx_buffer_size_ = rx_buffer_size; } + size_t get_rx_buffer_size() { return this->rx_buffer_size_; } void set_stop_bits(uint8_t stop_bits) { this->stop_bits_ = stop_bits; } uint8_t get_stop_bits() const { return this->stop_bits_; } diff --git a/esphome/components/uart/uart_debugger.cpp b/esphome/components/uart/uart_debugger.cpp index 9a535656a2..e2d92eac60 100644 --- a/esphome/components/uart/uart_debugger.cpp +++ b/esphome/components/uart/uart_debugger.cpp @@ -90,6 +90,11 @@ void UARTDummyReceiver::loop() { } } +// In the upcoming log functions, a delay was added after all log calls. +// This is done to allow the system to ship the log lines via the API +// TCP connection(s). Without these delays, debug log lines could go +// missing when UART devices block the main loop for too long. + void UARTDebug::log_hex(UARTDirection direction, std::vector bytes, uint8_t separator) { std::string res; if (direction == UART_DIRECTION_RX) { @@ -107,6 +112,7 @@ void UARTDebug::log_hex(UARTDirection direction, std::vector bytes, uin res += buf; } ESP_LOGD(TAG, "%s", res.c_str()); + delay(10); } void UARTDebug::log_string(UARTDirection direction, std::vector bytes) { @@ -150,6 +156,7 @@ void UARTDebug::log_string(UARTDirection direction, std::vector bytes) } res += '"'; ESP_LOGD(TAG, "%s", res.c_str()); + delay(10); } void UARTDebug::log_int(UARTDirection direction, std::vector bytes, uint8_t separator) { @@ -167,6 +174,7 @@ void UARTDebug::log_int(UARTDirection direction, std::vector bytes, uin res += to_string(bytes[i]); } ESP_LOGD(TAG, "%s", res.c_str()); + delay(10); } void UARTDebug::log_binary(UARTDirection direction, std::vector bytes, uint8_t separator) { @@ -186,6 +194,7 @@ void UARTDebug::log_binary(UARTDirection direction, std::vector bytes, res += buf; } ESP_LOGD(TAG, "%s", res.c_str()); + delay(10); } } // namespace uart diff --git a/tests/test3.yaml b/tests/test3.yaml index 61c7a3dcad..8ae4a383e0 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1313,6 +1313,7 @@ dsmr: max_telegram_length: 1000 request_pin: D5 request_interval: 20s + receive_timeout: 100ms daly_bms: update_interval: 20s From 6f07421911bd1390cbf776956e3f4c4f45301f6b Mon Sep 17 00:00:00 2001 From: mechanarchy <1166756+mechanarchy@users.noreply.github.com> Date: Tue, 30 Nov 2021 02:52:20 +1100 Subject: [PATCH 082/111] Optionally show internal components on the web server (#2627) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen --- esphome/components/web_server/__init__.py | 3 + esphome/components/web_server/web_server.cpp | 109 ++++++++----------- esphome/components/web_server/web_server.h | 7 ++ esphome/const.py | 1 + esphome/core/controller.cpp | 22 ++-- esphome/core/controller.h | 2 +- tests/test4.yaml | 1 + 7 files changed, 67 insertions(+), 78 deletions(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index dc652e0312..d9ff84d501 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -12,6 +12,7 @@ from esphome.const import ( CONF_AUTH, CONF_USERNAME, CONF_PASSWORD, + CONF_INCLUDE_INTERNAL, CONF_OTA, ) from esphome.core import CORE, coroutine_with_priority @@ -42,6 +43,7 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( web_server_base.WebServerBase ), + cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, cv.Optional(CONF_OTA, default=True): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA) @@ -75,3 +77,4 @@ async def to_code(config): path = CORE.relative_config_path(config[CONF_JS_INCLUDE]) with open(file=path, mode="r", encoding="utf-8") as myfile: cg.add(var.set_js_include(myfile.read())) + cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL])) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 6f47f460af..3f74c2e8a1 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -31,10 +31,10 @@ static const char *const TAG = "web_server"; void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action, const std::function &action_func = nullptr) { - if (obj->is_internal()) - return; stream->print("print(klass.c_str()); + if (obj->is_internal()) + stream->print(" internal"); stream->print("\" id=\""); stream->print(klass.c_str()); stream->print("-"); @@ -83,7 +83,7 @@ void WebServer::set_js_include(const char *js_include) { this->js_include_ = js_ void WebServer::setup() { ESP_LOGCONFIG(TAG, "Setting up web server..."); - this->setup_controller(); + this->setup_controller(this->include_internal_); this->base_->init(); this->events_.onConnect([this](AsyncEventSourceClient *client) { @@ -92,55 +92,55 @@ void WebServer::setup() { #ifdef USE_SENSOR for (auto *obj : App.get_sensors()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->sensor_json(obj, obj->state).c_str(), "state"); #endif #ifdef USE_SWITCH for (auto *obj : App.get_switches()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->switch_json(obj, obj->state).c_str(), "state"); #endif #ifdef USE_BINARY_SENSOR for (auto *obj : App.get_binary_sensors()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->binary_sensor_json(obj, obj->state).c_str(), "state"); #endif #ifdef USE_FAN for (auto *obj : App.get_fans()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->fan_json(obj).c_str(), "state"); #endif #ifdef USE_LIGHT for (auto *obj : App.get_lights()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->light_json(obj).c_str(), "state"); #endif #ifdef USE_TEXT_SENSOR for (auto *obj : App.get_text_sensors()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->text_sensor_json(obj, obj->state).c_str(), "state"); #endif #ifdef USE_COVER for (auto *obj : App.get_covers()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->cover_json(obj).c_str(), "state"); #endif #ifdef USE_NUMBER for (auto *obj : App.get_numbers()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->number_json(obj, obj->state).c_str(), "state"); #endif #ifdef USE_SELECT for (auto *obj : App.get_selects()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->select_json(obj, obj->state).c_str(), "state"); #endif }); @@ -188,57 +188,66 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #ifdef USE_SENSOR for (auto *obj : App.get_sensors()) - write_row(stream, obj, "sensor", ""); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "sensor", ""); #endif #ifdef USE_SWITCH for (auto *obj : App.get_switches()) - write_row(stream, obj, "switch", ""); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "switch", ""); #endif #ifdef USE_BINARY_SENSOR for (auto *obj : App.get_binary_sensors()) - write_row(stream, obj, "binary_sensor", ""); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "binary_sensor", ""); #endif #ifdef USE_FAN for (auto *obj : App.get_fans()) - write_row(stream, obj, "fan", ""); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "fan", ""); #endif #ifdef USE_LIGHT for (auto *obj : App.get_lights()) - write_row(stream, obj, "light", ""); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "light", ""); #endif #ifdef USE_TEXT_SENSOR for (auto *obj : App.get_text_sensors()) - write_row(stream, obj, "text_sensor", ""); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "text_sensor", ""); #endif #ifdef USE_COVER for (auto *obj : App.get_covers()) - write_row(stream, obj, "cover", ""); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "cover", ""); #endif #ifdef USE_NUMBER for (auto *obj : App.get_numbers()) - write_row(stream, obj, "number", ""); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "number", ""); #endif #ifdef USE_SELECT for (auto *obj : App.get_selects()) - write_row(stream, obj, "select", "", [](AsyncResponseStream &stream, EntityBase *obj) { - select::Select *select = (select::Select *) obj; - stream.print(""); - }); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "select", "", [](AsyncResponseStream &stream, EntityBase *obj) { + select::Select *select = (select::Select *) obj; + stream.print(""); + }); #endif stream->print(F("

See ESPHome Web API for " @@ -293,8 +302,6 @@ void WebServer::on_sensor_update(sensor::Sensor *obj, float state) { } void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (sensor::Sensor *obj : App.get_sensors()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; std::string data = this->sensor_json(obj, obj->state); @@ -321,8 +328,6 @@ void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::s } void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (text_sensor::TextSensor *obj : App.get_text_sensors()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; std::string data = this->text_sensor_json(obj, obj->state); @@ -353,8 +358,6 @@ std::string WebServer::switch_json(switch_::Switch *obj, bool value) { } void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (switch_::Switch *obj : App.get_switches()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; @@ -381,8 +384,6 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM #ifdef USE_BINARY_SENSOR void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { - if (obj->is_internal()) - return; this->events_.send(this->binary_sensor_json(obj, state).c_str(), "state"); } std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value) { @@ -394,8 +395,6 @@ std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool } void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; std::string data = this->binary_sensor_json(obj, obj->state); @@ -407,11 +406,7 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con #endif #ifdef USE_FAN -void WebServer::on_fan_update(fan::FanState *obj) { - if (obj->is_internal()) - return; - this->events_.send(this->fan_json(obj).c_str(), "state"); -} +void WebServer::on_fan_update(fan::FanState *obj) { this->events_.send(this->fan_json(obj).c_str(), "state"); } std::string WebServer::fan_json(fan::FanState *obj) { return json::build_json([obj](JsonObject &root) { root["id"] = "fan-" + obj->get_object_id(); @@ -442,8 +437,6 @@ std::string WebServer::fan_json(fan::FanState *obj) { } void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (fan::FanState *obj : App.get_fans()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; @@ -504,15 +497,9 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc #endif #ifdef USE_LIGHT -void WebServer::on_light_update(light::LightState *obj) { - if (obj->is_internal()) - return; - this->events_.send(this->light_json(obj).c_str(), "state"); -} +void WebServer::on_light_update(light::LightState *obj) { this->events_.send(this->light_json(obj).c_str(), "state"); } void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (light::LightState *obj : App.get_lights()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; @@ -579,15 +566,9 @@ std::string WebServer::light_json(light::LightState *obj) { #endif #ifdef USE_COVER -void WebServer::on_cover_update(cover::Cover *obj) { - if (obj->is_internal()) - return; - this->events_.send(this->cover_json(obj).c_str(), "state"); -} +void WebServer::on_cover_update(cover::Cover *obj) { this->events_.send(this->cover_json(obj).c_str(), "state"); } void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (cover::Cover *obj : App.get_covers()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; @@ -646,8 +627,6 @@ void WebServer::on_number_update(number::Number *obj, float state) { } void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_numbers()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; std::string data = this->number_json(obj, obj->state); @@ -673,8 +652,6 @@ void WebServer::on_select_update(select::Select *obj, const std::string &state) } void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_selects()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index cdfec51cf1..66fd082d19 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -58,6 +58,12 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { */ void set_js_include(const char *js_include); + /** Determine whether internal components should be displayed on the web server. + * Defaults to false. + * + * @param include_internal Whether internal components should be displayed. + */ + void set_include_internal(bool include_internal) { include_internal_ = include_internal; } /** Set whether or not the webserver should expose the OTA form and handler. * * @param allow_ota. @@ -188,6 +194,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { const char *css_include_{nullptr}; const char *js_url_{nullptr}; const char *js_include_{nullptr}; + bool include_internal_{false}; bool allow_ota_{true}; }; diff --git a/esphome/const.py b/esphome/const.py index f7beee8245..9006145dfe 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -286,6 +286,7 @@ CONF_ILLUMINANCE = "illuminance" CONF_IMPEDANCE = "impedance" CONF_IMPORT_ACTIVE_ENERGY = "import_active_energy" CONF_IMPORT_REACTIVE_ENERGY = "import_reactive_energy" +CONF_INCLUDE_INTERNAL = "include_internal" CONF_INCLUDES = "includes" CONF_INDEX = "index" CONF_INDOOR = "indoor" diff --git a/esphome/core/controller.cpp b/esphome/core/controller.cpp index 1d25be41f2..6d3a76a292 100644 --- a/esphome/core/controller.cpp +++ b/esphome/core/controller.cpp @@ -4,64 +4,64 @@ namespace esphome { -void Controller::setup_controller() { +void Controller::setup_controller(bool include_internal) { #ifdef USE_BINARY_SENSOR for (auto *obj : App.get_binary_sensors()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](bool state) { this->on_binary_sensor_update(obj, state); }); } #endif #ifdef USE_FAN for (auto *obj : App.get_fans()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj]() { this->on_fan_update(obj); }); } #endif #ifdef USE_LIGHT for (auto *obj : App.get_lights()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_new_remote_values_callback([this, obj]() { this->on_light_update(obj); }); } #endif #ifdef USE_SENSOR for (auto *obj : App.get_sensors()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](float state) { this->on_sensor_update(obj, state); }); } #endif #ifdef USE_SWITCH for (auto *obj : App.get_switches()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](bool state) { this->on_switch_update(obj, state); }); } #endif #ifdef USE_COVER for (auto *obj : App.get_covers()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj]() { this->on_cover_update(obj); }); } #endif #ifdef USE_TEXT_SENSOR for (auto *obj : App.get_text_sensors()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](const std::string &state) { this->on_text_sensor_update(obj, state); }); } #endif #ifdef USE_CLIMATE for (auto *obj : App.get_climates()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj]() { this->on_climate_update(obj); }); } #endif #ifdef USE_NUMBER for (auto *obj : App.get_numbers()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](float state) { this->on_number_update(obj, state); }); } #endif #ifdef USE_SELECT for (auto *obj : App.get_selects()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](const std::string &state) { this->on_select_update(obj, state); }); } #endif diff --git a/esphome/core/controller.h b/esphome/core/controller.h index 0de8f7ea19..f53924cd23 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -36,7 +36,7 @@ namespace esphome { class Controller { public: - void setup_controller(); + void setup_controller(bool include_internal = false); #ifdef USE_BINARY_SENSOR virtual void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state){}; #endif diff --git a/tests/test4.yaml b/tests/test4.yaml index 938145235a..ee88b422f2 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -49,6 +49,7 @@ web_server: auth: username: admin password: admin + include_internal: true time: - platform: sntp From f50e40e0b893618250e8537fa69cee4fad29e5f1 Mon Sep 17 00:00:00 2001 From: definitio <37266727+definitio@users.noreply.github.com> Date: Mon, 29 Nov 2021 20:09:09 +0300 Subject: [PATCH 083/111] Fix custom mode_state_topic (#2827) --- esphome/components/climate/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index b0f9146012..87b9a4b3e2 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -213,7 +213,7 @@ async def setup_climate_core_(var, config): if CONF_MODE_COMMAND_TOPIC in config: cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC])) if CONF_MODE_STATE_TOPIC in config: - cg.add(mqtt_.set_custom_state_topic(config[CONF_MODE_STATE_TOPIC])) + cg.add(mqtt_.set_custom_mode_state_topic(config[CONF_MODE_STATE_TOPIC])) if CONF_SWING_MODE_COMMAND_TOPIC in config: cg.add( From b5639a6472fdd06e2a6d72252a0f3097ab4b163a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 30 Nov 2021 08:00:51 +1300 Subject: [PATCH 084/111] Add support for button entities (#2824) --- CODEOWNERS | 1 + esphome/components/api/api.proto | 25 ++++ esphome/components/api/api_connection.cpp | 21 ++++ esphome/components/api/api_connection.h | 4 + esphome/components/api/api_pb2.cpp | 112 ++++++++++++++++++ esphome/components/api/api_pb2.h | 30 +++++ esphome/components/api/api_pb2_service.cpp | 34 ++++++ esphome/components/api/api_pb2_service.h | 12 ++ esphome/components/api/list_entities.cpp | 3 + esphome/components/api/list_entities.h | 3 + esphome/components/api/subscribe_state.h | 3 + esphome/components/api/util.cpp | 15 +++ esphome/components/api/util.h | 6 + esphome/components/button/__init__.py | 104 ++++++++++++++++ esphome/components/button/automation.h | 28 +++++ esphome/components/button/button.cpp | 21 ++++ esphome/components/button/button.h | 50 ++++++++ esphome/components/mqtt/__init__.py | 1 + esphome/components/mqtt/mqtt_button.cpp | 40 +++++++ esphome/components/mqtt/mqtt_button.h | 40 +++++++ esphome/components/restart/button/__init__.py | 23 ++++ .../restart/button/restart_button.cpp | 20 ++++ .../restart/button/restart_button.h | 18 +++ .../restart/{switch.py => switch/__init__.py} | 0 .../restart/{ => switch}/restart_switch.cpp | 0 .../restart/{ => switch}/restart_switch.h | 0 .../components/template/button/__init__.py | 13 ++ esphome/components/web_server/web_server.cpp | 37 ++++++ esphome/components/web_server/web_server.h | 5 + esphome/core/application.h | 19 +++ esphome/core/controller.h | 3 + esphome/core/defines.h | 1 + script/ci-custom.py | 1 + tests/test4.yaml | 4 + 34 files changed, 697 insertions(+) create mode 100644 esphome/components/button/__init__.py create mode 100644 esphome/components/button/automation.h create mode 100644 esphome/components/button/button.cpp create mode 100644 esphome/components/button/button.h create mode 100644 esphome/components/mqtt/mqtt_button.cpp create mode 100644 esphome/components/mqtt/mqtt_button.h create mode 100644 esphome/components/restart/button/__init__.py create mode 100644 esphome/components/restart/button/restart_button.cpp create mode 100644 esphome/components/restart/button/restart_button.h rename esphome/components/restart/{switch.py => switch/__init__.py} (100%) rename esphome/components/restart/{ => switch}/restart_switch.cpp (100%) rename esphome/components/restart/{ => switch}/restart_switch.h (100%) create mode 100644 esphome/components/template/button/__init__.py diff --git a/CODEOWNERS b/CODEOWNERS index 80c5dc34c1..687fad3948 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -30,6 +30,7 @@ esphome/components/bang_bang/* @OttoWinter esphome/components/binary_sensor/* @esphome/core esphome/components/ble_client/* @buxtronix esphome/components/bme680_bsec/* @trvrnrth +esphome/components/button/* @esphome/core esphome/components/canbus/* @danielschramm @mvturnho esphome/components/cap1188/* @MrEditor97 esphome/components/captive_portal/* @OttoWinter diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 0eb7ead735..eaad4b8d07 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -40,6 +40,7 @@ service APIConnection { rpc climate_command (ClimateCommandRequest) returns (void) {} rpc number_command (NumberCommandRequest) returns (void) {} rpc select_command (SelectCommandRequest) returns (void) {} + rpc button_command (ButtonCommandRequest) returns (void) {} } @@ -944,3 +945,27 @@ message SelectCommandRequest { fixed32 key = 1; string state = 2; } + +// ==================== BUTTON ==================== +message ListEntitiesButtonResponse { + option (id) = 61; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BUTTON"; + + string object_id = 1; + fixed32 key = 2; + string name = 3; + string unique_id = 4; + + string icon = 5; + bool disabled_by_default = 6; + EntityCategory entity_category = 7; +} +message ButtonCommandRequest { + option (id) = 62; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BUTTON"; + option (no_delay) = true; + + fixed32 key = 1; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 715a4f48c1..22b896a788 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -674,6 +674,27 @@ void APIConnection::select_command(const SelectCommandRequest &msg) { } #endif +#ifdef USE_BUTTON +bool APIConnection::send_button_info(button::Button *button) { + ListEntitiesButtonResponse msg; + msg.key = button->get_object_id_hash(); + msg.object_id = button->get_object_id(); + msg.name = button->get_name(); + msg.unique_id = get_default_unique_id("button", button); + msg.icon = button->get_icon(); + msg.disabled_by_default = button->is_disabled_by_default(); + msg.entity_category = static_cast(button->get_entity_category()); + return this->send_list_entities_button_response(msg); +} +void APIConnection::button_command(const ButtonCommandRequest &msg) { + button::Button *button = App.get_button_by_key(msg.key); + if (button == nullptr) + return; + + button->press(); +} +#endif + #ifdef USE_ESP32_CAMERA void APIConnection::send_camera_state(std::shared_ptr image) { if (!this->state_subscription_) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index a1f1769a19..72697b5911 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -73,6 +73,10 @@ class APIConnection : public APIServerConnection { bool send_select_state(select::Select *select, std::string state); bool send_select_info(select::Select *select); void select_command(const SelectCommandRequest &msg) override; +#endif +#ifdef USE_BUTTON + bool send_button_info(button::Button *button); + void button_command(const ButtonCommandRequest &msg) override; #endif bool send_log_message(int level, const char *tag, const char *line); void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 7bfa1e9edb..169349d995 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -4147,6 +4147,118 @@ void SelectCommandRequest::dump_to(std::string &out) const { out.append("}"); } #endif +bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 6: { + this->disabled_by_default = value.as_bool(); + return true; + } + case 7: { + this->entity_category = value.as_enum(); + return true; + } + default: + return false; + } +} +bool ListEntitiesButtonResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: { + this->object_id = value.as_string(); + return true; + } + case 3: { + this->name = value.as_string(); + return true; + } + case 4: { + this->unique_id = value.as_string(); + return true; + } + case 5: { + this->icon = value.as_string(); + return true; + } + default: + return false; + } +} +bool ListEntitiesButtonResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 2: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->object_id); + buffer.encode_fixed32(2, this->key); + buffer.encode_string(3, this->name); + buffer.encode_string(4, this->unique_id); + buffer.encode_string(5, this->icon); + buffer.encode_bool(6, this->disabled_by_default); + buffer.encode_enum(7, this->entity_category); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void ListEntitiesButtonResponse::dump_to(std::string &out) const { + char buffer[64]; + out.append("ListEntitiesButtonResponse {\n"); + out.append(" object_id: "); + out.append("'").append(this->object_id).append("'"); + out.append("\n"); + + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" name: "); + out.append("'").append(this->name).append("'"); + out.append("\n"); + + out.append(" unique_id: "); + out.append("'").append(this->unique_id).append("'"); + out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); + out.append("}"); +} +#endif +bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); } +#ifdef HAS_PROTO_MESSAGE_DUMP +void ButtonCommandRequest::dump_to(std::string &out) const { + char buffer[64]; + out.append("ButtonCommandRequest {\n"); + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index ec11732c7d..82fd8de687 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1041,6 +1041,36 @@ class SelectCommandRequest : public ProtoMessage { bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; }; +class ListEntitiesButtonResponse : public ProtoMessage { + public: + std::string object_id{}; + uint32_t key{0}; + std::string name{}; + std::string unique_id{}; + std::string icon{}; + bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class ButtonCommandRequest : public ProtoMessage { + public: + uint32_t key{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; +}; } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index ad2413ea57..567fbf02c9 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -282,6 +282,16 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon #endif #ifdef USE_SELECT #endif +#ifdef USE_BUTTON +bool APIServerConnectionBase::send_list_entities_button_response(const ListEntitiesButtonResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_list_entities_button_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 61); +} +#endif +#ifdef USE_BUTTON +#endif bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { switch (msg_type) { case 1: { @@ -513,6 +523,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str()); #endif this->on_select_command_request(msg); +#endif + break; + } + case 62: { +#ifdef USE_BUTTON + ButtonCommandRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str()); +#endif + this->on_button_command_request(msg); #endif break; } @@ -737,6 +758,19 @@ void APIServerConnection::on_select_command_request(const SelectCommandRequest & this->select_command(msg); } #endif +#ifdef USE_BUTTON +void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->button_command(msg); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 1b8d990b05..50b08d3ec4 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -129,6 +129,12 @@ class APIServerConnectionBase : public ProtoService { #endif #ifdef USE_SELECT virtual void on_select_command_request(const SelectCommandRequest &value){}; +#endif +#ifdef USE_BUTTON + bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg); +#endif +#ifdef USE_BUTTON + virtual void on_button_command_request(const ButtonCommandRequest &value){}; #endif protected: bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; @@ -171,6 +177,9 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_SELECT virtual void select_command(const SelectCommandRequest &msg) = 0; +#endif +#ifdef USE_BUTTON + virtual void button_command(const ButtonCommandRequest &msg) = 0; #endif protected: void on_hello_request(const HelloRequest &msg) override; @@ -209,6 +218,9 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_SELECT void on_select_command_request(const SelectCommandRequest &msg) override; #endif +#ifdef USE_BUTTON + void on_button_command_request(const ButtonCommandRequest &msg) override; +#endif }; } // namespace api diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index 745dd92c89..cb97df8ca1 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -27,6 +27,9 @@ bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->clie #ifdef USE_SWITCH bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); } #endif +#ifdef USE_BUTTON +bool ListEntitiesIterator::on_button(button::Button *button) { return this->client_->send_button_info(button); } +#endif #ifdef USE_TEXT_SENSOR bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { return this->client_->send_text_sensor_info(text_sensor); diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index c728fb0a97..714edaa91f 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -30,6 +30,9 @@ class ListEntitiesIterator : public ComponentIterator { #ifdef USE_SWITCH bool on_switch(switch_::Switch *a_switch) override; #endif +#ifdef USE_BUTTON + bool on_button(button::Button *button) override; +#endif #ifdef USE_TEXT_SENSOR bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; #endif diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index beb9b947d4..d3f2d3aa45 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -31,6 +31,9 @@ class InitialStateIterator : public ComponentIterator { #ifdef USE_SWITCH bool on_switch(switch_::Switch *a_switch) override; #endif +#ifdef USE_BUTTON + bool on_button(button::Button *button) override { return true; }; +#endif #ifdef USE_TEXT_SENSOR bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; #endif diff --git a/esphome/components/api/util.cpp b/esphome/components/api/util.cpp index 5085994607..f5fd752101 100644 --- a/esphome/components/api/util.cpp +++ b/esphome/components/api/util.cpp @@ -116,6 +116,21 @@ void ComponentIterator::advance() { } break; #endif +#ifdef USE_BUTTON + case IteratorState::BUTTON: + if (this->at_ >= App.get_buttons().size()) { + advance_platform = true; + } else { + auto *button = App.get_buttons()[this->at_]; + if (button->is_internal()) { + success = true; + break; + } else { + success = this->on_button(button); + } + } + break; +#endif #ifdef USE_TEXT_SENSOR case IteratorState::TEXT_SENSOR: if (this->at_ >= App.get_text_sensors().size()) { diff --git a/esphome/components/api/util.h b/esphome/components/api/util.h index e404a95619..7849b3e028 100644 --- a/esphome/components/api/util.h +++ b/esphome/components/api/util.h @@ -38,6 +38,9 @@ class ComponentIterator { #ifdef USE_SWITCH virtual bool on_switch(switch_::Switch *a_switch) = 0; #endif +#ifdef USE_BUTTON + virtual bool on_button(button::Button *button) = 0; +#endif #ifdef USE_TEXT_SENSOR virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0; #endif @@ -78,6 +81,9 @@ class ComponentIterator { #ifdef USE_SWITCH SWITCH, #endif +#ifdef USE_BUTTON + BUTTON, +#endif #ifdef USE_TEXT_SENSOR TEXT_SENSOR, #endif diff --git a/esphome/components/button/__init__.py b/esphome/components/button/__init__.py new file mode 100644 index 0000000000..495a85b6b4 --- /dev/null +++ b/esphome/components/button/__init__.py @@ -0,0 +1,104 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.automation import maybe_simple_id +from esphome.components import mqtt +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_ON_PRESS, + CONF_TRIGGER_ID, + CONF_MQTT_ID, +) +from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity + +CODEOWNERS = ["@esphome/core"] +IS_PLATFORM_COMPONENT = True + +button_ns = cg.esphome_ns.namespace("button") +Button = button_ns.class_("Button", cg.EntityBase) +ButtonPtr = Button.operator("ptr") + +PressAction = button_ns.class_("PressAction", automation.Action) + +ButtonPressTrigger = button_ns.class_( + "ButtonPressTrigger", automation.Trigger.template() +) + + +BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent), + cv.Optional(CONF_ON_PRESS): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger), + } + ), + } +) + +_UNDEF = object() + + +def button_schema( + icon: str = _UNDEF, + entity_category: str = _UNDEF, +) -> cv.Schema: + schema = BUTTON_SCHEMA + if icon is not _UNDEF: + schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon}) + if entity_category is not _UNDEF: + schema = schema.extend( + { + cv.Optional( + CONF_ENTITY_CATEGORY, default=entity_category + ): cv.entity_category + } + ) + return schema + + +async def setup_button_core_(var, config): + await setup_entity(var, config) + + for conf in config.get(CONF_ON_PRESS, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + if CONF_MQTT_ID in config: + mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) + await mqtt.register_mqtt_component(mqtt_, config) + + +async def register_button(var, config): + if not CORE.has_id(config[CONF_ID]): + var = cg.Pvariable(config[CONF_ID], var) + cg.add(cg.App.register_button(var)) + await setup_button_core_(var, config) + + +async def new_button(config): + var = cg.new_Pvariable(config[CONF_ID]) + await register_button(var, config) + return var + + +BUTTON_PRESS_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(Button), + } +) + + +@automation.register_action("button.press", PressAction, BUTTON_PRESS_SCHEMA) +async def button_press_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) + + +@coroutine_with_priority(100.0) +async def to_code(config): + cg.add_global(button_ns.using) + cg.add_define("USE_BUTTON") diff --git a/esphome/components/button/automation.h b/esphome/components/button/automation.h new file mode 100644 index 0000000000..a5fb9f35b7 --- /dev/null +++ b/esphome/components/button/automation.h @@ -0,0 +1,28 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace button { + +template class PressAction : public Action { + public: + explicit PressAction(Button *button) : button_(button) {} + + void play(Ts... x) override { this->button_->press(); } + + protected: + Button *button_; +}; + +class ButtonPressTrigger : public Trigger<> { + public: + ButtonPressTrigger(Button *button) { + button->add_on_press_callback([this]() { this->trigger(); }); + } +}; + +} // namespace button +} // namespace esphome diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp new file mode 100644 index 0000000000..fe39b1e458 --- /dev/null +++ b/esphome/components/button/button.cpp @@ -0,0 +1,21 @@ +#include "button.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace button { + +static const char *const TAG = "button"; + +Button::Button(const std::string &name) : EntityBase(name) {} +Button::Button() : Button("") {} + +void Button::press() { + ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str()); + this->press_action(); + this->press_callback_.call(); +} +void Button::add_on_press_callback(std::function &&callback) { this->press_callback_.add(std::move(callback)); } +uint32_t Button::hash_base() { return 1495763804UL; } + +} // namespace button +} // namespace esphome diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h new file mode 100644 index 0000000000..954afa0ab9 --- /dev/null +++ b/esphome/components/button/button.h @@ -0,0 +1,50 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/entity_base.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace button { + +#define LOG_BUTTON(prefix, type, obj) \ + if ((obj) != nullptr) { \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ + if (!(obj)->get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ + } \ + } + +/** Base class for all buttons. + * + * A button is just a momentary switch that does not have a state, only a trigger. + */ +class Button : public EntityBase { + public: + explicit Button(); + explicit Button(const std::string &name); + + /** Press this button. This is called by the front-end. + * + * For implementing buttons, please override press_action. + */ + void press(); + + /** Set callback for state changes. + * + * @param callback The void() callback. + */ + void add_on_press_callback(std::function &&callback); + + protected: + /** You should implement this virtual method if you want to create your own button. + */ + virtual void press_action(){}; + + uint32_t hash_base() override; + + CallbackManager press_callback_{}; +}; + +} // namespace button +} // namespace esphome diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 73af0bad90..d677d54d23 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -95,6 +95,7 @@ MQTTSwitchComponent = mqtt_ns.class_("MQTTSwitchComponent", MQTTComponent) MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent) MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent) MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent) +MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent) MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator") MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = { diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp new file mode 100644 index 0000000000..5a0d14b648 --- /dev/null +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -0,0 +1,40 @@ +#include "mqtt_button.h" +#include "esphome/core/log.h" + +#include "mqtt_const.h" + +#ifdef USE_MQTT +#ifdef USE_BUTTON + +namespace esphome { +namespace mqtt { + +static const char *const TAG = "mqtt.button"; + +using namespace esphome::button; + +MQTTButtonComponent::MQTTButtonComponent(button::Button *button) : MQTTComponent(), button_(button) {} + +void MQTTButtonComponent::setup() { + this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { + if (payload == "press") { + this->button_->press(); + } else { + ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str()); + this->status_momentary_warning("state", 5000); + } + }); +} +void MQTTButtonComponent::dump_config() { + ESP_LOGCONFIG(TAG, "MQTT Button '%s': ", this->button_->get_name().c_str()); + LOG_MQTT_COMPONENT(true, true); +} + +std::string MQTTButtonComponent::component_type() const { return "button"; } +const EntityBase *MQTTButtonComponent::get_entity() const { return this->button_; } + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_button.h b/esphome/components/mqtt/mqtt_button.h new file mode 100644 index 0000000000..a7e60db380 --- /dev/null +++ b/esphome/components/mqtt/mqtt_button.h @@ -0,0 +1,40 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_MQTT +#ifdef USE_BUTTON + +#include "esphome/components/button/button.h" +#include "mqtt_component.h" + +namespace esphome { +namespace mqtt { + +class MQTTButtonComponent : public mqtt::MQTTComponent { + public: + explicit MQTTButtonComponent(button::Button *button); + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + void setup() override; + void dump_config() override; + + /// Buttons do not send a state so just return true. + bool send_initial_state() override { return true; } + + void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override {} + + protected: + /// "button" component type. + std::string component_type() const override; + const EntityBase *get_entity() const override; + + button::Button *button_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/restart/button/__init__.py b/esphome/components/restart/button/__init__.py new file mode 100644 index 0000000000..257a8e35f7 --- /dev/null +++ b/esphome/components/restart/button/__init__.py @@ -0,0 +1,23 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button +from esphome.const import ( + CONF_ID, + ENTITY_CATEGORY_CONFIG, + ICON_RESTART, +) + +restart_ns = cg.esphome_ns.namespace("restart") +RestartButton = restart_ns.class_("RestartButton", button.Button, cg.Component) + +CONFIG_SCHEMA = ( + button.button_schema(icon=ICON_RESTART, entity_category=ENTITY_CATEGORY_CONFIG) + .extend({cv.GenerateID(): cv.declare_id(RestartButton)}) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await button.register_button(var, config) diff --git a/esphome/components/restart/button/restart_button.cpp b/esphome/components/restart/button/restart_button.cpp new file mode 100644 index 0000000000..d8ff061355 --- /dev/null +++ b/esphome/components/restart/button/restart_button.cpp @@ -0,0 +1,20 @@ +#include "restart_button.h" +#include "esphome/core/application.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace restart { + +static const char *const TAG = "restart.button"; + +void RestartButton::press_action() { + ESP_LOGI(TAG, "Restarting device..."); + // Let MQTT settle a bit + delay(100); // NOLINT + App.safe_reboot(); +} +void RestartButton::dump_config() { LOG_BUTTON("", "Restart Button", this); } + +} // namespace restart +} // namespace esphome diff --git a/esphome/components/restart/button/restart_button.h b/esphome/components/restart/button/restart_button.h new file mode 100644 index 0000000000..db18f1dadc --- /dev/null +++ b/esphome/components/restart/button/restart_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace restart { + +class RestartButton : public button::Button, public Component { + public: + void dump_config() override; + + protected: + void press_action() override; +}; + +} // namespace restart +} // namespace esphome diff --git a/esphome/components/restart/switch.py b/esphome/components/restart/switch/__init__.py similarity index 100% rename from esphome/components/restart/switch.py rename to esphome/components/restart/switch/__init__.py diff --git a/esphome/components/restart/restart_switch.cpp b/esphome/components/restart/switch/restart_switch.cpp similarity index 100% rename from esphome/components/restart/restart_switch.cpp rename to esphome/components/restart/switch/restart_switch.cpp diff --git a/esphome/components/restart/restart_switch.h b/esphome/components/restart/switch/restart_switch.h similarity index 100% rename from esphome/components/restart/restart_switch.h rename to esphome/components/restart/switch/restart_switch.h diff --git a/esphome/components/template/button/__init__.py b/esphome/components/template/button/__init__.py new file mode 100644 index 0000000000..aa192d118e --- /dev/null +++ b/esphome/components/template/button/__init__.py @@ -0,0 +1,13 @@ +import esphome.config_validation as cv +from esphome.components import button + + +CONFIG_SCHEMA = button.BUTTON_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(button.Button), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + await button.new_button(config) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 3f74c2e8a1..29cb4827bd 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -198,6 +198,11 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { write_row(stream, obj, "switch", ""); #endif +#ifdef USE_BUTTON + for (auto *obj : App.get_buttons()) + write_row(stream, obj, "button", ""); +#endif + #ifdef USE_BINARY_SENSOR for (auto *obj : App.get_binary_sensors()) if (this->include_internal_ || !obj->is_internal()) @@ -382,6 +387,26 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM } #endif +#ifdef USE_BUTTON +void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) { + for (button::Button *obj : App.get_buttons()) { + if (obj->is_internal()) + continue; + if (obj->get_object_id() != match.id) + continue; + + if (request->method() == HTTP_POST && match.method == "press") { + this->defer([obj]() { obj->press(); }); + request->send(200); + } else { + request->send(404); + } + return; + } + request->send(404); +} +#endif + #ifdef USE_BINARY_SENSOR void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { this->events_.send(this->binary_sensor_json(obj, state).c_str(), "state"); @@ -715,6 +740,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return true; #endif +#ifdef USE_BUTTON + if (request->method() == HTTP_POST && match.domain == "button") + return true; +#endif + #ifdef USE_BINARY_SENSOR if (request->method() == HTTP_GET && match.domain == "binary_sensor") return true; @@ -787,6 +817,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { } #endif +#ifdef USE_BUTTON + if (match.domain == "button") { + this->handle_button_request(request, match); + return; + } +#endif + #ifdef USE_BINARY_SENSOR if (match.domain == "binary_sensor") { this->handle_binary_sensor_request(request, match); diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 66fd082d19..8edb4237a2 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -112,6 +112,11 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { std::string switch_json(switch_::Switch *obj, bool value); #endif +#ifdef USE_BUTTON + /// Handle a button request under '/button//press'. + void handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match); +#endif + #ifdef USE_BINARY_SENSOR void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override; diff --git a/esphome/core/application.h b/esphome/core/application.h index 5c1483d301..ace0c9ad6d 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -17,6 +17,9 @@ #ifdef USE_SWITCH #include "esphome/components/switch/switch.h" #endif +#ifdef USE_BUTTON +#include "esphome/components/button/button.h" +#endif #ifdef USE_TEXT_SENSOR #include "esphome/components/text_sensor/text_sensor.h" #endif @@ -67,6 +70,10 @@ class Application { void register_switch(switch_::Switch *a_switch) { this->switches_.push_back(a_switch); } #endif +#ifdef USE_BUTTON + void register_button(button::Button *button) { this->buttons_.push_back(button); } +#endif + #ifdef USE_TEXT_SENSOR void register_text_sensor(text_sensor::TextSensor *sensor) { this->text_sensors_.push_back(sensor); } #endif @@ -167,6 +174,15 @@ class Application { return nullptr; } #endif +#ifdef USE_BUTTON + const std::vector &get_buttons() { return this->buttons_; } + button::Button *get_button_by_key(uint32_t key, bool include_internal = false) { + for (auto *obj : this->buttons_) + if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) + return obj; + return nullptr; + } +#endif #ifdef USE_SENSOR const std::vector &get_sensors() { return this->sensors_; } sensor::Sensor *get_sensor_by_key(uint32_t key, bool include_internal = false) { @@ -260,6 +276,9 @@ class Application { #ifdef USE_SWITCH std::vector switches_{}; #endif +#ifdef USE_BUTTON + std::vector buttons_{}; +#endif #ifdef USE_SENSOR std::vector sensors_{}; #endif diff --git a/esphome/core/controller.h b/esphome/core/controller.h index f53924cd23..0c3722855c 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -22,6 +22,9 @@ #ifdef USE_SWITCH #include "esphome/components/switch/switch.h" #endif +#ifdef USE_BUTTON +#include "esphome/components/button/button.h" +#endif #ifdef USE_CLIMATE #include "esphome/components/climate/climate.h" #endif diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 8dcae9fa31..a74755f651 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -16,6 +16,7 @@ #define USE_API_NOISE #define USE_API_PLAINTEXT #define USE_BINARY_SENSOR +#define USE_BUTTON #define USE_CLIMATE #define USE_COVER #define USE_DEEP_SLEEP diff --git a/script/ci-custom.py b/script/ci-custom.py index 3f01fb81bf..de2dfda44d 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -603,6 +603,7 @@ def lint_inclusive_language(fname, match): "esphome/components/switch/switch.h", "esphome/components/text_sensor/text_sensor.h", "esphome/components/climate/climate.h", + "esphome/components/button/button.h", "esphome/core/component.h", "esphome/core/gpio.h", "esphome/core/log.h", diff --git a/tests/test4.yaml b/tests/test4.yaml index ee88b422f2..b4708acf65 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -520,3 +520,7 @@ xpt2046: id(touchscreen).y_raw, id(touchscreen).z_raw ); + +button: + - platform: restart + name: Restart Button From 939fb313df622d3fabc14ea837e296c7aa5ec0ec Mon Sep 17 00:00:00 2001 From: dentra Date: Mon, 29 Nov 2021 22:08:52 +0300 Subject: [PATCH 085/111] Tuya text_sensor and raw data usage (#1812) --- CODEOWNERS | 1 + esphome/components/tuya/__init__.py | 89 ++++++++++++++++++- esphome/components/tuya/automation.cpp | 66 ++++++++++++++ esphome/components/tuya/automation.h | 53 +++++++++++ .../components/tuya/binary_sensor/__init__.py | 4 +- esphome/components/tuya/sensor/__init__.py | 4 +- .../components/tuya/text_sensor/__init__.py | 29 ++++++ .../tuya/text_sensor/tuya_text_sensor.cpp | 35 ++++++++ .../tuya/text_sensor/tuya_text_sensor.h | 24 +++++ esphome/const.py | 1 + 10 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 esphome/components/tuya/automation.cpp create mode 100644 esphome/components/tuya/automation.h create mode 100644 esphome/components/tuya/text_sensor/__init__.py create mode 100644 esphome/components/tuya/text_sensor/tuya_text_sensor.cpp create mode 100644 esphome/components/tuya/text_sensor/tuya_text_sensor.h diff --git a/CODEOWNERS b/CODEOWNERS index 687fad3948..6dbdef12ec 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -181,6 +181,7 @@ esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/climate/* @jesserockz esphome/components/tuya/sensor/* @jesserockz esphome/components/tuya/switch/* @jesserockz +esphome/components/tuya/text_sensor/* @dentra esphome/components/uart/* @esphome/core esphome/components/ultrasonic/* @OttoWinter esphome/components/version/* @esphome/core diff --git a/esphome/components/tuya/__init__.py b/esphome/components/tuya/__init__.py index 436759979a..965893e012 100644 --- a/esphome/components/tuya/__init__.py +++ b/esphome/components/tuya/__init__.py @@ -1,16 +1,84 @@ from esphome.components import time +from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import uart -from esphome.const import CONF_ID, CONF_TIME_ID +from esphome.const import CONF_ID, CONF_TIME_ID, CONF_TRIGGER_ID, CONF_SENSOR_DATAPOINT DEPENDENCIES = ["uart"] CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS = "ignore_mcu_update_on_datapoints" +CONF_ON_DATAPOINT_UPDATE = "on_datapoint_update" +CONF_DATAPOINT_TYPE = "datapoint_type" + tuya_ns = cg.esphome_ns.namespace("tuya") Tuya = tuya_ns.class_("Tuya", cg.Component, uart.UARTDevice) +DPTYPE_ANY = "any" +DPTYPE_RAW = "raw" +DPTYPE_BOOL = "bool" +DPTYPE_INT = "int" +DPTYPE_UINT = "uint" +DPTYPE_STRING = "string" +DPTYPE_ENUM = "enum" +DPTYPE_BITMASK = "bitmask" + +DATAPOINT_TYPES = { + DPTYPE_ANY: tuya_ns.struct("TuyaDatapoint"), + DPTYPE_RAW: cg.std_vector.template(cg.uint8), + DPTYPE_BOOL: cg.bool_, + DPTYPE_INT: cg.int_, + DPTYPE_UINT: cg.uint32, + DPTYPE_STRING: cg.std_string, + DPTYPE_ENUM: cg.uint8, + DPTYPE_BITMASK: cg.uint32, +} + +DATAPOINT_TRIGGERS = { + DPTYPE_ANY: tuya_ns.class_( + "TuyaDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_ANY]), + ), + DPTYPE_RAW: tuya_ns.class_( + "TuyaRawDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_RAW]), + ), + DPTYPE_BOOL: tuya_ns.class_( + "TuyaBoolDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_BOOL]), + ), + DPTYPE_INT: tuya_ns.class_( + "TuyaIntDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_INT]), + ), + DPTYPE_UINT: tuya_ns.class_( + "TuyaUIntDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_UINT]), + ), + DPTYPE_STRING: tuya_ns.class_( + "TuyaStringDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_STRING]), + ), + DPTYPE_ENUM: tuya_ns.class_( + "TuyaEnumDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_ENUM]), + ), + DPTYPE_BITMASK: tuya_ns.class_( + "TuyaBitmaskDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_BITMASK]), + ), +} + + +def assign_declare_id(value): + value = value.copy() + value[CONF_TRIGGER_ID] = cv.declare_id( + DATAPOINT_TRIGGERS[value[CONF_DATAPOINT_TYPE]] + )(value[CONF_TRIGGER_ID].id) + return value + + CONF_TUYA_ID = "tuya_id" CONFIG_SCHEMA = ( cv.Schema( @@ -20,6 +88,18 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS): cv.ensure_list( cv.uint8_t ), + cv.Optional(CONF_ON_DATAPOINT_UPDATE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + DATAPOINT_TRIGGERS[DPTYPE_ANY] + ), + cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_DATAPOINT_TYPE, default=DPTYPE_ANY): cv.one_of( + *DATAPOINT_TRIGGERS, lower=True + ), + }, + extra_validators=assign_declare_id, + ), } ) .extend(cv.COMPONENT_SCHEMA) @@ -37,3 +117,10 @@ async def to_code(config): if CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS in config: for dp in config[CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS]: cg.add(var.add_ignore_mcu_update_on_datapoints(dp)) + for conf in config.get(CONF_ON_DATAPOINT_UPDATE, []): + trigger = cg.new_Pvariable( + conf[CONF_TRIGGER_ID], var, conf[CONF_SENSOR_DATAPOINT] + ) + await automation.build_automation( + trigger, [(DATAPOINT_TYPES[conf[CONF_DATAPOINT_TYPE]], "x")], conf + ) diff --git a/esphome/components/tuya/automation.cpp b/esphome/components/tuya/automation.cpp new file mode 100644 index 0000000000..eb1c170083 --- /dev/null +++ b/esphome/components/tuya/automation.cpp @@ -0,0 +1,66 @@ +#include "esphome/core/log.h" + +#include "automation.h" + +static const char *const TAG = "tuya.automation"; + +namespace esphome { +namespace tuya { + +void check_expected_datapoint(const TuyaDatapoint &dp, TuyaDatapointType expected) { + if (dp.type != expected) { + ESP_LOGW(TAG, "Tuya sensor %u expected datapoint type %#02hhX but got %#02hhX", dp.id, expected, dp.type); + } +} + +TuyaRawDatapointUpdateTrigger::TuyaRawDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::RAW); + this->trigger(dp.value_raw); + }); +} + +TuyaBoolDatapointUpdateTrigger::TuyaBoolDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::BOOLEAN); + this->trigger(dp.value_bool); + }); +} + +TuyaIntDatapointUpdateTrigger::TuyaIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::INTEGER); + this->trigger(dp.value_int); + }); +} + +TuyaUIntDatapointUpdateTrigger::TuyaUIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::INTEGER); + this->trigger(dp.value_uint); + }); +} + +TuyaStringDatapointUpdateTrigger::TuyaStringDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::STRING); + this->trigger(dp.value_string); + }); +} + +TuyaEnumDatapointUpdateTrigger::TuyaEnumDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::ENUM); + this->trigger(dp.value_enum); + }); +} + +TuyaBitmaskDatapointUpdateTrigger::TuyaBitmaskDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::BITMASK); + this->trigger(dp.value_bitmask); + }); +} + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/automation.h b/esphome/components/tuya/automation.h new file mode 100644 index 0000000000..d7706e1d60 --- /dev/null +++ b/esphome/components/tuya/automation.h @@ -0,0 +1,53 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "tuya.h" + +namespace esphome { +namespace tuya { + +class TuyaDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { this->trigger(dp); }); + } +}; + +class TuyaRawDatapointUpdateTrigger : public Trigger> { + public: + explicit TuyaRawDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaBoolDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaBoolDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaIntDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaUIntDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaUIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaStringDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaStringDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaEnumDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaEnumDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaBitmaskDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaBitmaskDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/binary_sensor/__init__.py b/esphome/components/tuya/binary_sensor/__init__.py index 65f13ea422..cd4a2db89f 100644 --- a/esphome/components/tuya/binary_sensor/__init__.py +++ b/esphome/components/tuya/binary_sensor/__init__.py @@ -1,14 +1,12 @@ from esphome.components import binary_sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] CODEOWNERS = ["@jesserockz"] -CONF_SENSOR_DATAPOINT = "sensor_datapoint" - TuyaBinarySensor = tuya_ns.class_( "TuyaBinarySensor", binary_sensor.BinarySensor, cg.Component ) diff --git a/esphome/components/tuya/sensor/__init__.py b/esphome/components/tuya/sensor/__init__.py index d87a2e7ce4..441400fa43 100644 --- a/esphome/components/tuya/sensor/__init__.py +++ b/esphome/components/tuya/sensor/__init__.py @@ -1,14 +1,12 @@ from esphome.components import sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] CODEOWNERS = ["@jesserockz"] -CONF_SENSOR_DATAPOINT = "sensor_datapoint" - TuyaSensor = tuya_ns.class_("TuyaSensor", sensor.Sensor, cg.Component) CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( diff --git a/esphome/components/tuya/text_sensor/__init__.py b/esphome/components/tuya/text_sensor/__init__.py new file mode 100644 index 0000000000..1989ca10e3 --- /dev/null +++ b/esphome/components/tuya/text_sensor/__init__.py @@ -0,0 +1,29 @@ +from esphome.components import text_sensor +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT +from .. import tuya_ns, CONF_TUYA_ID, Tuya + +DEPENDENCIES = ["tuya"] +CODEOWNERS = ["@dentra"] + +TuyaTextSensor = tuya_ns.class_("TuyaTextSensor", text_sensor.TextSensor, cg.Component) + +CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TuyaTextSensor), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await text_sensor.register_text_sensor(var, config) + + paren = await cg.get_variable(config[CONF_TUYA_ID]) + cg.add(var.set_tuya_parent(paren)) + + cg.add(var.set_sensor_id(config[CONF_SENSOR_DATAPOINT])) diff --git a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp new file mode 100644 index 0000000000..e939225453 --- /dev/null +++ b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp @@ -0,0 +1,35 @@ +#include "esphome/core/log.h" +#include "tuya_text_sensor.h" + +namespace esphome { +namespace tuya { + +static const char *const TAG = "tuya.text_sensor"; + +void TuyaTextSensor::setup() { + this->parent_->register_listener(this->sensor_id_, [this](const TuyaDatapoint &datapoint) { + switch (datapoint.type) { + case TuyaDatapointType::STRING: + ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, datapoint.value_string.c_str()); + this->publish_state(datapoint.value_string); + break; + case TuyaDatapointType::RAW: { + std::string data = hexencode(datapoint.value_raw); + ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, data.c_str()); + this->publish_state(data); + break; + } + default: + ESP_LOGW(TAG, "Unsupported data type for tuya text sensor %u: %#02hhX", datapoint.id, datapoint.type); + break; + } + }); +} + +void TuyaTextSensor::dump_config() { + ESP_LOGCONFIG(TAG, "Tuya Text Sensor:"); + ESP_LOGCONFIG(TAG, " Text Sensor has datapoint ID %u", this->sensor_id_); +} + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/text_sensor/tuya_text_sensor.h b/esphome/components/tuya/text_sensor/tuya_text_sensor.h new file mode 100644 index 0000000000..502ae5e8c7 --- /dev/null +++ b/esphome/components/tuya/text_sensor/tuya_text_sensor.h @@ -0,0 +1,24 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/tuya/tuya.h" +#include "esphome/components/text_sensor/text_sensor.h" + +namespace esphome { +namespace tuya { + +class TuyaTextSensor : public text_sensor::TextSensor, public Component { + public: + void setup() override; + void dump_config() override; + void set_sensor_id(uint8_t sensor_id) { this->sensor_id_ = sensor_id; } + + void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } + + protected: + Tuya *parent_; + uint8_t sensor_id_{0}; +}; + +} // namespace tuya +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 9006145dfe..3510e500f5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -579,6 +579,7 @@ CONF_SEND_EVERY = "send_every" CONF_SEND_FIRST_AT = "send_first_at" CONF_SENSING_PIN = "sensing_pin" CONF_SENSOR = "sensor" +CONF_SENSOR_DATAPOINT = "sensor_datapoint" CONF_SENSOR_ID = "sensor_id" CONF_SENSORS = "sensors" CONF_SEQUENCE = "sequence" From 556d071e7fa4ac62082aeecfe039f5875dec464c Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Tue, 30 Nov 2021 00:30:45 -0600 Subject: [PATCH 086/111] Fix 8266 SPI Clock Polarity Setting (#2836) --- esphome/components/spi/spi.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 601a5c5a7e..6c3fd17e56 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -195,7 +195,14 @@ class SPIComponent : public Component { void enable(GPIOPin *cs) { #ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { - uint8_t data_mode = (uint8_t(CLOCK_POLARITY) << 1) | uint8_t(CLOCK_PHASE); + uint8_t data_mode = SPI_MODE0; + if (!CLOCK_POLARITY && CLOCK_PHASE) { + data_mode = SPI_MODE1; + } else if (CLOCK_POLARITY && !CLOCK_PHASE) { + data_mode = SPI_MODE2; + } else if (CLOCK_POLARITY && CLOCK_PHASE) { + data_mode = SPI_MODE3; + } SPISettings settings(DATA_RATE, BIT_ORDER, data_mode); this->hw_spi_->beginTransaction(settings); } else { From ab027a6ae2df3fabb851686efe82b7ea2f350fd9 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 30 Nov 2021 09:35:52 +0100 Subject: [PATCH 087/111] Fix too-broad matcher for custom CI script (#2829) --- .github/workflows/matchers/ci-custom.json | 2 +- script/ci-custom.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/matchers/ci-custom.json b/.github/workflows/matchers/ci-custom.json index 9888dbb440..1d5f2551cd 100644 --- a/.github/workflows/matchers/ci-custom.json +++ b/.github/workflows/matchers/ci-custom.json @@ -4,7 +4,7 @@ "owner": "ci-custom", "pattern": [ { - "regexp": "^(.*):(\\d+):(\\d+):\\s+(.*)$", + "regexp": "^(.*):(\\d+):(\\d+):\\s+lint:\\s+(.*)$", "file": 1, "line": 2, "column": 3, diff --git a/script/ci-custom.py b/script/ci-custom.py index de2dfda44d..9acfbcdc23 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -660,7 +660,9 @@ for fname in files: run_checks(LINT_POST_CHECKS, "POST") for f, errs in sorted(errors.items()): - err_str = (f"{styled(colorama.Style.BRIGHT, f'{f}:{lineno}:{col}:')} {msg}\n" for lineno, col, msg in errs) + bold = functools.partial(styled, colorama.Style.BRIGHT) + bold_red = functools.partial(styled, (colorama.Style.BRIGHT, colorama.Fore.RED)) + err_str = (f"{bold(f'{f}:{lineno}:{col}:')} {bold_red('lint:')} {msg}\n" for lineno, col, msg in errs) print_error_for_file(f, "\n".join(err_str)) if args.print_slowest: From 24dfecb6f03e66fa4e88c462d6092c5ba7e61372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Panella?= Date: Tue, 30 Nov 2021 09:08:00 -0600 Subject: [PATCH 088/111] cse7766: add energy sensor (#2822) --- esphome/components/cse7766/cse7766.cpp | 16 ++++++++++++++++ esphome/components/cse7766/cse7766.h | 4 ++++ esphome/components/cse7766/sensor.py | 14 ++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index 87bc4c4bdf..55e1ec82cf 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -90,6 +90,7 @@ void CSE7766Component::parse_data_() { uint32_t power_cycle = this->get_24_bit_uint_(17); uint8_t adj = this->raw_data_[20]; + uint32_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22]; bool power_ok = true; bool voltage_ok = true; @@ -127,6 +128,18 @@ void CSE7766Component::parse_data_() { power = power_calib / float(power_cycle); this->power_acc_ += power; this->power_counts_ += 1; + + uint32_t difference; + if (this->cf_pulses_last_ == 0) + this->cf_pulses_last_ = cf_pulses; + + if (cf_pulses < this->cf_pulses_last_) { + difference = cf_pulses + (0x10000 - this->cf_pulses_last_); + } else { + difference = cf_pulses - this->cf_pulses_last_; + } + this->cf_pulses_last_ = cf_pulses; + this->energy_total_ += difference * float(power_calib) / 1000000.0 / 3600.0; } if ((adj & 0x20) == 0x20 && current_ok && voltage_ok && power != 0.0) { @@ -152,6 +165,8 @@ void CSE7766Component::update() { this->current_sensor_->publish_state(current); if (this->power_sensor_ != nullptr) this->power_sensor_->publish_state(power); + if (this->energy_sensor_ != nullptr) + this->energy_sensor_->publish_state(this->energy_total_); this->voltage_acc_ = 0.0f; this->current_acc_ = 0.0f; @@ -172,6 +187,7 @@ void CSE7766Component::dump_config() { LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); LOG_SENSOR(" ", "Current", this->current_sensor_); LOG_SENSOR(" ", "Power", this->power_sensor_); + LOG_SENSOR(" ", "Energy", this->energy_sensor_); this->check_uart_settings(4800); } diff --git a/esphome/components/cse7766/cse7766.h b/esphome/components/cse7766/cse7766.h index 6cacfee072..d6062c251c 100644 --- a/esphome/components/cse7766/cse7766.h +++ b/esphome/components/cse7766/cse7766.h @@ -12,6 +12,7 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice { void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } + void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } void loop() override; float get_setup_priority() const override; @@ -29,9 +30,12 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice { sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr}; sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; float voltage_acc_{0.0f}; float current_acc_{0.0f}; float power_acc_{0.0f}; + float energy_total_{0.0f}; + uint32_t cf_pulses_last_{0}; uint32_t voltage_counts_{0}; uint32_t current_counts_{0}; uint32_t power_counts_{0}; diff --git a/esphome/components/cse7766/sensor.py b/esphome/components/cse7766/sensor.py index 1c8efc4f72..2f48aff0aa 100644 --- a/esphome/components/cse7766/sensor.py +++ b/esphome/components/cse7766/sensor.py @@ -3,16 +3,20 @@ import esphome.config_validation as cv from esphome.components import sensor, uart from esphome.const import ( CONF_CURRENT, + CONF_ENERGY, CONF_ID, CONF_POWER, CONF_VOLTAGE, DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, + UNIT_WATT_HOURS, ) DEPENDENCIES = ["uart"] @@ -44,6 +48,12 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_ENERGY): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), } ) .extend(cv.polling_component_schema("60s")) @@ -71,3 +81,7 @@ async def to_code(config): conf = config[CONF_POWER] sens = await sensor.new_sensor(conf) cg.add(var.set_power_sensor(sens)) + if CONF_ENERGY in config: + conf = config[CONF_ENERGY] + sens = await sensor.new_sensor(conf) + cg.add(var.set_energy_sensor(sens)) From cd018ad3a59e941a2bc45d2757c10dd886187d01 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Tue, 30 Nov 2021 16:12:52 +0100 Subject: [PATCH 089/111] Burst read for BME280, to reduce spurious spikes (#2809) --- esphome/components/bme280/bme280.cpp | 33 ++++++++++++++-------------- esphome/components/bme280/bme280.h | 6 ++--- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/esphome/components/bme280/bme280.cpp b/esphome/components/bme280/bme280.cpp index 18386430a2..627072443e 100644 --- a/esphome/components/bme280/bme280.cpp +++ b/esphome/components/bme280/bme280.cpp @@ -33,6 +33,7 @@ static const uint8_t BME280_REGISTER_CONTROLHUMID = 0xF2; static const uint8_t BME280_REGISTER_STATUS = 0xF3; static const uint8_t BME280_REGISTER_CONTROL = 0xF4; static const uint8_t BME280_REGISTER_CONFIG = 0xF5; +static const uint8_t BME280_REGISTER_MEASUREMENTS = 0xF7; static const uint8_t BME280_REGISTER_PRESSUREDATA = 0xF7; static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA; static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD; @@ -178,21 +179,27 @@ void BME280Component::update() { return; } - float meas_time = 1.5; + float meas_time = 1.5f; meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_); meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f; meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f; this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { + uint8_t data[8]; + if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) { + ESP_LOGW(TAG, "Error reading registers."); + this->status_set_warning(); + return; + } int32_t t_fine = 0; - float temperature = this->read_temperature_(&t_fine); + float temperature = this->read_temperature_(data, &t_fine); if (std::isnan(temperature)) { ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values."); this->status_set_warning(); return; } - float pressure = this->read_pressure_(t_fine); - float humidity = this->read_humidity_(t_fine); + float pressure = this->read_pressure_(data, t_fine); + float humidity = this->read_humidity_(data, t_fine); ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity); if (this->temperature_sensor_ != nullptr) @@ -204,11 +211,8 @@ void BME280Component::update() { this->status_clear_warning(); }); } -float BME280Component::read_temperature_(int32_t *t_fine) { - uint8_t data[3]; - if (!this->read_bytes(BME280_REGISTER_TEMPDATA, data, 3)) - return NAN; - int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); +float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) { + int32_t adc = ((data[3] & 0xFF) << 16) | ((data[4] & 0xFF) << 8) | (data[5] & 0xFF); adc >>= 4; if (adc == 0x80000) // temperature was disabled @@ -226,10 +230,7 @@ float BME280Component::read_temperature_(int32_t *t_fine) { return temperature / 100.0f; } -float BME280Component::read_pressure_(int32_t t_fine) { - uint8_t data[3]; - if (!this->read_bytes(BME280_REGISTER_PRESSUREDATA, data, 3)) - return NAN; +float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) { int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); adc >>= 4; if (adc == 0x80000) @@ -265,9 +266,9 @@ float BME280Component::read_pressure_(int32_t t_fine) { return (p / 256.0f) / 100.0f; } -float BME280Component::read_humidity_(int32_t t_fine) { - uint16_t raw_adc; - if (!this->read_byte_16(BME280_REGISTER_HUMIDDATA, &raw_adc) || raw_adc == 0x8000) +float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) { + uint16_t raw_adc = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF); + if (raw_adc == 0x8000) return NAN; int32_t adc = raw_adc; diff --git a/esphome/components/bme280/bme280.h b/esphome/components/bme280/bme280.h index 82724d6887..8511f73382 100644 --- a/esphome/components/bme280/bme280.h +++ b/esphome/components/bme280/bme280.h @@ -82,11 +82,11 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice { protected: /// Read the temperature value and store the calculated ambient temperature in t_fine. - float read_temperature_(int32_t *t_fine); + float read_temperature_(const uint8_t *data, int32_t *t_fine); /// Read the pressure value in hPa using the provided t_fine value. - float read_pressure_(int32_t t_fine); + float read_pressure_(const uint8_t *data, int32_t t_fine); /// Read the humidity value in % using the provided t_fine value. - float read_humidity_(int32_t t_fine); + float read_humidity_(const uint8_t *data, int32_t t_fine); uint8_t read_u8_(uint8_t a_register); uint16_t read_u16_le_(uint8_t a_register); int16_t read_s16_le_(uint8_t a_register); From 0f47ffd9085ee7655d49b4f567e2d9702337b32f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Nov 2021 16:17:48 +0100 Subject: [PATCH 090/111] Bump aioesphomeapi from 10.2.0 to 10.6.0 (#2840) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c4b211283d..c99c5efad9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 esphome-dashboard==20211021.1 -aioesphomeapi==10.2.0 +aioesphomeapi==10.6.0 zeroconf==0.36.13 # esp-idf requires this, but doesn't bundle it by default From b32b918936cc1cdc796de27f2e022b743fa6c421 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 1 Dec 2021 04:18:21 +1300 Subject: [PATCH 091/111] Button device class (#2835) --- esphome/components/api/api.proto | 1 + esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 9 ++++++++ esphome/components/api/api_pb2.h | 1 + esphome/components/button/__init__.py | 23 +++++++++++++++++++ esphome/components/button/button.cpp | 7 ++++++ esphome/components/button/button.h | 7 ++++++ esphome/components/mqtt/mqtt_button.cpp | 5 ++++ esphome/components/mqtt/mqtt_button.h | 2 +- esphome/components/restart/button/__init__.py | 6 +++-- esphome/const.py | 6 ++++- 11 files changed, 64 insertions(+), 4 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index eaad4b8d07..3e2c806135 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -960,6 +960,7 @@ message ListEntitiesButtonResponse { string icon = 5; bool disabled_by_default = 6; EntityCategory entity_category = 7; + string device_class = 8; } message ButtonCommandRequest { option (id) = 62; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 22b896a788..8367afc042 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -684,6 +684,7 @@ bool APIConnection::send_button_info(button::Button *button) { msg.icon = button->get_icon(); msg.disabled_by_default = button->is_disabled_by_default(); msg.entity_category = static_cast(button->get_entity_category()); + msg.device_class = button->get_device_class(); return this->send_list_entities_button_response(msg); } void APIConnection::button_command(const ButtonCommandRequest &msg) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 169349d995..b6974de08e 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -4179,6 +4179,10 @@ bool ListEntitiesButtonResponse::decode_length(uint32_t field_id, ProtoLengthDel this->icon = value.as_string(); return true; } + case 8: { + this->device_class = value.as_string(); + return true; + } default: return false; } @@ -4201,6 +4205,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); + buffer.encode_string(8, this->device_class); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesButtonResponse::dump_to(std::string &out) const { @@ -4234,6 +4239,10 @@ void ListEntitiesButtonResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" device_class: "); + out.append("'").append(this->device_class).append("'"); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 82fd8de687..4d1f658910 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1050,6 +1050,7 @@ class ListEntitiesButtonResponse : public ProtoMessage { std::string icon{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; + std::string device_class{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/button/__init__.py b/esphome/components/button/__init__.py index 495a85b6b4..1e248ddf07 100644 --- a/esphome/components/button/__init__.py +++ b/esphome/components/button/__init__.py @@ -4,12 +4,15 @@ from esphome import automation from esphome.automation import maybe_simple_id from esphome.components import mqtt from esphome.const import ( + CONF_DEVICE_CLASS, CONF_ENTITY_CATEGORY, CONF_ICON, CONF_ID, CONF_ON_PRESS, CONF_TRIGGER_ID, CONF_MQTT_ID, + DEVICE_CLASS_RESTART, + DEVICE_CLASS_UPDATE, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_helpers import setup_entity @@ -17,6 +20,11 @@ from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True +DEVICE_CLASSES = [ + DEVICE_CLASS_RESTART, + DEVICE_CLASS_UPDATE, +] + button_ns = cg.esphome_ns.namespace("button") Button = button_ns.class_("Button", cg.EntityBase) ButtonPtr = Button.operator("ptr") @@ -27,10 +35,13 @@ ButtonPressTrigger = button_ns.class_( "ButtonPressTrigger", automation.Trigger.template() ) +validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") + BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent), + cv.Optional(CONF_DEVICE_CLASS): validate_device_class, cv.Optional(CONF_ON_PRESS): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger), @@ -45,6 +56,7 @@ _UNDEF = object() def button_schema( icon: str = _UNDEF, entity_category: str = _UNDEF, + device_class: str = _UNDEF, ) -> cv.Schema: schema = BUTTON_SCHEMA if icon is not _UNDEF: @@ -57,6 +69,14 @@ def button_schema( ): cv.entity_category } ) + if device_class is not _UNDEF: + schema = schema.extend( + { + cv.Optional( + CONF_DEVICE_CLASS, default=device_class + ): validate_device_class + } + ) return schema @@ -67,6 +87,9 @@ async def setup_button_core_(var, config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + if CONF_DEVICE_CLASS in config: + cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) + if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp index fe39b1e458..d57b46e9aa 100644 --- a/esphome/components/button/button.cpp +++ b/esphome/components/button/button.cpp @@ -17,5 +17,12 @@ void Button::press() { void Button::add_on_press_callback(std::function &&callback) { this->press_callback_.add(std::move(callback)); } uint32_t Button::hash_base() { return 1495763804UL; } +void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } +std::string Button::get_device_class() { + if (this->device_class_.has_value()) + return *this->device_class_; + return ""; +} + } // namespace button } // namespace esphome diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h index 954afa0ab9..b21a96b8e1 100644 --- a/esphome/components/button/button.h +++ b/esphome/components/button/button.h @@ -36,6 +36,12 @@ class Button : public EntityBase { */ void add_on_press_callback(std::function &&callback); + /// Set the Home Assistant device class (see button::device_class). + void set_device_class(const std::string &device_class); + + /// Get the device class for this button. + std::string get_device_class(); + protected: /** You should implement this virtual method if you want to create your own button. */ @@ -44,6 +50,7 @@ class Button : public EntityBase { uint32_t hash_base() override; CallbackManager press_callback_{}; + optional device_class_{}; }; } // namespace button diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index 5a0d14b648..25ff327cf9 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -30,6 +30,11 @@ void MQTTButtonComponent::dump_config() { LOG_MQTT_COMPONENT(true, true); } +void MQTTButtonComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { + if (!this->button_->get_device_class().empty()) + root[MQTT_DEVICE_CLASS] = this->button_->get_device_class(); +} + std::string MQTTButtonComponent::component_type() const { return "button"; } const EntityBase *MQTTButtonComponent::get_entity() const { return this->button_; } diff --git a/esphome/components/mqtt/mqtt_button.h b/esphome/components/mqtt/mqtt_button.h index a7e60db380..66e4b2609f 100644 --- a/esphome/components/mqtt/mqtt_button.h +++ b/esphome/components/mqtt/mqtt_button.h @@ -23,7 +23,7 @@ class MQTTButtonComponent : public mqtt::MQTTComponent { /// Buttons do not send a state so just return true. bool send_initial_state() override { return true; } - void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override {} + void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; protected: /// "button" component type. diff --git a/esphome/components/restart/button/__init__.py b/esphome/components/restart/button/__init__.py index 257a8e35f7..1a0e9cdc3d 100644 --- a/esphome/components/restart/button/__init__.py +++ b/esphome/components/restart/button/__init__.py @@ -3,15 +3,17 @@ import esphome.config_validation as cv from esphome.components import button from esphome.const import ( CONF_ID, + DEVICE_CLASS_RESTART, ENTITY_CATEGORY_CONFIG, - ICON_RESTART, ) restart_ns = cg.esphome_ns.namespace("restart") RestartButton = restart_ns.class_("RestartButton", button.Button, cg.Component) CONFIG_SCHEMA = ( - button.button_schema(icon=ICON_RESTART, entity_category=ENTITY_CATEGORY_CONFIG) + button.button_schema( + device_class=DEVICE_CLASS_RESTART, entity_category=ENTITY_CATEGORY_CONFIG + ) .extend({cv.GenerateID(): cv.declare_id(RestartButton)}) .extend(cv.COMPONENT_SCHEMA) ) diff --git a/esphome/const.py b/esphome/const.py index 3510e500f5..740e38cf44 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -865,7 +865,6 @@ DEVICE_CLASS_SAFETY = "safety" DEVICE_CLASS_SMOKE = "smoke" DEVICE_CLASS_SOUND = "sound" DEVICE_CLASS_TAMPER = "tamper" -DEVICE_CLASS_UPDATE = "update" DEVICE_CLASS_VIBRATION = "vibration" DEVICE_CLASS_WINDOW = "window" # device classes of both binary_sensor and sensor component @@ -897,6 +896,11 @@ DEVICE_CLASS_TEMPERATURE = "temperature" DEVICE_CLASS_TIMESTAMP = "timestamp" DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds" DEVICE_CLASS_VOLTAGE = "voltage" +# device classes of both binary_sensor and button component +DEVICE_CLASS_UPDATE = "update" +# device classes of button component +DEVICE_CLASS_RESTART = "restart" + # state classes STATE_CLASS_NONE = "" From b5a0e8b2c0d016ea384f4f962e0f0e4c61a8dc85 Mon Sep 17 00:00:00 2001 From: puuu Date: Wed, 1 Dec 2021 00:20:59 +0900 Subject: [PATCH 092/111] Implement unit_of_measurement for number component (#2804) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/api/api.proto | 1 + esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 9 +++++++++ esphome/components/api/api_pb2.h | 1 + esphome/components/mqtt/mqtt_number.cpp | 2 ++ esphome/components/number/__init__.py | 4 ++++ esphome/components/number/number.cpp | 9 +++++++++ esphome/components/number/number.h | 9 +++++++++ tests/test5.yaml | 1 + 9 files changed, 37 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 3e2c806135..0f7a5839ab 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -885,6 +885,7 @@ message ListEntitiesNumberResponse { float step = 8; bool disabled_by_default = 9; EntityCategory entity_category = 10; + string unit_of_measurement = 11; } message NumberStateResponse { option (id) = 50; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 8367afc042..b41a7633a8 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -619,6 +619,7 @@ bool APIConnection::send_number_info(number::Number *number) { msg.icon = number->get_icon(); msg.disabled_by_default = number->is_disabled_by_default(); msg.entity_category = static_cast(number->get_entity_category()); + msg.unit_of_measurement = number->traits.get_unit_of_measurement(); msg.min_value = number->traits.get_min_value(); msg.max_value = number->traits.get_max_value(); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index b6974de08e..62cecb7818 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -3780,6 +3780,10 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel this->icon = value.as_string(); return true; } + case 11: { + this->unit_of_measurement = value.as_string(); + return true; + } default: return false; } @@ -3817,6 +3821,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(8, this->step); buffer.encode_bool(9, this->disabled_by_default); buffer.encode_enum(10, this->entity_category); + buffer.encode_string(11, this->unit_of_measurement); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { @@ -3865,6 +3870,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" unit_of_measurement: "); + out.append("'").append(this->unit_of_measurement).append("'"); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 4d1f658910..4866f50c9b 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -957,6 +957,7 @@ class ListEntitiesNumberResponse : public ProtoMessage { float step{0.0f}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; + std::string unit_of_measurement{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 337013055a..bf8b6b39c5 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -43,6 +43,8 @@ void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo root[MQTT_MIN] = traits.get_min_value(); root[MQTT_MAX] = traits.get_max_value(); root[MQTT_STEP] = traits.get_step(); + if (!this->number_->traits.get_unit_of_measurement().empty()) + root[MQTT_UNIT_OF_MEASUREMENT] = this->number_->traits.get_unit_of_measurement(); config.command_topic = true; } diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 1da25caafe..ae15704a91 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_ON_VALUE, CONF_ON_VALUE_RANGE, CONF_TRIGGER_ID, + CONF_UNIT_OF_MEASUREMENT, CONF_MQTT_ID, CONF_VALUE, ) @@ -58,6 +59,7 @@ NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e }, cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), ), + cv.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string_strict, } ) @@ -86,6 +88,8 @@ async def setup_number_core_( cg.add(trigger.set_max(template_)) await automation.build_automation(trigger, [(float, "x")], conf) + if CONF_UNIT_OF_MEASUREMENT in config: + cg.add(var.traits.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT])) if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index 57a5c7c4bd..99a2c04a22 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -41,6 +41,15 @@ void Number::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } +std::string NumberTraits::get_unit_of_measurement() { + if (this->unit_of_measurement_.has_value()) + return *this->unit_of_measurement_; + return ""; +} +void NumberTraits::set_unit_of_measurement(const std::string &unit_of_measurement) { + this->unit_of_measurement_ = unit_of_measurement; +} + uint32_t Number::hash_base() { return 2282307003UL; } } // namespace number diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index ed104fb477..b3214913d9 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -13,6 +13,9 @@ namespace number { if (!(obj)->get_icon().empty()) { \ ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ } \ + if (!(obj)->traits.get_unit_of_measurement().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->traits.get_unit_of_measurement().c_str()); \ + } \ } class Number; @@ -42,10 +45,16 @@ class NumberTraits { void set_step(float step) { step_ = step; } float get_step() const { return step_; } + /// Get the unit of measurement, using the manual override if set. + std::string get_unit_of_measurement(); + /// Manually set the unit of measurement. + void set_unit_of_measurement(const std::string &unit_of_measurement); + protected: float min_value_ = NAN; float max_value_ = NAN; float step_ = NAN; + optional unit_of_measurement_; ///< Unit of measurement override }; /** Base-class for all numbers. diff --git a/tests/test5.yaml b/tests/test5.yaml index f1fb786fe5..708db55044 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -97,6 +97,7 @@ number: max_value: 100 min_value: 0 step: 5 + unit_of_measurement: '%' select: - platform: template From d9513e5ff2905d654a35249722df4528158a6bc3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 1 Dec 2021 08:11:38 +1300 Subject: [PATCH 093/111] Number mode (#2838) --- esphome/components/api/api.proto | 6 ++++++ esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 21 +++++++++++++++++++++ esphome/components/api/api_pb2.h | 6 ++++++ esphome/components/mqtt/mqtt_const.h | 1 + esphome/components/mqtt/mqtt_number.cpp | 10 ++++++++++ esphome/components/number/__init__.py | 12 ++++++++++++ esphome/components/number/number.h | 11 +++++++++++ tests/test5.yaml | 3 ++- 9 files changed, 70 insertions(+), 1 deletion(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 0f7a5839ab..dca722dca5 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -869,6 +869,11 @@ message ClimateCommandRequest { } // ==================== NUMBER ==================== +enum NumberMode { + NUMBER_MODE_AUTO = 0; + NUMBER_MODE_BOX = 1; + NUMBER_MODE_SLIDER = 2; +} message ListEntitiesNumberResponse { option (id) = 49; option (source) = SOURCE_SERVER; @@ -886,6 +891,7 @@ message ListEntitiesNumberResponse { bool disabled_by_default = 9; EntityCategory entity_category = 10; string unit_of_measurement = 11; + NumberMode mode = 12; } message NumberStateResponse { option (id) = 50; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index b41a7633a8..92699df0da 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -620,6 +620,7 @@ bool APIConnection::send_number_info(number::Number *number) { msg.disabled_by_default = number->is_disabled_by_default(); msg.entity_category = static_cast(number->get_entity_category()); msg.unit_of_measurement = number->traits.get_unit_of_measurement(); + msg.mode = static_cast(number->traits.get_mode()); msg.min_value = number->traits.get_min_value(); msg.max_value = number->traits.get_max_value(); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 62cecb7818..9228be0860 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -266,6 +266,18 @@ template<> const char *proto_enum_to_string(enums::Climate return "UNKNOWN"; } } +template<> const char *proto_enum_to_string(enums::NumberMode value) { + switch (value) { + case enums::NUMBER_MODE_AUTO: + return "NUMBER_MODE_AUTO"; + case enums::NUMBER_MODE_BOX: + return "NUMBER_MODE_BOX"; + case enums::NUMBER_MODE_SLIDER: + return "NUMBER_MODE_SLIDER"; + default: + return "UNKNOWN"; + } +} bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -3758,6 +3770,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->entity_category = value.as_enum(); return true; } + case 12: { + this->mode = value.as_enum(); + return true; + } default: return false; } @@ -3822,6 +3838,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(9, this->disabled_by_default); buffer.encode_enum(10, this->entity_category); buffer.encode_string(11, this->unit_of_measurement); + buffer.encode_enum(12, this->mode); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { @@ -3874,6 +3891,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append(" unit_of_measurement: "); out.append("'").append(this->unit_of_measurement).append("'"); out.append("\n"); + + out.append(" mode: "); + out.append(proto_enum_to_string(this->mode)); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 4866f50c9b..e92b2fa4b6 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -123,6 +123,11 @@ enum ClimatePreset : uint32_t { CLIMATE_PRESET_SLEEP = 6, CLIMATE_PRESET_ACTIVITY = 7, }; +enum NumberMode : uint32_t { + NUMBER_MODE_AUTO = 0, + NUMBER_MODE_BOX = 1, + NUMBER_MODE_SLIDER = 2, +}; } // namespace enums @@ -958,6 +963,7 @@ class ListEntitiesNumberResponse : public ProtoMessage { bool disabled_by_default{false}; enums::EntityCategory entity_category{}; std::string unit_of_measurement{}; + enums::NumberMode mode{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h index 1d5e22efde..8134a6b53e 100644 --- a/esphome/components/mqtt/mqtt_const.h +++ b/esphome/components/mqtt/mqtt_const.h @@ -514,6 +514,7 @@ constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area"; // Additional MQTT fields where no abbreviation is defined in HA source constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category"; +constexpr const char *const MQTT_MODE = "mode"; } // namespace mqtt } // namespace esphome diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index bf8b6b39c5..18e3a61417 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -45,6 +45,16 @@ void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo root[MQTT_STEP] = traits.get_step(); if (!this->number_->traits.get_unit_of_measurement().empty()) root[MQTT_UNIT_OF_MEASUREMENT] = this->number_->traits.get_unit_of_measurement(); + switch (this->number_->traits.get_mode()) { + case NUMBER_MODE_AUTO: + break; + case NUMBER_MODE_BOX: + root[MQTT_MODE] = "box"; + break; + case NUMBER_MODE_SLIDER: + root[MQTT_MODE] = "slider"; + break; + } config.command_topic = true; } diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index ae15704a91..71e288a4cc 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -7,6 +7,7 @@ from esphome.const import ( CONF_ABOVE, CONF_BELOW, CONF_ID, + CONF_MODE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, CONF_TRIGGER_ID, @@ -40,6 +41,14 @@ NumberInRangeCondition = number_ns.class_( "NumberInRangeCondition", automation.Condition ) +NumberMode = number_ns.enum("NumberMode") + +NUMBER_MODES = { + "AUTO": NumberMode.NUMBER_MODE_AUTO, + "BOX": NumberMode.NUMBER_MODE_BOX, + "SLIDER": NumberMode.NUMBER_MODE_SLIDER, +} + icon = cv.icon NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( @@ -60,6 +69,7 @@ NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), ), cv.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string_strict, + cv.Optional(CONF_MODE, default="AUTO"): cv.enum(NUMBER_MODES, upper=True), } ) @@ -74,6 +84,8 @@ async def setup_number_core_( if step is not None: cg.add(var.traits.set_step(step)) + cg.add(var.traits.set_mode(config[CONF_MODE])) + for conf in config.get(CONF_ON_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [(float, "x")], conf) diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index b3214913d9..40fdfceec1 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -36,6 +36,12 @@ class NumberCall { optional value_; }; +enum NumberMode : uint8_t { + NUMBER_MODE_AUTO = 0, + NUMBER_MODE_BOX = 1, + NUMBER_MODE_SLIDER = 2, +}; + class NumberTraits { public: void set_min_value(float min_value) { min_value_ = min_value; } @@ -50,11 +56,16 @@ class NumberTraits { /// Manually set the unit of measurement. void set_unit_of_measurement(const std::string &unit_of_measurement); + // Get/set the frontend mode. + NumberMode get_mode() const { return this->mode_; } + void set_mode(NumberMode mode) { this->mode_ = mode; } + protected: float min_value_ = NAN; float max_value_ = NAN; float step_ = NAN; optional unit_of_measurement_; ///< Unit of measurement override + NumberMode mode_{NUMBER_MODE_AUTO}; }; /** Base-class for all numbers. diff --git a/tests/test5.yaml b/tests/test5.yaml index 708db55044..aa3d057252 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -10,7 +10,7 @@ esp32: framework: type: esp-idf advanced: - ignore_efuse_mac_crc: true + ignore_efuse_mac_crc: true wifi: networks: @@ -98,6 +98,7 @@ number: min_value: 0 step: 5 unit_of_measurement: '%' + mode: slider select: - platform: template From 5719cc1a248f8be62fbfa3a4551a18b4194e52fe Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 1 Dec 2021 16:54:30 +1300 Subject: [PATCH 094/111] Bump esphome-dashboard to 20211201.0 (#2842) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c99c5efad9..6061476802 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20211021.1 +esphome-dashboard==20211201.0 aioesphomeapi==10.6.0 zeroconf==0.36.13 From 08cbb97ec9bfd7b5dec872b3cb25bc7c2b9684ca Mon Sep 17 00:00:00 2001 From: mechanarchy <1166756+mechanarchy@users.noreply.github.com> Date: Wed, 1 Dec 2021 15:10:25 +1100 Subject: [PATCH 095/111] Allow Git credentials to be loaded from secrets (#2825) --- .../components/external_components/__init__.py | 6 ++++++ esphome/components/packages/__init__.py | 6 ++++++ esphome/git.py | 15 ++++++++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index e0548e8981..d0153f6104 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -12,6 +12,8 @@ from esphome.const import ( CONF_TYPE, CONF_EXTERNAL_COMPONENTS, CONF_PATH, + CONF_USERNAME, + CONF_PASSWORD, ) from esphome.core import CORE from esphome import git, loader @@ -27,6 +29,8 @@ TYPE_LOCAL = "local" GIT_SCHEMA = { cv.Required(CONF_URL): cv.url, cv.Optional(CONF_REF): cv.git_ref, + cv.Optional(CONF_USERNAME): cv.string, + cv.Optional(CONF_PASSWORD): cv.string, } LOCAL_SCHEMA = { cv.Required(CONF_PATH): cv.directory, @@ -99,6 +103,8 @@ def _process_git_config(config: dict, refresh) -> str: ref=config.get(CONF_REF), refresh=refresh, domain=DOMAIN, + username=config.get(CONF_USERNAME), + password=config.get(CONF_PASSWORD), ) if (repo_dir / "esphome" / "components").is_dir(): diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index df0f0de13d..7483d65b9d 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -10,6 +10,8 @@ from esphome.const import ( CONF_REF, CONF_REFRESH, CONF_URL, + CONF_USERNAME, + CONF_PASSWORD, ) import esphome.config_validation as cv @@ -93,6 +95,8 @@ BASE_SCHEMA = cv.All( cv.Schema( { cv.Required(CONF_URL): cv.url, + cv.Optional(CONF_USERNAME): cv.string, + cv.Optional(CONF_PASSWORD): cv.string, cv.Exclusive(CONF_FILE, "files"): validate_yaml_filename, cv.Exclusive(CONF_FILES, "files"): cv.All( cv.ensure_list(validate_yaml_filename), @@ -124,6 +128,8 @@ def _process_base_package(config: dict) -> dict: ref=config.get(CONF_REF), refresh=config[CONF_REFRESH], domain=DOMAIN, + username=config.get(CONF_USERNAME), + password=config.get(CONF_PASSWORD), ) files: str = config[CONF_FILES] diff --git a/esphome/git.py b/esphome/git.py index 25d893b2f5..64c8d6a6b7 100644 --- a/esphome/git.py +++ b/esphome/git.py @@ -2,6 +2,7 @@ from pathlib import Path import subprocess import hashlib import logging +import urllib.parse from datetime import datetime @@ -36,9 +37,21 @@ def _compute_destination_path(key: str, domain: str) -> Path: def clone_or_update( - *, url: str, ref: str = None, refresh: TimePeriodSeconds, domain: str + *, + url: str, + ref: str = None, + refresh: TimePeriodSeconds, + domain: str, + username: str = None, + password: str = None, ) -> Path: key = f"{url}@{ref}" + + if username is not None and password is not None: + url = url.replace( + "://", f"://{urllib.parse.quote(username)}:{urllib.parse.quote(password)}@" + ) + repo_dir = _compute_destination_path(key, domain) fetch_pr_branch = ref is not None and ref.startswith("pull/") if not repo_dir.is_dir(): From cbc1334b8dd5e541fa7f934cc613675625c90934 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 05:11:21 +0100 Subject: [PATCH 096/111] Fix compile warning in Tuya automations (#2837) --- esphome/components/tuya/automation.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/tuya/automation.cpp b/esphome/components/tuya/automation.cpp index eb1c170083..a8cfd098f1 100644 --- a/esphome/components/tuya/automation.cpp +++ b/esphome/components/tuya/automation.cpp @@ -9,7 +9,8 @@ namespace tuya { void check_expected_datapoint(const TuyaDatapoint &dp, TuyaDatapointType expected) { if (dp.type != expected) { - ESP_LOGW(TAG, "Tuya sensor %u expected datapoint type %#02hhX but got %#02hhX", dp.id, expected, dp.type); + ESP_LOGW(TAG, "Tuya sensor %u expected datapoint type %#02hhX but got %#02hhX", dp.id, + static_cast(expected), static_cast(dp.type)); } } From bfeb0b36397661e72c5441c1a399357f590ad2ca Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 05:12:14 +0100 Subject: [PATCH 097/111] Add problem matcher for Python formatting errors (#2833) --- .github/workflows/matchers/lint-python.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/matchers/lint-python.json b/.github/workflows/matchers/lint-python.json index e88f74f6f6..6a09f04770 100644 --- a/.github/workflows/matchers/lint-python.json +++ b/.github/workflows/matchers/lint-python.json @@ -1,5 +1,16 @@ { "problemMatcher": [ + { + "owner": "black", + "severity": "error", + "pattern": [ + { + "regexp": "^(.*): (Please format this file with the black formatter)", + "file": 1, + "message": 2 + } + ] + }, { "owner": "flake8", "severity": "error", From c9190574a95de84bfca91e017944c4ccd8dd2001 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 05:14:25 +0100 Subject: [PATCH 098/111] Fix CI check for Windows line endings (#2831) --- script/ci-custom.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/script/ci-custom.py b/script/ci-custom.py index 9acfbcdc23..52ac4025ca 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -20,7 +20,7 @@ def find_all(a_str, sub): # Optimization: If str is not in whole text, then do not try # on each line return - for i, line in enumerate(a_str.splitlines()): + for i, line in enumerate(a_str.split('\n')): column = 0 while True: column = line.find(sub, column) @@ -172,7 +172,7 @@ def lint_re_check(regex, **kwargs): return decorator -def lint_content_find_check(find, **kwargs): +def lint_content_find_check(find, only_first=False, **kwargs): decor = lint_content_check(**kwargs) def decorator(func): @@ -185,6 +185,8 @@ def lint_content_find_check(find, **kwargs): for line, col in find_all(content, find_): err = func(fname) errors.append((line + 1, col + 1, err)) + if only_first: + break return errors return decor(new_func) @@ -234,6 +236,7 @@ def lint_executable_bit(fname): @lint_content_find_check( "\t", + only_first=True, exclude=[ "esphome/dashboard/static/ace.js", "esphome/dashboard/static/ext-searchbox.js", @@ -243,9 +246,9 @@ def lint_tabs(fname): return "File contains tab character. Please convert tabs to spaces." -@lint_content_find_check("\r") +@lint_content_find_check("\r", only_first=True) def lint_newline(fname): - return "File contains windows newline. Please set your editor to unix newline mode." + return "File contains Windows newline. Please set your editor to Unix newline mode." @lint_content_check(exclude=["*.svg"]) From ca8db7696e2965c6ffffad1c462891ad1ca4e0ab Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 05:21:19 +0100 Subject: [PATCH 099/111] Don't enable namespace comment clang-tidy check twice (#2830) --- .clang-tidy | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 1c7e65b762..05858c8e52 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -51,6 +51,7 @@ Checks: >- -google-explicit-constructor, -google-readability-braces-around-statements, -google-readability-casting, + -google-readability-namespace-comments, -google-readability-todo, -google-runtime-references, -hicpp-*, @@ -97,12 +98,12 @@ CheckOptions: value: '1' - key: google-readability-function-size.StatementThreshold value: '800' - - key: google-readability-namespace-comments.ShortNamespaceLines - value: '10' - - key: google-readability-namespace-comments.SpacesBeforeComments - value: '2' - key: google-runtime-int.TypeSuffix value: '_t' + - key: llvm-namespace-comment.ShortNamespaceLines + value: '10' + - key: llvm-namespace-comment.SpacesBeforeComments + value: '2' - key: modernize-loop-convert.MaxCopySize value: '16' - key: modernize-loop-convert.MinConfidence From 1ec3140759f2a6c124b88a6a7318ad750f47e56c Mon Sep 17 00:00:00 2001 From: Yuval Brik Date: Wed, 1 Dec 2021 10:38:58 +0200 Subject: [PATCH 100/111] ESP32 Deep Sleep: correct level value (#2812) Upon registering for ESP32 deep sleep, DeepSleepComponent::begin_sleep calculates the level value to wake up on. As part of PR #2303, the level was changed to be based on `inverted` instead of `!inverted`: Before: https://github.com/esphome/esphome/blob/1e8e471dec19ceafba1997b1d9663f7912f244a2/esphome/components/deep_sleep/deep_sleep_component.cpp#L76 After: https://github.com/esphome/esphome/blob/2b04152482da3e9faaa4f6d0fd3370134d792fd1/esphome/components/deep_sleep/deep_sleep_component.cpp#L80 The level argument to `esp_sleep_enable_ext0_wakeup(pin, level)` [0] should be 0 when the inverted property is true (low triggers wakeup), and 1 when inverted property is false (high triggers wakeup). Also revert the changes of #2644. [0] https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/sleep_modes.html#_CPPv428esp_sleep_enable_ext0_wakeup10gpio_num_ti --- esphome/components/deep_sleep/deep_sleep_component.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 0998a57af3..c854b6da6e 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -77,8 +77,8 @@ void DeepSleepComponent::begin_sleep(bool manual) { if (this->sleep_duration_.has_value()) esp_sleep_enable_timer_wakeup(*this->sleep_duration_); if (this->wakeup_pin_ != nullptr) { - bool level = this->wakeup_pin_->is_inverted(); - if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && !this->wakeup_pin_->digital_read()) { + bool level = !this->wakeup_pin_->is_inverted(); + if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) { level = !level; } esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level); From 24a5325db35a49d0aa3c4a9a8b0258da12c4688d Mon Sep 17 00:00:00 2001 From: Mark Dietzer Date: Wed, 1 Dec 2021 01:01:15 -0800 Subject: [PATCH 101/111] Declare arch_get_cpu_cycle_count for esp8266 as IRAM (#2843) --- esphome/components/esp8266/core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp index 51f3ca50ec..137d4382b4 100644 --- a/esphome/components/esp8266/core.cpp +++ b/esphome/components/esp8266/core.cpp @@ -27,7 +27,7 @@ void IRAM_ATTR HOT arch_feed_wdt() { uint8_t progmem_read_byte(const uint8_t *addr) { return pgm_read_byte(addr); // NOLINT } -uint32_t arch_get_cpu_cycle_count() { +uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { return ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) } uint32_t arch_get_cpu_freq_hz() { return F_CPU; } From fbe1bca1b9896ba8c8b754c5a4faf790bffd887b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 17:37:24 +0100 Subject: [PATCH 102/111] Fix compilation using subprocesses (#2834) --- esphome/util.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/esphome/util.py b/esphome/util.py index 937635fa43..b2ba0c22c3 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -219,24 +219,23 @@ def run_external_process(*cmd, **kwargs): capture_stdout = kwargs.get("capture_stdout", False) if capture_stdout: - sub_stdout = io.BytesIO() + sub_stdout = subprocess.PIPE else: sub_stdout = RedirectText(sys.stdout, filter_lines=filter_lines) sub_stderr = RedirectText(sys.stderr, filter_lines=filter_lines) try: - return subprocess.call(cmd, stdout=sub_stdout, stderr=sub_stderr) + proc = subprocess.run( + cmd, stdout=sub_stdout, stderr=sub_stderr, encoding="utf-8", check=False + ) + return proc.stdout if capture_stdout else proc.returncode except KeyboardInterrupt: # pylint: disable=try-except-raise raise except Exception as err: # pylint: disable=broad-except _LOGGER.error("Running command failed: %s", err) _LOGGER.error("Please try running %s locally.", full_cmd) return 1 - finally: - if capture_stdout: - # pylint: disable=lost-exception - return sub_stdout.getvalue() def is_dev_esphome_version(): From 11330af05f0b5da70333d299b2f080518e7ae0ab Mon Sep 17 00:00:00 2001 From: Leon Loopik <489021+Lewn@users.noreply.github.com> Date: Wed, 1 Dec 2021 20:31:04 +0100 Subject: [PATCH 103/111] Expand uart invert feature to ESP8266 (#1727) --- esphome/components/uart/__init__.py | 15 +++++++++++++++ .../components/uart/uart_component_esp8266.cpp | 5 +++++ tests/test3.yaml | 4 +++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 61b54044d7..a63b220fc7 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -14,6 +14,7 @@ from esphome.const import ( CONF_UART_ID, CONF_DATA, CONF_RX_BUFFER_SIZE, + CONF_INVERTED, CONF_INVERT, CONF_TRIGGER_ID, CONF_SEQUENCE, @@ -67,6 +68,19 @@ def validate_rx_pin(value): return value +def validate_invert_esp32(config): + if ( + CORE.is_esp32 + and CONF_TX_PIN in config + and CONF_RX_PIN in config + and config[CONF_TX_PIN][CONF_INVERTED] != config[CONF_RX_PIN][CONF_INVERTED] + ): + raise cv.Invalid( + "Different invert values for TX and RX pin are not (yet) supported for ESP32." + ) + return config + + def _uart_declare_type(value): if CORE.is_esp8266: return cv.declare_id(ESP8266UartComponent)(value) @@ -162,6 +176,7 @@ CONFIG_SCHEMA = cv.All( } ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN), + validate_invert_esp32, ) diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index c367de05bb..408c83a0db 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -45,6 +45,11 @@ uint32_t ESP8266UartComponent::get_config() { else config |= UART_NB_STOP_BIT_2; + if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) + config |= BIT(22); + if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) + config |= BIT(19); + return config; } diff --git a/tests/test3.yaml b/tests/test3.yaml index 8ae4a383e0..50cd6d6cf6 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -227,7 +227,9 @@ spi: uart: - id: uart1 - tx_pin: GPIO1 + tx_pin: + number: GPIO1 + inverted: yes rx_pin: GPIO3 baud_rate: 115200 - id: uart2 From f58828cb8219d0e4e17e2fc66786eedd987ff37b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 20:55:27 +0100 Subject: [PATCH 104/111] Support setting manual_ip under networks option (#2839) --- esphome/components/wifi/__init__.py | 19 ++++++++++++++++--- tests/test5.yaml | 4 ++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 7a9319f5e0..a24791b458 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -221,10 +221,22 @@ def _validate(config): raise cv.Invalid("Fast connect can only be used with one network!") if CONF_USE_ADDRESS not in config: + use_address = CORE.name + config[CONF_DOMAIN] if CONF_MANUAL_IP in config: use_address = str(config[CONF_MANUAL_IP][CONF_STATIC_IP]) - else: - use_address = CORE.name + config[CONF_DOMAIN] + elif CONF_NETWORKS in config: + ips = set( + str(net[CONF_MANUAL_IP][CONF_STATIC_IP]) + for net in config[CONF_NETWORKS] + if CONF_MANUAL_IP in net + ) + if len(ips) > 1: + raise cv.Invalid( + "Must specify use_address when using multiple static IP addresses." + ) + if len(ips) == 1: + use_address = next(iter(ips)) + config[CONF_USE_ADDRESS] = use_address return config @@ -334,7 +346,8 @@ async def to_code(config): cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) for network in config.get(CONF_NETWORKS, []): - cg.add(var.add_sta(wifi_network(network, config.get(CONF_MANUAL_IP)))) + ip_config = network.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP)) + cg.add(var.add_sta(wifi_network(network, ip_config))) if CONF_AP in config: conf = config[CONF_AP] diff --git a/tests/test5.yaml b/tests/test5.yaml index aa3d057252..37e65e7da2 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -16,6 +16,10 @@ wifi: networks: - ssid: 'MySSID' password: 'password1' + manual_ip: + static_ip: 192.168.1.23 + gateway: 192.168.1.1 + subnet: 255.255.255.0 api: From 607601b3a4be5f8e5941d59744f438177c015815 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 21:03:51 +0100 Subject: [PATCH 105/111] Enable a bunch of clang-tidy checks (#2149) --- .clang-tidy | 8 +- .../adalight/adalight_light_effect.cpp | 2 +- .../adalight/adalight_light_effect.h | 2 +- esphome/components/aht10/aht10.cpp | 4 +- esphome/components/am2320/am2320.cpp | 4 +- esphome/components/anova/anova_base.cpp | 8 +- esphome/components/api/api_connection.cpp | 2 +- esphome/components/api/api_frame_helper.cpp | 22 +---- esphome/components/api/api_pb2.cpp | 92 +++++++++---------- .../captive_portal/captive_portal.cpp | 9 +- esphome/components/cs5460a/cs5460a.cpp | 2 - esphome/components/cse7766/cse7766.cpp | 6 +- esphome/components/daikin/daikin.cpp | 2 +- esphome/components/display/display_buffer.cpp | 6 +- esphome/components/dsmr/dsmr.h | 4 +- esphome/components/esp8266/gpio.cpp | 2 +- esphome/components/esp8266/preferences.cpp | 4 +- esphome/components/ezo/ezo.cpp | 2 +- esphome/components/graph/graph.cpp | 16 ++-- .../hitachi_ac344/hitachi_ac344.cpp | 4 +- .../hitachi_ac424/hitachi_ac424.cpp | 4 +- .../components/ili9341/ili9341_display.cpp | 4 +- .../improv_serial/improv_serial_component.cpp | 2 +- .../components/lcd_gpio/gpio_lcd_display.cpp | 2 +- .../light/addressable_light_effect.h | 4 +- esphome/components/light/light_call.cpp | 2 +- esphome/components/ltr390/ltr390.cpp | 2 +- esphome/components/max31865/max31865.cpp | 12 +-- .../components/max7219digit/max7219digit.cpp | 6 +- esphome/components/mcp23s08/mcp23s08.cpp | 1 - esphome/components/mcp2515/mcp2515.cpp | 3 - .../binary_sensor/modbus_binarysensor.cpp | 2 - .../modbus_controller/modbus_controller.h | 4 +- .../number/modbus_number.cpp | 11 --- .../output/modbus_output.cpp | 5 - .../sensor/modbus_sensor.cpp | 5 - esphome/components/nextion/nextion.cpp | 24 ++--- esphome/components/nextion/nextion_upload.cpp | 4 +- .../nextion/sensor/nextion_sensor.cpp | 2 +- esphome/components/nfc/ndef_message.cpp | 2 +- esphome/components/nfc/nfc.cpp | 4 +- esphome/components/ota/ota_component.cpp | 28 +++--- esphome/components/pid/pid_autotuner.cpp | 2 +- esphome/components/pipsolar/pipsolar.cpp | 6 +- esphome/components/pn532/pn532.cpp | 4 +- .../pulse_meter/pulse_meter_sensor.cpp | 2 +- esphome/components/rc522/rc522.cpp | 4 +- .../remote_base/pronto_protocol.cpp | 2 +- .../components/remote_base/remote_base.cpp | 2 +- .../remote_transmitter_esp32.cpp | 2 +- esphome/components/rtttl/rtttl.cpp | 2 +- esphome/components/sgp30/sgp30.cpp | 4 +- .../sgp40/sensirion_voc_algorithm.cpp | 2 +- esphome/components/sgp40/sgp40.cpp | 4 +- esphome/components/sgp40/sgp40.h | 2 +- esphome/components/sm300d2/sm300d2.cpp | 8 +- .../components/socket/lwip_raw_tcp_impl.cpp | 4 +- esphome/components/spi/spi.cpp | 8 +- .../components/ssd1306_base/ssd1306_base.cpp | 2 +- .../components/ssd1306_i2c/ssd1306_i2c.cpp | 4 +- .../components/ssd1306_spi/ssd1306_spi.cpp | 4 +- esphome/components/st7735/st7735.cpp | 4 +- esphome/components/st7920/st7920.cpp | 2 +- esphome/components/teleinfo/teleinfo.cpp | 2 +- esphome/components/text_sensor/filter.cpp | 2 +- .../components/tof10120/tof10120_sensor.cpp | 2 +- esphome/components/toshiba/toshiba.cpp | 4 +- esphome/components/tuya/tuya.cpp | 4 +- esphome/components/tx20/tx20.cpp | 2 +- .../uart/uart_component_esp8266.cpp | 4 +- .../waveshare_epaper/waveshare_epaper.cpp | 41 ++++----- .../waveshare_epaper/waveshare_epaper.h | 6 +- .../wifi/wifi_component_esp_idf.cpp | 10 +- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 6 +- esphome/core/application.cpp | 2 +- esphome/core/application.h | 2 +- esphome/core/component.cpp | 2 +- platformio.ini | 3 +- script/api_protobuf/api_protobuf.py | 2 +- 79 files changed, 206 insertions(+), 296 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 05858c8e52..b40e606121 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -5,11 +5,8 @@ Checks: >- -altera-*, -android-*, -boost-*, - -bugprone-branch-clone, - -bugprone-easily-swappable-parameters, -bugprone-narrowing-conversions, -bugprone-signed-char-misuse, - -bugprone-too-small-loop-variable, -cert-dcl50-cpp, -cert-err58-cpp, -cert-oop57-cpp, @@ -19,12 +16,10 @@ Checks: >- -clang-diagnostic-delete-abstract-non-virtual-dtor, -clang-diagnostic-delete-non-abstract-non-virtual-dtor, -clang-diagnostic-shadow-field, - -clang-diagnostic-sign-compare, - -clang-diagnostic-unused-variable, -clang-diagnostic-unused-const-variable, + -clang-diagnostic-unused-parameter, -concurrency-*, -cppcoreguidelines-avoid-c-arrays, - -cppcoreguidelines-avoid-goto, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-init-variables, -cppcoreguidelines-macro-usage, @@ -41,7 +36,6 @@ Checks: >- -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-special-member-functions, - -fuchsia-default-arguments, -fuchsia-multiple-inheritance, -fuchsia-overloaded-operator, -fuchsia-statically-constructed-objects, diff --git a/esphome/components/adalight/adalight_light_effect.cpp b/esphome/components/adalight/adalight_light_effect.cpp index d9c2892d21..35e98d7360 100644 --- a/esphome/components/adalight/adalight_light_effect.cpp +++ b/esphome/components/adalight/adalight_light_effect.cpp @@ -25,7 +25,7 @@ void AdalightLightEffect::stop() { AddressableLightEffect::stop(); } -int AdalightLightEffect::get_frame_size_(int led_count) const { +unsigned int AdalightLightEffect::get_frame_size_(int led_count) const { // 3 bytes: Ada // 2 bytes: LED count // 1 byte: checksum diff --git a/esphome/components/adalight/adalight_light_effect.h b/esphome/components/adalight/adalight_light_effect.h index c1df55659b..b757191864 100644 --- a/esphome/components/adalight/adalight_light_effect.h +++ b/esphome/components/adalight/adalight_light_effect.h @@ -25,7 +25,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U CONSUMED, }; - int get_frame_size_(int led_count) const; + unsigned int get_frame_size_(int led_count) const; void reset_frame_(light::AddressableLight &it); void blank_all_leds_(light::AddressableLight &it); Frame parse_frame_(light::AddressableLight &it); diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp index 78f98cb14f..3c690c39b5 100644 --- a/esphome/components/aht10/aht10.cpp +++ b/esphome/components/aht10/aht10.cpp @@ -110,12 +110,12 @@ void AHT10Component::update() { uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4; - float temperature = ((200.0 * (float) raw_temperature) / 1048576.0) - 50.0; + float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f; float humidity; if (raw_humidity == 0) { // unrealistic value humidity = NAN; } else { - humidity = (float) raw_humidity * 100.0 / 1048576.0; + humidity = (float) raw_humidity * 100.0f / 1048576.0f; } if (this->temperature_sensor_ != nullptr) { diff --git a/esphome/components/am2320/am2320.cpp b/esphome/components/am2320/am2320.cpp index b53eb69464..c06a2a34d7 100644 --- a/esphome/components/am2320/am2320.cpp +++ b/esphome/components/am2320/am2320.cpp @@ -38,9 +38,9 @@ void AM2320Component::update() { return; } - float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0; + float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0f; temperature = (data[4] & 0x80) ? -temperature : temperature; - float humidity = ((data[2] << 8) + data[3]) / 10.0; + float humidity = ((data[2] << 8) + data[3]) / 10.0f; ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity); if (this->temperature_sensor_ != nullptr) diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp index cb877bef35..ce4febbe37 100644 --- a/esphome/components/anova/anova_base.cpp +++ b/esphome/components/anova/anova_base.cpp @@ -103,13 +103,7 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) { } break; } - case READ_TARGET_TEMPERATURE: { - this->target_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f); - if (this->fahrenheit_) - this->target_temp_ = ftoc(this->target_temp_); - this->has_target_temp_ = true; - break; - } + case READ_TARGET_TEMPERATURE: case SET_TARGET_TEMPERATURE: { this->target_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f); if (this->fahrenheit_) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 92699df0da..f615815023 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -132,7 +132,7 @@ void APIConnection::loop() { if (state_subs_at_ != -1) { const auto &subs = this->parent_->get_state_subs(); - if (state_subs_at_ >= subs.size()) { + if (state_subs_at_ >= (int) subs.size()) { state_subs_at_ = -1; } else { auto &it = subs[state_subs_at_]; diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index c0e37ec90d..23766ec1b1 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -174,9 +174,6 @@ APIError APINoiseFrameHelper::loop() { * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase. */ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { - int err; - APIError aerr; - if (frame == nullptr) { HELPER_LOG("Bad argument for try_read_frame_"); return APIError::BAD_ARG; @@ -200,7 +197,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { return APIError::CONNECTION_CLOSED; } rx_header_buf_len_ += received; - if (received != to_read) { + if ((size_t) received != to_read) { // not a full read return APIError::WOULD_BLOCK; } @@ -247,7 +244,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { return APIError::CONNECTION_CLOSED; } rx_buf_len_ += received; - if (received != to_read) { + if ((size_t) received != to_read) { // not all read return APIError::WOULD_BLOCK; } @@ -544,7 +541,6 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() { APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { if (iovcnt == 0) return APIError::OK; - int err; APIError aerr; size_t total_write_len = 0; @@ -584,7 +580,7 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); return APIError::SOCKET_WRITE_FAILED; - } else if (sent != total_write_len) { + } else if ((size_t) sent != total_write_len) { // partially sent, add end to tx_buf size_t to_consume = sent; for (int i = 0; i < iovcnt; i++) { @@ -778,9 +774,6 @@ APIError APIPlaintextFrameHelper::loop() { * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. */ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { - int err; - APIError aerr; - if (frame == nullptr) { HELPER_LOG("Bad argument for try_read_frame_"); return APIError::BAD_ARG; @@ -854,7 +847,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { return APIError::CONNECTION_CLOSED; } rx_buf_len_ += received; - if (received != to_read) { + if ((size_t) received != to_read) { // not all read return APIError::WOULD_BLOCK; } @@ -874,7 +867,6 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { } APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { - int err; APIError aerr; if (state_ != State::DATA) { @@ -894,9 +886,6 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { } bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) { - int err; - APIError aerr; - if (state_ != State::DATA) { return APIError::BAD_STATE; } @@ -940,7 +929,6 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() { APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { if (iovcnt == 0) return APIError::OK; - int err; APIError aerr; size_t total_write_len = 0; @@ -980,7 +968,7 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); return APIError::SOCKET_WRITE_FAILED; - } else if (sent != total_write_len) { + } else if ((size_t) sent != total_write_len) { // partially sent, add end to tx_buf size_t to_consume = sent; for (int i = 0; i < iovcnt; i++) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 9228be0860..5b6853c276 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -291,7 +291,7 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); } #ifdef HAS_PROTO_MESSAGE_DUMP void HelloRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HelloRequest {\n"); out.append(" client_info: "); out.append("'").append(this->client_info).append("'"); @@ -330,7 +330,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void HelloResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HelloResponse {\n"); out.append(" api_version_major: "); sprintf(buffer, "%u", this->api_version_major); @@ -361,7 +361,7 @@ bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); } #ifdef HAS_PROTO_MESSAGE_DUMP void ConnectRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ConnectRequest {\n"); out.append(" password: "); out.append("'").append(this->password).append("'"); @@ -382,7 +382,7 @@ bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); } #ifdef HAS_PROTO_MESSAGE_DUMP void ConnectResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ConnectResponse {\n"); out.append(" invalid_password: "); out.append(YESNO(this->invalid_password)); @@ -476,7 +476,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("DeviceInfoResponse {\n"); out.append(" uses_password: "); out.append(YESNO(this->uses_password)); @@ -600,7 +600,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesBinarySensorResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -672,7 +672,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void BinarySensorStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("BinarySensorStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -766,7 +766,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesCoverResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -856,7 +856,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void CoverStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("CoverStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -939,7 +939,7 @@ void CoverCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void CoverCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("CoverCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1055,7 +1055,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesFanResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesFanResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -1151,7 +1151,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void FanStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("FanStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1252,7 +1252,7 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void FanCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("FanCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1403,7 +1403,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLightResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesLightResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -1561,7 +1561,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void LightStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("LightStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1784,7 +1784,7 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void LightCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("LightCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1995,7 +1995,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSensorResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesSensorResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -2084,7 +2084,7 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SensorStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SensorStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -2164,7 +2164,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSwitchResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesSwitchResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -2227,7 +2227,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SwitchStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SwitchStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -2266,7 +2266,7 @@ void SwitchCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SwitchCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SwitchCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -2336,7 +2336,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesTextSensorResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -2406,7 +2406,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void TextSensorStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("TextSensorStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -2443,7 +2443,7 @@ void SubscribeLogsRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeLogsRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SubscribeLogsRequest {\n"); out.append(" level: "); out.append(proto_enum_to_string(this->level)); @@ -2486,7 +2486,7 @@ void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeLogsResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SubscribeLogsResponse {\n"); out.append(" level: "); out.append(proto_enum_to_string(this->level)); @@ -2528,7 +2528,7 @@ void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void HomeassistantServiceMap::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HomeassistantServiceMap {\n"); out.append(" key: "); out.append("'").append(this->key).append("'"); @@ -2587,7 +2587,7 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void HomeassistantServiceResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HomeassistantServiceResponse {\n"); out.append(" service: "); out.append("'").append(this->service).append("'"); @@ -2643,7 +2643,7 @@ void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const } #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SubscribeHomeAssistantStateResponse {\n"); out.append(" entity_id: "); out.append("'").append(this->entity_id).append("'"); @@ -2680,7 +2680,7 @@ void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void HomeAssistantStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HomeAssistantStateResponse {\n"); out.append(" entity_id: "); out.append("'").append(this->entity_id).append("'"); @@ -2713,7 +2713,7 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); } #ifdef HAS_PROTO_MESSAGE_DUMP void GetTimeResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("GetTimeResponse {\n"); out.append(" epoch_seconds: "); sprintf(buffer, "%u", this->epoch_seconds); @@ -2748,7 +2748,7 @@ void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesServicesArgument::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesServicesArgument {\n"); out.append(" name: "); out.append("'").append(this->name).append("'"); @@ -2793,7 +2793,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesServicesResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesServicesResponse {\n"); out.append(" name: "); out.append("'").append(this->name).append("'"); @@ -2887,7 +2887,7 @@ void ExecuteServiceArgument::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ExecuteServiceArgument::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ExecuteServiceArgument {\n"); out.append(" bool_: "); out.append(YESNO(this->bool_)); @@ -2968,7 +2968,7 @@ void ExecuteServiceRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ExecuteServiceRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ExecuteServiceRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3040,7 +3040,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCameraResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesCameraResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -3110,7 +3110,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void CameraImageResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("CameraImageResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3147,7 +3147,7 @@ void CameraImageRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void CameraImageRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("CameraImageRequest {\n"); out.append(" single: "); out.append(YESNO(this->single)); @@ -3293,7 +3293,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesClimateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesClimateResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -3480,7 +3480,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ClimateStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ClimateStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3668,7 +3668,7 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ClimateCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ClimateCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3842,7 +3842,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesNumberResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -3929,7 +3929,7 @@ void NumberStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void NumberStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("NumberStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3967,7 +3967,7 @@ void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void NumberCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("NumberCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -4045,7 +4045,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSelectResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesSelectResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -4121,7 +4121,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SelectStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SelectStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -4164,7 +4164,7 @@ void SelectCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SelectCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SelectCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index ad4c32bb1f..d4e37f62f2 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -85,14 +85,7 @@ void CaptivePortal::start() { this->dns_server_->start(53, "*", (uint32_t) ip); this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { - bool not_found = false; - if (!this->active_) { - not_found = true; - } else if (req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) { - not_found = true; - } - - if (not_found) { + if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) { req->send(404, "text/html", "File not found"); return; } diff --git a/esphome/components/cs5460a/cs5460a.cpp b/esphome/components/cs5460a/cs5460a.cpp index a172bcdf56..b0c0531936 100644 --- a/esphome/components/cs5460a/cs5460a.cpp +++ b/esphome/components/cs5460a/cs5460a.cpp @@ -102,8 +102,6 @@ void CS5460AComponent::hw_init_() { /* Doesn't reset the register values etc., just restarts the "computation cycle" */ void CS5460AComponent::restart_() { - int cnt; - this->enable(); /* Stop running conversion, wake up if needed */ this->write_byte(CMD_POWER_UP); diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index 55e1ec82cf..25d75da3e6 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -149,9 +149,9 @@ void CSE7766Component::parse_data_() { } } void CSE7766Component::update() { - float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0; - float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0; - float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0; + float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0f; + float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0f; + float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0f; ESP_LOGV(TAG, "Got voltage_acc=%.2f current_acc=%.2f power_acc=%.2f", this->voltage_acc_, this->current_acc_, this->power_acc_); diff --git a/esphome/components/daikin/daikin.cpp b/esphome/components/daikin/daikin.cpp index 5f8d0288e2..83d0253691 100644 --- a/esphome/components/daikin/daikin.cpp +++ b/esphome/components/daikin/daikin.cpp @@ -231,7 +231,7 @@ bool DaikinClimate::on_receive(remote_base::RemoteReceiveData data) { // frame header if (byte != 0x27) return false; - } else if (pos == 3) { + } else if (pos == 3) { // NOLINT(bugprone-branch-clone) // frame header if (byte != 0x00) return false; diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index ac806611b5..1458629acd 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -496,7 +496,7 @@ bool Animation::get_pixel(int x, int y) const { return false; const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; const uint32_t frame_index = this->height_ * width_8 * this->current_frame_; - if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) + if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_)) return false; const uint32_t pos = x + y * width_8 + frame_index; return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); @@ -505,7 +505,7 @@ Color Animation::get_color_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) return Color::BLACK; const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_; - if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) + if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_)) return Color::BLACK; const uint32_t pos = (x + y * this->width_ + frame_index) * 3; const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) | @@ -517,7 +517,7 @@ Color Animation::get_grayscale_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) return Color::BLACK; const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_; - if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) + if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_)) return Color::BLACK; const uint32_t pos = (x + y * this->width_ + frame_index); const uint8_t gray = progmem_read_byte(this->data_start_ + pos); diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index db0bf95ca1..76f79ee55c 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -114,10 +114,10 @@ class Dsmr : public Component, public uart::UARTDevice { bool receive_timeout_reached_(); size_t max_telegram_len_; char *telegram_{nullptr}; - int bytes_read_{0}; + size_t bytes_read_{0}; uint8_t *crypt_telegram_{nullptr}; size_t crypt_telegram_len_{0}; - int crypt_bytes_read_{0}; + size_t crypt_bytes_read_{0}; uint32_t last_read_time_{0}; bool header_found_{false}; bool footer_found_{false}; diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index 2660318182..a24f217756 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -9,7 +9,7 @@ namespace esp8266 { static const char *const TAG = "esp8266"; static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) { - if (flags == gpio::FLAG_INPUT) { + if (flags == gpio::FLAG_INPUT) { // NOLINT(bugprone-branch-clone) return INPUT; } else if (flags == gpio::FLAG_OUTPUT) { return OUTPUT; diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index 041736943b..a8f8bd0d41 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -55,7 +55,7 @@ static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) { extern "C" uint32_t _SPIFFS_end; // NOLINT -static const uint32_t get_esp8266_flash_sector() { +static uint32_t get_esp8266_flash_sector() { union { uint32_t *ptr; uint32_t uint; @@ -63,7 +63,7 @@ static const uint32_t get_esp8266_flash_sector() { data.ptr = &_SPIFFS_end; return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE; } -static const uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; } +static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; } template uint32_t calculate_crc(It first, It last, uint32_t type) { uint32_t crc = type; diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index ca6f121dbb..3c1b6e33e8 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -75,7 +75,7 @@ void EZOSensor::loop() { return; // some sensors return multiple comma-separated values, terminate string after first one - for (int i = 1; i < sizeof(buf) - 1; i++) + for (size_t i = 1; i < sizeof(buf) - 1; i++) if (buf[i] == ',') buf[i] = '\0'; diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index a9daad4ab9..daff89e0a6 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -86,7 +86,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo // Look back in trace data to best-fit into local range float mx = NAN; float mn = NAN; - for (int16_t i = 0; i < this->width_; i++) { + for (uint32_t i = 0; i < this->width_; i++) { for (auto *trace : traces_) { float v = trace->get_tracedata()->get_value(i); if (!std::isnan(v)) { @@ -132,7 +132,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo if (!std::isnan(this->gridspacing_y_)) { for (int y = yn; y <= ym; y++) { int16_t py = (int16_t) roundf((this->height_ - 1) * (1.0 - (float) (y - yn) / (ym - yn))); - for (int x = 0; x < this->width_; x += 2) { + for (uint32_t x = 0; x < this->width_; x += 2) { buff->draw_pixel_at(x_offset + x, y_offset + py, color); } } @@ -147,7 +147,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo ESP_LOGW(TAG, "Graphing reducing x-scale to prevent too many gridlines"); } for (int i = 0; i <= n; i++) { - for (int y = 0; y < this->height_; y += 2) { + for (uint32_t y = 0; y < this->height_; y += 2) { buff->draw_pixel_at(x_offset + i * (this->width_ - 1) / n, y_offset + y, color); } } @@ -158,14 +158,14 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo for (auto *trace : traces_) { Color c = trace->get_line_color(); uint16_t thick = trace->get_line_thickness(); - for (int16_t i = 0; i < this->width_; i++) { + for (uint32_t i = 0; i < this->width_; i++) { float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange; if (!std::isnan(v) && (thick > 0)) { int16_t x = this->width_ - 1 - i; uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2; - for (int16_t t = 0; t < thick; t++) { + for (uint16_t t = 0; t < thick; t++) { buff->draw_pixel_at(x_offset + x, y_offset + y + t, c); } } @@ -179,8 +179,8 @@ void GraphLegend::init(Graph *g) { parent_ = g; // Determine maximum expected text and value width / height - int txtw = 0, txtos = 0, txtbl = 0, txth = 0; - int valw = 0, valos = 0, valbl = 0, valh = 0; + int txtw = 0, txth = 0; + int valw = 0, valh = 0; int lt = 0; for (auto *trace : g->traces_) { std::string txtstr = trace->get_name(); @@ -320,7 +320,7 @@ void Graph::draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_ if (legend_->lines_) { uint16_t thick = trace->get_line_thickness(); - for (int16_t i = 0; i < legend_->x0_ * 4 / 3; i++) { + for (int i = 0; i < legend_->x0_ * 4 / 3; i++) { uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { buff->vertical_line(x - legend_->x0_ * 2 / 3 + i, y + legend_->yl_ - thick / 2, thick, diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.cpp b/esphome/components/hitachi_ac344/hitachi_ac344.cpp index 067ea39d07..7702baf312 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.cpp +++ b/esphome/components/hitachi_ac344/hitachi_ac344.cpp @@ -299,9 +299,7 @@ bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) { GETBITS8(remote_state[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE); ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC344_SWINGH_BYTE], swing_modeh); - if ((swing_modeh & 0x7) == 0x0) { - this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; - } else if ((swing_modeh & 0x3) == 0x3) { + if ((swing_modeh & 0x3) == 0x3) { this->swing_mode = climate::CLIMATE_SWING_OFF; } else { this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.cpp b/esphome/components/hitachi_ac424/hitachi_ac424.cpp index 2e5423a37a..713bc0be25 100644 --- a/esphome/components/hitachi_ac424/hitachi_ac424.cpp +++ b/esphome/components/hitachi_ac424/hitachi_ac424.cpp @@ -300,9 +300,7 @@ bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) { HITACHI_AC424_SWINGH_SIZE); ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC424_SWINGH_BYTE], swing_modeh); - if ((swing_modeh & 0x7) == 0x0) { - this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; - } else if ((swing_modeh & 0x3) == 0x3) { + if ((swing_modeh & 0x3) == 0x3) { this->swing_mode = climate::CLIMATE_SWING_OFF; } else { this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; diff --git a/esphome/components/ili9341/ili9341_display.cpp b/esphome/components/ili9341/ili9341_display.cpp index ab5586fa28..a24f0bbb64 100644 --- a/esphome/components/ili9341/ili9341_display.cpp +++ b/esphome/components/ili9341/ili9341_display.cpp @@ -86,8 +86,8 @@ void ILI9341Display::update() { void ILI9341Display::display_() { // we will only update the changed window to the display - int w = this->x_high_ - this->x_low_ + 1; - int h = this->y_high_ - this->y_low_ + 1; + uint16_t w = this->x_high_ - this->x_low_ + 1; + uint16_t h = this->y_high_ - this->y_low_ + 1; set_addr_window_(this->x_low_, this->y_low_, w, h); this->start_data_(); diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index a9a7467125..b4d1d88370 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -145,7 +145,7 @@ bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) { if (at == 8 + data_len + 1) { uint8_t checksum = 0x00; - for (uint8_t i = 0; i < at; i++) + for (size_t i = 0; i < at; i++) checksum += raw[i]; if (checksum != byte) { diff --git a/esphome/components/lcd_gpio/gpio_lcd_display.cpp b/esphome/components/lcd_gpio/gpio_lcd_display.cpp index b0344d313c..94ddc34051 100644 --- a/esphome/components/lcd_gpio/gpio_lcd_display.cpp +++ b/esphome/components/lcd_gpio/gpio_lcd_display.cpp @@ -17,7 +17,7 @@ void GPIOLCDDisplay::setup() { this->enable_pin_->setup(); // OUTPUT this->enable_pin_->digital_write(false); - for (uint8_t i = 0; i < (this->is_four_bit_mode() ? 4 : 8); i++) { + for (uint8_t i = 0; i < (uint8_t)(this->is_four_bit_mode() ? 4u : 8u); i++) { this->data_pins_[i]->setup(); // OUTPUT this->data_pins_[i]->digital_write(false); } diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index 358fe69c23..5091bae2d5 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -167,7 +167,7 @@ class AddressableScanEffect : public AddressableLightEffect { this->last_move_ = now; it.all() = Color::BLACK; - for (auto i = 0; i < this->scan_width_; i++) { + for (uint32_t i = 0; i < this->scan_width_; i++) { it[this->at_led_ + i] = current_color; } @@ -178,7 +178,7 @@ class AddressableScanEffect : public AddressableLightEffect { uint32_t move_interval_{}; uint32_t scan_width_{1}; uint32_t last_move_{0}; - int at_led_{0}; + uint32_t at_led_{0}; bool direction_{true}; }; diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 9858590850..3f1b8aef30 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -98,7 +98,7 @@ void LightCall::perform() { // EFFECT auto effect = this->effect_; const char *effect_s; - if (effect == 0) + if (effect == 0u) effect_s = "None"; else effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str(); diff --git a/esphome/components/ltr390/ltr390.cpp b/esphome/components/ltr390/ltr390.cpp index 36f3835724..959af68235 100644 --- a/esphome/components/ltr390/ltr390.cpp +++ b/esphome/components/ltr390/ltr390.cpp @@ -97,7 +97,7 @@ void LTR390Component::read_mode_(int mode_index) { // If there are more modes to read then begin the next // otherwise stop - if (mode_index + 1 < this->mode_funcs_.size()) { + if (mode_index + 1 < (int) this->mode_funcs_.size()) { this->read_mode_(mode_index + 1); } else { this->reading_ = false; diff --git a/esphome/components/max31865/max31865.cpp b/esphome/components/max31865/max31865.cpp index 91946cde2c..126915dc15 100644 --- a/esphome/components/max31865/max31865.cpp +++ b/esphome/components/max31865/max31865.cpp @@ -203,16 +203,16 @@ float MAX31865Sensor::calc_temperature_(float rtd_ratio) { rtd_resistance *= 100; } float rpoly = rtd_resistance; - float neg_temp = -242.02; - neg_temp += 2.2228 * rpoly; + float neg_temp = -242.02f; + neg_temp += 2.2228f * rpoly; rpoly *= rtd_resistance; // square - neg_temp += 2.5859e-3 * rpoly; + neg_temp += 2.5859e-3f * rpoly; rpoly *= rtd_resistance; // ^3 - neg_temp -= 4.8260e-6 * rpoly; + neg_temp -= 4.8260e-6f * rpoly; rpoly *= rtd_resistance; // ^4 - neg_temp -= 2.8183e-8 * rpoly; + neg_temp -= 2.8183e-8f * rpoly; rpoly *= rtd_resistance; // ^5 - neg_temp += 1.5243e-10 * rpoly; + neg_temp += 1.5243e-10f * rpoly; return neg_temp; } diff --git a/esphome/components/max7219digit/max7219digit.cpp b/esphome/components/max7219digit/max7219digit.cpp index 0f86ac635c..2368c17448 100644 --- a/esphome/components/max7219digit/max7219digit.cpp +++ b/esphome/components/max7219digit/max7219digit.cpp @@ -76,7 +76,7 @@ void MAX7219Component::loop() { this->stepsleft_ = 0; // Return if there is no need to scroll or scroll is off - if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= get_width_internal())) { + if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= (size_t) get_width_internal())) { this->display(); return; } @@ -88,7 +88,7 @@ void MAX7219Component::loop() { // Dwell time at end of string in case of stop at end if (this->scroll_mode_ == ScrollMode::STOP) { - if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - get_width_internal() + 1) { + if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - (size_t) get_width_internal() + 1) { if (now - this->last_scroll_ >= this->scroll_dwell_) { this->stepsleft_ = 0; this->last_scroll_ = now; @@ -155,7 +155,7 @@ int MAX7219Component::get_height_internal() { int MAX7219Component::get_width_internal() { return this->num_chips_ / this->num_chip_lines_ * 8; } void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) { - if (x + 1 > this->max_displaybuffer_[0].size()) { // Extend the display buffer in case required + if (x + 1 > (int) this->max_displaybuffer_[0].size()) { // Extend the display buffer in case required for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { this->max_displaybuffer_[chip_line].resize(x + 1, this->bckgrnd_); } diff --git a/esphome/components/mcp23s08/mcp23s08.cpp b/esphome/components/mcp23s08/mcp23s08.cpp index b7adeb94d2..af834b4c40 100644 --- a/esphome/components/mcp23s08/mcp23s08.cpp +++ b/esphome/components/mcp23s08/mcp23s08.cpp @@ -35,7 +35,6 @@ void MCP23S08::dump_config() { } bool MCP23S08::read_reg(uint8_t reg, uint8_t *value) { - uint8_t data; this->enable(); this->transfer_byte(this->device_opcode_ | 1); this->transfer_byte(reg); diff --git a/esphome/components/mcp2515/mcp2515.cpp b/esphome/components/mcp2515/mcp2515.cpp index ce451cbb33..e845c79a64 100644 --- a/esphome/components/mcp2515/mcp2515.cpp +++ b/esphome/components/mcp2515/mcp2515.cpp @@ -127,9 +127,6 @@ canbus::Error MCP2515::set_mode_(const CanctrlReqopMode mode) { } canbus::Error MCP2515::set_clk_out_(const CanClkOut divisor) { - canbus::Error res; - uint8_t cfg3; - if (divisor == CLKOUT_DISABLE) { /* Turn off CLKEN */ modify_register_(MCP_CANCTRL, CANCTRL_CLKEN, 0x00); diff --git a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp index 81066b3f5c..c3eb3d4411 100644 --- a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp +++ b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp @@ -13,8 +13,6 @@ void ModbusBinarySensor::parse_and_publish(const std::vector &data) { switch (this->register_type) { case ModbusRegisterType::DISCRETE_INPUT: - value = coil_from_vector(this->offset, data); - break; case ModbusRegisterType::COIL: // offset for coil is the actual number of the coil not the byte offset value = coil_from_vector(this->offset, data); diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 39c0d8026f..045075f5e0 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -102,8 +102,6 @@ inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_ return ModbusFunctionCode::READ_WRITE_MULTIPLE_REGISTERS; break; case ModbusRegisterType::READ: - return ModbusFunctionCode::CUSTOM; - break; default: return ModbusFunctionCode::CUSTOM; break; @@ -221,7 +219,7 @@ template N mask_and_shift_by_rightbit(N data, uint32_t mask) { if (result == 0) { return result; } - for (int pos = 0; pos < sizeof(N) << 3; pos++) { + for (size_t pos = 0; pos < sizeof(N) << 3; pos++) { if ((mask & (1 << pos)) != 0) return result >> pos; } diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index 95c6ac6f6a..ba2ffdd09f 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -8,11 +8,6 @@ namespace modbus_controller { static const char *const TAG = "modbus.number"; void ModbusNumber::parse_and_publish(const std::vector &data) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - float result = payload_to_float(data, *this); // Is there a lambda registered @@ -31,13 +26,7 @@ void ModbusNumber::parse_and_publish(const std::vector &data) { } void ModbusNumber::control(float value) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - std::vector data; - auto original_value = value; // Is there are lambda configured? if (this->write_transform_func_.has_value()) { // data is passed by reference diff --git a/esphome/components/modbus_controller/output/modbus_output.cpp b/esphome/components/modbus_controller/output/modbus_output.cpp index f7d7c42342..d2b5d02bda 100644 --- a/esphome/components/modbus_controller/output/modbus_output.cpp +++ b/esphome/components/modbus_controller/output/modbus_output.cpp @@ -13,11 +13,6 @@ void ModbusOutput::setup() {} * */ void ModbusOutput::write_state(float value) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - std::vector data; auto original_value = value; // Is there are lambda configured? diff --git a/esphome/components/modbus_controller/sensor/modbus_sensor.cpp b/esphome/components/modbus_controller/sensor/modbus_sensor.cpp index dbd0525347..a21fd91032 100644 --- a/esphome/components/modbus_controller/sensor/modbus_sensor.cpp +++ b/esphome/components/modbus_controller/sensor/modbus_sensor.cpp @@ -10,11 +10,6 @@ static const char *const TAG = "modbus_controller.sensor"; void ModbusSensor::dump_config() { LOG_SENSOR(TAG, "Modbus Controller Sensor", this); } void ModbusSensor::parse_and_publish(const std::vector &data) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - float result = payload_to_float(data, *this); // Is there a lambda registered diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index f23f55c9bb..494765db4d 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -64,7 +64,7 @@ bool Nextion::check_connect_() { if (response.empty() || response.find("comok") == std::string::npos) { #ifdef NEXTION_PROTOCOL_LOG ESP_LOGN(TAG, "Bad connect request %s", response.c_str()); - for (int i = 0; i < response.length(); i++) { + for (size_t i = 0; i < response.length(); i++) { ESP_LOGN(TAG, "response %s %d %d %c", response.c_str(), i, response[i], response[i]); } #endif @@ -563,11 +563,10 @@ void Nextion::process_nextion_commands_() { // FF FF FF - End case 0x90: { // Switched component std::string variable_name; - uint8_t index = 0; // Get variable name - index = to_process.find('\0'); - if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + auto index = to_process.find('\0'); + if (index == std::string::npos || (to_process_length - index - 1) < 1) { ESP_LOGE(TAG, "Bad switch component data received for 0x90 event!"); ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); break; @@ -591,10 +590,9 @@ void Nextion::process_nextion_commands_() { // FF FF FF - End case 0x91: { // Sensor component std::string variable_name; - uint8_t index = 0; - index = to_process.find('\0'); - if (static_cast(index) == std::string::npos || (to_process_length - index - 1) != 4) { + auto index = to_process.find('\0'); + if (index == std::string::npos || (to_process_length - index - 1) != 4) { ESP_LOGE(TAG, "Bad sensor component data received for 0x91 event!"); ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); break; @@ -626,11 +624,10 @@ void Nextion::process_nextion_commands_() { case 0x92: { // Text Sensor Component std::string variable_name; std::string text_value; - uint8_t index = 0; // Get variable name - index = to_process.find('\0'); - if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + auto index = to_process.find('\0'); + if (index == std::string::npos || (to_process_length - index - 1) < 1) { ESP_LOGE(TAG, "Bad text sensor component data received for 0x92 event!"); ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); break; @@ -660,11 +657,10 @@ void Nextion::process_nextion_commands_() { // FF FF FF - End case 0x93: { // Binary Sensor component std::string variable_name; - uint8_t index = 0; // Get variable name - index = to_process.find('\0'); - if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + auto index = to_process.find('\0'); + if (index == std::string::npos || (to_process_length - index - 1) < 1) { ESP_LOGE(TAG, "Bad binary sensor component data received for 0x92 event!"); ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); break; @@ -736,7 +732,7 @@ void Nextion::process_nextion_commands_() { uint32_t ms = millis(); if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) { - for (int i = 0; i < this->nextion_queue_.size(); i++) { + for (size_t i = 0; i < this->nextion_queue_.size(); i++) { NextionComponentBase *component = this->nextion_queue_[i]->component; if (this->nextion_queue_[i]->queue_time + this->max_q_age_ms_ < ms) { if (this->nextion_queue_[i]->queue_time == 0) diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index cd1c073320..b16f2fe7eb 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -95,7 +95,7 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { } http->end(); ESP_LOGN(TAG, "this->content_length_ %d sent %d", this->content_length_, sent); - for (uint32_t i = 0; i < range; i += 4096) { + for (int i = 0; i < range; i += 4096) { this->write_array(&this->transfer_buffer_[i], 4096); this->content_length_ -= 4096; ESP_LOGN(TAG, "this->content_length_ %d range %d range_end %d range_start %d", this->content_length_, range, @@ -238,7 +238,7 @@ void Nextion::upload_tft() { // The Nextion display will, if it's ready to accept data, send a 0x05 byte. ESP_LOGD(TAG, "Upgrade response is %s %zu", response.c_str(), response.length()); - for (int i = 0; i < response.length(); i++) { + for (size_t i = 0; i < response.length(); i++) { ESP_LOGD(TAG, "Available %d : 0x%02X", i, response[i]); } diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp index 4b7532d32d..32bfccf9f8 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.cpp +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -24,7 +24,7 @@ void NextionSensor::add_to_wave_buffer(float state) { wave_buffer_.push_back(wave_state); - if (this->wave_buffer_.size() > this->wave_max_length_) { + if (this->wave_buffer_.size() > (size_t) this->wave_max_length_) { this->wave_buffer_.erase(this->wave_buffer_.begin()); } } diff --git a/esphome/components/nfc/ndef_message.cpp b/esphome/components/nfc/ndef_message.cpp index d8c940254e..d7d134aedb 100644 --- a/esphome/components/nfc/ndef_message.cpp +++ b/esphome/components/nfc/ndef_message.cpp @@ -93,7 +93,7 @@ bool NdefMessage::add_uri_record(const std::string &uri) { return this->add_reco std::vector NdefMessage::encode() { std::vector data; - for (uint8_t i = 0; i < this->records_.size(); i++) { + for (size_t i = 0; i < this->records_.size(); i++) { auto encoded_record = this->records_[i]->encode(i == 0, (i + 1) == this->records_.size()); data.insert(data.end(), encoded_record.begin(), encoded_record.end()); } diff --git a/esphome/components/nfc/nfc.cpp b/esphome/components/nfc/nfc.cpp index 706c09a5aa..09dbdcfe94 100644 --- a/esphome/components/nfc/nfc.cpp +++ b/esphome/components/nfc/nfc.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "nfc"; std::string format_uid(std::vector &uid) { char buf[(uid.size() * 2) + uid.size() - 1]; int offset = 0; - for (uint8_t i = 0; i < uid.size(); i++) { + for (size_t i = 0; i < uid.size(); i++) { const char *format = "%02X"; if (i + 1 < uid.size()) format = "%02X-"; @@ -22,7 +22,7 @@ std::string format_uid(std::vector &uid) { std::string format_bytes(std::vector &bytes) { char buf[(bytes.size() * 2) + bytes.size() - 1]; int offset = 0; - for (uint8_t i = 0; i < bytes.size(); i++) { + for (size_t i = 0; i < bytes.size(); i++) { const char *format = "%02X"; if (i + 1 < bytes.size()) format = "%02X "; diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index e49c108320..79edd91173 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -141,14 +141,14 @@ void OTAComponent::handle_() { if (!this->readall_(buf, 5)) { ESP_LOGW(TAG, "Reading magic bytes failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // 0x6C, 0x26, 0xF7, 0x5C, 0x45 if (buf[0] != 0x6C || buf[1] != 0x26 || buf[2] != 0xF7 || buf[3] != 0x5C || buf[4] != 0x45) { ESP_LOGW(TAG, "Magic bytes do not match! 0x%02X-0x%02X-0x%02X-0x%02X-0x%02X", buf[0], buf[1], buf[2], buf[3], buf[4]); error_code = OTA_RESPONSE_ERROR_MAGIC; - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // Send OK and version - 2 bytes @@ -161,7 +161,7 @@ void OTAComponent::handle_() { // Read features - 1 byte if (!this->readall_(buf, 1)) { ESP_LOGW(TAG, "Reading features failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } ota_features = buf[0]; // NOLINT ESP_LOGV(TAG, "OTA features is 0x%02X", ota_features); @@ -189,7 +189,7 @@ void OTAComponent::handle_() { // Send nonce, 32 bytes hex MD5 if (!this->writeall_(reinterpret_cast(sbuf), 32)) { ESP_LOGW(TAG, "Auth: Writing nonce failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // prepare challenge @@ -201,7 +201,7 @@ void OTAComponent::handle_() { // Receive cnonce, 32 bytes hex MD5 if (!this->readall_(buf, 32)) { ESP_LOGW(TAG, "Auth: Reading cnonce failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } sbuf[32] = '\0'; ESP_LOGV(TAG, "Auth: CNonce is %s", sbuf); @@ -216,7 +216,7 @@ void OTAComponent::handle_() { // Receive result, 32 bytes hex MD5 if (!this->readall_(buf + 64, 32)) { ESP_LOGW(TAG, "Auth: Reading response failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } sbuf[64 + 32] = '\0'; ESP_LOGV(TAG, "Auth: Response is %s", sbuf + 64); @@ -228,7 +228,7 @@ void OTAComponent::handle_() { if (!matches) { ESP_LOGW(TAG, "Auth failed! Passwords do not match!"); error_code = OTA_RESPONSE_ERROR_AUTH_INVALID; - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } } #endif // USE_OTA_PASSWORD @@ -240,7 +240,7 @@ void OTAComponent::handle_() { // Read size, 4 bytes MSB first if (!this->readall_(buf, 4)) { ESP_LOGW(TAG, "Reading size failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } ota_size = 0; for (uint8_t i = 0; i < 4; i++) { @@ -251,7 +251,7 @@ void OTAComponent::handle_() { error_code = backend->begin(ota_size); if (error_code != OTA_RESPONSE_OK) - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) update_started = true; // Acknowledge prepare OK - 1 byte @@ -261,7 +261,7 @@ void OTAComponent::handle_() { // Read binary MD5, 32 bytes if (!this->readall_(buf, 32)) { ESP_LOGW(TAG, "Reading binary MD5 checksum failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } sbuf[32] = '\0'; ESP_LOGV(TAG, "Update: Binary MD5 is %s", sbuf); @@ -281,19 +281,19 @@ void OTAComponent::handle_() { continue; } ESP_LOGW(TAG, "Error receiving data for update, errno: %d", errno); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } else if (read == 0) { // $ man recv // "When a stream socket peer has performed an orderly shutdown, the return value will // be 0 (the traditional "end-of-file" return)." ESP_LOGW(TAG, "Remote end closed connection"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } error_code = backend->write(buf, read); if (error_code != OTA_RESPONSE_OK) { ESP_LOGW(TAG, "Error writing binary data to flash!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } total += read; @@ -317,7 +317,7 @@ void OTAComponent::handle_() { error_code = backend->end(); if (error_code != OTA_RESPONSE_OK) { ESP_LOGW(TAG, "Error ending OTA!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // Acknowledge Update end OK - 1 byte diff --git a/esphome/components/pid/pid_autotuner.cpp b/esphome/components/pid/pid_autotuner.cpp index 15c1c5f076..fc012aaa39 100644 --- a/esphome/components/pid/pid_autotuner.cpp +++ b/esphome/components/pid/pid_autotuner.cpp @@ -330,7 +330,7 @@ bool PIDAutotuner::OscillationAmplitudeDetector::has_enough_data() const { float PIDAutotuner::OscillationAmplitudeDetector::get_mean_oscillation_amplitude() const { float total_amplitudes = 0; size_t total_amplitudes_n = 0; - for (int i = 1; i < std::min(phase_mins.size(), phase_maxs.size()) - 1; i++) { + for (size_t i = 1; i < std::min(phase_mins.size(), phase_maxs.size()) - 1; i++) { total_amplitudes += std::abs(phase_maxs[i] - phase_mins[i + 1]); total_amplitudes_n++; } diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index 9f8b57003a..13a08bbd16 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -413,8 +413,6 @@ void Pipsolar::loop() { this->state_ = STATE_IDLE; break; case POLLING_QT: - this->state_ = STATE_IDLE; - break; case POLLING_QMN: this->state_ = STATE_IDLE; break; @@ -481,7 +479,7 @@ void Pipsolar::loop() { ESP_LOGD(TAG, "Decode QFLAG"); // result like:"(EbkuvxzDajy" // get through all char: ignore first "(" Enable flag on 'E', Disable on 'D') else set the corresponding value - for (int i = 1; i < strlen(tmp); i++) { + for (size_t i = 1; i < strlen(tmp); i++) { switch (tmp[i]) { case 'E': enabled = true; @@ -530,7 +528,7 @@ void Pipsolar::loop() { this->value_warnings_present_ = false; this->value_faults_present_ = true; - for (int i = 1; i < strlen(tmp); i++) { + for (size_t i = 1; i < strlen(tmp); i++) { enabled = tmp[i] == '1'; switch (i) { case 1: diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index ed2a2c1e35..0c46ff8a57 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -145,7 +145,7 @@ void PN532::loop() { if (nfcid.size() == this->current_uid_.size()) { bool same_uid = false; - for (uint8_t i = 0; i < nfcid.size(); i++) + for (size_t i = 0; i < nfcid.size(); i++) same_uid |= nfcid[i] == this->current_uid_[i]; if (same_uid) return; @@ -367,7 +367,7 @@ bool PN532BinarySensor::process(std::vector &data) { if (data.size() != this->uid_.size()) return false; - for (uint8_t i = 0; i < data.size(); i++) { + for (size_t i = 0; i < data.size(); i++) { if (data[i] != this->uid_[i]) return false; } diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.cpp b/esphome/components/pulse_meter/pulse_meter_sensor.cpp index fd1403b4fd..7d526b241b 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.cpp +++ b/esphome/components/pulse_meter/pulse_meter_sensor.cpp @@ -35,7 +35,7 @@ void PulseMeterSensor::loop() { this->publish_state(0); } else { // Calculate pulses/min from the pulse width in ms - this->publish_state((60.0 * 1000.0) / pulse_width_ms); + this->publish_state((60.0f * 1000.0f) / pulse_width_ms); } } diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp index 385641fea0..d203b3ce8f 100644 --- a/esphome/components/rc522/rc522.cpp +++ b/esphome/components/rc522/rc522.cpp @@ -28,7 +28,7 @@ std::string format_buffer(uint8_t *b, uint8_t len) { std::string format_uid(std::vector &uid) { char buf[32]; int offset = 0; - for (uint8_t i = 0; i < uid.size(); i++) { + for (size_t i = 0; i < uid.size(); i++) { const char *format = "%02X"; if (i + 1 < uid.size()) format = "%02X-"; @@ -479,7 +479,7 @@ bool RC522BinarySensor::process(std::vector &data) { if (data.size() != this->uid_.size()) result = false; else { - for (uint8_t i = 0; i < data.size(); i++) { + for (size_t i = 0; i < data.size(); i++) { if (data[i] != this->uid_[i]) { result = false; break; diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp index 11aebb6c5d..4f6ace720c 100644 --- a/esphome/components/remote_base/pronto_protocol.cpp +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -113,7 +113,7 @@ void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &st const char *p = str.c_str(); char *endptr[1]; - for (uint16_t i = 0; i < len; i++) { + for (size_t i = 0; i < len; i++) { uint16_t x = strtol(p, endptr, 16); if (x == 0 && i >= NUMBERS_IN_PREAMBLE) { // Alignment error?, bail immediately (often right result). diff --git a/esphome/components/remote_base/remote_base.cpp b/esphome/components/remote_base/remote_base.cpp index a853c9849e..97ee027b84 100644 --- a/esphome/components/remote_base/remote_base.cpp +++ b/esphome/components/remote_base/remote_base.cpp @@ -33,7 +33,7 @@ void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) { uint32_t buffer_offset = 0; buffer_offset += sprintf(buffer, "Sending times=%u wait=%ums: ", send_times, send_wait); - for (int32_t i = 0; i < vec.size(); i++) { + for (size_t i = 0; i < vec.size(); i++) { const int32_t value = vec[i]; const uint32_t remaining_length = sizeof(buffer) - buffer_offset; int written; diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index c3b61b72c2..368b21f892 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -113,7 +113,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen this->rmt_temp_.push_back(rmt_item); } - for (uint16_t i = 0; i < send_times; i++) { + for (uint32_t i = 0; i < send_times; i++) { esp_err_t error = rmt_write_items(this->channel_, this->rmt_temp_.data(), this->rmt_temp_.size(), true); if (error != ESP_OK) { ESP_LOGW(TAG, "rmt_write_items failed: %s", esp_err_to_name(error)); diff --git a/esphome/components/rtttl/rtttl.cpp b/esphome/components/rtttl/rtttl.cpp index d571c2f287..c76d4a89b0 100644 --- a/esphome/components/rtttl/rtttl.cpp +++ b/esphome/components/rtttl/rtttl.cpp @@ -159,7 +159,7 @@ void Rtttl::loop() { // Now play the note if (note) { auto note_index = (scale - 4) * 12 + note; - if (note_index < 0 || note_index >= sizeof(NOTES)) { + if (note_index < 0 || note_index >= (int) sizeof(NOTES)) { ESP_LOGE(TAG, "Note out of valid range"); return; } diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 87cf0fa61a..4157fd55cf 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -147,8 +147,8 @@ void SGP30Component::read_iaq_baseline_() { // much if (this->store_baseline_ && (this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL || - abs(this->baselines_storage_.eco2 - this->eco2_baseline_) > MAXIMUM_STORAGE_DIFF || - abs(this->baselines_storage_.tvoc - this->tvoc_baseline_) > MAXIMUM_STORAGE_DIFF)) { + (uint32_t) abs(this->baselines_storage_.eco2 - this->eco2_baseline_) > MAXIMUM_STORAGE_DIFF || + (uint32_t) abs(this->baselines_storage_.tvoc - this->tvoc_baseline_) > MAXIMUM_STORAGE_DIFF)) { this->seconds_since_last_store_ = 0; this->baselines_storage_.eco2 = this->eco2_baseline_; this->baselines_storage_.tvoc = this->tvoc_baseline_; diff --git a/esphome/components/sgp40/sensirion_voc_algorithm.cpp b/esphome/components/sgp40/sensirion_voc_algorithm.cpp index f3cdeee35b..d76b776641 100644 --- a/esphome/components/sgp40/sensirion_voc_algorithm.cpp +++ b/esphome/components/sgp40/sensirion_voc_algorithm.cpp @@ -149,7 +149,7 @@ static fix16_t fix16_div(fix16_t a, fix16_t b) { /* Figure out the sign of result */ if ((a ^ b) & 0x80000000) { #ifndef FIXMATH_NO_OVERFLOW - if (result == FIX16_MINIMUM) + if (result == FIX16_MINIMUM) // NOLINT(clang-diagnostic-sign-compare) return FIX16_OVERFLOW; #endif diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index a3d2c74eb7..9561efcde2 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -144,8 +144,8 @@ int32_t SGP40Component::measure_voc_index_() { // much if (this->store_baseline_ && this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL) { voc_algorithm_get_states(&voc_algorithm_params_, &this->state0_, &this->state1_); - if (abs(this->baselines_storage_.state0 - this->state0_) > MAXIMUM_STORAGE_DIFF || - abs(this->baselines_storage_.state1 - this->state1_) > MAXIMUM_STORAGE_DIFF) { + if ((uint32_t) abs(this->baselines_storage_.state0 - this->state0_) > MAXIMUM_STORAGE_DIFF || + (uint32_t) abs(this->baselines_storage_.state1 - this->state1_) > MAXIMUM_STORAGE_DIFF) { this->seconds_since_last_store_ = 0; this->baselines_storage_.state0 = this->state0_; this->baselines_storage_.state1 = this->state1_; diff --git a/esphome/components/sgp40/sgp40.h b/esphome/components/sgp40/sgp40.h index bb68a1ffcf..c854b21060 100644 --- a/esphome/components/sgp40/sgp40.h +++ b/esphome/components/sgp40/sgp40.h @@ -66,7 +66,7 @@ class SGP40Component : public PollingComponent, public sensor::Sensor, public i2 uint8_t generate_crc_(const uint8_t *data, uint8_t datalen); uint16_t measure_raw_(); ESPPreferenceObject pref_; - int32_t seconds_since_last_store_; + uint32_t seconds_since_last_store_; SGP40Baselines baselines_storage_; VocAlgorithmParams voc_algorithm_params_; bool self_test_complete_; diff --git a/esphome/components/sm300d2/sm300d2.cpp b/esphome/components/sm300d2/sm300d2.cpp index e41a4855db..c726faec48 100644 --- a/esphome/components/sm300d2/sm300d2.cpp +++ b/esphome/components/sm300d2/sm300d2.cpp @@ -50,10 +50,10 @@ void SM300D2Sensor::update() { const uint16_t pm_2_5 = (response[8] * 256) + response[9]; const uint16_t pm_10_0 = (response[10] * 256) + response[11]; // A negative value is indicated by adding 0x80 (128) to the temperature value - const float temperature = ((response[12] + (response[13] * 0.1)) > 128) - ? (((response[12] + (response[13] * 0.1)) - 128) * -1) - : response[12] + (response[13] * 0.1); - const float humidity = response[14] + (response[15] * 0.1); + const float temperature = ((response[12] + (response[13] * 0.1f)) > 128) + ? (((response[12] + (response[13] * 0.1f)) - 128) * -1) + : response[12] + (response[13] * 0.1f); + const float humidity = response[14] + (response[15] * 0.1f); ESP_LOGD(TAG, "Received CO₂: %u ppm", co2); if (this->co2_sensor_ != nullptr) diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 922d895ff4..d57413c739 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -383,7 +383,7 @@ class LWIPRawImpl : public Socket { return err; } ret += err; - if (err != iov[i].iov_len) + if ((size_t) err != iov[i].iov_len) break; } return ret; @@ -462,7 +462,7 @@ class LWIPRawImpl : public Socket { return err; } written += err; - if (err != iov[i].iov_len) + if ((size_t) err != iov[i].iov_len) break; } if (written == 0) diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index d883142c81..d427e2c91b 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -55,13 +55,9 @@ void SPIComponent::setup() { } } #ifdef USE_ESP8266 - if (clk_pin == 6 && miso_pin == 7 && mosi_pin == 8) { - // pass - } else if (clk_pin == 14 && (!has_miso || miso_pin == 12) && (!has_mosi || mosi_pin == 13)) { - // pass - } else { + if (!(clk_pin == 6 && miso_pin == 7 && mosi_pin == 8) && + !(clk_pin == 14 && (!has_miso || miso_pin == 12) && (!has_mosi || mosi_pin == 13))) use_hw_spi = false; - } if (use_hw_spi) { this->hw_spi_ = &SPI; diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index 2537133605..4b9feb10ce 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -292,7 +292,7 @@ const char *SSD1306::model_str_() { case SSD1305_MODEL_128_32: return "SSD1305 128x32"; case SSD1305_MODEL_128_64: - return "SSD1305 128x32"; + return "SSD1305 128x64"; default: return "Unknown"; } diff --git a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp index fddea25fc8..64b09c0672 100644 --- a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp +++ b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp @@ -40,12 +40,12 @@ void I2CSSD1306::command(uint8_t value) { this->write_byte(0x00, value); } void HOT I2CSSD1306::write_display_data() { if (this->is_sh1106_()) { uint32_t i = 0; - for (uint8_t page = 0; page < this->get_height_internal() / 8; page++) { + for (uint8_t page = 0; page < (uint8_t) this->get_height_internal() / 8; page++) { this->command(0xB0 + page); // row this->command(0x02); // lower column this->command(0x10); // higher column - for (uint8_t x = 0; x < this->get_width_internal() / 16; x++) { + for (uint8_t x = 0; x < (uint8_t) this->get_width_internal() / 16; x++) { uint8_t data[16]; for (uint8_t &j : data) j = this->buffer_[i++]; diff --git a/esphome/components/ssd1306_spi/ssd1306_spi.cpp b/esphome/components/ssd1306_spi/ssd1306_spi.cpp index 33d474a8ee..7f025d77cd 100644 --- a/esphome/components/ssd1306_spi/ssd1306_spi.cpp +++ b/esphome/components/ssd1306_spi/ssd1306_spi.cpp @@ -37,12 +37,12 @@ void SPISSD1306::command(uint8_t value) { } void HOT SPISSD1306::write_display_data() { if (this->is_sh1106_()) { - for (uint8_t y = 0; y < this->get_height_internal() / 8; y++) { + for (uint8_t y = 0; y < (uint8_t) this->get_height_internal() / 8; y++) { this->command(0xB0 + y); this->command(0x02); this->command(0x10); this->dc_pin_->digital_write(true); - for (uint8_t x = 0; x < this->get_width_internal(); x++) { + for (uint8_t x = 0; x < (uint8_t) this->get_width_internal(); x++) { this->enable(); this->write_byte(this->buffer_[x + y * this->get_width_internal()]); this->disable(); diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index 8490aa1fe4..c5178986f3 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -275,7 +275,7 @@ void ST7735::setup() { uint8_t data = 0; if (this->model_ != INITR_HALLOWING) { - uint8_t data = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY; + data = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY; } if (this->usebgr_) { data = data | ST7735_MADCTL_BGR; @@ -446,7 +446,7 @@ void HOT ST7735::write_display_data_() { this->dc_pin_->digital_write(true); if (this->eightbitcolor_) { - for (int line = 0; line < this->get_buffer_length(); line = line + this->get_width_internal()) { + for (size_t line = 0; line < this->get_buffer_length(); line = line + this->get_width_internal()) { for (int index = 0; index < this->get_width_internal(); ++index) { auto color332 = display::ColorUtil::to_color(this->buffer_[index + line], display::ColorOrder::COLOR_ORDER_RGB, display::ColorBitness::COLOR_BITNESS_332, true); diff --git a/esphome/components/st7920/st7920.cpp b/esphome/components/st7920/st7920.cpp index d985b0a426..63fa0ba72f 100644 --- a/esphome/components/st7920/st7920.cpp +++ b/esphome/components/st7920/st7920.cpp @@ -74,7 +74,7 @@ void ST7920::goto_xy_(uint16_t x, uint16_t y) { void HOT ST7920::write_display_data() { uint8_t i, j, b; - for (j = 0; j < this->get_height_internal() / 2; j++) { + for (j = 0; j < (uint8_t)(this->get_height_internal() / 2); j++) { this->goto_xy_(0, j); this->enable(); for (i = 0; i < 16; i++) { // 16 bytes from line #0+ diff --git a/esphome/components/teleinfo/teleinfo.cpp b/esphome/components/teleinfo/teleinfo.cpp index 5a1e44ac8b..d9f80134f4 100644 --- a/esphome/components/teleinfo/teleinfo.cpp +++ b/esphome/components/teleinfo/teleinfo.cpp @@ -118,7 +118,7 @@ void TeleInfo::loop() { * */ while ((buf_finger = static_cast(memchr(buf_finger, (int) 0xa, buf_index_ - 1))) && - ((buf_finger - buf_) < buf_index_)) { + ((buf_finger - buf_) < buf_index_)) { // NOLINT(clang-diagnostic-sign-compare) /* * Make sure timesamp is nullified between each tag as some tags don't * have a timestamp diff --git a/esphome/components/text_sensor/filter.cpp b/esphome/components/text_sensor/filter.cpp index a692378e8b..c6cbcd7c1e 100644 --- a/esphome/components/text_sensor/filter.cpp +++ b/esphome/components/text_sensor/filter.cpp @@ -64,7 +64,7 @@ optional PrependFilter::new_value(std::string value) { return this- // Substitute optional SubstituteFilter::new_value(std::string value) { std::size_t pos; - for (int i = 0; i < this->from_strings_.size(); i++) + for (size_t i = 0; i < this->from_strings_.size(); i++) while ((pos = value.find(this->from_strings_[i])) != std::string::npos) value.replace(pos, this->from_strings_[i].size(), this->to_strings_[i]); return value; diff --git a/esphome/components/tof10120/tof10120_sensor.cpp b/esphome/components/tof10120/tof10120_sensor.cpp index 4ba591f9c4..5cd086938e 100644 --- a/esphome/components/tof10120/tof10120_sensor.cpp +++ b/esphome/components/tof10120/tof10120_sensor.cpp @@ -50,7 +50,7 @@ void TOF10120Sensor::update() { ESP_LOGW(TAG, "Distance measurement out of range"); this->publish_state(NAN); } else { - this->publish_state(distance_mm / 1000.0); + this->publish_state(distance_mm / 1000.0f); } this->status_clear_warning(); } diff --git a/esphome/components/toshiba/toshiba.cpp b/esphome/components/toshiba/toshiba.cpp index 25528abbe1..975a149b52 100644 --- a/esphome/components/toshiba/toshiba.cpp +++ b/esphome/components/toshiba/toshiba.cpp @@ -580,13 +580,13 @@ bool ToshibaClimate::on_receive(remote_base::RemoteReceiveData data) { temperature_code = (message[4] >> 4) | (message[14] & RAC_PT1411HWRU_FLAG_FRAC) | (message[15] & RAC_PT1411HWRU_FLAG_NEG); if (message[15] & RAC_PT1411HWRU_FLAG_FAH) { - for (uint8_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_F.size(); i++) { + for (size_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_F.size(); i++) { if (RAC_PT1411HWRU_TEMPERATURE_F[i] == temperature_code) { this->target_temperature = static_cast((i + TOSHIBA_RAC_PT1411HWRU_TEMP_F_MIN - 32) * 5) / 9; } } } else { - for (uint8_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_C.size(); i++) { + for (size_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_C.size(); i++) { if (RAC_PT1411HWRU_TEMPERATURE_C[i] == temperature_code) { this->target_temperature = i + TOSHIBA_RAC_PT1411HWRU_TEMP_C_MIN; } diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 4f65fa7118..2677731224 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -143,7 +143,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff case TuyaCommandType::PRODUCT_QUERY: { // check it is a valid string made up of printable characters bool valid = true; - for (int i = 0; i < len; i++) { + for (size_t i = 0; i < len; i++) { if (!std::isprint(buffer[i])) { valid = false; break; @@ -339,8 +339,6 @@ void Tuya::send_raw_command_(TuyaCommand command) { this->expected_response_ = TuyaCommandType::CONF_QUERY; break; case TuyaCommandType::DATAPOINT_DELIVER: - this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT; - break; case TuyaCommandType::DATAPOINT_QUERY: this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT; break; diff --git a/esphome/components/tx20/tx20.cpp b/esphome/components/tx20/tx20.cpp index 6e0b6343d1..fefcc8f4d5 100644 --- a/esphome/components/tx20/tx20.cpp +++ b/esphome/components/tx20/tx20.cpp @@ -113,7 +113,7 @@ void Tx20Component::decode_and_publish_() { if (tx20_sa == 4) { if (chk == tx20_sd) { if (tx20_sf == tx20_sc) { - tx20_wind_speed_kmh = float(tx20_sc) * 0.36; + tx20_wind_speed_kmh = float(tx20_sc) * 0.36f; ESP_LOGV(TAG, "WindSpeed %f", tx20_wind_speed_kmh); if (this->wind_speed_sensor_ != nullptr) this->wind_speed_sensor_->publish_state(tx20_wind_speed_kmh); diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index 408c83a0db..370adad779 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -214,9 +214,7 @@ void IRAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) { /* If parity is enabled, just read it and ignore it. */ /* TODO: Should we check parity? Or is it too slow for nothing added..*/ - if (arg->parity_ == UART_CONFIG_PARITY_EVEN) - arg->read_bit_(&wait, start); - else if (arg->parity_ == UART_CONFIG_PARITY_ODD) + if (arg->parity_ == UART_CONFIG_PARITY_EVEN || arg->parity_ == UART_CONFIG_PARITY_ODD) arg->read_bit_(&wait, start); // Stop bit diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index ee3fb2fe47..322c375f0e 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -360,15 +360,18 @@ void HOT WaveshareEPaperTypeA::display() { // COMMAND DISPLAY UPDATE CONTROL 2 this->command(0x22); - if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2 || this->model_ == WAVESHARE_EPAPER_1_54_IN_V2) { - this->data(full_update ? 0xF7 : 0xFF); - } else if (this->model_ == TTGO_EPAPER_2_13_IN_B73) { - this->data(0xC7); - } else if (this->model_ == TTGO_EPAPER_2_13_IN_B74) { - // this->data(0xC7); - this->data(full_update ? 0xF7 : 0xFF); - } else { - this->data(0xC4); + switch (this->model_) { + case WAVESHARE_EPAPER_2_9_IN_V2: + case WAVESHARE_EPAPER_1_54_IN_V2: + case TTGO_EPAPER_2_13_IN_B74: + this->data(full_update ? 0xF7 : 0xFF); + break; + case TTGO_EPAPER_2_13_IN_B73: + this->data(0xC7); + break; + default: + this->data(0xC4); + break; } // COMMAND MASTER ACTIVATION @@ -381,20 +384,14 @@ void HOT WaveshareEPaperTypeA::display() { int WaveshareEPaperTypeA::get_width_internal() { switch (this->model_) { case WAVESHARE_EPAPER_1_54_IN: - return 200; case WAVESHARE_EPAPER_1_54_IN_V2: return 200; case WAVESHARE_EPAPER_2_13_IN: - return 128; case TTGO_EPAPER_2_13_IN: - return 128; case TTGO_EPAPER_2_13_IN_B73: case TTGO_EPAPER_2_13_IN_B74: - return 128; case TTGO_EPAPER_2_13_IN_B1: - return 128; case WAVESHARE_EPAPER_2_9_IN: - return 128; case WAVESHARE_EPAPER_2_9_IN_V2: return 128; } @@ -403,20 +400,15 @@ int WaveshareEPaperTypeA::get_width_internal() { int WaveshareEPaperTypeA::get_height_internal() { switch (this->model_) { case WAVESHARE_EPAPER_1_54_IN: - return 200; case WAVESHARE_EPAPER_1_54_IN_V2: return 200; case WAVESHARE_EPAPER_2_13_IN: - return 250; case TTGO_EPAPER_2_13_IN: - return 250; case TTGO_EPAPER_2_13_IN_B73: case TTGO_EPAPER_2_13_IN_B74: - return 250; case TTGO_EPAPER_2_13_IN_B1: return 250; case WAVESHARE_EPAPER_2_9_IN: - return 296; case WAVESHARE_EPAPER_2_9_IN_V2: return 296; } @@ -433,11 +425,10 @@ void WaveshareEPaperTypeA::set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; } -int WaveshareEPaperTypeA::idle_timeout_() { +uint32_t WaveshareEPaperTypeA::idle_timeout_() { switch (this->model_) { case TTGO_EPAPER_2_13_IN_B1: return 2500; - break; default: return WaveshareEPaper::idle_timeout_(); } @@ -646,7 +637,7 @@ void HOT WaveshareEPaper2P9InB::display() { this->command(0x13); delay(2); this->start_data_(); - for (int i = 0; i < this->get_buffer_length_(); i++) + for (size_t i = 0; i < this->get_buffer_length_(); i++) this->write_byte(0x00); this->end_data_(); delay(2); @@ -825,7 +816,7 @@ void HOT WaveshareEPaper4P2InBV2::display() { // COMMAND DATA START TRANSMISSION 2 (RED data) this->command(0x13); this->start_data_(); - for (int i = 0; i < this->get_buffer_length_(); i++) + for (size_t i = 0; i < this->get_buffer_length_(); i++) this->write_byte(0xFF); this->end_data_(); delay(2); @@ -1293,7 +1284,7 @@ void HOT WaveshareEPaper2P13InDKE::display() { int WaveshareEPaper2P13InDKE::get_width_internal() { return 128; } int WaveshareEPaper2P13InDKE::get_height_internal() { return 250; } -int WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; } +uint32_t WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; } void WaveshareEPaper2P13InDKE::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this); ESP_LOGCONFIG(TAG, " Model: 2.13inDKE"); diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index b50596643d..a1e2f6037a 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -61,7 +61,7 @@ class WaveshareEPaper : public PollingComponent, GPIOPin *reset_pin_{nullptr}; GPIOPin *dc_pin_; GPIOPin *busy_pin_{nullptr}; - virtual int idle_timeout_() { return 1000; } // NOLINT(readability-identifier-naming) + virtual uint32_t idle_timeout_() { return 1000u; } // NOLINT(readability-identifier-naming) }; enum WaveshareEPaperTypeAModel { @@ -110,7 +110,7 @@ class WaveshareEPaperTypeA : public WaveshareEPaper { uint32_t full_update_every_{30}; uint32_t at_update_{0}; WaveshareEPaperTypeAModel model_; - int idle_timeout_() override; + uint32_t idle_timeout_() override; }; enum WaveshareEPaperTypeBModel { @@ -346,7 +346,7 @@ class WaveshareEPaper2P13InDKE : public WaveshareEPaper { int get_height_internal() override; - int idle_timeout_() override; + uint32_t idle_timeout_() override; uint32_t full_update_every_{30}; uint32_t at_update_{0}; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 1d346c0a8e..d2217eb88a 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -67,9 +67,9 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi memset(&event, 0, sizeof(IDFWiFiEvent)); event.event_base = event_base; event.event_id = event_id; - if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { // NOLINT(bugprone-branch-clone) // no data - } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_STOP) { + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_STOP) { // NOLINT(bugprone-branch-clone) // no data } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_AUTHMODE_CHANGE) { memcpy(&event.data.sta_authmode_change, event_data, sizeof(wifi_event_sta_authmode_change_t)); @@ -79,13 +79,13 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi memcpy(&event.data.sta_disconnected, event_data, sizeof(wifi_event_sta_disconnected_t)); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { memcpy(&event.data.ip_got_ip, event_data, sizeof(ip_event_got_ip_t)); - } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP) { + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP) { // NOLINT(bugprone-branch-clone) // no data } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) { memcpy(&event.data.sta_scan_done, event_data, sizeof(wifi_event_sta_scan_done_t)); - } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_START) { + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_START) { // NOLINT(bugprone-branch-clone) // no data - } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STOP) { + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STOP) { // NOLINT(bugprone-branch-clone) // no data } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_PROBEREQRECVED) { memcpy(&event.data.ap_probe_req_rx, event_data, sizeof(wifi_event_ap_probe_req_rx_t)); diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 884969f793..7588198c70 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -171,10 +171,8 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service result.type = XiaomiParseResult::TYPE_MUE4094RT; result.name = "MUE4094RT"; result.raw_offset -= 6; - } else if ((raw[2] == 0x47) && (raw[3] == 0x03)) { // ClearGrass-branded, round body, e-ink display - result.type = XiaomiParseResult::TYPE_CGG1; - result.name = "CGG1"; - } else if ((raw[2] == 0x48) && (raw[3] == 0x0B)) { // Qingping-branded, round body, e-ink display — with bindkeys + } else if ((raw[2] == 0x47 && raw[3] == 0x03) || // ClearGrass-branded, round body, e-ink display + (raw[2] == 0x48 && raw[3] == 0x0B)) { // Qingping-branded, round body, e-ink display — with bindkeys result.type = XiaomiParseResult::TYPE_CGG1; result.name = "CGG1"; } else if ((raw[2] == 0xbc) && (raw[3] == 0x03)) { // VegTrug Grow Care Garden diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index f67fc826cf..1bef99e868 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -94,7 +94,7 @@ void Application::loop() { } this->last_loop_ = now; - if (this->dump_config_at_ >= 0 && this->dump_config_at_ < this->components_.size()) { + if (this->dump_config_at_ < this->components_.size()) { if (this->dump_config_at_ == 0) { ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", this->compilation_time_.c_str()); #ifdef ESPHOME_PROJECT_NAME diff --git a/esphome/core/application.h b/esphome/core/application.h index ace0c9ad6d..f4fe571490 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -309,7 +309,7 @@ class Application { bool name_add_mac_suffix_; uint32_t last_loop_{0}; uint32_t loop_interval_{16}; - int dump_config_at_{-1}; + size_t dump_config_at_{SIZE_MAX}; uint32_t app_state_{0}; }; diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index f97818cfb1..591c9943b5 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -96,7 +96,7 @@ void Component::call() { // State loop: Call loop this->call_loop(); break; - case COMPONENT_STATE_FAILED: + case COMPONENT_STATE_FAILED: // NOLINT(bugprone-branch-clone) // State failed: Do nothing break; default: diff --git a/platformio.ini b/platformio.ini index 0f80d6d8d3..3c0b725d65 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,7 +11,6 @@ include_dir = [runtime] ; This are the flags as set by the runtime. build_flags = - -Wno-unused-variable -Wno-unused-but-set-variable -Wno-sign-compare @@ -19,10 +18,12 @@ build_flags = ; This are the flags for clang-tidy. build_flags = -Wall + -Wextra -Wunreachable-code -Wfor-loop-analysis -Wshadow-field -Wshadow-field-in-constructor + -Wshadow-uncaptured-local [common] lib_deps = diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 7ccdc5a24e..016a0995b9 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -649,7 +649,7 @@ def build_message_type(desc): o += f" {dump[0]} " else: o += "\n" - o += f" char buffer[64];\n" + o += f" __attribute__((unused)) char buffer[64];\n" o += f' out.append("{desc.name} {{\\n");\n' o += indent("\n".join(dump)) + "\n" o += f' out.append("}}");\n' From 54106179a11ec9ff4586f73f8246507165ba9390 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 21:05:42 +0100 Subject: [PATCH 106/111] Set ESP32 watchdog to loop task (#2846) --- esphome/components/esp32/__init__.py | 7 ++++++ esphome/components/esp32/core.cpp | 35 ++++++++++++++++------------ esphome/components/esp8266/core.cpp | 1 + esphome/core/application.h | 2 ++ esphome/core/hal.h | 1 + sdkconfig.defaults | 4 ++++ 6 files changed, 35 insertions(+), 15 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 1c249476e7..d6f1180aa7 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -311,9 +311,16 @@ async def to_code(config): ) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True) + # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000) + # Setup watchdog + add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True) + add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True) + add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0", False) + add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1", False) + cg.add_platformio_option("board_build.partitions", "partitions.csv") for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 359999120f..a9756b41cd 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -6,12 +6,17 @@ #include #include #include +#include #include #if ESP_IDF_VERSION_MAJOR >= 4 #include #endif +#ifdef USE_ARDUINO +#include +#endif + void setup(); void loop(); @@ -29,24 +34,24 @@ void arch_restart() { yield(); } } -void IRAM_ATTR HOT arch_feed_wdt() { -#ifdef USE_ARDUINO -#if CONFIG_ARDUINO_RUNNING_CORE == 0 -#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 - // ESP32 uses "Task Watchdog" which is hooked to the FreeRTOS idle task. - // To cause the Watchdog to be triggered we need to put the current task - // to sleep to get the idle task scheduled. - delay(1); -#endif -#endif -#endif // USE_ARDUINO -#ifdef USE_ESP_IDF -#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 - delay(1); +void arch_init() { + // Enable the task watchdog only on the loop task (from which we're currently running) +#if defined(USE_ESP_IDF) + esp_task_wdt_add(nullptr); + // Idle task watchdog is disabled on ESP-IDF +#elif defined(USE_ARDUINO) + enableLoopWDT(); + // Disable idle task watchdog on the core we're using (Arduino pins the process to a core) +#if CONFIG_ARDUINO_RUNNING_CORE == 0 + disableCore0WDT(); +#endif +#if CONFIG_ARDUINO_RUNNING_CORE == 1 + disableCore1WDT(); +#endif #endif -#endif // USE_ESP_IDF } +void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); } uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } uint32_t arch_get_cpu_cycle_count() { diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp index 137d4382b4..828d71a3bd 100644 --- a/esphome/components/esp8266/core.cpp +++ b/esphome/components/esp8266/core.cpp @@ -20,6 +20,7 @@ void arch_restart() { yield(); } } +void arch_init() {} void IRAM_ATTR HOT arch_feed_wdt() { ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance) } diff --git a/esphome/core/application.h b/esphome/core/application.h index f4fe571490..2a20793c19 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -5,6 +5,7 @@ #include "esphome/core/defines.h" #include "esphome/core/preferences.h" #include "esphome/core/component.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/scheduler.h" @@ -47,6 +48,7 @@ namespace esphome { class Application { public: void pre_setup(const std::string &name, const char *compilation_time, bool name_add_mac_suffix) { + arch_init(); this->name_add_mac_suffix_ = name_add_mac_suffix; if (name_add_mac_suffix) { this->name_ = name + "-" + get_mac_address().substr(6); diff --git a/esphome/core/hal.h b/esphome/core/hal.h index a86dbf2534..034f9d692f 100644 --- a/esphome/core/hal.h +++ b/esphome/core/hal.h @@ -39,6 +39,7 @@ uint32_t micros(); void delay(uint32_t ms); void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming) void __attribute__((noreturn)) arch_restart(); +void arch_init(); void arch_feed_wdt(); uint32_t arch_get_cpu_cycle_count(); uint32_t arch_get_cpu_freq_hz(); diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 26db4705b8..72ca3f6e9c 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -9,6 +9,10 @@ CONFIG_PARTITION_TABLE_CUSTOM=y #CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_SINGLE_APP=n CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT=y +CONFIG_ESP_TASK_WDT_PANIC=y +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n # esp32_ble CONFIG_BT_ENABLED=y From caf352ff066275c0952b7dbf96a7b73ef9e6b5b7 Mon Sep 17 00:00:00 2001 From: Paul Nicholls Date: Thu, 2 Dec 2021 15:26:56 +1300 Subject: [PATCH 107/111] Tuya Cover improvements (#2637) --- esphome/components/tuya/cover/__init__.py | 24 +++++ esphome/components/tuya/cover/tuya_cover.cpp | 108 ++++++++++++++++--- esphome/components/tuya/cover/tuya_cover.h | 17 ++- esphome/components/tuya/tuya.cpp | 103 ++++++++++++------ esphome/components/tuya/tuya.h | 16 ++- 5 files changed, 215 insertions(+), 53 deletions(-) diff --git a/esphome/components/tuya/cover/__init__.py b/esphome/components/tuya/cover/__init__.py index 5a654841f7..f886c7030f 100644 --- a/esphome/components/tuya/cover/__init__.py +++ b/esphome/components/tuya/cover/__init__.py @@ -5,16 +5,27 @@ from esphome.const import ( CONF_OUTPUT_ID, CONF_MIN_VALUE, CONF_MAX_VALUE, + CONF_RESTORE_MODE, ) from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] +CONF_CONTROL_DATAPOINT = "control_datapoint" +CONF_DIRECTION_DATAPOINT = "direction_datapoint" CONF_POSITION_DATAPOINT = "position_datapoint" +CONF_POSITION_REPORT_DATAPOINT = "position_report_datapoint" CONF_INVERT_POSITION = "invert_position" TuyaCover = tuya_ns.class_("TuyaCover", cover.Cover, cg.Component) +TuyaCoverRestoreMode = tuya_ns.enum("TuyaCoverRestoreMode") +RESTORE_MODES = { + "NO_RESTORE": TuyaCoverRestoreMode.COVER_NO_RESTORE, + "RESTORE": TuyaCoverRestoreMode.COVER_RESTORE, + "RESTORE_AND_CALL": TuyaCoverRestoreMode.COVER_RESTORE_AND_CALL, +} + def validate_range(config): if config[CONF_MIN_VALUE] > config[CONF_MAX_VALUE]: @@ -29,10 +40,16 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaCover), cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Optional(CONF_CONTROL_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_DIRECTION_DATAPOINT): cv.uint8_t, cv.Required(CONF_POSITION_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_POSITION_REPORT_DATAPOINT): cv.uint8_t, cv.Optional(CONF_MIN_VALUE, default=0): cv.int_, cv.Optional(CONF_MAX_VALUE, default=100): cv.int_, cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, + cv.Optional(CONF_RESTORE_MODE, default="RESTORE"): cv.enum( + RESTORE_MODES, upper=True + ), }, ).extend(cv.COMPONENT_SCHEMA), validate_range, @@ -44,9 +61,16 @@ async def to_code(config): await cg.register_component(var, config) await cover.register_cover(var, config) + if CONF_CONTROL_DATAPOINT in config: + cg.add(var.set_control_id(config[CONF_CONTROL_DATAPOINT])) + if CONF_DIRECTION_DATAPOINT in config: + cg.add(var.set_direction_id(config[CONF_DIRECTION_DATAPOINT])) cg.add(var.set_position_id(config[CONF_POSITION_DATAPOINT])) + if CONF_POSITION_REPORT_DATAPOINT in config: + cg.add(var.set_position_report_id(config[CONF_POSITION_REPORT_DATAPOINT])) cg.add(var.set_min_value(config[CONF_MIN_VALUE])) cg.add(var.set_max_value(config[CONF_MAX_VALUE])) cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) + cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) paren = await cg.get_variable(config[CONF_TUYA_ID]) cg.add(var.set_tuya_parent(paren)) diff --git a/esphome/components/tuya/cover/tuya_cover.cpp b/esphome/components/tuya/cover/tuya_cover.cpp index 7da1312938..b63eb9109d 100644 --- a/esphome/components/tuya/cover/tuya_cover.cpp +++ b/esphome/components/tuya/cover/tuya_cover.cpp @@ -4,48 +4,122 @@ namespace esphome { namespace tuya { +const uint8_t COMMAND_OPEN = 0x00; +const uint8_t COMMAND_CLOSE = 0x02; +const uint8_t COMMAND_STOP = 0x01; + +using namespace esphome::cover; + static const char *const TAG = "tuya.cover"; void TuyaCover::setup() { this->value_range_ = this->max_value_ - this->min_value_; - if (this->position_id_.has_value()) { - this->parent_->register_listener(*this->position_id_, [this](const TuyaDatapoint &datapoint) { - auto pos = float(datapoint.value_uint - this->min_value_) / this->value_range_; - if (this->invert_position_) - pos = 1.0f - pos; - this->position = pos; - this->publish_state(); - }); + + this->parent_->add_on_initialized_callback([this]() { + // Set the direction (if configured/supported). + this->set_direction_(this->invert_position_); + + // Handle configured restore mode. + switch (this->restore_mode_) { + case COVER_NO_RESTORE: + break; + case COVER_RESTORE: { + auto restore = this->restore_state_(); + if (restore.has_value()) + restore->apply(this); + break; + } + case COVER_RESTORE_AND_CALL: { + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->to_call(this).perform(); + } + break; + } + } + }); + + uint8_t report_id = *this->position_id_; + if (this->position_report_id_.has_value()) { + // A position report datapoint is configured; listen to that instead. + report_id = *this->position_report_id_; } + + this->parent_->register_listener(report_id, [this](const TuyaDatapoint &datapoint) { + if (datapoint.value_int == 123) { + ESP_LOGD(TAG, "Ignoring MCU position report - not calibrated"); + return; + } + auto pos = float(datapoint.value_uint - this->min_value_) / this->value_range_; + this->position = 1.0f - pos; + this->publish_state(); + }); } void TuyaCover::control(const cover::CoverCall &call) { if (call.get_stop()) { - auto pos = this->position; - if (this->invert_position_) + if (this->control_id_.has_value()) { + this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_STOP); + } else { + auto pos = this->position; pos = 1.0f - pos; - auto position_int = static_cast(pos * this->value_range_); - position_int = position_int + this->min_value_; + auto position_int = static_cast(pos * this->value_range_); + position_int = position_int + this->min_value_; - parent_->set_integer_datapoint_value(*this->position_id_, position_int); + parent_->set_integer_datapoint_value(*this->position_id_, position_int); + } } if (call.get_position().has_value()) { auto pos = *call.get_position(); - if (this->invert_position_) + if (this->control_id_.has_value() && (pos == COVER_OPEN || pos == COVER_CLOSED)) { + if (pos == COVER_OPEN) { + this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_OPEN); + } else { + this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_CLOSE); + } + } else { pos = 1.0f - pos; - auto position_int = static_cast(pos * this->value_range_); - position_int = position_int + this->min_value_; + auto position_int = static_cast(pos * this->value_range_); + position_int = position_int + this->min_value_; - parent_->set_integer_datapoint_value(*this->position_id_, position_int); + parent_->set_integer_datapoint_value(*this->position_id_, position_int); + } } this->publish_state(); } +void TuyaCover::set_direction_(bool inverted) { + if (!this->direction_id_.has_value()) { + return; + } + + if (inverted) { + ESP_LOGD(TAG, "Setting direction: inverted"); + } else { + ESP_LOGD(TAG, "Setting direction: normal"); + } + + this->parent_->set_boolean_datapoint_value(*this->direction_id_, inverted); +} + void TuyaCover::dump_config() { ESP_LOGCONFIG(TAG, "Tuya Cover:"); + if (this->invert_position_) { + if (this->direction_id_.has_value()) { + ESP_LOGCONFIG(TAG, " Inverted"); + } else { + ESP_LOGCONFIG(TAG, " Configured as Inverted, but direction_datapoint isn't configured"); + } + } + if (this->control_id_.has_value()) + ESP_LOGCONFIG(TAG, " Control has datapoint ID %u", *this->control_id_); + if (this->direction_id_.has_value()) + ESP_LOGCONFIG(TAG, " Direction has datapoint ID %u", *this->direction_id_); if (this->position_id_.has_value()) ESP_LOGCONFIG(TAG, " Position has datapoint ID %u", *this->position_id_); + if (this->position_report_id_.has_value()) + ESP_LOGCONFIG(TAG, " Position Report has datapoint ID %u", *this->position_report_id_); } cover::CoverTraits TuyaCover::get_traits() { diff --git a/esphome/components/tuya/cover/tuya_cover.h b/esphome/components/tuya/cover/tuya_cover.h index c3b0c3e069..87c72b0e66 100644 --- a/esphome/components/tuya/cover/tuya_cover.h +++ b/esphome/components/tuya/cover/tuya_cover.h @@ -7,22 +7,37 @@ namespace esphome { namespace tuya { +enum TuyaCoverRestoreMode { + COVER_NO_RESTORE, + COVER_RESTORE, + COVER_RESTORE_AND_CALL, +}; + class TuyaCover : public cover::Cover, public Component { public: void setup() override; void dump_config() override; - void set_position_id(uint8_t dimmer_id) { this->position_id_ = dimmer_id; } + void set_control_id(uint8_t control_id) { this->control_id_ = control_id; } + void set_direction_id(uint8_t direction_id) { this->direction_id_ = direction_id; } + void set_position_id(uint8_t position_id) { this->position_id_ = position_id; } + void set_position_report_id(uint8_t position_report_id) { this->position_report_id_ = position_report_id; } void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } void set_min_value(uint32_t min_value) { min_value_ = min_value; } void set_max_value(uint32_t max_value) { max_value_ = max_value; } void set_invert_position(bool invert_position) { invert_position_ = invert_position; } + void set_restore_mode(TuyaCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; } protected: void control(const cover::CoverCall &call) override; + void set_direction_(bool inverted); cover::CoverTraits get_traits() override; Tuya *parent_; + TuyaCoverRestoreMode restore_mode_{}; + optional control_id_{}; + optional direction_id_{}; optional position_id_{}; + optional position_report_id_{}; uint32_t min_value_; uint32_t max_value_; uint32_t value_range_; diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 2677731224..404a70a80e 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -195,6 +195,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff if (this->init_state_ == TuyaInitState::INIT_DATAPOINT) { this->init_state_ = TuyaInitState::INIT_DONE; this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); }); + this->initialized_callback_.call(); } this->handle_datapoint_(buffer, len); break; @@ -439,53 +440,51 @@ void Tuya::send_local_time_() { #endif void Tuya::set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value) { - ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, hexencode(value).c_str()); - optional datapoint = this->get_datapoint_(datapoint_id); - if (!datapoint.has_value()) { - ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); - } else if (datapoint->type != TuyaDatapointType::RAW) { - ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); - return; - } else if (datapoint->value_raw == value) { - ESP_LOGV(TAG, "Not sending unchanged value"); - return; - } - this->send_datapoint_command_(datapoint_id, TuyaDatapointType::RAW, value); + this->set_raw_datapoint_value_(datapoint_id, value, false); } void Tuya::set_boolean_datapoint_value(uint8_t datapoint_id, bool value) { - this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1); + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1, false); } void Tuya::set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) { - this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4); + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4, false); } void Tuya::set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) { - ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, value.c_str()); - optional datapoint = this->get_datapoint_(datapoint_id); - if (!datapoint.has_value()) { - ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); - } else if (datapoint->type != TuyaDatapointType::STRING) { - ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); - return; - } else if (datapoint->value_string == value) { - ESP_LOGV(TAG, "Not sending unchanged value"); - return; - } - std::vector data; - for (char const &c : value) { - data.push_back(c); - } - this->send_datapoint_command_(datapoint_id, TuyaDatapointType::STRING, data); + this->set_string_datapoint_value_(datapoint_id, value, false); } void Tuya::set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) { - this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1); + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1, false); } void Tuya::set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) { - this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length); + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length, false); +} + +void Tuya::force_set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value) { + this->set_raw_datapoint_value_(datapoint_id, value, true); +} + +void Tuya::force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1, true); +} + +void Tuya::force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4, true); +} + +void Tuya::force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) { + this->set_string_datapoint_value_(datapoint_id, value, true); +} + +void Tuya::force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1, true); +} + +void Tuya::force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length, true); } optional Tuya::get_datapoint_(uint8_t datapoint_id) { @@ -496,7 +495,7 @@ optional Tuya::get_datapoint_(uint8_t datapoint_id) { } void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, const uint32_t value, - uint8_t length) { + uint8_t length, bool forced) { ESP_LOGD(TAG, "Setting datapoint %u to %u", datapoint_id, value); optional datapoint = this->get_datapoint_(datapoint_id); if (!datapoint.has_value()) { @@ -504,7 +503,7 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType } else if (datapoint->type != datapoint_type) { ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); return; - } else if (datapoint->value_uint == value) { + } else if (!forced && datapoint->value_uint == value) { ESP_LOGV(TAG, "Not sending unchanged value"); return; } @@ -526,6 +525,40 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType this->send_datapoint_command_(datapoint_id, datapoint_type, data); } +void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector &value, bool forced) { + ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, hexencode(value).c_str()); + optional datapoint = this->get_datapoint_(datapoint_id); + if (!datapoint.has_value()) { + ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); + } else if (datapoint->type != TuyaDatapointType::RAW) { + ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); + return; + } else if (!forced && datapoint->value_raw == value) { + ESP_LOGV(TAG, "Not sending unchanged value"); + return; + } + this->send_datapoint_command_(datapoint_id, TuyaDatapointType::RAW, value); +} + +void Tuya::set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced) { + ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, value.c_str()); + optional datapoint = this->get_datapoint_(datapoint_id); + if (!datapoint.has_value()) { + ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); + } else if (datapoint->type != TuyaDatapointType::STRING) { + ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); + return; + } else if (!forced && datapoint->value_string == value) { + ESP_LOGV(TAG, "Not sending unchanged value"); + return; + } + std::vector data; + for (char const &c : value) { + data.push_back(c); + } + this->send_datapoint_command_(datapoint_id, TuyaDatapointType::STRING, data); +} + void Tuya::send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector data) { std::vector buffer; buffer.push_back(datapoint_id); @@ -550,5 +583,7 @@ void Tuya::register_listener(uint8_t datapoint_id, const std::functioninit_state_; } + } // namespace tuya } // namespace esphome diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 785399502b..c46d61119e 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" #include "esphome/components/uart/uart.h" #ifdef USE_TIME @@ -81,12 +82,22 @@ class Tuya : public Component, public uart::UARTDevice { void set_string_datapoint_value(uint8_t datapoint_id, const std::string &value); void set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value); void set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length); + void force_set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value); + void force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value); + void force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value); + void force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value); + void force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value); + void force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length); + TuyaInitState get_init_state(); #ifdef USE_TIME void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; } #endif void add_ignore_mcu_update_on_datapoints(uint8_t ignore_mcu_update_on_datapoints) { this->ignore_mcu_update_on_datapoints_.push_back(ignore_mcu_update_on_datapoints); } + void add_on_initialized_callback(std::function callback) { + this->initialized_callback_.add(std::move(callback)); + } protected: void handle_char_(uint8_t c); @@ -100,7 +111,9 @@ class Tuya : public Component, public uart::UARTDevice { void send_command_(const TuyaCommand &command); void send_empty_command_(TuyaCommandType command); void set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, uint32_t value, - uint8_t length); + uint8_t length, bool forced); + void set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced); + void set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector &value, bool forced); void send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector data); void send_wifi_status_(); @@ -122,6 +135,7 @@ class Tuya : public Component, public uart::UARTDevice { std::vector command_queue_; optional expected_response_{}; uint8_t wifi_status_ = -1; + CallbackManager initialized_callback_{}; }; } // namespace tuya From 1b88b7a1664396cabb3a339eecbf12f1c0fe2c29 Mon Sep 17 00:00:00 2001 From: Alexandre-Jacques St-Jacques Date: Wed, 1 Dec 2021 21:33:48 -0500 Subject: [PATCH 108/111] Fix wifi not working with manual_ip using esp-idf (#2849) --- esphome/components/wifi/wifi_component_esp_idf.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index d2217eb88a..5a81fd0a39 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -430,7 +430,7 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { info.netmask.addr = static_cast(manual_ip->subnet); err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); - if (err != ESP_OK) { + if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) { ESP_LOGV(TAG, "tcpip_adapter_dhcpc_stop failed: %s", esp_err_to_name(err)); return false; } From 9ca4e8f32a78d9ec0bbb247ba67504682092c371 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Thu, 2 Dec 2021 03:45:11 +0100 Subject: [PATCH 109/111] modbus_controller: bugfix: enable overriding calculated register size (#2845) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/modbus_controller/__init__.py | 5 +++++ esphome/components/modbus_controller/modbus_controller.h | 8 +++++--- .../modbus_controller/text_sensor/modbus_textsensor.cpp | 2 +- .../modbus_controller/text_sensor/modbus_textsensor.h | 3 +-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 825b91280e..b927faf9a7 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -13,6 +13,7 @@ from .const import ( CONF_MODBUS_CONTROLLER_ID, CONF_REGISTER_COUNT, CONF_REGISTER_TYPE, + CONF_RESPONSE_SIZE, CONF_SKIP_UPDATES, CONF_VALUE_TYPE, ) @@ -125,6 +126,7 @@ ModbusItemBaseSchema = cv.Schema( cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_RESPONSE_SIZE, default=0): cv.positive_int, }, ) @@ -180,6 +182,9 @@ async def add_modbus_base_properties( if CONF_CUSTOM_COMMAND in config: cg.add(var.set_custom_data(config[CONF_CUSTOM_COMMAND])) + if config[CONF_RESPONSE_SIZE] > 0: + cg.add(var.set_register_size(config[CONF_RESPONSE_SIZE])) + if CONF_LAMBDA in config: template_ = await cg.process_lambda( config[CONF_LAMBDA], diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 045075f5e0..f4948e6ff9 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -254,16 +254,18 @@ class SensorItem { size_t virtual get_register_size() const { if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) return 1; - else - return register_count * 2; + else // if CONF_RESPONSE_BYTES is used override the default + return response_bytes > 0 ? response_bytes : register_count * 2; } - + // Override register size for modbus devices not using 1 register for one dword + void set_register_size(uint8_t register_size) { response_bytes = register_size; } ModbusRegisterType register_type; SensorValueType sensor_value_type; uint16_t start_address; uint32_t bitmask; uint8_t offset; uint8_t register_count; + uint8_t response_bytes{0}; uint8_t skip_updates; std::vector custom_data{}; bool force_new_range{false}; diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp index a06d44e90b..25b79474e8 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp @@ -13,7 +13,7 @@ void ModbusTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Modbus Controller Te void ModbusTextSensor::parse_and_publish(const std::vector &data) { std::ostringstream output; - uint8_t max_items = this->response_bytes_; + uint8_t max_items = this->response_bytes; char buffer[4]; bool add_comma = false; for (auto b : data) { diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h index 77b5b9363a..3db4d94a45 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h @@ -17,7 +17,7 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi this->register_type = register_type; this->start_address = start_address; this->offset = offset; - this->response_bytes_ = response_bytes; + this->response_bytes = response_bytes; this->register_count = register_count; this->encode_ = encode; this->skip_updates = skip_updates; @@ -38,7 +38,6 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi protected: RawEncoding encode_; - uint16_t response_bytes_; }; } // namespace modbus_controller From c6414138c723ff18492f1ce75a8ae7b2a2b3281a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 2 Dec 2021 19:38:49 +1300 Subject: [PATCH 110/111] Bump version to 2021.12.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f3189a6918..27feacb296 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.12.0-dev" +__version__ = "2021.12.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 86c205fe43089b7a7f3d1e23eb31978385150f15 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 2 Dec 2021 21:08:11 +1300 Subject: [PATCH 111/111] Remove blank line --- esphome/const.py | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 27feacb296..925e198fd7 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -14,7 +14,6 @@ HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"} SECRETS_FILES = {"secrets.yaml", "secrets.yml"} - CONF_ABOVE = "above" CONF_ACCELERATION = "acceleration" CONF_ACCELERATION_X = "acceleration_x"