From 8f69d070612d9ed7e862fc31c340568dffabf9f7 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sun, 1 Dec 2024 10:08:52 -0600 Subject: [PATCH 01/40] [hx711] clang-tidy fixes for #7822 (#7900) --- esphome/components/hx711/hx711.cpp | 2 +- esphome/components/hx711/hx711.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/hx711/hx711.cpp b/esphome/components/hx711/hx711.cpp index 1a7169eed7..9643d0c411 100644 --- a/esphome/components/hx711/hx711.cpp +++ b/esphome/components/hx711/hx711.cpp @@ -53,7 +53,7 @@ bool HX711Sensor::read_sensor_(uint32_t *result) { } // Cycle clock pin for gain setting - for (uint8_t i = 0; i < this->gain_; i++) { + for (uint8_t i = 0; i < static_cast(this->gain_); i++) { this->sck_pin_->digital_write(true); delayMicroseconds(1); this->sck_pin_->digital_write(false); diff --git a/esphome/components/hx711/hx711.h b/esphome/components/hx711/hx711.h index 0cb6868ab5..a92bb9945d 100644 --- a/esphome/components/hx711/hx711.h +++ b/esphome/components/hx711/hx711.h @@ -9,7 +9,7 @@ namespace esphome { namespace hx711 { -enum HX711Gain { +enum HX711Gain : uint8_t { HX711_GAIN_128 = 1, HX711_GAIN_32 = 2, HX711_GAIN_64 = 3, From 83d6834e277e9e9159f12653a2f39bc1113f9104 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 2 Dec 2024 05:10:18 +1300 Subject: [PATCH 02/40] Cast port to int for ota pushing (#7888) --- esphome/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 86d529e1bf..dce041e5ac 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -363,7 +363,7 @@ def upload_program(config, args, host): from esphome import espota2 - remote_port = ota_conf[CONF_PORT] + remote_port = int(ota_conf[CONF_PORT]) password = ota_conf.get(CONF_PASSWORD, "") if ( From edd847ea403c4a0fba05d652b8e30e09fd3e6a85 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sun, 1 Dec 2024 18:27:32 -0600 Subject: [PATCH 03/40] [modbus_controller] Clang fixes (#7899) --- .../modbus_controller/modbus_controller.cpp | 40 ++++++++--------- .../modbus_controller/modbus_controller.h | 44 +++++++++---------- .../number/modbus_number.cpp | 16 +++---- .../modbus_controller/number/modbus_number.h | 8 ++-- .../output/modbus_output.cpp | 13 +++--- .../modbus_controller/output/modbus_output.h | 10 ++--- .../select/modbus_select.cpp | 7 +-- .../modbus_controller/select/modbus_select.h | 8 ++-- .../switch/modbus_switch.cpp | 8 ++-- .../modbus_controller/switch/modbus_switch.h | 4 +- 10 files changed, 80 insertions(+), 78 deletions(-) diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index f8b72af817..641ba68223 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -152,11 +152,11 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t } SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const { - auto reg_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) { + auto reg_it = find_if(begin(this->register_ranges_), end(this->register_ranges_), [=](RegisterRange const &r) { return (r.start_address == start_address && r.register_type == register_type); }); - if (reg_it == register_ranges_.end()) { + if (reg_it == this->register_ranges_.end()) { ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address); } else { return reg_it->sensors; @@ -240,18 +240,18 @@ void ModbusController::update() { // walk through the sensors and determine the register ranges to read size_t ModbusController::create_register_ranges_() { - register_ranges_.clear(); - if (this->parent_->role == modbus::ModbusRole::CLIENT && sensorset_.empty()) { + this->register_ranges_.clear(); + if (this->parent_->role == modbus::ModbusRole::CLIENT && this->sensorset_.empty()) { ESP_LOGW(TAG, "No sensors registered"); return 0; } // iterator is sorted see SensorItemsComparator for details - auto ix = sensorset_.begin(); + auto ix = this->sensorset_.begin(); RegisterRange r = {}; uint8_t buffer_offset = 0; SensorItem *prev = nullptr; - while (ix != sensorset_.end()) { + while (ix != this->sensorset_.end()) { SensorItem *curr = *ix; ESP_LOGV(TAG, "Register: 0x%X %d %d %d offset=%u skip=%u addr=%p", curr->start_address, curr->register_count, @@ -278,12 +278,12 @@ size_t ModbusController::create_register_ranges_() { // this register can re-use the data from the previous register // remove this sensore because start_address is changed (sort-order) - ix = sensorset_.erase(ix); + ix = this->sensorset_.erase(ix); curr->start_address = r.start_address; curr->offset += prev->offset; - sensorset_.insert(curr); + this->sensorset_.insert(curr); // move iterator backwards because it will be incremented later ix--; @@ -293,14 +293,14 @@ size_t ModbusController::create_register_ranges_() { // this register can extend the current range // remove this sensore because start_address is changed (sort-order) - ix = sensorset_.erase(ix); + ix = this->sensorset_.erase(ix); curr->start_address = r.start_address; curr->offset += buffer_offset; buffer_offset += curr->get_register_size(); r.register_count += curr->register_count; - sensorset_.insert(curr); + this->sensorset_.insert(curr); // move iterator backwards because it will be incremented later ix--; @@ -327,7 +327,7 @@ size_t ModbusController::create_register_ranges_() { ix++; } else { ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates); - register_ranges_.push_back(r); + this->register_ranges_.push_back(r); r = {}; buffer_offset = 0; // do not increment the iterator here because the current sensor has to be re-evaluated @@ -339,10 +339,10 @@ size_t ModbusController::create_register_ranges_() { if (r.register_count > 0) { // Add the last range ESP_LOGV(TAG, "Add last range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates); - register_ranges_.push_back(r); + this->register_ranges_.push_back(r); } - return register_ranges_.size(); + return this->register_ranges_.size(); } void ModbusController::dump_config() { @@ -352,18 +352,18 @@ void ModbusController::dump_config() { ESP_LOGCONFIG(TAG, " Offline Skip Updates: %d", this->offline_skip_updates_); #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE ESP_LOGCONFIG(TAG, "sensormap"); - for (auto &it : sensorset_) { + for (auto &it : this->sensorset_) { ESP_LOGCONFIG(TAG, " Sensor type=%zu start=0x%X offset=0x%X count=%d size=%d", static_cast(it->register_type), it->start_address, it->offset, it->register_count, it->get_register_size()); } ESP_LOGCONFIG(TAG, "ranges"); - for (auto &it : register_ranges_) { + for (auto &it : this->register_ranges_) { ESP_LOGCONFIG(TAG, " Range type=%zu start=0x%X count=%d skip_updates=%d", static_cast(it.register_type), it.start_address, it.register_count, it.skip_updates); } ESP_LOGCONFIG(TAG, "server registers"); - for (auto &r : server_registers_) { + for (auto &r : this->server_registers_) { ESP_LOGCONFIG(TAG, " Address=0x%02X value_type=%zu register_count=%u", r->address, static_cast(r->value_type), r->register_count); } @@ -372,11 +372,11 @@ void ModbusController::dump_config() { void ModbusController::loop() { // Incoming data to process? - if (!incoming_queue_.empty()) { - auto &message = incoming_queue_.front(); + if (!this->incoming_queue_.empty()) { + auto &message = this->incoming_queue_.front(); if (message != nullptr) process_modbus_data_(message.get()); - incoming_queue_.pop(); + this->incoming_queue_.pop(); } else { // all messages processed send pending commands @@ -391,7 +391,7 @@ void ModbusController::on_write_register_response(ModbusRegisterType register_ty void ModbusController::dump_sensors_() { ESP_LOGV(TAG, "sensors"); - for (auto &it : sensorset_) { + for (auto &it : this->sensorset_) { ESP_LOGV(TAG, " Sensor start=0x%X count=%d size=%d offset=%d", it->start_address, it->register_count, it->get_register_size(), it->offset); } diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 2a0b936bf5..dfd52e44bc 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -240,14 +240,14 @@ class SensorItem { } // 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; + ModbusRegisterType register_type{ModbusRegisterType::CUSTOM}; + SensorValueType sensor_value_type{SensorValueType::RAW}; + uint16_t start_address{0}; + uint32_t bitmask{0}; + uint8_t offset{0}; + uint8_t register_count{0}; uint8_t response_bytes{0}; - uint16_t skip_updates; + uint16_t skip_updates{0}; std::vector custom_data{}; bool force_new_range{false}; }; @@ -261,9 +261,9 @@ class ServerRegister { this->register_count = register_count; this->read_lambda = std::move(read_lambda); } - uint16_t address; - SensorValueType value_type; - uint8_t register_count; + uint16_t address{0}; + SensorValueType value_type{SensorValueType::RAW}; + uint8_t register_count{0}; std::function read_lambda; }; @@ -312,11 +312,11 @@ struct RegisterRange { class ModbusCommandItem { public: static const size_t MAX_PAYLOAD_BYTES = 240; - ModbusController *modbusdevice; - uint16_t register_address; - uint16_t register_count; - ModbusFunctionCode function_code; - ModbusRegisterType register_type; + ModbusController *modbusdevice{nullptr}; + uint16_t register_address{0}; + uint16_t register_count{0}; + ModbusFunctionCode function_code{ModbusFunctionCode::CUSTOM}; + ModbusRegisterType register_type{ModbusRegisterType::CUSTOM}; std::function &data)> on_data_func; std::vector payload = {}; @@ -493,23 +493,23 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { /// Collection of all sensors for this component SensorSet sensorset_; /// Collection of all server registers for this component - std::vector server_registers_; + std::vector server_registers_{}; /// Continuous range of modbus registers - std::vector register_ranges_; + std::vector register_ranges_{}; /// Hold the pending requests to be sent std::list> command_queue_; /// modbus response data waiting to get processed std::queue> incoming_queue_; /// if duplicate commands can be sent - bool allow_duplicate_commands_; + bool allow_duplicate_commands_{false}; /// when was the last send operation - uint32_t last_command_timestamp_; + uint32_t last_command_timestamp_{0}; /// min time in ms between sending modbus commands - uint16_t command_throttle_; + uint16_t command_throttle_{0}; /// if module didn't respond the last command - bool module_offline_; + bool module_offline_{false}; /// how many updates to skip if module is offline - uint16_t offline_skip_updates_; + uint16_t offline_skip_updates_{0}; /// How many times we will retry a command if we get no response uint8_t max_cmd_retries_{4}; /// Command sent callback diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index 001cfb5787..ea8467d5a3 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -8,7 +8,7 @@ namespace modbus_controller { static const char *const TAG = "modbus.number"; void ModbusNumber::parse_and_publish(const std::vector &data) { - float result = payload_to_float(data, *this) / multiply_by_; + float result = payload_to_float(data, *this) / this->multiply_by_; // Is there a lambda registered // call it with the pre converted value and the raw data array @@ -43,7 +43,7 @@ void ModbusNumber::control(float value) { return; } } else { - write_value = multiply_by_ * write_value; + write_value = this->multiply_by_ * write_value; } if (!data.empty()) { @@ -63,21 +63,21 @@ void ModbusNumber::control(float value) { // Create and send the write command if (this->register_count == 1 && !this->use_write_multiple_) { // since offset is in bytes and a register is 16 bits we get the start by adding offset/2 - write_cmd = - ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]); + write_cmd = ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset / 2, + data[0]); } else { - write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, - this->register_count, data); + write_cmd = ModbusCommandItem::create_write_multiple_command( + this->parent_, this->start_address + this->offset / 2, this->register_count, data); } // publish new value write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { // gets called when the write command is ack'd from the device - parent_->on_write_register_response(write_cmd.register_type, start_address, data); + this->parent_->on_write_register_response(write_cmd.register_type, start_address, data); this->publish_state(value); }; } - parent_->queue_command(write_cmd); + this->parent_->queue_command(write_cmd); this->publish_state(value); } void ModbusNumber::dump_config() { LOG_NUMBER(TAG, "Modbus Number", this); } diff --git a/esphome/components/modbus_controller/number/modbus_number.h b/esphome/components/modbus_controller/number/modbus_number.h index 544d161cbc..8f77b2e014 100644 --- a/esphome/components/modbus_controller/number/modbus_number.h +++ b/esphome/components/modbus_controller/number/modbus_number.h @@ -29,7 +29,7 @@ class ModbusNumber : public number::Number, public Component, public SensorItem void parse_and_publish(const std::vector &data) override; float get_setup_priority() const override { return setup_priority::HARDWARE; } void set_parent(ModbusController *parent) { this->parent_ = parent; } - void set_write_multiply(float factor) { multiply_by_ = factor; } + void set_write_multiply(float factor) { this->multiply_by_ = factor; } using transform_func_t = std::function(ModbusNumber *, float, const std::vector &)>; using write_transform_func_t = std::function(ModbusNumber *, float, std::vector &)>; @@ -39,9 +39,9 @@ class ModbusNumber : public number::Number, public Component, public SensorItem protected: void control(float value) override; - optional transform_func_; - optional write_transform_func_; - ModbusController *parent_; + optional transform_func_{nullopt}; + optional write_transform_func_{nullopt}; + ModbusController *parent_{nullptr}; float multiply_by_{1.0}; bool use_write_multiple_{false}; }; diff --git a/esphome/components/modbus_controller/output/modbus_output.cpp b/esphome/components/modbus_controller/output/modbus_output.cpp index 79cd2d49c2..f0f6e64f10 100644 --- a/esphome/components/modbus_controller/output/modbus_output.cpp +++ b/esphome/components/modbus_controller/output/modbus_output.cpp @@ -27,7 +27,7 @@ void ModbusFloatOutput::write_state(float value) { return; } } else { - value = multiply_by_ * value; + value = this->multiply_by_ * value; } // lambda didn't set payload if (data.empty()) { @@ -40,12 +40,13 @@ void ModbusFloatOutput::write_state(float value) { // Create and send the write command ModbusCommandItem write_cmd; if (this->register_count == 1 && !this->use_write_multiple_) { - write_cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, data[0]); + write_cmd = + ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset, data[0]); } else { - write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, + write_cmd = ModbusCommandItem::create_write_multiple_command(this->parent_, this->start_address + this->offset, this->register_count, data); } - parent_->queue_command(write_cmd); + this->parent_->queue_command(write_cmd); } void ModbusFloatOutput::dump_config() { @@ -90,9 +91,9 @@ void ModbusBinaryOutput::write_state(bool state) { // offset for coil and discrete inputs is the coil/register number not bytes if (this->use_write_multiple_) { std::vector states{state}; - cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states); + cmd = ModbusCommandItem::create_write_multiple_coils(this->parent_, this->start_address + this->offset, states); } else { - cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state); + cmd = ModbusCommandItem::create_write_single_coil(this->parent_, this->start_address + this->offset, state); } } this->parent_->queue_command(cmd); diff --git a/esphome/components/modbus_controller/output/modbus_output.h b/esphome/components/modbus_controller/output/modbus_output.h index f424671cd1..bceb97affb 100644 --- a/esphome/components/modbus_controller/output/modbus_output.h +++ b/esphome/components/modbus_controller/output/modbus_output.h @@ -25,7 +25,7 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S void dump_config() override; void set_parent(ModbusController *parent) { this->parent_ = parent; } - void set_write_multiply(float factor) { multiply_by_ = factor; } + void set_write_multiply(float factor) { this->multiply_by_ = factor; } // Do nothing void parse_and_publish(const std::vector &data) override{}; @@ -37,9 +37,9 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S void write_state(float value) override; optional write_transform_func_{nullopt}; - ModbusController *parent_; + ModbusController *parent_{nullptr}; float multiply_by_{1.0}; - bool use_write_multiple_; + bool use_write_multiple_{false}; }; class ModbusBinaryOutput : public output::BinaryOutput, public Component, public SensorItem { @@ -68,8 +68,8 @@ class ModbusBinaryOutput : public output::BinaryOutput, public Component, public void write_state(bool state) override; optional write_transform_func_{nullopt}; - ModbusController *parent_; - bool use_write_multiple_; + ModbusController *parent_{nullptr}; + bool use_write_multiple_{false}; }; } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/select/modbus_select.cpp b/esphome/components/modbus_controller/select/modbus_select.cpp index 33cef39a18..56b8c783ed 100644 --- a/esphome/components/modbus_controller/select/modbus_select.cpp +++ b/esphome/components/modbus_controller/select/modbus_select.cpp @@ -74,12 +74,13 @@ void ModbusSelect::control(const std::string &value) { const uint16_t write_address = this->start_address + this->offset / 2; ModbusCommandItem write_cmd; if ((this->register_count == 1) && (!this->use_write_multiple_)) { - write_cmd = ModbusCommandItem::create_write_single_command(parent_, write_address, data[0]); + write_cmd = ModbusCommandItem::create_write_single_command(this->parent_, write_address, data[0]); } else { - write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, write_address, this->register_count, data); + write_cmd = + ModbusCommandItem::create_write_multiple_command(this->parent_, write_address, this->register_count, data); } - parent_->queue_command(write_cmd); + this->parent_->queue_command(write_cmd); if (this->optimistic_) this->publish_state(value); diff --git a/esphome/components/modbus_controller/select/modbus_select.h b/esphome/components/modbus_controller/select/modbus_select.h index 1c046b11d0..55fb2107dd 100644 --- a/esphome/components/modbus_controller/select/modbus_select.h +++ b/esphome/components/modbus_controller/select/modbus_select.h @@ -42,12 +42,12 @@ class ModbusSelect : public Component, public select::Select, public SensorItem void control(const std::string &value) override; protected: - std::vector mapping_; - ModbusController *parent_; + std::vector mapping_{}; + ModbusController *parent_{nullptr}; bool use_write_multiple_{false}; bool optimistic_{false}; - optional transform_func_; - optional write_transform_func_; + optional transform_func_{nullopt}; + optional write_transform_func_{nullopt}; }; } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/switch/modbus_switch.cpp b/esphome/components/modbus_controller/switch/modbus_switch.cpp index 3a679fbeb8..ec29eca7f8 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.cpp +++ b/esphome/components/modbus_controller/switch/modbus_switch.cpp @@ -80,18 +80,18 @@ void ModbusSwitch::write_state(bool state) { // offset for coil and discrete inputs is the coil/register number not bytes if (this->use_write_multiple_) { std::vector states{state}; - cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states); + cmd = ModbusCommandItem::create_write_multiple_coils(this->parent_, this->start_address + this->offset, states); } else { - cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state); + cmd = ModbusCommandItem::create_write_single_coil(this->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 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, + cmd = ModbusCommandItem::create_write_multiple_command(this->parent_, this->start_address + this->offset / 2, 1, bool_states); } else { - cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, + cmd = ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset / 2, state ? 0xFFFF & this->bitmask : 0u); } } diff --git a/esphome/components/modbus_controller/switch/modbus_switch.h b/esphome/components/modbus_controller/switch/modbus_switch.h index bfe46f3ac8..fe4b7c1ad5 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.h +++ b/esphome/components/modbus_controller/switch/modbus_switch.h @@ -40,8 +40,8 @@ class ModbusSwitch : public Component, public switch_::Switch, public SensorItem void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } protected: - ModbusController *parent_; - bool use_write_multiple_; + ModbusController *parent_{nullptr}; + bool use_write_multiple_{false}; optional publish_transform_func_{nullopt}; optional write_transform_func_{nullopt}; }; From fb96e3588d1f771f7430beec570da2d09a83e2f2 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Mon, 2 Dec 2024 08:16:58 +0000 Subject: [PATCH 04/40] Add H-Bridge switch component (#7421) Co-authored-by: Keith Burzinski --- CODEOWNERS | 1 + esphome/components/hbridge/switch/__init__.py | 44 +++++++++ .../hbridge/switch/hbridge_switch.cpp | 95 +++++++++++++++++++ .../hbridge/switch/hbridge_switch.h | 50 ++++++++++ tests/components/hbridge/common.yaml | 39 ++++++++ tests/components/hbridge/test.esp32-ard.yaml | 46 +++------ .../components/hbridge/test.esp32-c3-ard.yaml | 45 +++------ .../components/hbridge/test.esp32-c3-idf.yaml | 44 +++------ tests/components/hbridge/test.esp32-idf.yaml | 45 +++------ .../components/hbridge/test.esp8266-ard.yaml | 45 +++------ tests/components/hbridge/test.rp2040-ard.yaml | 45 +++------ 11 files changed, 313 insertions(+), 186 deletions(-) create mode 100644 esphome/components/hbridge/switch/__init__.py create mode 100644 esphome/components/hbridge/switch/hbridge_switch.cpp create mode 100644 esphome/components/hbridge/switch/hbridge_switch.h create mode 100644 tests/components/hbridge/common.yaml diff --git a/CODEOWNERS b/CODEOWNERS index fb6d11d1fb..74c205b302 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -179,6 +179,7 @@ esphome/components/haier/text_sensor/* @paveldn esphome/components/havells_solar/* @sourabhjaiswal esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/light/* @DotNetDann +esphome/components/hbridge/switch/* @dwmw2 esphome/components/he60r/* @clydebarrow esphome/components/heatpumpir/* @rob-deutsch esphome/components/hitachi_ac424/* @sourabhjaiswal diff --git a/esphome/components/hbridge/switch/__init__.py b/esphome/components/hbridge/switch/__init__.py new file mode 100644 index 0000000000..e26bd6b1d8 --- /dev/null +++ b/esphome/components/hbridge/switch/__init__.py @@ -0,0 +1,44 @@ +from esphome import pins +import esphome.codegen as cg +from esphome.components import switch +import esphome.config_validation as cv +from esphome.const import CONF_OPTIMISTIC, CONF_PULSE_LENGTH, CONF_WAIT_TIME + +from .. import hbridge_ns + +HBridgeSwitch = hbridge_ns.class_("HBridgeSwitch", switch.Switch, cg.Component) + +CODEOWNERS = ["@dwmw2"] + +CONF_OFF_PIN = "off_pin" +CONF_ON_PIN = "on_pin" + +CONFIG_SCHEMA = ( + switch.switch_schema(HBridgeSwitch) + .extend( + { + cv.Required(CONF_ON_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_OFF_PIN): pins.gpio_output_pin_schema, + cv.Optional( + CONF_PULSE_LENGTH, default="100ms" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_WAIT_TIME): cv.positive_time_period_milliseconds, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = await switch.new_switch(config) + await cg.register_component(var, config) + + on_pin = await cg.gpio_pin_expression(config[CONF_ON_PIN]) + cg.add(var.set_on_pin(on_pin)) + off_pin = await cg.gpio_pin_expression(config[CONF_OFF_PIN]) + cg.add(var.set_off_pin(off_pin)) + cg.add(var.set_pulse_length(config[CONF_PULSE_LENGTH])) + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + if wait_time := config.get(CONF_WAIT_TIME): + cg.add(var.set_wait_time(wait_time)) diff --git a/esphome/components/hbridge/switch/hbridge_switch.cpp b/esphome/components/hbridge/switch/hbridge_switch.cpp new file mode 100644 index 0000000000..12d1c01bca --- /dev/null +++ b/esphome/components/hbridge/switch/hbridge_switch.cpp @@ -0,0 +1,95 @@ +#include "hbridge_switch.h" +#include "esphome/core/log.h" + +#include + +namespace esphome { +namespace hbridge { + +static const char *const TAG = "switch.hbridge"; + +float HBridgeSwitch::get_setup_priority() const { return setup_priority::HARDWARE; } +void HBridgeSwitch::setup() { + ESP_LOGCONFIG(TAG, "Setting up H-Bridge Switch '%s'...", this->name_.c_str()); + + optional initial_state = this->get_initial_state_with_restore_mode().value_or(false); + + // Like GPIOSwitch does, set the pin state both before and after pin setup() + this->on_pin_->digital_write(false); + this->on_pin_->setup(); + this->on_pin_->digital_write(false); + + this->off_pin_->digital_write(false); + this->off_pin_->setup(); + this->off_pin_->digital_write(false); + + if (initial_state.has_value()) + this->write_state(initial_state); +} + +void HBridgeSwitch::dump_config() { + LOG_SWITCH("", "H-Bridge Switch", this); + LOG_PIN(" On Pin: ", this->on_pin_); + LOG_PIN(" Off Pin: ", this->off_pin_); + ESP_LOGCONFIG(TAG, " Pulse length: %" PRId32 " ms", this->pulse_length_); + if (this->wait_time_) + ESP_LOGCONFIG(TAG, " Wait time %" PRId32 " ms", this->wait_time_); +} + +void HBridgeSwitch::write_state(bool state) { + this->desired_state_ = state; + if (!this->timer_running_) + this->timer_fn_(); +} + +void HBridgeSwitch::timer_fn_() { + uint32_t next_timeout = 0; + + while ((uint8_t) this->desired_state_ != this->relay_state_) { + switch (this->relay_state_) { + case RELAY_STATE_ON: + case RELAY_STATE_OFF: + case RELAY_STATE_UNKNOWN: + if (this->desired_state_) { + this->on_pin_->digital_write(true); + this->relay_state_ = RELAY_STATE_SWITCHING_ON; + } else { + this->off_pin_->digital_write(true); + this->relay_state_ = RELAY_STATE_SWITCHING_OFF; + } + next_timeout = this->pulse_length_; + if (!this->optimistic_) + this->publish_state(this->desired_state_); + break; + + case RELAY_STATE_SWITCHING_ON: + this->on_pin_->digital_write(false); + this->relay_state_ = RELAY_STATE_ON; + if (this->optimistic_) + this->publish_state(true); + next_timeout = this->wait_time_; + break; + + case RELAY_STATE_SWITCHING_OFF: + this->off_pin_->digital_write(false); + this->relay_state_ = RELAY_STATE_OFF; + if (this->optimistic_) + this->publish_state(false); + next_timeout = this->wait_time_; + break; + } + + if (next_timeout) { + this->timer_running_ = true; + this->set_timeout(next_timeout, [this]() { this->timer_fn_(); }); + return; + } + + // In the case where ON/OFF state has been reached but we need to + // immediately change back again to reach desired_state_, we loop. + } + this->timer_running_ = false; +} + +} // namespace hbridge +} // namespace esphome diff --git a/esphome/components/hbridge/switch/hbridge_switch.h b/esphome/components/hbridge/switch/hbridge_switch.h new file mode 100644 index 0000000000..ce00c6baa2 --- /dev/null +++ b/esphome/components/hbridge/switch/hbridge_switch.h @@ -0,0 +1,50 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/switch/switch.h" + +#include + +namespace esphome { +namespace hbridge { + +enum RelayState : uint8_t { + RELAY_STATE_OFF = 0, + RELAY_STATE_ON = 1, + RELAY_STATE_SWITCHING_ON = 2, + RELAY_STATE_SWITCHING_OFF = 3, + RELAY_STATE_UNKNOWN = 4, +}; + +class HBridgeSwitch : public switch_::Switch, public Component { + public: + void set_on_pin(GPIOPin *pin) { this->on_pin_ = pin; } + void set_off_pin(GPIOPin *pin) { this->off_pin_ = pin; } + void set_pulse_length(uint32_t pulse_length) { this->pulse_length_ = pulse_length; } + void set_wait_time(uint32_t wait_time) { this->wait_time_ = wait_time; } + void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + float get_setup_priority() const override; + + void setup() override; + void dump_config() override; + + protected: + void write_state(bool state) override; + void timer_fn_(); + + bool timer_running_{false}; + bool desired_state_{false}; + RelayState relay_state_{RELAY_STATE_UNKNOWN}; + GPIOPin *on_pin_{nullptr}; + GPIOPin *off_pin_{nullptr}; + uint32_t pulse_length_{0}; + uint32_t wait_time_{0}; + bool optimistic_{false}; +}; + +} // namespace hbridge +} // namespace esphome diff --git a/tests/components/hbridge/common.yaml b/tests/components/hbridge/common.yaml new file mode 100644 index 0000000000..0504cdea03 --- /dev/null +++ b/tests/components/hbridge/common.yaml @@ -0,0 +1,39 @@ +output: + - platform: ${pwm_platform} + pin: ${output1_pin} + id: gpio_output1 + - platform: ${pwm_platform} + pin: ${output2_pin} + id: gpio_output2 + - platform: ${pwm_platform} + pin: ${output3_pin} + id: gpio_output3 + - platform: ${pwm_platform} + pin: ${output4_pin} + id: gpio_output4 + +light: + - platform: hbridge + name: Icicle Lights + pin_a: gpio_output3 + pin_b: gpio_output4 + +fan: + - platform: hbridge + id: fan_hbridge + speed_count: 4 + name: H-bridge Fan with Presets + pin_a: gpio_output1 + pin_b: gpio_output2 + preset_modes: + - Preset 1 + - Preset 2 + on_preset_set: + then: + - logger.log: Preset mode was changed! + +switch: + - platform: hbridge + id: switch_hbridge + on_pin: ${hbridge_on_pin} + off_pin: ${hbridge_off_pin} diff --git a/tests/components/hbridge/test.esp32-ard.yaml b/tests/components/hbridge/test.esp32-ard.yaml index 6a80aaaf3b..e50d537749 100644 --- a/tests/components/hbridge/test.esp32-ard.yaml +++ b/tests/components/hbridge/test.esp32-ard.yaml @@ -1,33 +1,17 @@ -output: - - platform: ledc - pin: 14 - id: gpio_output1 - - platform: ledc - pin: 15 - id: gpio_output2 - - platform: ledc - pin: 12 - id: gpio_output3 - - platform: ledc - pin: 13 - id: gpio_output4 +substitutions: + pwm_platform: ledc + output1_pin: "14" + output2_pin: "15" + output3_pin: "12" + output4_pin: "13" + hbridge_on_pin: "4" + hbridge_off_pin: "5" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + pulse_length: 60ms + wait_time: 10ms + optimistic: false diff --git a/tests/components/hbridge/test.esp32-c3-ard.yaml b/tests/components/hbridge/test.esp32-c3-ard.yaml index 70cfd6ab6f..b9e8738442 100644 --- a/tests/components/hbridge/test.esp32-c3-ard.yaml +++ b/tests/components/hbridge/test.esp32-c3-ard.yaml @@ -1,33 +1,16 @@ -output: - - platform: ledc - pin: 4 - id: gpio_output1 - - platform: ledc - pin: 5 - id: gpio_output2 - - platform: ledc - pin: 6 - id: gpio_output3 - - platform: ledc - pin: 7 - id: gpio_output4 +substitutions: + pwm_platform: "ledc" + output1_pin: "4" + output2_pin: "5" + output3_pin: "6" + output4_pin: "7" + hbridge_on_pin: "2" + hbridge_off_pin: "3" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + wait_time: 10ms + optimistic: true diff --git a/tests/components/hbridge/test.esp32-c3-idf.yaml b/tests/components/hbridge/test.esp32-c3-idf.yaml index 70cfd6ab6f..c73f08b6de 100644 --- a/tests/components/hbridge/test.esp32-c3-idf.yaml +++ b/tests/components/hbridge/test.esp32-c3-idf.yaml @@ -1,33 +1,15 @@ -output: - - platform: ledc - pin: 4 - id: gpio_output1 - - platform: ledc - pin: 5 - id: gpio_output2 - - platform: ledc - pin: 6 - id: gpio_output3 - - platform: ledc - pin: 7 - id: gpio_output4 +substitutions: + pwm_platform: "ledc" + output1_pin: "4" + output2_pin: "5" + output3_pin: "6" + output4_pin: "7" + hbridge_on_pin: "2" + hbridge_off_pin: "3" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + pulse_length: 60ms diff --git a/tests/components/hbridge/test.esp32-idf.yaml b/tests/components/hbridge/test.esp32-idf.yaml index 6a80aaaf3b..dbbfa738c7 100644 --- a/tests/components/hbridge/test.esp32-idf.yaml +++ b/tests/components/hbridge/test.esp32-idf.yaml @@ -1,33 +1,16 @@ -output: - - platform: ledc - pin: 14 - id: gpio_output1 - - platform: ledc - pin: 15 - id: gpio_output2 - - platform: ledc - pin: 12 - id: gpio_output3 - - platform: ledc - pin: 13 - id: gpio_output4 +substitutions: + pwm_platform: "ledc" + output1_pin: "14" + output2_pin: "15" + output3_pin: "12" + output4_pin: "13" + hbridge_on_pin: "4" + hbridge_off_pin: "5" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + pulse_length: 60ms + wait_time: 10ms diff --git a/tests/components/hbridge/test.esp8266-ard.yaml b/tests/components/hbridge/test.esp8266-ard.yaml index 4f8915879d..f560da5d38 100644 --- a/tests/components/hbridge/test.esp8266-ard.yaml +++ b/tests/components/hbridge/test.esp8266-ard.yaml @@ -1,33 +1,16 @@ -output: - - platform: esp8266_pwm - pin: 4 - id: gpio_output1 - - platform: esp8266_pwm - pin: 5 - id: gpio_output2 - - platform: esp8266_pwm - pin: 12 - id: gpio_output3 - - platform: esp8266_pwm - pin: 13 - id: gpio_output4 +substitutions: + pwm_platform: "esp8266_pwm" + output1_pin: "4" + output2_pin: "5" + output3_pin: "12" + output4_pin: "13" + hbridge_on_pin: "14" + hbridge_off_pin: "15" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + pulse_length: 60ms + wait_time: 10ms diff --git a/tests/components/hbridge/test.rp2040-ard.yaml b/tests/components/hbridge/test.rp2040-ard.yaml index e21b55091d..aa6e290cab 100644 --- a/tests/components/hbridge/test.rp2040-ard.yaml +++ b/tests/components/hbridge/test.rp2040-ard.yaml @@ -1,33 +1,16 @@ -output: - - platform: rp2040_pwm - pin: 4 - id: gpio_output1 - - platform: rp2040_pwm - pin: 5 - id: gpio_output2 - - platform: rp2040_pwm - pin: 6 - id: gpio_output3 - - platform: rp2040_pwm - pin: 7 - id: gpio_output4 +substitutions: + pwm_platform: "rp2040_pwm" + output1_pin: "4" + output2_pin: "5" + output3_pin: "6" + output4_pin: "7" + hbridge_on_pin: "2" + hbridge_off_pin: "3" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + wait_time: 10ms + optimistic: true From b79a3d672782fee909f929da8cb0a516ee52132d Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 2 Dec 2024 11:42:44 -0600 Subject: [PATCH 05/40] [CI] Bump GHA runners to ``ubuntu-24.04`` (#7905) --- .github/workflows/ci.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5af3ec9e9..82e823adde 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ concurrency: jobs: common: name: Create common environment - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: cache-key: ${{ steps.cache-key.outputs.key }} steps: @@ -62,7 +62,7 @@ jobs: black: name: Check black - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -83,7 +83,7 @@ jobs: flake8: name: Check flake8 - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -104,7 +104,7 @@ jobs: pylint: name: Check pylint - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -125,7 +125,7 @@ jobs: pyupgrade: name: Check pyupgrade - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -146,7 +146,7 @@ jobs: ci-custom: name: Run script/ci-custom - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -225,7 +225,7 @@ jobs: clang-format: name: Check clang-format - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -251,7 +251,7 @@ jobs: clang-tidy: name: ${{ matrix.name }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common - black @@ -345,7 +345,7 @@ jobs: if: always() list-components: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common if: github.event_name == 'pull_request' @@ -387,7 +387,7 @@ jobs: test-build-components: name: Component test ${{ matrix.file }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common - list-components @@ -421,7 +421,7 @@ jobs: test-build-components-splitter: name: Split components for testing into 20 groups maximum - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common - list-components @@ -439,7 +439,7 @@ jobs: test-build-components-split: name: Test split components - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common - list-components @@ -483,7 +483,7 @@ jobs: ci-status: name: CI Status - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common - black From e08a9cc3a33ab431f6eeef869bdc49c46f17dcf1 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 3 Dec 2024 09:27:51 +1100 Subject: [PATCH 06/40] [font et. al.] Remove explicit check for pillow installed. (#7891) --- esphome/components/animation/__init__.py | 3 +-- esphome/components/font/__init__.py | 24 ++---------------------- esphome/components/ili9xxx/display.py | 3 +-- esphome/components/image/__init__.py | 3 +-- 4 files changed, 5 insertions(+), 28 deletions(-) diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index 5a308855de..21a82649f0 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -2,7 +2,6 @@ import logging from esphome import automation, core import esphome.codegen as cg -from esphome.components import font import esphome.components.image as espImage from esphome.components.image import ( CONF_USE_TRANSPARENCY, @@ -131,7 +130,7 @@ ANIMATION_SCHEMA = cv.Schema( ) ) -CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA) +CONFIG_SCHEMA = ANIMATION_SCHEMA NEXT_FRAME_SCHEMA = automation.maybe_simple_id( { diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 6fd2d7c310..1b4fe4bff0 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -1,4 +1,3 @@ -from collections.abc import Iterable import functools import hashlib import logging @@ -8,7 +7,6 @@ import re import freetype import glyphsets -from packaging import version import requests from esphome import core, external_files @@ -88,7 +86,7 @@ def flatten(lists) -> list: return list(chain.from_iterable(lists)) -def check_missing_glyphs(file, codepoints: Iterable, warning: bool = False): +def check_missing_glyphs(file, codepoints, warning: bool = False): """ Check that the given font file actually contains the requested glyphs :param file: A Truetype font file @@ -177,24 +175,6 @@ def validate_glyphs(config): return config -def validate_pillow_installed(value): - try: - import PIL - except ImportError as err: - raise cv.Invalid( - "Please install the pillow python package to use this feature. " - '(pip install "pillow==10.4.0")' - ) from err - - if version.parse(PIL.__version__) != version.parse("10.4.0"): - raise cv.Invalid( - "Please update your pillow installation to 10.4.0. " - '(pip install "pillow==10.4.0")' - ) - - return value - - FONT_EXTENSIONS = (".ttf", ".woff", ".otf") @@ -421,7 +401,7 @@ FONT_SCHEMA = cv.Schema( }, ) -CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA, validate_glyphs) +CONFIG_SCHEMA = cv.All(FONT_SCHEMA, validate_glyphs) # PIL doesn't provide a consistent interface for both TrueType and bitmap diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index 739ad07843..3c9dd2dab9 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -1,6 +1,6 @@ from esphome import core, pins import esphome.codegen as cg -from esphome.components import display, font, spi +from esphome.components import display, spi from esphome.components.display import validate_rotation import esphome.config_validation as cv from esphome.const import ( @@ -147,7 +147,6 @@ def _validate(config): CONFIG_SCHEMA = cv.All( - font.validate_pillow_installed, display.FULL_DISPLAY_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(ILI9XXXDisplay), diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index 8742540067..4669a3418a 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -10,7 +10,6 @@ import puremagic from esphome import core, external_files import esphome.codegen as cg -from esphome.components import font import esphome.config_validation as cv from esphome.const import ( CONF_DITHER, @@ -233,7 +232,7 @@ IMAGE_SCHEMA = cv.Schema( ) ) -CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA) +CONFIG_SCHEMA = IMAGE_SCHEMA def load_svg_image(file: bytes, resize: tuple[int, int]): From 9c8976be1328b4c5c1d019aa835f53f129262324 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 2 Dec 2024 16:29:45 -0600 Subject: [PATCH 07/40] [CI] Update clang-tidy to 18.1.3 (#7822) --- .clang-tidy | 29 +++++++++++++++++++++++++++-- .github/workflows/ci.yml | 5 ----- docker/Dockerfile | 8 ++++++-- requirements_dev.txt | 2 +- script/clang-tidy | 8 +++----- 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 994416b2f1..8dba033e2d 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -7,28 +7,38 @@ Checks: >- -boost-*, -bugprone-easily-swappable-parameters, -bugprone-implicit-widening-of-multiplication-result, + -bugprone-multi-level-implicit-pointer-conversion, -bugprone-narrowing-conversions, -bugprone-signed-char-misuse, + -bugprone-switch-missing-default-case, -cert-dcl50-cpp, -cert-err33-c, -cert-err58-cpp, -cert-oop57-cpp, -cert-str34-c, + -clang-analyzer-optin.core.EnumCastOutOfRange, -clang-analyzer-optin.cplusplus.UninitializedObject, -clang-analyzer-osx.*, -clang-diagnostic-delete-abstract-non-virtual-dtor, -clang-diagnostic-delete-non-abstract-non-virtual-dtor, -clang-diagnostic-ignored-optimization-argument, + -clang-diagnostic-missing-field-initializers, -clang-diagnostic-shadow-field, -clang-diagnostic-unused-const-variable, -clang-diagnostic-unused-parameter, + -clang-diagnostic-vla-cxx-extension, -concurrency-*, -cppcoreguidelines-avoid-c-arrays, + -cppcoreguidelines-avoid-const-or-ref-data-members, + -cppcoreguidelines-avoid-do-while, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-init-variables, + -cppcoreguidelines-macro-to-enum, -cppcoreguidelines-macro-usage, + -cppcoreguidelines-missing-std-forward, -cppcoreguidelines-narrowing-conversions, -cppcoreguidelines-non-private-member-variables-in-classes, + -cppcoreguidelines-owning-memory, -cppcoreguidelines-prefer-member-initializer, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-constant-array-index, @@ -40,7 +50,9 @@ Checks: >- -cppcoreguidelines-pro-type-static-cast-downcast, -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-vararg, + -cppcoreguidelines-rvalue-reference-param-not-moved, -cppcoreguidelines-special-member-functions, + -cppcoreguidelines-use-default-member-init, -cppcoreguidelines-virtual-class-destructor, -fuchsia-multiple-inheritance, -fuchsia-overloaded-operator, @@ -60,21 +72,32 @@ Checks: >- -llvm-include-order, -llvm-qualified-auto, -llvmlibc-*, - -misc-non-private-member-variables-in-classes, + -misc-const-correctness, + -misc-include-cleaner, -misc-no-recursion, + -misc-non-private-member-variables-in-classes, -misc-unused-parameters, + -misc-use-anonymous-namespace, -modernize-avoid-bind, -modernize-avoid-c-arrays, -modernize-concat-nested-namespaces, + -modernize-macro-to-enum, -modernize-return-braced-init-list, + -modernize-type-traits, -modernize-use-auto, + -modernize-use-constraints, -modernize-use-default-member-init, -modernize-use-equals-default, -modernize-use-nodiscard, -modernize-use-nullptr, + -modernize-use-nodiscard, + -modernize-use-nullptr, -modernize-use-trailing-return-type, -mpi-*, -objc-*, + -performance-enum-size, + -readability-avoid-nested-conditional-operator, + -readability-container-contains, -readability-container-data-pointer, -readability-convert-member-functions-to-static, -readability-else-after-return, @@ -84,11 +107,13 @@ Checks: >- -readability-magic-numbers, -readability-make-member-function-const, -readability-named-parameter, + -readability-redundant-casting, + -readability-redundant-inline-specifier, + -readability-redundant-member-init, -readability-redundant-string-init, -readability-uppercase-literal-suffix, -readability-use-anyofallof, WarningsAsErrors: '*' -AnalyzeTemporaryDtors: false FormatStyle: google CheckOptions: - key: google-readability-function-size.StatementThreshold diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82e823adde..e4d3934c59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -314,11 +314,6 @@ jobs: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }} - - name: Install clang-tidy - run: | - sudo apt-get update - sudo apt-get install clang-tidy-14 - - name: Register problem matchers run: | echo "::add-matcher::.github/workflows/matchers/gcc.json" diff --git a/docker/Dockerfile b/docker/Dockerfile index c2902a9dd1..c53856d0e8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -209,11 +209,12 @@ ENV \ PLATFORMIO_CORE_DIR=/esphome/.temp/platformio RUN \ - apt-get update \ + curl -L https://apt.llvm.org/llvm-snapshot.gpg.key -o /etc/apt/trusted.gpg.d/apt.llvm.org.asc \ + && echo "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-18 main" > /etc/apt/sources.list.d/llvm.sources.list \ + && apt-get update \ # Use pinned versions so that we get updates with build caching && apt-get install -y --no-install-recommends \ clang-format-13=1:13.0.1-11+b2 \ - clang-tidy-14=1:14.0.6-12 \ patch=2.7.6-7 \ software-properties-common=0.99.30-4.1~deb12u1 \ nano=7.2-1+deb12u1 \ @@ -227,6 +228,9 @@ RUN \ COPY requirements_test.txt / RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ + else \ + # move this up into RUN above after armv7 is retired + apt-get install -y --no-install-recommends clang-tidy-18=1:18.1.8~++20240731024826+3b5b5c1ec4a3-1~exp1~20240731144843.145 ; \ fi; \ pip3 install \ --break-system-packages --no-cache-dir -r /requirements_test.txt diff --git a/requirements_dev.txt b/requirements_dev.txt index eb749a861d..2ef8330215 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,4 +1,4 @@ # Useful stuff when working in a development environment clang-format==13.0.1 # also change in .pre-commit-config.yaml and Dockerfile when updating -clang-tidy==14.0.6 # When updating clang-tidy, also update Dockerfile +clang-tidy==18.1.3 # When updating clang-tidy, also update Dockerfile yamllint==1.35.1 # also change in .pre-commit-config.yaml when updating diff --git a/script/clang-tidy b/script/clang-tidy index 319fab70a2..5c19f81043 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -63,8 +63,6 @@ def clang_options(idedata): "-Ddeprecated(x)=", # allow to condition code on the presence of clang-tidy "-DCLANG_TIDY", - # (esp-idf) Disable this header because they use asm with registers clang-tidy doesn't know - "-D__XTENSA_API_H__", # (esp-idf) Fix __once_callable in some libstdc++ headers "-D_GLIBCXX_HAVE_TLS", ] @@ -238,7 +236,7 @@ def main(): failed_files = [] try: - executable = get_binary("clang-tidy", 14) + executable = get_binary("clang-tidy", 18) task_queue = queue.Queue(args.jobs) lock = threading.Lock() for _ in range(args.jobs): @@ -283,12 +281,12 @@ def main(): print("Applying fixes ...") try: try: - subprocess.call(["clang-apply-replacements-14", tmpdir]) + subprocess.call(["clang-apply-replacements-18", tmpdir]) except FileNotFoundError: subprocess.call(["clang-apply-replacements", tmpdir]) except FileNotFoundError: print( - "Error please install clang-apply-replacements-14 or clang-apply-replacements.\n", + "Error please install clang-apply-replacements-18 or clang-apply-replacements.\n", file=sys.stderr, ) except: From 584dbf2668f888c774be7ad6ce4172d827965e9b Mon Sep 17 00:00:00 2001 From: kbullet Date: Tue, 3 Dec 2024 06:50:05 +0700 Subject: [PATCH 08/40] MQTT sensors handling of publishing NaN values (#7768) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/mqtt/__init__.py | 4 ++++ esphome/components/mqtt/mqtt_client.cpp | 4 ++++ esphome/components/mqtt/mqtt_client.h | 6 ++++++ esphome/components/mqtt/mqtt_sensor.cpp | 2 ++ esphome/const.py | 1 + tests/components/mqtt/common.yaml | 1 + 6 files changed, 18 insertions(+) diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 86d163e61d..2b0d941220 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -49,6 +49,7 @@ from esphome.const import ( CONF_USE_ABBREVIATIONS, CONF_USERNAME, CONF_WILL_MESSAGE, + CONF_PUBLISH_NAN_AS_NONE, PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, @@ -296,6 +297,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, } ), + cv.Optional(CONF_PUBLISH_NAN_AS_NONE, default=False): cv.boolean, } ), validate_config, @@ -449,6 +451,8 @@ async def to_code(config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + cg.add(var.set_publish_nan_as_none(config[CONF_PUBLISH_NAN_AS_NONE])) + MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema( { diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 106192c0e3..c7ace505a8 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -608,6 +608,10 @@ void MQTTClientComponent::set_log_message_template(MQTTMessage &&message) { this const MQTTDiscoveryInfo &MQTTClientComponent::get_discovery_info() const { return this->discovery_info_; } void MQTTClientComponent::set_topic_prefix(const std::string &topic_prefix) { this->topic_prefix_ = topic_prefix; } const std::string &MQTTClientComponent::get_topic_prefix() const { return this->topic_prefix_; } +void MQTTClientComponent::set_publish_nan_as_none(bool publish_nan_as_none) { + this->publish_nan_as_none_ = publish_nan_as_none; +} +bool MQTTClientComponent::is_publish_nan_as_none() const { return this->publish_nan_as_none_; } void MQTTClientComponent::disable_birth_message() { this->birth_message_.topic = ""; this->recalculate_availability_(); diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 7ae3a6c5e8..34eac29464 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -263,6 +263,10 @@ class MQTTClientComponent : public Component { void set_on_connect(mqtt_on_connect_callback_t &&callback); void set_on_disconnect(mqtt_on_disconnect_callback_t &&callback); + // Publish None state instead of NaN for Home Assistant + void set_publish_nan_as_none(bool publish_nan_as_none); + bool is_publish_nan_as_none() const; + protected: void send_device_info_(); @@ -328,6 +332,8 @@ class MQTTClientComponent : public Component { uint32_t connect_begin_; uint32_t last_connected_{0}; optional disconnect_reason_{}; + + bool publish_nan_as_none_{false}; }; extern MQTTClientComponent *global_mqtt_client; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index fff75a3c00..2cbc291ccf 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -69,6 +69,8 @@ bool MQTTSensorComponent::send_initial_state() { } } bool MQTTSensorComponent::publish_state(float value) { + if (mqtt::global_mqtt_client->is_publish_nan_as_none() && std::isnan(value)) + return this->publish(this->get_state_topic_(), "None"); int8_t accuracy = this->sensor_->get_accuracy_decimals(); return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); } diff --git a/esphome/const.py b/esphome/const.py index d2df83aa43..3d3bfcc244 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -692,6 +692,7 @@ CONF_PRIORITY = "priority" CONF_PROJECT = "project" CONF_PROTOCOL = "protocol" CONF_PUBLISH_INITIAL_STATE = "publish_initial_state" +CONF_PUBLISH_NAN_AS_NONE = "publish_nan_as_none" CONF_PULL_MODE = "pull_mode" CONF_PULLDOWN = "pulldown" CONF_PULLUP = "pullup" diff --git a/tests/components/mqtt/common.yaml b/tests/components/mqtt/common.yaml index d22fe9579f..a4bdf58809 100644 --- a/tests/components/mqtt/common.yaml +++ b/tests/components/mqtt/common.yaml @@ -60,6 +60,7 @@ mqtt: - mqtt.publish: topic: some/topic payload: Good-bye + publish_nan_as_none: false binary_sensor: - platform: template From dc5942a59b521781279ff568057adf1dc01f11c8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 3 Dec 2024 18:38:44 +1300 Subject: [PATCH 09/40] [ble] Allow setting shorter name for ble advertisements (#7867) Co-authored-by: Keith Burzinski --- esphome/components/esp32_ble/__init__.py | 24 +++++++++++++++++-- esphome/components/esp32_ble/ble.cpp | 18 ++++++++++---- esphome/components/esp32_ble/ble.h | 2 ++ .../components/esp32_ble/ble_advertising.cpp | 2 +- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 75cf9d707d..08e30c9247 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -2,8 +2,10 @@ from esphome import automation import esphome.codegen as cg from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant import esphome.config_validation as cv -from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ID +from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ESPHOME, CONF_ID, CONF_NAME from esphome.core import CORE +from esphome.core.config import CONF_NAME_ADD_MAC_SUFFIX +import esphome.final_validate as fv DEPENDENCIES = ["esp32"] CODEOWNERS = ["@jesserockz", "@Rapsssito"] @@ -50,6 +52,7 @@ TX_POWER_LEVELS = { CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(ESP32BLE), + cv.Optional(CONF_NAME): cv.All(cv.string, cv.Length(max=20)), cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum( IO_CAPABILITY, lower=True ), @@ -67,7 +70,22 @@ def validate_variant(_): raise cv.Invalid(f"{variant} does not support Bluetooth") -FINAL_VALIDATE_SCHEMA = validate_variant +def final_validation(config): + validate_variant(config) + if (name := config.get(CONF_NAME)) is not None: + full_config = fv.full_config.get() + max_length = 20 + if full_config[CONF_ESPHOME][CONF_NAME_ADD_MAC_SUFFIX]: + max_length -= 7 # "-AABBCC" is appended when add mac suffix option is used + if len(name) > max_length: + raise cv.Invalid( + f"Name '{name}' is too long, maximum length is {max_length} characters" + ) + + return config + + +FINAL_VALIDATE_SCHEMA = final_validation async def to_code(config): @@ -75,6 +93,8 @@ async def to_code(config): cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT])) cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY])) cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME])) + if (name := config.get(CONF_NAME)) is not None: + cg.add(var.set_name(name)) await cg.register_component(var, config) if CORE.using_esp_idf: diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 5d08b6e973..d7dcf93f86 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -188,12 +188,20 @@ bool ESP32BLE::ble_setup_() { } } - std::string name = App.get_name(); - if (name.length() > 20) { + std::string name; + if (this->name_.has_value()) { + name = this->name_.value(); if (App.is_name_add_mac_suffix_enabled()) { - name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address - } else { - name = name.substr(0, 20); + name += "-" + get_mac_address().substr(6); + } + } else { + name = App.get_name(); + if (name.length() > 20) { + if (App.is_name_add_mac_suffix_enabled()) { + name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address + } else { + name = name.substr(0, 20); + } } } diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 7c55583852..ed7575f128 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -90,6 +90,7 @@ class ESP32BLE : public Component { void loop() override; void dump_config() override; float get_setup_priority() const override; + void set_name(const std::string &name) { this->name_ = name; } void advertising_start(); void advertising_set_service_data(const std::vector &data); @@ -131,6 +132,7 @@ class ESP32BLE : public Component { esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; uint32_t advertising_cycle_time_; bool enable_on_boot_; + optional name_; }; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index 1d340c76d9..92b7c60368 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -83,7 +83,7 @@ esp_err_t BLEAdvertising::services_advertisement_() { esp_err_t err; this->advertising_data_.set_scan_rsp = false; - this->advertising_data_.include_name = !this->scan_response_; + this->advertising_data_.include_name = true; this->advertising_data_.include_txpower = !this->scan_response_; err = esp_ble_gap_config_adv_data(&this->advertising_data_); if (err != ESP_OK) { From c95887a14a61c8a5b9cab909ee9f9493eeef8cc3 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:50:11 +1100 Subject: [PATCH 10/40] [lvgl] Bugfixes (#7896) --- esphome/components/lvgl/defines.py | 2 +- esphome/components/lvgl/lvgl_esphome.h | 3 +++ esphome/components/lvgl/widgets/line.py | 6 ++++++ tests/components/lvgl/lvgl-package.yaml | 4 ++-- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 81984637bd..5371f110a6 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -38,7 +38,7 @@ def literal(arg): def call_lambda(lamb: LambdaExpression): expr = lamb.content.strip() if expr.startswith("return") and expr.endswith(";"): - return expr[7:][:-1] + return expr[6:][:-1].strip() return f"{lamb}()" diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index 921b7c109f..56413ad77e 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -56,6 +56,9 @@ static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BIT inline void lv_img_set_src(lv_obj_t *obj, esphome::image::Image *image) { lv_img_set_src(obj, image->get_lv_img_dsc()); } +inline void lv_disp_set_bg_image(lv_disp_t *disp, esphome::image::Image *image) { + lv_disp_set_bg_image(disp, image->get_lv_img_dsc()); +} #endif // USE_LVGL_IMAGE #ifdef USE_LVGL_ANIMIMG inline void lv_animimg_set_src(lv_obj_t *img, std::vector images) { diff --git a/esphome/components/lvgl/widgets/line.py b/esphome/components/lvgl/widgets/line.py index 548dfa8452..0156fb1780 100644 --- a/esphome/components/lvgl/widgets/line.py +++ b/esphome/components/lvgl/widgets/line.py @@ -35,6 +35,11 @@ LINE_SCHEMA = { cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t), } +LINE_MODIFY_SCHEMA = { + cv.Optional(CONF_POINTS): cv_point_list, + cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t), +} + class LineType(WidgetType): def __init__(self): @@ -43,6 +48,7 @@ class LineType(WidgetType): LvType("lv_line_t"), (CONF_MAIN,), LINE_SCHEMA, + modify_schema=LINE_MODIFY_SCHEMA, ) async def to_code(self, w: Widget, config): diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 81b18c4ff8..6611209756 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -337,7 +337,7 @@ lvgl: id: button_button width: 20% height: 10% - transform_angle: !lambda return 180*100; + transform_angle: !lambda return(180*100); arc_width: !lambda return 4; border_width: !lambda return 6; shadow_ofs_x: !lambda return 6; @@ -581,7 +581,7 @@ lvgl: - 180, 60 - 240, 10 on_click: - - lvgl.widget.update: + - lvgl.line.update: id: lv_line_id line_color: 0xFFFF - lvgl.page.next: From 00ddb0a427b2cae4a0bda8dfc26066e6857d8e6d Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:50:56 +1100 Subject: [PATCH 11/40] [font] Restore correct default glyphs for bitmap fonts (#7907) --- esphome/components/font/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 1b4fe4bff0..c397ba8306 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -373,7 +373,9 @@ def font_file_schema(value): # Default if no glyphs or glyphsets are provided DEFAULT_GLYPHSET = "GF_Latin_Kernel" # default for bitmap fonts -DEFAULT_GLYPHS = ' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz' +DEFAULT_GLYPHS = ( + ' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' +) CONF_RAW_GLYPH_ID = "raw_glyph_id" From a37ff2dbd97d17c5b9b522c2a4515fd8311b7003 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 4 Dec 2024 07:48:50 +1100 Subject: [PATCH 12/40] [lvgl] Fix msgbox content (#7912) --- esphome/components/lvgl/widgets/msgbox.py | 3 ++- tests/components/lvgl/lvgl-package.yaml | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/components/lvgl/widgets/msgbox.py b/esphome/components/lvgl/widgets/msgbox.py index be0f2100d7..c3393940b6 100644 --- a/esphome/components/lvgl/widgets/msgbox.py +++ b/esphome/components/lvgl/widgets/msgbox.py @@ -29,7 +29,7 @@ from ..lvcode import ( ) from ..schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema, part_schema from ..types import LV_EVENT, char_ptr, lv_obj_t -from . import Widget, set_obj_properties +from . import Widget, add_widgets, set_obj_properties from .button import button_spec from .buttonmatrix import ( BUTTONMATRIX_BUTTON_SCHEMA, @@ -119,6 +119,7 @@ async def msgbox_to_code(top_layer, conf): button_style = {CONF_ITEMS: button_style} await set_obj_properties(buttonmatrix_widget, button_style) await set_obj_properties(msgbox_widget, conf) + await add_widgets(msgbox_widget, conf) async with LambdaContext(EVENT_ARG, where=messagebox_id) as close_action: outer_widget.add_flag("LV_OBJ_FLAG_HIDDEN") if close_button: diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 6611209756..4b7e13db91 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -109,6 +109,10 @@ lvgl: close_button: true title: Messagebox bg_color: 0xffff + widgets: + - label: + text: Hello Msgbox + id: msgbox_label body: text: This is a sample messagebox bg_color: 0x808080 @@ -137,6 +141,9 @@ lvgl: - lvgl.widget.focus: mark - lvgl.widget.redraw: hello_label - lvgl.widget.redraw: + - lvgl.label.update: + id: msgbox_label + text: Unloaded on_all_events: logger.log: format: "Event %s" From d00ec7e544f26b3ec3844f531f4027b9947c8d14 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Tue, 3 Dec 2024 17:23:17 -0600 Subject: [PATCH 13/40] [helpers] clang-tidy fix for #7706 (#7909) --- esphome/core/helpers.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 103173095b..4e8caeae99 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -259,10 +259,15 @@ bool random_bytes(uint8_t *data, size_t len) { bool str_equals_case_insensitive(const std::string &a, const std::string &b) { return strcasecmp(a.c_str(), b.c_str()) == 0; } +#if ESP_IDF_VERSION_MAJOR >= 5 +bool str_startswith(const std::string &str, const std::string &start) { return str.starts_with(start); } +bool str_endswith(const std::string &str, const std::string &end) { return str.ends_with(end); } +#else bool str_startswith(const std::string &str, const std::string &start) { return str.rfind(start, 0) == 0; } bool str_endswith(const std::string &str, const std::string &end) { return str.rfind(end) == (str.size() - end.size()); } +#endif std::string str_truncate(const std::string &str, size_t length) { return str.length() > length ? str.substr(0, length) : str; } From dbed74b50d76cb7a6feb903908018aa3b96b9825 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Tue, 3 Dec 2024 17:26:27 -0600 Subject: [PATCH 14/40] [docker] Fix clang-tidy installation (#7910) --- docker/Dockerfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index c53856d0e8..cc05849271 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -220,7 +220,11 @@ RUN \ nano=7.2-1+deb12u1 \ build-essential=12.9 \ python3-dev=3.11.2-1+b1 \ - && rm -rf \ + && if [ "$TARGETARCH$TARGETVARIANT" != "armv7" ]; then \ + # move this up after armv7 is retired + apt-get install -y --no-install-recommends clang-tidy-18=1:18.1.8~++20240731024826+3b5b5c1ec4a3-1~exp1~20240731144843.145 ; \ + fi; \ + rm -rf \ /tmp/* \ /var/{cache,log}/* \ /var/lib/apt/lists/* @@ -228,9 +232,6 @@ RUN \ COPY requirements_test.txt / RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ - else \ - # move this up into RUN above after armv7 is retired - apt-get install -y --no-install-recommends clang-tidy-18=1:18.1.8~++20240731024826+3b5b5c1ec4a3-1~exp1~20240731144843.145 ; \ fi; \ pip3 install \ --break-system-packages --no-cache-dir -r /requirements_test.txt From 79478cdb8a2731c23861da1c607e332117359179 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:13:07 +1100 Subject: [PATCH 15/40] [sntp] Resolve warnings from ESP-IDF 5.x (#7913) --- esphome/components/sntp/sntp_component.cpp | 34 +++++++++------------- esphome/components/sntp/sntp_component.h | 15 ++++------ esphome/components/sntp/time.py | 11 +++---- 3 files changed, 24 insertions(+), 36 deletions(-) diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 4ded98d483..21add1451d 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -9,11 +9,6 @@ #include "lwip/apps/sntp.h" #endif -// Yes, the server names are leaked, but that's fine. -#ifdef CLANG_TIDY -#define strdup(x) (const_cast(x)) -#endif - namespace esphome { namespace sntp { @@ -26,30 +21,29 @@ void SNTPComponent::setup() { esp_sntp_stop(); } esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL); + size_t i = 0; + for (auto &server : this->servers_) { + esp_sntp_setservername(i++, server.c_str()); + } + esp_sntp_set_sync_interval(this->get_update_interval()); + esp_sntp_init(); #else sntp_stop(); sntp_setoperatingmode(SNTP_OPMODE_POLL); -#endif - sntp_setservername(0, strdup(this->server_1_.c_str())); - if (!this->server_2_.empty()) { - sntp_setservername(1, strdup(this->server_2_.c_str())); + size_t i = 0; + for (auto &server : this->servers_) { + sntp_setservername(i++, server.c_str()); } - if (!this->server_3_.empty()) { - sntp_setservername(2, strdup(this->server_3_.c_str())); - } -#ifdef USE_ESP_IDF - esp_sntp_set_sync_interval(this->get_update_interval()); -#endif - sntp_init(); +#endif } void SNTPComponent::dump_config() { ESP_LOGCONFIG(TAG, "SNTP Time:"); - ESP_LOGCONFIG(TAG, " Server 1: '%s'", this->server_1_.c_str()); - ESP_LOGCONFIG(TAG, " Server 2: '%s'", this->server_2_.c_str()); - ESP_LOGCONFIG(TAG, " Server 3: '%s'", this->server_3_.c_str()); - ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); + size_t i = 0; + for (auto &server : this->servers_) { + ESP_LOGCONFIG(TAG, " Server %zu: '%s'", i++, server.c_str()); + } } void SNTPComponent::update() { #if !defined(USE_ESP_IDF) diff --git a/esphome/components/sntp/sntp_component.h b/esphome/components/sntp/sntp_component.h index 987dd23a19..a4e8267383 100644 --- a/esphome/components/sntp/sntp_component.h +++ b/esphome/components/sntp/sntp_component.h @@ -14,23 +14,20 @@ namespace sntp { /// \see https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html class SNTPComponent : public time::RealTimeClock { public: + SNTPComponent(const std::vector &servers) : servers_(servers) {} + + // Note: set_servers() has been removed and replaced by a constructor - calling set_servers after setup would + // have had no effect anyway, and making the strings immutable avoids the need to strdup their contents. + void setup() override; void dump_config() override; - /// Change the servers used by SNTP for timekeeping - void set_servers(const std::string &server_1, const std::string &server_2, const std::string &server_3) { - this->server_1_ = server_1; - this->server_2_ = server_2; - this->server_3_ = server_3; - } float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; } void update() override; void loop() override; protected: - std::string server_1_; - std::string server_2_; - std::string server_3_; + std::vector servers_; bool has_time_{false}; }; diff --git a/esphome/components/sntp/time.py b/esphome/components/sntp/time.py index 7cc82e3dff..6f883d5bed 100644 --- a/esphome/components/sntp/time.py +++ b/esphome/components/sntp/time.py @@ -1,16 +1,16 @@ +import esphome.codegen as cg from esphome.components import time as time_ import esphome.config_validation as cv -import esphome.codegen as cg -from esphome.core import CORE from esphome.const import ( CONF_ID, CONF_SERVERS, + PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, PLATFORM_RTL87XX, - PLATFORM_BK72XX, ) +from esphome.core import CORE DEPENDENCIES = ["network"] sntp_ns = cg.esphome_ns.namespace("sntp") @@ -40,11 +40,8 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - servers = config[CONF_SERVERS] - servers += [""] * (3 - len(servers)) - cg.add(var.set_servers(*servers)) + var = cg.new_Pvariable(config[CONF_ID], servers) await cg.register_component(var, config) await time_.register_time(var, config) From 016fac249649b475a918eb2c17f9daa0ad399485 Mon Sep 17 00:00:00 2001 From: mikosoft83 <63317931+mikosoft83@users.noreply.github.com> Date: Wed, 4 Dec 2024 01:18:00 +0100 Subject: [PATCH 16/40] Add strftime variant with background color (#7714) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/display/display.cpp | 14 +++++++++----- esphome/components/display/display.h | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/esphome/components/display/display.cpp b/esphome/components/display/display.cpp index 145a4f5278..1d996bd59b 100644 --- a/esphome/components/display/display.cpp +++ b/esphome/components/display/display.cpp @@ -662,20 +662,24 @@ void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) { if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to)) this->trigger(from, to); } -void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) { +void Display::strftime(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format, + ESPTime time) { char buffer[64]; size_t ret = time.strftime(buffer, sizeof(buffer), format); if (ret > 0) - this->print(x, y, font, color, align, buffer); + this->print(x, y, font, color, align, buffer, background); +} +void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) { + this->strftime(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, time); } void Display::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) { - this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time); + this->strftime(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, time); } void Display::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) { - this->strftime(x, y, font, COLOR_ON, align, format, time); + this->strftime(x, y, font, COLOR_ON, COLOR_OFF, align, format, time); } void Display::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) { - this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time); + this->strftime(x, y, font, COLOR_ON, COLOR_OFF, TextAlign::TOP_LEFT, format, time); } void Display::start_clipping(Rect rect) { diff --git a/esphome/components/display/display.h b/esphome/components/display/display.h index 54e897cdec..43da08f4ac 100644 --- a/esphome/components/display/display.h +++ b/esphome/components/display/display.h @@ -437,6 +437,20 @@ class Display : public PollingComponent { */ void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6))); + /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. + * + * @param x The x coordinate of the text alignment anchor point. + * @param y The y coordinate of the text alignment anchor point. + * @param font The font to draw the text with. + * @param color The color to draw the text with. + * @param background The background color to draw the text with. + * @param align The alignment of the text. + * @param format The format to use. + * @param ... The arguments to use for the text formatting. + */ + void strftime(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format, + ESPTime time) __attribute__((format(strftime, 8, 0))); + /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. * * @param x The x coordinate of the text alignment anchor point. From 472402745d2f3821802bbdadf2ebc45d2ee6ccf5 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Wed, 4 Dec 2024 16:18:14 -0500 Subject: [PATCH 17/40] [i2s_audio] Bugfix: Follow configured bits per sample (#7916) --- .../i2s_audio/speaker/i2s_audio_speaker.cpp | 123 +++++++++--------- .../i2s_audio/speaker/i2s_audio_speaker.h | 25 ++-- 2 files changed, 70 insertions(+), 78 deletions(-) diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index 53b3cc8dc0..194cc06a60 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -33,14 +33,15 @@ enum SpeakerEventGroupBits : uint32_t { STATE_RUNNING = (1 << 11), STATE_STOPPING = (1 << 12), STATE_STOPPED = (1 << 13), - ERR_INVALID_FORMAT = (1 << 14), - ERR_TASK_FAILED_TO_START = (1 << 15), - ERR_ESP_INVALID_STATE = (1 << 16), + ERR_TASK_FAILED_TO_START = (1 << 14), + ERR_ESP_INVALID_STATE = (1 << 15), + ERR_ESP_NOT_SUPPORTED = (1 << 16), ERR_ESP_INVALID_ARG = (1 << 17), ERR_ESP_INVALID_SIZE = (1 << 18), ERR_ESP_NO_MEM = (1 << 19), ERR_ESP_FAIL = (1 << 20), - ALL_ERR_ESP_BITS = ERR_ESP_INVALID_STATE | ERR_ESP_INVALID_ARG | ERR_ESP_INVALID_SIZE | ERR_ESP_NO_MEM | ERR_ESP_FAIL, + ALL_ERR_ESP_BITS = ERR_ESP_INVALID_STATE | ERR_ESP_NOT_SUPPORTED | ERR_ESP_INVALID_ARG | ERR_ESP_INVALID_SIZE | + ERR_ESP_NO_MEM | ERR_ESP_FAIL, ALL_BITS = 0x00FFFFFF, // All valid FreeRTOS event group bits }; @@ -55,6 +56,8 @@ static esp_err_t err_bit_to_esp_err(uint32_t bit) { return ESP_ERR_INVALID_SIZE; case SpeakerEventGroupBits::ERR_ESP_NO_MEM: return ESP_ERR_NO_MEM; + case SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED: + return ESP_ERR_NOT_SUPPORTED; default: return ESP_FAIL; } @@ -135,19 +138,19 @@ void I2SAudioSpeaker::loop() { xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START); } - if (event_group_bits & SpeakerEventGroupBits::ERR_INVALID_FORMAT) { + if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) { + uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS; + ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits))); + this->status_set_warning(); + } + + if (event_group_bits & SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED) { this->status_set_error("Failed to adjust I2S bus to match the incoming audio"); ESP_LOGE(TAG, "Incompatible audio format: sample rate = %" PRIu32 ", channels = %" PRIu8 ", bits per sample = %" PRIu8, this->audio_stream_info_.sample_rate, this->audio_stream_info_.channels, this->audio_stream_info_.bits_per_sample); } - - if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) { - uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS; - ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits))); - this->status_set_warning(); - } } void I2SAudioSpeaker::set_volume(float volume) { @@ -236,13 +239,14 @@ void I2SAudioSpeaker::speaker_task(void *params) { xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STARTING); audio::AudioStreamInfo audio_stream_info = this_speaker->audio_stream_info_; - const ssize_t bytes_per_sample = audio_stream_info.get_bytes_per_sample(); - const uint8_t number_of_channels = audio_stream_info.channels; - const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * this_speaker->sample_rate_ / 1000 * - bytes_per_sample * number_of_channels; - const size_t ring_buffer_size = - this_speaker->buffer_duration_ms_ * this_speaker->sample_rate_ / 1000 * bytes_per_sample * number_of_channels; + const uint32_t bytes_per_ms = + audio_stream_info.channels * audio_stream_info.get_bytes_per_sample() * audio_stream_info.sample_rate / 1000; + + const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * bytes_per_ms; + + // Ensure ring buffer is at least as large as the total size of the DMA buffers + const size_t ring_buffer_size = std::min(dma_buffers_size, this_speaker->buffer_duration_ms_ * bytes_per_ms); if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) { // Failed to allocate buffers @@ -250,14 +254,7 @@ void I2SAudioSpeaker::speaker_task(void *params) { this_speaker->delete_task_(dma_buffers_size); } - if (this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_())) { - // Failed to start I2S driver - this_speaker->delete_task_(dma_buffers_size); - } - - if (!this_speaker->send_esp_err_to_event_group_(this_speaker->reconfigure_i2s_stream_info_(audio_stream_info))) { - // Successfully set the I2S stream info, ready to write audio data to the I2S port - + if (!this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_(audio_stream_info))) { xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_RUNNING); bool stop_gracefully = false; @@ -275,6 +272,12 @@ void I2SAudioSpeaker::speaker_task(void *params) { stop_gracefully = true; } + if (this_speaker->audio_stream_info_ != audio_stream_info) { + // Audio stream info has changed, stop the speaker task so it will restart with the proper settings. + + break; + } + i2s_event_t i2s_event; while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) { if (i2s_event.type == I2S_EVENT_TX_Q_OVF) { @@ -316,17 +319,14 @@ void I2SAudioSpeaker::speaker_task(void *params) { } } } - } else { - // Couldn't configure the I2S port to be compatible with the incoming audio - xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::ERR_INVALID_FORMAT); + + xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING); + + i2s_driver_uninstall(this_speaker->parent_->get_port()); + + this_speaker->parent_->unlock(); } - i2s_zero_dma_buffer(this_speaker->parent_->get_port()); - xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING); - - i2s_driver_uninstall(this_speaker->parent_->get_port()); - - this_speaker->parent_->unlock(); this_speaker->delete_task_(dma_buffers_size); } @@ -382,6 +382,9 @@ bool I2SAudioSpeaker::send_esp_err_to_event_group_(esp_err_t err) { case ESP_ERR_NO_MEM: xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM); return true; + case ESP_ERR_NOT_SUPPORTED: + xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED); + return true; default: xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_FAIL); return true; @@ -411,18 +414,40 @@ esp_err_t I2SAudioSpeaker::allocate_buffers_(size_t data_buffer_size, size_t rin return ESP_OK; } -esp_err_t I2SAudioSpeaker::start_i2s_driver_() { +esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info) { + if ((this->i2s_mode_ & I2S_MODE_SLAVE) && (this->sample_rate_ != audio_stream_info.sample_rate)) { // NOLINT + // Can't reconfigure I2S bus, so the sample rate must match the configured value + return ESP_ERR_NOT_SUPPORTED; + } + + if ((i2s_bits_per_sample_t) audio_stream_info.bits_per_sample > this->bits_per_sample_) { + // Currently can't handle the case when the incoming audio has more bits per sample than the configured value + return ESP_ERR_NOT_SUPPORTED; + } + if (!this->parent_->try_lock()) { return ESP_ERR_INVALID_STATE; } + i2s_channel_fmt_t channel = this->channel_; + + if (audio_stream_info.channels == 1) { + if (this->channel_ == I2S_CHANNEL_FMT_ONLY_LEFT) { + channel = I2S_CHANNEL_FMT_ONLY_LEFT; + } else { + channel = I2S_CHANNEL_FMT_ONLY_RIGHT; + } + } else if (audio_stream_info.channels == 2) { + channel = I2S_CHANNEL_FMT_RIGHT_LEFT; + } + int dma_buffer_length = DMA_BUFFER_DURATION_MS * this->sample_rate_ / 1000; i2s_driver_config_t config = { .mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_TX), - .sample_rate = this->sample_rate_, + .sample_rate = audio_stream_info.sample_rate, .bits_per_sample = this->bits_per_sample_, - .channel_format = this->channel_, + .channel_format = channel, .communication_format = this->i2s_comm_fmt_, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = DMA_BUFFERS_COUNT, @@ -477,30 +502,6 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_() { return err; } -esp_err_t I2SAudioSpeaker::reconfigure_i2s_stream_info_(audio::AudioStreamInfo &audio_stream_info) { - if (this->i2s_mode_ & I2S_MODE_MASTER) { - // ESP controls for the the I2S bus, so adjust the sample rate and bits per sample to match the incoming audio - this->sample_rate_ = audio_stream_info.sample_rate; - this->bits_per_sample_ = (i2s_bits_per_sample_t) audio_stream_info.bits_per_sample; - } else if (this->sample_rate_ != audio_stream_info.sample_rate) { - // Can't reconfigure I2S bus, so the sample rate must match the configured value - return ESP_ERR_INVALID_ARG; - } - - if ((i2s_bits_per_sample_t) audio_stream_info.bits_per_sample > this->bits_per_sample_) { - // Currently can't handle the case when the incoming audio has more bits per sample than the configured value - return ESP_ERR_INVALID_ARG; - } - - if (audio_stream_info.channels == 1) { - return i2s_set_clk(this->parent_->get_port(), this->sample_rate_, this->bits_per_sample_, I2S_CHANNEL_MONO); - } else if (audio_stream_info.channels == 2) { - return i2s_set_clk(this->parent_->get_port(), this->sample_rate_, this->bits_per_sample_, I2S_CHANNEL_STEREO); - } - - return ESP_ERR_INVALID_ARG; -} - void I2SAudioSpeaker::delete_task_(size_t buffer_size) { this->audio_ring_buffer_.reset(); // Releases onwership of the shared_ptr diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h index 2b90f39399..d706deb0f4 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h @@ -91,24 +91,15 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp esp_err_t allocate_buffers_(size_t data_buffer_size, size_t ring_buffer_size); /// @brief Starts the ESP32 I2S driver. - /// Attempts to lock the I2S port, starts the I2S driver, and sets the data out pin. If it fails, it will unlock - /// the I2S port and uninstall the driver, if necessary. - /// @return ESP_ERR_INVALID_STATE if the I2S port is already locked. - /// ESP_ERR_INVALID_ARG if installing the driver or setting the data out pin fails due to a parameter error. + /// Attempts to lock the I2S port, starts the I2S driver using the passed in stream information, and sets the data out + /// pin. If it fails, it will unlock the I2S port and uninstall the driver, if necessary. + /// @param audio_stream_info Stream information for the I2S driver. + /// @return ESP_ERR_NOT_ALLOWED if the I2S port can't play the incoming audio stream. + /// ESP_ERR_INVALID_STATE if the I2S port is already locked. + /// ESP_ERR_INVALID_ARG if nstalling the driver or setting the data outpin fails due to a parameter error. /// ESP_ERR_NO_MEM if the driver fails to install due to a memory allocation error. - /// ESP_FAIL if setting the data out pin fails due to an IO error - /// ESP_OK if successful - esp_err_t start_i2s_driver_(); - - /// @brief Adjusts the I2S driver configuration to match the incoming audio stream. - /// Modifies I2S driver's sample rate, bits per sample, and number of channel settings. If the I2S is in secondary - /// mode, it only modifies the number of channels. - /// @param audio_stream_info Describes the incoming audio stream - /// @return ESP_ERR_INVALID_ARG if there is a parameter error, if there is more than 2 channels in the stream, or if - /// the audio settings are incompatible with the configuration. - /// ESP_ERR_NO_MEM if the driver fails to reconfigure due to a memory allocation error. - /// ESP_OK if successful. - esp_err_t reconfigure_i2s_stream_info_(audio::AudioStreamInfo &audio_stream_info); + /// ESP_FAIL if setting the data out pin fails due to an IO error ESP_OK if successful + esp_err_t start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info); /// @brief Deletes the speaker's task. /// Deallocates the data_buffer_ and audio_ring_buffer_, if necessary, and deletes the task. Should only be called by From d429aa8bb8d9c8375996c0ab2335d66132cb3472 Mon Sep 17 00:00:00 2001 From: Pavlo Dudnytskyi Date: Wed, 4 Dec 2024 22:43:00 +0100 Subject: [PATCH 18/40] Haier AC quiet mode switch fix (#7902) --- esphome/components/haier/hon_climate.cpp | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/esphome/components/haier/hon_climate.cpp b/esphome/components/haier/hon_climate.cpp index 85e9cf37b9..c95a87223d 100644 --- a/esphome/components/haier/hon_climate.cpp +++ b/esphome/components/haier/hon_climate.cpp @@ -35,7 +35,9 @@ void HonClimate::set_beeper_state(bool state) { if (state != this->settings_.beeper_state) { this->settings_.beeper_state = state; #ifdef USE_SWITCH - this->beeper_switch_->publish_state(state); + if (this->beeper_switch_ != nullptr) { + this->beeper_switch_->publish_state(state); + } #endif this->hon_rtc_.save(&this->settings_); } @@ -45,10 +47,17 @@ bool HonClimate::get_beeper_state() const { return this->settings_.beeper_state; void HonClimate::set_quiet_mode_state(bool state) { if (state != this->get_quiet_mode_state()) { - this->quiet_mode_state_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF; + if ((this->mode != ClimateMode::CLIMATE_MODE_OFF) && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) { + this->quiet_mode_state_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF; + this->force_send_control_ = true; + } else { + this->quiet_mode_state_ = state ? SwitchState::ON : SwitchState::OFF; + } this->settings_.quiet_mode_state = state; #ifdef USE_SWITCH - this->quiet_mode_switch_->publish_state(state); + if (this->quiet_mode_switch_ != nullptr) { + this->quiet_mode_switch_->publish_state(state); + } #endif this->hon_rtc_.save(&this->settings_); } @@ -509,7 +518,7 @@ void HonClimate::initialization() { } this->current_vertical_swing_ = this->settings_.last_vertiacal_swing; this->current_horizontal_swing_ = this->settings_.last_horizontal_swing; - this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF; + this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::ON : SwitchState::OFF; } haier_protocol::HaierMessage HonClimate::get_control_message() { @@ -932,7 +941,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * if (this->mode == CLIMATE_MODE_OFF) { // AC just turned on from remote need to turn off display this->force_send_control_ = true; - } else if ((((uint8_t) this->health_mode_) & 0b10) == 0) { + } else if ((((uint8_t) this->display_status_) & 0b10) == 0) { this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF; } } @@ -1004,6 +1013,11 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * if (new_quiet_mode != this->get_quiet_mode_state()) { this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF; this->settings_.quiet_mode_state = new_quiet_mode; +#ifdef USE_SWITCH + if (this->quiet_mode_switch_ != nullptr) { + this->quiet_mode_switch_->publish_state(new_quiet_mode); + } +#endif // USE_SWITCH this->hon_rtc_.save(&this->settings_); } } From 4e839d42d050f3d6c83e045512fcf5bfab8d27ac Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Wed, 4 Dec 2024 22:44:34 +0100 Subject: [PATCH 19/40] [CI] Update clang-tidy to 18.1.8 (#7915) --- requirements_dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index 2ef8330215..1a98a15ab2 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,4 +1,4 @@ # Useful stuff when working in a development environment clang-format==13.0.1 # also change in .pre-commit-config.yaml and Dockerfile when updating -clang-tidy==18.1.3 # When updating clang-tidy, also update Dockerfile +clang-tidy==18.1.8 # When updating clang-tidy, also update Dockerfile yamllint==1.35.1 # also change in .pre-commit-config.yaml when updating From ece72c6b189e130bdc8a7dee7920abaa04cb61d9 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Wed, 4 Dec 2024 21:03:38 -0600 Subject: [PATCH 20/40] [i2s_audio] Speaker type fix (#7919) --- esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index 194cc06a60..d2a582c2cc 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -246,7 +246,8 @@ void I2SAudioSpeaker::speaker_task(void *params) { const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * bytes_per_ms; // Ensure ring buffer is at least as large as the total size of the DMA buffers - const size_t ring_buffer_size = std::min(dma_buffers_size, this_speaker->buffer_duration_ms_ * bytes_per_ms); + const size_t ring_buffer_size = + std::min((uint32_t) dma_buffers_size, this_speaker->buffer_duration_ms_ * bytes_per_ms); if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) { // Failed to allocate buffers From f3cc1e541a904d5ef44b431300e5457e4d782dd5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:44:59 +1300 Subject: [PATCH 21/40] [esp32_rmt_led_strip] Add ``COMPONENT_SCHEMA`` extending (#7918) --- esphome/components/esp32_rmt_led_strip/light.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_rmt_led_strip/light.py b/esphome/components/esp32_rmt_led_strip/light.py index 79f339e248..976f70e858 100644 --- a/esphome/components/esp32_rmt_led_strip/light.py +++ b/esphome/components/esp32_rmt_led_strip/light.py @@ -1,9 +1,9 @@ from dataclasses import dataclass -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins +import esphome.codegen as cg from esphome.components import esp32_rmt, light +import esphome.config_validation as cv from esphome.const import ( CONF_CHIPSET, CONF_IS_RGBW, @@ -103,7 +103,7 @@ CONFIG_SCHEMA = cv.All( default="0 us", ): cv.positive_time_period_nanoseconds, } - ), + ).extend(cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH), ) From acc8d24a325b34cd4af035bb2e26b26f1d6e4f83 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Thu, 5 Dec 2024 02:39:30 -0600 Subject: [PATCH 22/40] [esp32] Use pioarduino + IDF 5.1.5 as default for IDF builds (#7706) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .clang-tidy | 1 + esphome/components/esp32/__init__.py | 106 ++++++++++++++++-- platformio.ini | 4 +- ...build_components_base.esp32-c3-idf-51.yaml | 19 ---- .../build_components_base.esp32-idf-51.yaml | 19 ---- ...build_components_base.esp32-s2-idf-51.yaml | 20 ---- ...build_components_base.esp32-s3-idf-51.yaml | 20 ---- 7 files changed, 97 insertions(+), 92 deletions(-) delete mode 100644 tests/test_build_components/build_components_base.esp32-c3-idf-51.yaml delete mode 100644 tests/test_build_components/build_components_base.esp32-idf-51.yaml delete mode 100644 tests/test_build_components/build_components_base.esp32-s2-idf-51.yaml delete mode 100644 tests/test_build_components/build_components_base.esp32-s3-idf-51.yaml diff --git a/.clang-tidy b/.clang-tidy index 8dba033e2d..5878028f48 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -21,6 +21,7 @@ Checks: >- -clang-analyzer-osx.*, -clang-diagnostic-delete-abstract-non-virtual-dtor, -clang-diagnostic-delete-non-abstract-non-virtual-dtor, + -clang-diagnostic-deprecated-declarations, -clang-diagnostic-ignored-optimization-argument, -clang-diagnostic-missing-field-initializers, -clang-diagnostic-shadow-field, diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 61fbb53e3a..580c3fc081 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -65,6 +65,8 @@ _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@esphome/core"] AUTO_LOAD = ["preferences"] +CONF_RELEASE = "release" + def set_core_data(config): CORE.data[KEY_ESP32] = {} @@ -216,11 +218,17 @@ def _format_framework_arduino_version(ver: cv.Version) -> str: return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" -def _format_framework_espidf_version(ver: cv.Version) -> str: +def _format_framework_espidf_version( + ver: cv.Version, release: str, for_platformio: bool +) -> str: # format the given arduino (https://github.com/espressif/esp-idf/releases) version to # a PIO platformio/framework-espidf value # List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf - return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + if for_platformio: + return f"platformio/framework-espidf@~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + if release: + return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.zip" + return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.zip" # NOTE: Keep this in mind when updating the recommended version: @@ -241,11 +249,33 @@ ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0) # The default/recommended esp-idf framework version # - https://github.com/espressif/esp-idf/releases # - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf -RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 8) +RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 1, 5) # The platformio/espressif32 version to use for esp-idf frameworks # - https://github.com/platformio/platform-espressif32/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 -ESP_IDF_PLATFORM_VERSION = cv.Version(5, 4, 0) +ESP_IDF_PLATFORM_VERSION = cv.Version(51, 3, 7) + +# List based on https://registry.platformio.org/tools/platformio/framework-espidf/versions +SUPPORTED_PLATFORMIO_ESP_IDF_5X = [ + cv.Version(5, 3, 1), + cv.Version(5, 3, 0), + cv.Version(5, 2, 2), + cv.Version(5, 2, 1), + cv.Version(5, 1, 2), + cv.Version(5, 1, 1), + cv.Version(5, 1, 0), + cv.Version(5, 0, 2), + cv.Version(5, 0, 1), + cv.Version(5, 0, 0), +] + +# pioarduino versions that don't require a release number +# List based on https://github.com/pioarduino/esp-idf/releases +SUPPORTED_PIOARDUINO_ESP_IDF_5X = [ + cv.Version(5, 3, 1), + cv.Version(5, 3, 0), + cv.Version(5, 1, 5), +] def _arduino_check_versions(value): @@ -286,8 +316,8 @@ def _arduino_check_versions(value): def _esp_idf_check_versions(value): value = value.copy() lookups = { - "dev": (cv.Version(5, 1, 2), "https://github.com/espressif/esp-idf.git"), - "latest": (cv.Version(5, 1, 2), None), + "dev": (cv.Version(5, 1, 5), "https://github.com/espressif/esp-idf.git"), + "latest": (cv.Version(5, 1, 5), None), "recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None), } @@ -305,13 +335,51 @@ def _esp_idf_check_versions(value): if version < cv.Version(4, 0, 0): raise cv.Invalid("Only ESP-IDF 4.0+ is supported.") - value[CONF_VERSION] = str(version) - value[CONF_SOURCE] = source or _format_framework_espidf_version(version) + # flag this for later *before* we set value[CONF_PLATFORM_VERSION] below + has_platform_ver = CONF_PLATFORM_VERSION in value value[CONF_PLATFORM_VERSION] = value.get( CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION)) ) + if ( + (is_platformio := _platform_is_platformio(value[CONF_PLATFORM_VERSION])) + and version.major >= 5 + and version not in SUPPORTED_PLATFORMIO_ESP_IDF_5X + ): + raise cv.Invalid( + f"ESP-IDF {str(version)} not supported by platformio/espressif32" + ) + + if ( + version.major < 5 + or ( + version in SUPPORTED_PLATFORMIO_ESP_IDF_5X + and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X + ) + ) and not has_platform_ver: + raise cv.Invalid( + f"ESP-IDF {value[CONF_VERSION]} may be supported by platformio/espressif32; please specify '{CONF_PLATFORM_VERSION}'" + ) + + if ( + not is_platformio + and CONF_RELEASE not in value + and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X + ): + raise cv.Invalid( + f"ESP-IDF {value[CONF_VERSION]} is not available with pioarduino; you may need to specify '{CONF_RELEASE}'" + ) + + value[CONF_VERSION] = str(version) + value[CONF_SOURCE] = source or _format_framework_espidf_version( + version, value.get(CONF_RELEASE, None), is_platformio + ) + + if value[CONF_SOURCE].startswith("http"): + # prefix is necessary or platformio will complain with a cryptic error + value[CONF_SOURCE] = f"framework-espidf@{value[CONF_SOURCE]}" + if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION: _LOGGER.warning( "The selected ESP-IDF framework version is not the recommended one. " @@ -323,6 +391,12 @@ def _esp_idf_check_versions(value): def _parse_platform_version(value): try: + ver = cv.Version.parse(cv.version_number(value)) + if ver.major >= 50: # a pioarduino version + if "-" in value: + # maybe a release candidate?...definitely not our default, just use it as-is... + return f"https://github.com/pioarduino/platform-espressif32.git#{value}" + return f"https://github.com/pioarduino/platform-espressif32.git#{ver.major}.{ver.minor:02d}.{ver.patch:02d}" # if platform version is a valid version constraint, prefix the default package cv.platformio_version_constraint(value) return f"platformio/espressif32@{value}" @@ -330,6 +404,14 @@ def _parse_platform_version(value): return value +def _platform_is_platformio(value): + try: + ver = cv.Version.parse(cv.version_number(value)) + return ver.major < 50 + except cv.Invalid: + return "platformio" in value + + def _detect_variant(value): board = value[CONF_BOARD] if board in BOARDS: @@ -412,6 +494,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All( cv.Schema( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, + cv.Optional(CONF_RELEASE): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict, cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { @@ -515,10 +598,9 @@ async def to_code(config): cg.add_build_flag("-DUSE_ESP_IDF") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF") cg.add_build_flag("-Wno-nonnull-compare") - cg.add_platformio_option( - "platform_packages", - [f"platformio/framework-espidf@{conf[CONF_SOURCE]}"], - ) + + cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]]) + # platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years # This is espressif's own published version which is more up to date. cg.add_platformio_option( diff --git a/platformio.ini b/platformio.ini index 04afc059af..b9b80e933f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -137,9 +137,9 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script ; This are common settings for the ESP32 (all variants) using IDF. [common:esp32-idf] extends = common:idf -platform = platformio/espressif32@5.4.0 +platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.06/platform-espressif32.zip platform_packages = - platformio/framework-espidf@~3.40408.0 + pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.1.5/esp-idf-v5.1.5.zip framework = espidf lib_deps = diff --git a/tests/test_build_components/build_components_base.esp32-c3-idf-51.yaml b/tests/test_build_components/build_components_base.esp32-c3-idf-51.yaml deleted file mode 100644 index eb5b23a4ec..0000000000 --- a/tests/test_build_components/build_components_base.esp32-c3-idf-51.yaml +++ /dev/null @@ -1,19 +0,0 @@ -esphome: - name: componenttestesp32c3idf51 - friendly_name: $component_name - -esp32: - board: lolin_c3_mini - framework: - type: esp-idf - version: 5.1.2 - platform_version: 6.5.0 - -logger: - level: VERY_VERBOSE - -packages: - component_under_test: !include - file: $component_test_file - vars: - component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-idf-51.yaml b/tests/test_build_components/build_components_base.esp32-idf-51.yaml deleted file mode 100644 index b5e3dd6d83..0000000000 --- a/tests/test_build_components/build_components_base.esp32-idf-51.yaml +++ /dev/null @@ -1,19 +0,0 @@ -esphome: - name: componenttestesp32idf51 - friendly_name: $component_name - -esp32: - board: nodemcu-32s - framework: - type: esp-idf - version: 5.1.2 - platform_version: 6.5.0 - -logger: - level: VERY_VERBOSE - -packages: - component_under_test: !include - file: $component_test_file - vars: - component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-s2-idf-51.yaml b/tests/test_build_components/build_components_base.esp32-s2-idf-51.yaml deleted file mode 100644 index 11b077509e..0000000000 --- a/tests/test_build_components/build_components_base.esp32-s2-idf-51.yaml +++ /dev/null @@ -1,20 +0,0 @@ -esphome: - name: componenttestesp32s2idf51 - friendly_name: $component_name - -esp32: - board: esp32-s2-saola-1 - variant: ESP32S2 - framework: - type: esp-idf - version: 5.1.2 - platform_version: 6.5.0 - -logger: - level: VERY_VERBOSE - -packages: - component_under_test: !include - file: $component_test_file - vars: - component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-s3-idf-51.yaml b/tests/test_build_components/build_components_base.esp32-s3-idf-51.yaml deleted file mode 100644 index 4357b3581b..0000000000 --- a/tests/test_build_components/build_components_base.esp32-s3-idf-51.yaml +++ /dev/null @@ -1,20 +0,0 @@ -esphome: - name: componenttestesp32s3idf51 - friendly_name: $component_name - -esp32: - board: esp32s3box - variant: ESP32S3 - framework: - type: esp-idf - version: 5.1.2 - platform_version: 6.5.0 - -logger: - level: VERY_VERBOSE - -packages: - component_under_test: !include - file: $component_test_file - vars: - component_test_file: $component_test_file From 555bdac604147b1f918c6c11609589ac88f5c368 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:11:31 +1300 Subject: [PATCH 23/40] Bump actions/cache from 4.1.2 to 4.2.0 (#7926) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4d3934c59..6ce4159da0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.1.2 + uses: actions/cache@v4.2.0 with: path: venv # yamllint disable-line rule:line-length @@ -302,14 +302,14 @@ jobs: - name: Cache platformio if: github.ref == 'refs/heads/dev' - uses: actions/cache@v4.1.2 + uses: actions/cache@v4.2.0 with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }} - name: Cache platformio if: github.ref != 'refs/heads/dev' - uses: actions/cache/restore@v4.1.2 + uses: actions/cache/restore@v4.2.0 with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }} From d3a71a1d45fa3958fee3aac4e1afacb5408e726a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:11:46 +1300 Subject: [PATCH 24/40] Bump actions/cache from 4.1.2 to 4.2.0 in /.github/actions/restore-python (#7925) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/restore-python/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/restore-python/action.yml b/.github/actions/restore-python/action.yml index 06c54578f5..6b87cf0170 100644 --- a/.github/actions/restore-python/action.yml +++ b/.github/actions/restore-python/action.yml @@ -22,7 +22,7 @@ runs: python-version: ${{ inputs.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache/restore@v4.1.2 + uses: actions/cache/restore@v4.2.0 with: path: venv # yamllint disable-line rule:line-length From 4e3195b474e5ffeda909e9d8933f8611da758779 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:16:59 +1100 Subject: [PATCH 25/40] [esp32] Fix crash with empty `platformio_options:` value (#7920) --- esphome/components/esp32/__init__.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 580c3fc081..b0bde75451 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -437,24 +437,20 @@ def _detect_variant(value): def final_validate(config): - if CONF_PLATFORMIO_OPTIONS not in fv.full_config.get()[CONF_ESPHOME]: + if not ( + pio_options := fv.full_config.get()[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS) + ): + # Not specified or empty return config pio_flash_size_key = "board_upload.flash_size" pio_partitions_key = "board_build.partitions" - if ( - CONF_PARTITIONS in config - and pio_partitions_key - in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS] - ): + if CONF_PARTITIONS in config and pio_partitions_key in pio_options: raise cv.Invalid( f"Do not specify '{pio_partitions_key}' in '{CONF_PLATFORMIO_OPTIONS}' with '{CONF_PARTITIONS}' in esp32" ) - if ( - pio_flash_size_key - in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS] - ): + if pio_flash_size_key in pio_options: raise cv.Invalid( f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only" ) From bfd75d736c3693f7b055e890bf9a36d51543f646 Mon Sep 17 00:00:00 2001 From: alorente Date: Fri, 6 Dec 2024 01:21:14 +0100 Subject: [PATCH 26/40] Add OCI Image Labels (#7924) --- docker/Dockerfile | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index cc05849271..1754d951e5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -196,8 +196,16 @@ LABEL \ io.hass.name="ESPHome" \ io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ io.hass.type="addon" \ - io.hass.version="${BUILD_VERSION}" + io.hass.version="${BUILD_VERSION}" \ # io.hass.arch is inherited from addon-debian-base + org.opencontainers.image.authors="The ESPHome Authors" \ + org.opencontainers.image.title="ESPHome" \ + org.opencontainers.image.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ + org.opencontainers.image.url="https://esphome.io/" \ + org.opencontainers.image.documentation="https://esphome.io/" \ + org.opencontainers.image.source="https://github.com/esphome/esphome" \ + org.opencontainers.image.licenses="ESPHome" \ + org.opencontainers.image.version=${BUILD_VERSION} From 58123845ff5f5a61139b9144b266fc1d32136926 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:11:11 +1300 Subject: [PATCH 27/40] Move docker oci labels to correct image (#7927) --- docker/Dockerfile | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 1754d951e5..947e410fe1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -163,6 +163,18 @@ ENTRYPOINT ["/entrypoint.sh"] CMD ["dashboard", "/config"] +ARG BUILD_VERSION=dev + +# Labels +LABEL \ + org.opencontainers.image.authors="The ESPHome Authors" \ + org.opencontainers.image.title="ESPHome" \ + org.opencontainers.image.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ + org.opencontainers.image.url="https://esphome.io/" \ + org.opencontainers.image.documentation="https://esphome.io/" \ + org.opencontainers.image.source="https://github.com/esphome/esphome" \ + org.opencontainers.image.licenses="ESPHome" \ + org.opencontainers.image.version=${BUILD_VERSION} # ======================= hassio-type image ======================= @@ -196,16 +208,8 @@ LABEL \ io.hass.name="ESPHome" \ io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ io.hass.type="addon" \ - io.hass.version="${BUILD_VERSION}" \ + io.hass.version="${BUILD_VERSION}" # io.hass.arch is inherited from addon-debian-base - org.opencontainers.image.authors="The ESPHome Authors" \ - org.opencontainers.image.title="ESPHome" \ - org.opencontainers.image.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ - org.opencontainers.image.url="https://esphome.io/" \ - org.opencontainers.image.documentation="https://esphome.io/" \ - org.opencontainers.image.source="https://github.com/esphome/esphome" \ - org.opencontainers.image.licenses="ESPHome" \ - org.opencontainers.image.version=${BUILD_VERSION} From b0e3ac01e83258329244ca0efc557c2225c7d790 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:24:20 +1300 Subject: [PATCH 28/40] Update project description (#7928) --- docker/Dockerfile | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 947e410fe1..0bb558d35e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -169,7 +169,7 @@ ARG BUILD_VERSION=dev LABEL \ org.opencontainers.image.authors="The ESPHome Authors" \ org.opencontainers.image.title="ESPHome" \ - org.opencontainers.image.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ + org.opencontainers.image.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \ org.opencontainers.image.url="https://esphome.io/" \ org.opencontainers.image.documentation="https://esphome.io/" \ org.opencontainers.image.source="https://github.com/esphome/esphome" \ @@ -206,7 +206,7 @@ RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ # Labels LABEL \ io.hass.name="ESPHome" \ - io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ + io.hass.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \ io.hass.type="addon" \ io.hass.version="${BUILD_VERSION}" # io.hass.arch is inherited from addon-debian-base diff --git a/pyproject.toml b/pyproject.toml index cfc279845f..7789f6d645 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "esphome" license = {text = "MIT"} -description = "Make creating custom firmwares for ESP32/ESP8266 super easy." +description = "ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems." readme = "README.md" authors = [ {name = "The ESPHome Authors", email = "esphome@nabucasa.com"} From 749a5e3348e5da84b8a199ef49ea5122d993d6c1 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Thu, 5 Dec 2024 20:41:53 -0600 Subject: [PATCH 29/40] [modbus] More clean-up (#7921) --- .../components/modbus_controller/modbus_controller.cpp | 10 +++++----- .../modbus_controller/switch/modbus_switch.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 641ba68223..3f487abc94 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -152,9 +152,9 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t } SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const { - auto reg_it = find_if(begin(this->register_ranges_), end(this->register_ranges_), [=](RegisterRange const &r) { - return (r.start_address == start_address && r.register_type == register_type); - }); + auto reg_it = std::find_if( + std::begin(this->register_ranges_), std::end(this->register_ranges_), + [=](RegisterRange const &r) { return (r.start_address == start_address && r.register_type == register_type); }); if (reg_it == this->register_ranges_.end()) { ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address); @@ -375,12 +375,12 @@ void ModbusController::loop() { if (!this->incoming_queue_.empty()) { auto &message = this->incoming_queue_.front(); if (message != nullptr) - process_modbus_data_(message.get()); + this->process_modbus_data_(message.get()); this->incoming_queue_.pop(); } else { // all messages processed send pending commands - send_next_command_(); + this->send_next_command_(); } } diff --git a/esphome/components/modbus_controller/switch/modbus_switch.cpp b/esphome/components/modbus_controller/switch/modbus_switch.cpp index ec29eca7f8..b729e2659f 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.cpp +++ b/esphome/components/modbus_controller/switch/modbus_switch.cpp @@ -97,7 +97,7 @@ void ModbusSwitch::write_state(bool state) { } } this->parent_->queue_command(cmd); - publish_state(state); + this->publish_state(state); } // ModbusSwitch end } // namespace modbus_controller From 39cbc6b183c69fce10eb77c1c9393687335b7a30 Mon Sep 17 00:00:00 2001 From: Oleg Tarasov Date: Tue, 26 Nov 2024 00:47:01 +0300 Subject: [PATCH 30/40] [opentherm] Fix out of memory errors on ESP8266 (#7835) --- esphome/components/opentherm/hub.cpp | 13 ++++---- esphome/components/opentherm/opentherm.cpp | 36 ++++++---------------- esphome/components/opentherm/opentherm.h | 7 ++--- esphome/core/helpers.cpp | 12 ++++++++ esphome/core/helpers.h | 8 +++++ 5 files changed, 39 insertions(+), 37 deletions(-) diff --git a/esphome/components/opentherm/hub.cpp b/esphome/components/opentherm/hub.cpp index dfa8ea95c5..aac2966ed1 100644 --- a/esphome/components/opentherm/hub.cpp +++ b/esphome/components/opentherm/hub.cpp @@ -138,7 +138,7 @@ OpenthermHub::OpenthermHub() : Component(), in_pin_{}, out_pin_{} {} void OpenthermHub::process_response(OpenthermData &data) { ESP_LOGD(TAG, "Received OpenTherm response with id %d (%s)", data.id, this->opentherm_->message_id_to_str((MessageId) data.id)); - ESP_LOGD(TAG, "%s", this->opentherm_->debug_data(data).c_str()); + this->opentherm_->debug_data(data); switch (data.id) { OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_RESPONSE_MESSAGE, OPENTHERM_MESSAGE_RESPONSE_ENTITY, , @@ -315,7 +315,7 @@ void OpenthermHub::start_conversation_() { ESP_LOGD(TAG, "Sending request with id %d (%s)", request.id, this->opentherm_->message_id_to_str((MessageId) request.id)); - ESP_LOGD(TAG, "%s", this->opentherm_->debug_data(request).c_str()); + this->opentherm_->debug_data(request); // Send the request this->last_conversation_start_ = millis(); this->opentherm_->send(request); @@ -340,19 +340,18 @@ void OpenthermHub::stop_opentherm_() { this->opentherm_->stop(); this->last_conversation_end_ = millis(); } - void OpenthermHub::handle_protocol_write_error_() { ESP_LOGW(TAG, "Error while sending request: %s", this->opentherm_->operation_mode_to_str(this->opentherm_->get_mode())); - ESP_LOGW(TAG, "%s", this->opentherm_->debug_data(this->last_request_).c_str()); + this->opentherm_->debug_data(this->last_request_); } - void OpenthermHub::handle_protocol_read_error_() { OpenThermError error; this->opentherm_->get_protocol_error(error); - ESP_LOGW(TAG, "Protocol error occured while receiving response: %s", this->opentherm_->debug_error(error).c_str()); + ESP_LOGW(TAG, "Protocol error occured while receiving response: %s", + this->opentherm_->protocol_error_to_to_str(error.error_type)); + this->opentherm_->debug_error(error); } - void OpenthermHub::handle_timeout_error_() { ESP_LOGW(TAG, "Receive response timed out at a protocol level"); this->stop_opentherm_(); diff --git a/esphome/components/opentherm/opentherm.cpp b/esphome/components/opentherm/opentherm.cpp index 26c707f9a0..62cfcdceea 100644 --- a/esphome/components/opentherm/opentherm.cpp +++ b/esphome/components/opentherm/opentherm.cpp @@ -15,15 +15,11 @@ #include "Arduino.h" #endif #include -#include -#include namespace esphome { namespace opentherm { using std::string; -using std::bitset; -using std::stringstream; using std::to_string; static const char *const TAG = "opentherm"; @@ -545,29 +541,17 @@ const char *OpenTherm::message_id_to_str(MessageId id) { } } -string OpenTherm::debug_data(OpenthermData &data) { - stringstream result; - result << bitset<8>(data.type) << " " << bitset<8>(data.id) << " " << bitset<8>(data.valueHB) << " " - << bitset<8>(data.valueLB) << "\n"; - result << "type: " << this->message_type_to_str((MessageType) data.type) << "; "; - result << "id: " << to_string(data.id) << "; "; - result << "HB: " << to_string(data.valueHB) << "; "; - result << "LB: " << to_string(data.valueLB) << "; "; - result << "uint_16: " << to_string(data.u16()) << "; "; - result << "float: " << to_string(data.f88()); - - return result.str(); +void OpenTherm::debug_data(OpenthermData &data) { + ESP_LOGD(TAG, "%s %s %s %s", format_bin(data.type).c_str(), format_bin(data.id).c_str(), + format_bin(data.valueHB).c_str(), format_bin(data.valueLB).c_str()); + ESP_LOGD(TAG, "type: %s; id: %s; HB: %s; LB: %s; uint_16: %s; float: %s", + this->message_type_to_str((MessageType) data.type), to_string(data.id).c_str(), + to_string(data.valueHB).c_str(), to_string(data.valueLB).c_str(), to_string(data.u16()).c_str(), + to_string(data.f88()).c_str()); } -std::string OpenTherm::debug_error(OpenThermError &error) { - stringstream result; - result << "type: " << this->protocol_error_to_to_str(error.error_type) << "; "; - result << "data: "; - result << format_hex(error.data); - result << "; clock: " << to_string(clock_); - result << "; capture: " << bitset<32>(error.capture); - result << "; bit_pos: " << to_string(error.bit_pos); - - return result.str(); +void OpenTherm::debug_error(OpenThermError &error) const { + ESP_LOGD(TAG, "data: %s; clock: %s; capture: %s; bit_pos: %s", format_hex(error.data).c_str(), + to_string(clock_).c_str(), format_bin(error.capture).c_str(), to_string(error.bit_pos).c_str()); } float OpenthermData::f88() { return ((float) this->s16()) / 256.0; } diff --git a/esphome/components/opentherm/opentherm.h b/esphome/components/opentherm/opentherm.h index 85f4611125..76710af5f5 100644 --- a/esphome/components/opentherm/opentherm.h +++ b/esphome/components/opentherm/opentherm.h @@ -8,10 +8,9 @@ #pragma once #include -#include -#include #include "esphome/core/hal.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" #if defined(ESP32) || defined(USE_ESP_IDF) #include "driver/timer.h" @@ -318,8 +317,8 @@ class OpenTherm { OperationMode get_mode() { return mode_; } - std::string debug_data(OpenthermData &data); - std::string debug_error(OpenThermError &error); + void debug_data(OpenthermData &data); + void debug_error(OpenThermError &error) const; const char *protocol_error_to_to_str(ProtocolErrorType error_type); const char *message_type_to_str(MessageType message_type); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index dae60a4e1d..befc84516c 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -397,6 +397,18 @@ std::string format_hex_pretty(const uint16_t *data, size_t length) { } std::string format_hex_pretty(const std::vector &data) { return format_hex_pretty(data.data(), data.size()); } +std::string format_bin(const uint8_t *data, size_t length) { + std::string result; + result.resize(length * 8); + for (size_t byte_idx = 0; byte_idx < length; byte_idx++) { + for (size_t bit_idx = 0; bit_idx < 8; bit_idx++) { + result[byte_idx * 8 + bit_idx] = ((data[byte_idx] >> (7 - bit_idx)) & 1) + '0'; + } + } + + return result; +} + ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { if (on == nullptr && strcasecmp(str, "on") == 0) return PARSE_ON; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 43001bafdd..305ec47f76 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -420,6 +420,14 @@ template::value, int> = 0> std::stri return format_hex_pretty(reinterpret_cast(&val), sizeof(T)); } +/// Format the byte array \p data of length \p len in binary. +std::string format_bin(const uint8_t *data, size_t length); +/// Format an unsigned integer in binary, starting with the most significant byte. +template::value, int> = 0> std::string format_bin(T val) { + val = convert_big_endian(val); + return format_bin(reinterpret_cast(&val), sizeof(T)); +} + /// Return values for parse_on_off(). enum ParseOnOffState { PARSE_NONE = 0, From e623989878b3f73b5512c6380b4d4446183443da Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Mon, 25 Nov 2024 14:15:01 -1000 Subject: [PATCH 31/40] fix local time timestamp calculation (#7807) Co-authored-by: Samuel Sieb --- .../components/datetime/datetime_entity.cpp | 4 +-- esphome/core/time.cpp | 34 +++++++++++-------- esphome/core/time.h | 4 +-- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/esphome/components/datetime/datetime_entity.cpp b/esphome/components/datetime/datetime_entity.cpp index f215b7acb5..3d92194efa 100644 --- a/esphome/components/datetime/datetime_entity.cpp +++ b/esphome/components/datetime/datetime_entity.cpp @@ -60,9 +60,7 @@ ESPTime DateTimeEntity::state_as_esptime() const { obj.hour = this->hour_; obj.minute = this->minute_; obj.second = this->second_; - obj.day_of_week = 1; // Required to be valid for recalc_timestamp_local but not used. - obj.day_of_year = 1; // Required to be valid for recalc_timestamp_local but not used. - obj.recalc_timestamp_local(false); + obj.recalc_timestamp_local(); return obj; } diff --git a/esphome/core/time.cpp b/esphome/core/time.cpp index f7aa4fdddb..31977d972b 100644 --- a/esphome/core/time.cpp +++ b/esphome/core/time.cpp @@ -5,20 +5,18 @@ namespace esphome { -bool is_leap_year(uint32_t year) { return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); } - uint8_t days_in_month(uint8_t month, uint16_t year) { static const uint8_t DAYS_IN_MONTH[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - uint8_t days = DAYS_IN_MONTH[month]; - if (month == 2 && is_leap_year(year)) + if (month == 2 && (year % 4 == 0)) return 29; - return days; + return DAYS_IN_MONTH[month]; } size_t ESPTime::strftime(char *buffer, size_t buffer_len, const char *format) { struct tm c_tm = this->to_c_tm(); return ::strftime(buffer, buffer_len, format, &c_tm); } + ESPTime ESPTime::from_c_tm(struct tm *c_tm, time_t c_time) { ESPTime res{}; res.second = uint8_t(c_tm->tm_sec); @@ -33,6 +31,7 @@ ESPTime ESPTime::from_c_tm(struct tm *c_tm, time_t c_time) { res.timestamp = c_time; return res; } + struct tm ESPTime::to_c_tm() { struct tm c_tm {}; c_tm.tm_sec = this->second; @@ -46,6 +45,7 @@ struct tm ESPTime::to_c_tm() { c_tm.tm_isdst = this->is_dst; return c_tm; } + std::string ESPTime::strftime(const std::string &format) { std::string timestr; timestr.resize(format.size() * 4); @@ -142,6 +142,7 @@ void ESPTime::increment_second() { this->year++; } } + void ESPTime::increment_day() { this->timestamp += 86400; @@ -159,23 +160,22 @@ void ESPTime::increment_day() { this->year++; } } + void ESPTime::recalc_timestamp_utc(bool use_day_of_year) { time_t res = 0; - if (!this->fields_in_range()) { this->timestamp = -1; return; } for (int i = 1970; i < this->year; i++) - res += is_leap_year(i) ? 366 : 365; + res += (year % 4 == 0) ? 366 : 365; if (use_day_of_year) { res += this->day_of_year - 1; } else { for (int i = 1; i < this->month; i++) res += days_in_month(i, this->year); - res += this->day_of_month - 1; } @@ -188,13 +188,17 @@ void ESPTime::recalc_timestamp_utc(bool use_day_of_year) { this->timestamp = res; } -void ESPTime::recalc_timestamp_local(bool use_day_of_year) { - this->recalc_timestamp_utc(use_day_of_year); - this->timestamp -= ESPTime::timezone_offset(); - ESPTime temp = ESPTime::from_epoch_local(this->timestamp); - if (temp.is_dst) { - this->timestamp -= 3600; - } +void ESPTime::recalc_timestamp_local() { + struct tm tm; + + tm.tm_year = this->year - 1900; + tm.tm_mon = this->month - 1; + tm.tm_mday = this->day_of_month; + tm.tm_hour = this->hour; + tm.tm_min = this->minute; + tm.tm_sec = this->second; + + this->timestamp = mktime(&tm); } int32_t ESPTime::timezone_offset() { diff --git a/esphome/core/time.h b/esphome/core/time.h index bce1108d93..5cbd9369fb 100644 --- a/esphome/core/time.h +++ b/esphome/core/time.h @@ -9,8 +9,6 @@ namespace esphome { template bool increment_time_value(T ¤t, uint16_t begin, uint16_t end); -bool is_leap_year(uint32_t year); - uint8_t days_in_month(uint8_t month, uint16_t year); /// A more user-friendly version of struct tm from time.h @@ -100,7 +98,7 @@ struct ESPTime { void recalc_timestamp_utc(bool use_day_of_year = true); /// Recalculate the timestamp field from the other fields of this ESPTime instance assuming local fields. - void recalc_timestamp_local(bool use_day_of_year = true); + void recalc_timestamp_local(); /// Convert this ESPTime instance back to a tm struct. struct tm to_c_tm(); From 3bac45e737655af9f8fbf7734e3a17620fcdec47 Mon Sep 17 00:00:00 2001 From: guillempages Date: Thu, 28 Nov 2024 04:55:20 +0100 Subject: [PATCH 32/40] [online_image]Don't access decoder if not initialized (#7882) --- esphome/components/online_image/png_image.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/online_image/png_image.cpp b/esphome/components/online_image/png_image.cpp index c8e215a91d..4c4c22f9b7 100644 --- a/esphome/components/online_image/png_image.cpp +++ b/esphome/components/online_image/png_image.cpp @@ -49,6 +49,10 @@ void PngDecoder::prepare(uint32_t download_size) { } int HOT PngDecoder::decode(uint8_t *buffer, size_t size) { + if (!this->pngle_) { + ESP_LOGE(TAG, "PNG decoder engine not initialized!"); + return -1; + } if (size < 256 && size < this->download_size_ - this->decoded_bytes_) { ESP_LOGD(TAG, "Waiting for data"); return 0; From 5717d557f5f421ae3402a5d13510dda81ac4bebd Mon Sep 17 00:00:00 2001 From: FreeBear-nc <67865163+FreeBear-nc@users.noreply.github.com> Date: Thu, 28 Nov 2024 03:56:37 +0000 Subject: [PATCH 33/40] Add IRAM_ATTR to all functions used during interrupts on esp8266 chips. (#7840) --- esphome/components/opentherm/opentherm.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/opentherm/opentherm.cpp b/esphome/components/opentherm/opentherm.cpp index 62cfcdceea..c56b49ccb8 100644 --- a/esphome/components/opentherm/opentherm.cpp +++ b/esphome/components/opentherm/opentherm.cpp @@ -220,7 +220,7 @@ void IRAM_ATTR OpenTherm::bit_read_(uint8_t value) { this->bit_pos_++; } -ProtocolErrorType OpenTherm::verify_stop_bit_(uint8_t value) { +ProtocolErrorType IRAM_ATTR OpenTherm::verify_stop_bit_(uint8_t value) { if (value) { // stop bit detected return check_parity_(this->data_) ? ProtocolErrorType::NO_ERROR : ProtocolErrorType::PARITY_ERROR; } else { // no stop bit detected, error @@ -365,7 +365,7 @@ void IRAM_ATTR OpenTherm::stop_timer_() { #ifdef ESP8266 // 5 kHz timer_ -void OpenTherm::start_read_timer_() { +void IRAM_ATTR OpenTherm::start_read_timer_() { InterruptLock const lock; timer1_attachInterrupt(OpenTherm::esp8266_timer_isr); timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP); // 5MHz (5 ticks/us - 1677721.4 us max) @@ -373,14 +373,14 @@ void OpenTherm::start_read_timer_() { } // 2 kHz timer_ -void OpenTherm::start_write_timer_() { +void IRAM_ATTR OpenTherm::start_write_timer_() { InterruptLock const lock; timer1_attachInterrupt(OpenTherm::esp8266_timer_isr); timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP); // 5MHz (5 ticks/us - 1677721.4 us max) timer1_write(2500); // 2kHz } -void OpenTherm::stop_timer_() { +void IRAM_ATTR OpenTherm::stop_timer_() { InterruptLock const lock; timer1_disable(); timer1_detachInterrupt(); @@ -389,7 +389,7 @@ void OpenTherm::stop_timer_() { #endif // END ESP8266 // https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd -bool OpenTherm::check_parity_(uint32_t val) { +bool IRAM_ATTR OpenTherm::check_parity_(uint32_t val) { val ^= val >> 16; val ^= val >> 8; val ^= val >> 4; From 5fcd26bfe964f670759e08bc89386ee9c2b1c0d4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:57:11 +1300 Subject: [PATCH 34/40] [st7920] Remove unnecessary warning when drawing outside display bounds (#7868) --- esphome/components/st7920/st7920.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/esphome/components/st7920/st7920.cpp b/esphome/components/st7920/st7920.cpp index f336d24e24..171e7095dd 100644 --- a/esphome/components/st7920/st7920.cpp +++ b/esphome/components/st7920/st7920.cpp @@ -1,7 +1,7 @@ #include "st7920.h" -#include "esphome/core/log.h" -#include "esphome/core/application.h" #include "esphome/components/display/display_buffer.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" namespace esphome { namespace st7920 { @@ -118,7 +118,6 @@ size_t ST7920::get_buffer_length_() { void HOT ST7920::draw_absolute_pixel_internal(int x, int y, Color color) { if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) { - ESP_LOGW(TAG, "Position out of area: %dx%d", x, y); return; } int width = this->get_width_internal() / 8u; From f042c6e643a0c1b67816cc1824611b98d6317ac4 Mon Sep 17 00:00:00 2001 From: Krzysztof Zdulski Date: Fri, 29 Nov 2024 22:05:00 +0100 Subject: [PATCH 35/40] Fix recalc_timestamp_utc (#7894) --- esphome/core/time.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/time.cpp b/esphome/core/time.cpp index 31977d972b..66a0e1c0a7 100644 --- a/esphome/core/time.cpp +++ b/esphome/core/time.cpp @@ -169,7 +169,7 @@ void ESPTime::recalc_timestamp_utc(bool use_day_of_year) { } for (int i = 1970; i < this->year; i++) - res += (year % 4 == 0) ? 366 : 365; + res += (i % 4 == 0) ? 366 : 365; if (use_day_of_year) { res += this->day_of_year - 1; From 982ce1db727b103250b69c91e3edaac918bd5f37 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 2 Dec 2024 05:10:18 +1300 Subject: [PATCH 36/40] Cast port to int for ota pushing (#7888) --- esphome/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 86d529e1bf..dce041e5ac 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -363,7 +363,7 @@ def upload_program(config, args, host): from esphome import espota2 - remote_port = ota_conf[CONF_PORT] + remote_port = int(ota_conf[CONF_PORT]) password = ota_conf.get(CONF_PASSWORD, "") if ( From d0958f7cf28dd68e7149ae32d06b3489caf55c11 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:50:11 +1100 Subject: [PATCH 37/40] [lvgl] Bugfixes (#7896) --- esphome/components/lvgl/defines.py | 2 +- esphome/components/lvgl/lvgl_esphome.h | 3 +++ esphome/components/lvgl/widgets/line.py | 6 ++++++ tests/components/lvgl/lvgl-package.yaml | 4 ++-- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index ea345fa55c..bb7d25c2c7 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -38,7 +38,7 @@ def literal(arg): def call_lambda(lamb: LambdaExpression): expr = lamb.content.strip() if expr.startswith("return") and expr.endswith(";"): - return expr[7:][:-1] + return expr[6:][:-1].strip() return f"{lamb}()" diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index 208cb1cbd5..7bc6b00cf5 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -56,6 +56,9 @@ static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BIT inline void lv_img_set_src(lv_obj_t *obj, esphome::image::Image *image) { lv_img_set_src(obj, image->get_lv_img_dsc()); } +inline void lv_disp_set_bg_image(lv_disp_t *disp, esphome::image::Image *image) { + lv_disp_set_bg_image(disp, image->get_lv_img_dsc()); +} #endif // USE_LVGL_IMAGE // Parent class for things that wrap an LVGL object diff --git a/esphome/components/lvgl/widgets/line.py b/esphome/components/lvgl/widgets/line.py index 548dfa8452..0156fb1780 100644 --- a/esphome/components/lvgl/widgets/line.py +++ b/esphome/components/lvgl/widgets/line.py @@ -35,6 +35,11 @@ LINE_SCHEMA = { cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t), } +LINE_MODIFY_SCHEMA = { + cv.Optional(CONF_POINTS): cv_point_list, + cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t), +} + class LineType(WidgetType): def __init__(self): @@ -43,6 +48,7 @@ class LineType(WidgetType): LvType("lv_line_t"), (CONF_MAIN,), LINE_SCHEMA, + modify_schema=LINE_MODIFY_SCHEMA, ) async def to_code(self, w: Widget, config): diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index db0443b3bb..b83ff2a3d5 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -333,7 +333,7 @@ lvgl: id: button_button width: 20% height: 10% - transform_angle: !lambda return 180*100; + transform_angle: !lambda return(180*100); arc_width: !lambda return 4; border_width: !lambda return 6; shadow_ofs_x: !lambda return 6; @@ -577,7 +577,7 @@ lvgl: - 180, 60 - 240, 10 on_click: - - lvgl.widget.update: + - lvgl.line.update: id: lv_line_id line_color: 0xFFFF - lvgl.page.next: From 86ae1c59315261fc36c207b4b76d32385e68a3ba Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 4 Dec 2024 07:48:50 +1100 Subject: [PATCH 38/40] [lvgl] Fix msgbox content (#7912) --- esphome/components/lvgl/widgets/msgbox.py | 3 ++- tests/components/lvgl/lvgl-package.yaml | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/components/lvgl/widgets/msgbox.py b/esphome/components/lvgl/widgets/msgbox.py index be0f2100d7..c3393940b6 100644 --- a/esphome/components/lvgl/widgets/msgbox.py +++ b/esphome/components/lvgl/widgets/msgbox.py @@ -29,7 +29,7 @@ from ..lvcode import ( ) from ..schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema, part_schema from ..types import LV_EVENT, char_ptr, lv_obj_t -from . import Widget, set_obj_properties +from . import Widget, add_widgets, set_obj_properties from .button import button_spec from .buttonmatrix import ( BUTTONMATRIX_BUTTON_SCHEMA, @@ -119,6 +119,7 @@ async def msgbox_to_code(top_layer, conf): button_style = {CONF_ITEMS: button_style} await set_obj_properties(buttonmatrix_widget, button_style) await set_obj_properties(msgbox_widget, conf) + await add_widgets(msgbox_widget, conf) async with LambdaContext(EVENT_ARG, where=messagebox_id) as close_action: outer_widget.add_flag("LV_OBJ_FLAG_HIDDEN") if close_button: diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index b83ff2a3d5..e5df30f136 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -109,6 +109,10 @@ lvgl: close_button: true title: Messagebox bg_color: 0xffff + widgets: + - label: + text: Hello Msgbox + id: msgbox_label body: text: This is a sample messagebox bg_color: 0x808080 @@ -137,6 +141,9 @@ lvgl: - lvgl.widget.focus: mark - lvgl.widget.redraw: hello_label - lvgl.widget.redraw: + - lvgl.label.update: + id: msgbox_label + text: Unloaded on_all_events: logger.log: format: "Event %s" From c8ec0bb7eab3d77cb5b62a227d4041677f1225ad Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:16:59 +1100 Subject: [PATCH 39/40] [esp32] Fix crash with empty `platformio_options:` value (#7920) --- esphome/components/esp32/__init__.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 61fbb53e3a..aaef68fa27 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -355,24 +355,20 @@ def _detect_variant(value): def final_validate(config): - if CONF_PLATFORMIO_OPTIONS not in fv.full_config.get()[CONF_ESPHOME]: + if not ( + pio_options := fv.full_config.get()[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS) + ): + # Not specified or empty return config pio_flash_size_key = "board_upload.flash_size" pio_partitions_key = "board_build.partitions" - if ( - CONF_PARTITIONS in config - and pio_partitions_key - in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS] - ): + if CONF_PARTITIONS in config and pio_partitions_key in pio_options: raise cv.Invalid( f"Do not specify '{pio_partitions_key}' in '{CONF_PLATFORMIO_OPTIONS}' with '{CONF_PARTITIONS}' in esp32" ) - if ( - pio_flash_size_key - in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS] - ): + if pio_flash_size_key in pio_options: raise cv.Invalid( f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only" ) From c80e035bd56fbfe89475fdb149b0ff26fb279e61 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:55:51 +1300 Subject: [PATCH 40/40] Bump version to 2024.11.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 4b19e2865d..ae7feda6d8 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.11.2" +__version__ = "2024.11.3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = (