diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3295cf070a..06f9bf2a5b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.14.11 + rev: v0.14.13 hooks: # Run the linter. - id: ruff diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index 3b0fb0aa3c..4432195365 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -2,7 +2,6 @@ #include "esphome/core/application.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include namespace esphome { namespace cse7766 { @@ -10,32 +9,6 @@ namespace cse7766 { static const char *const TAG = "cse7766"; static constexpr size_t CSE7766_RAW_DATA_SIZE = 24; -#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE -/// @brief Safely append formatted string to buffer. -/// @param buf Destination buffer (must be non-null) -/// @param size Total buffer size in bytes -/// @param pos Current write position (0 to size-1 for valid positions, size means full) -/// @param fmt printf-style format string -/// @return New write position: pos + chars_written, capped at size when buffer is full. -/// Returns size (not size-1) when full because vsnprintf already wrote the null -/// terminator at buf[size-1]. Returning size signals "no room for more content". -/// On encoding error, returns pos unchanged (no write occurred). -__attribute__((format(printf, 4, 5))) static size_t buf_append(char *buf, size_t size, size_t pos, const char *fmt, - ...) { - if (pos >= size) { - return size; - } - va_list args; - va_start(args, fmt); - int written = vsnprintf(buf + pos, size - pos, fmt, args); - va_end(args); - if (written < 0) { - return pos; // encoding error - } - return std::min(pos + static_cast(written), size); -} -#endif - void CSE7766Component::loop() { const uint32_t now = App.get_loop_component_start_time(); if (now - this->last_transmission_ >= 500) { @@ -237,18 +210,19 @@ void CSE7766Component::parse_data_() { // Buffer: 7 + 15 + 33 + 15 + 25 = 95 chars max + null, rounded to 128 for safety margin. // Float sizes with %.4f can be up to 11 chars for large values (e.g., 999999.9999). char buf[128]; - size_t pos = buf_append(buf, sizeof(buf), 0, "Parsed:"); + size_t pos = buf_append_printf(buf, sizeof(buf), 0, "Parsed:"); if (have_voltage) { - pos = buf_append(buf, sizeof(buf), pos, " V=%.4fV", voltage); + pos = buf_append_printf(buf, sizeof(buf), pos, " V=%.4fV", voltage); } if (have_current) { - pos = buf_append(buf, sizeof(buf), pos, " I=%.4fmA (~%.4fmA)", current * 1000.0f, calculated_current * 1000.0f); + pos = buf_append_printf(buf, sizeof(buf), pos, " I=%.4fmA (~%.4fmA)", current * 1000.0f, + calculated_current * 1000.0f); } if (have_power) { - pos = buf_append(buf, sizeof(buf), pos, " P=%.4fW", power); + pos = buf_append_printf(buf, sizeof(buf), pos, " P=%.4fW", power); } if (energy != 0.0f) { - buf_append(buf, sizeof(buf), pos, " E=%.4fkWh (%u)", energy, cf_pulses); + buf_append_printf(buf, sizeof(buf), pos, " E=%.4fkWh (%u)", energy, cf_pulses); } ESP_LOGVV(TAG, "%s", buf); } diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index 7c50fe02c0..c51131a292 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -8,17 +8,20 @@ from esphome.const import ( CONF_ICON, CONF_ID, CONF_INDEX, + CONF_LAMBDA, CONF_MODE, CONF_MQTT_ID, CONF_ON_VALUE, CONF_OPERATION, CONF_OPTION, + CONF_OPTIONS, CONF_TRIGGER_ID, CONF_WEB_SERVER, ) -from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity -from esphome.cpp_generator import MockObjClass +from esphome.cpp_generator import MockObjClass, TemplateArguments +from esphome.cpp_types import global_ns CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True @@ -38,6 +41,9 @@ SelectSetAction = select_ns.class_("SelectSetAction", automation.Action) SelectSetIndexAction = select_ns.class_("SelectSetIndexAction", automation.Action) SelectOperationAction = select_ns.class_("SelectOperationAction", automation.Action) +# Conditions +SelectIsCondition = select_ns.class_("SelectIsCondition", automation.Condition) + # Enums SelectOperation = select_ns.enum("SelectOperation") SELECT_OPERATION_OPTIONS = { @@ -165,6 +171,41 @@ async def select_set_index_to_code(config, action_id, template_arg, args): return var +@automation.register_condition( + "select.is", + SelectIsCondition, + OPERATION_BASE_SCHEMA.extend( + { + cv.Optional(CONF_OPTIONS): cv.All( + cv.ensure_list(cv.string_strict), cv.Length(min=1) + ), + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + } + ).add_extra(cv.has_exactly_one_key(CONF_OPTIONS, CONF_LAMBDA)), +) +async def select_is_to_code(config, condition_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + if options := config.get(CONF_OPTIONS): + # List of constant options + # Create a constexpr and pass that with a template length + arr_id = ID( + f"{condition_id}_data", + is_declaration=True, + type=global_ns.namespace("constexpr char * const"), + ) + arg = cg.static_const_array(arr_id, cg.ArrayInitializer(*options)) + template_arg = TemplateArguments(len(options), *template_arg) + else: + # Lambda + arg = await cg.process_lambda( + config[CONF_LAMBDA], + [(global_ns.namespace("StringRef &").operator("const"), "current")] + args, + return_type=cg.bool_, + ) + template_arg = TemplateArguments(0, *template_arg) + return cg.new_Pvariable(condition_id, template_arg, paren, arg) + + @automation.register_action( "select.operation", SelectOperationAction, diff --git a/esphome/components/select/automation.h b/esphome/components/select/automation.h index dda5403557..81e8a3561d 100644 --- a/esphome/components/select/automation.h +++ b/esphome/components/select/automation.h @@ -66,4 +66,34 @@ template class SelectOperationAction : public Action { Select *select_; }; +template class SelectIsCondition : public Condition { + public: + SelectIsCondition(Select *parent, const char *const *option_list) : parent_(parent), option_list_(option_list) {} + + bool check(const Ts &...x) override { + auto current = this->parent_->current_option(); + for (size_t i = 0; i != N; i++) { + if (current == this->option_list_[i]) { + return true; + } + } + return false; + } + + protected: + Select *parent_; + const char *const *option_list_; +}; + +template class SelectIsCondition<0, Ts...> : public Condition { + public: + SelectIsCondition(Select *parent, std::function &&f) + : parent_(parent), f_(f) {} + + bool check(const Ts &...x) override { return this->f_(this->parent_->current_option(), x...); } + + protected: + Select *parent_; + std::function f_; +}; } // namespace esphome::select diff --git a/requirements_test.txt b/requirements_test.txt index e0bc943ab4..d93a5d108f 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==4.0.4 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating -ruff==0.14.12 # also change in .pre-commit-config.yaml when updating +ruff==0.14.13 # also change in .pre-commit-config.yaml when updating pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating pre-commit diff --git a/tests/components/template/common-base.yaml b/tests/components/template/common-base.yaml index 134ad4d046..3b888c3d19 100644 --- a/tests/components/template/common-base.yaml +++ b/tests/components/template/common-base.yaml @@ -53,6 +53,17 @@ binary_sensor: // Garage Door is closed. return false; } + - platform: template + id: select_binary_sensor + name: Select is one or two + condition: + any: + - select.is: + id: template_select + options: [one, two] + - select.is: + id: template_select + lambda: return current == id(template_text).state; - platform: template id: other_binary_sensor name: "Garage Door Closed" @@ -320,6 +331,7 @@ valve: text: - platform: template + id: template_text name: "Template text" optimistic: true min_length: 0