diff --git a/.github/workflows/codeowner-review-request.yml b/.github/workflows/codeowner-review-request.yml index 121619e049..ab3377365d 100644 --- a/.github/workflows/codeowner-review-request.yml +++ b/.github/workflows/codeowner-review-request.yml @@ -182,11 +182,14 @@ jobs: }); // Check for previous comments from this workflow to avoid duplicate pings - const { data: comments } = await github.rest.issues.listComments({ - owner, - repo, - issue_number: pr_number - }); + const comments = await github.paginate( + github.rest.issues.listComments, + { + owner, + repo, + issue_number: pr_number + } + ); const previouslyPingedUsers = new Set(); const previouslyPingedTeams = new Set(); @@ -194,7 +197,6 @@ jobs: // Look for comments from github-actions bot that contain our bot marker const workflowComments = comments.filter(comment => comment.user.type === 'Bot' && - comment.user.login === 'github-actions[bot]' && comment.body.includes(BOT_COMMENT_MARKER) ); diff --git a/.github/workflows/external-component-bot.yml b/.github/workflows/external-component-bot.yml index 5f5bc703ad..29103e8eee 100644 --- a/.github/workflows/external-component-bot.yml +++ b/.github/workflows/external-component-bot.yml @@ -61,7 +61,8 @@ jobs: } async function createComment(octokit, owner, repo, prNumber, esphomeChanges, componentChanges) { - const commentMarker = ""; + const commentMarker = ""; + const legacyCommentMarker = ""; let commentBody; if (esphomeChanges.length === 1) { commentBody = generateExternalComponentInstructions(prNumber, componentChanges, owner, repo); @@ -71,14 +72,23 @@ jobs: commentBody += `\n\n---\n(Added by the PR bot)\n\n${commentMarker}`; // Check for existing bot comment - const comments = await github.rest.issues.listComments({ - owner: owner, - repo: repo, - issue_number: prNumber, - }); + const comments = await github.paginate( + github.rest.issues.listComments, + { + owner: owner, + repo: repo, + issue_number: prNumber, + per_page: 100, + } + ); - const botComment = comments.data.find(comment => - comment.body.includes(commentMarker) + const sorted = comments.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at)); + + const botComment = sorted.find(comment => + ( + comment.body.includes(commentMarker) || + comment.body.includes(legacyCommentMarker) + ) && comment.user.type === "Bot" ); if (botComment && botComment.body === commentBody) { diff --git a/.github/workflows/issue-codeowner-notify.yml b/.github/workflows/issue-codeowner-notify.yml index 27976a7952..3639d346f5 100644 --- a/.github/workflows/issue-codeowner-notify.yml +++ b/.github/workflows/issue-codeowner-notify.yml @@ -29,6 +29,9 @@ jobs: console.log(`Processing issue #${issue_number} with label: ${labelName}`); + // Hidden marker to identify bot comments from this workflow + const BOT_COMMENT_MARKER = ''; + // Extract component name from label const componentName = labelName.replace('component: ', ''); console.log(`Component: ${componentName}`); @@ -93,11 +96,14 @@ jobs: ); // Check for previous comments from this workflow to avoid duplicate pings - const { data: comments } = await github.rest.issues.listComments({ - owner, - repo, - issue_number: issue_number - }); + const comments = await github.paginate( + github.rest.issues.listComments, + { + owner, + repo, + issue_number: issue_number + } + ); const previouslyPingedUsers = new Set(); const previouslyPingedTeams = new Set(); @@ -105,9 +111,8 @@ jobs: // Look for comments from github-actions bot that contain codeowner pings for this component const workflowComments = comments.filter(comment => comment.user.type === 'Bot' && - comment.user.login === 'github-actions[bot]' && - comment.body.includes(`component: ${componentName}`) && - comment.body.includes("you've been identified as a codeowner") + comment.body.includes(BOT_COMMENT_MARKER) && + comment.body.includes(`component: ${componentName}`) ); // Extract previously mentioned users and teams from workflow comments @@ -140,7 +145,7 @@ jobs: // Create comment body const mentionString = allMentions.join(', '); - const commentBody = `šŸ‘‹ Hey ${mentionString}!\n\nThis issue has been labeled with \`component: ${componentName}\` and you've been identified as a codeowner of this component. Please take a look when you have a chance!\n\nThanks for maintaining this component! šŸ™`; + const commentBody = `${BOT_COMMENT_MARKER}\nšŸ‘‹ Hey ${mentionString}!\n\nThis issue has been labeled with \`component: ${componentName}\` and you've been identified as a codeowner of this component. Please take a look when you have a chance!\n\nThanks for maintaining this component! šŸ™`; // Post comment await github.rest.issues.createComment({ diff --git a/esphome/components/audio/audio.h b/esphome/components/audio/audio.h index 95c31872e3..e01d7eb101 100644 --- a/esphome/components/audio/audio.h +++ b/esphome/components/audio/audio.h @@ -15,7 +15,7 @@ class AudioStreamInfo { * - An audio sample represents a unit of audio for one channel. * - A frame represents a unit of audio with a sample for every channel. * - * In gneneral, converting between bytes, samples, and frames shouldn't result in rounding errors so long as frames + * In general, converting between bytes, samples, and frames shouldn't result in rounding errors so long as frames * are used as the main unit when transferring audio data. Durations may result in rounding for certain sample rates; * e.g., 44.1 KHz. The ``frames_to_milliseconds_with_remainder`` function should be used for accuracy, as it takes * into account the remainder rather than just ignoring any rounding. @@ -76,7 +76,7 @@ class AudioStreamInfo { /// @brief Computes the duration, in microseconds, the given amount of frames represents. /// @param frames Number of audio frames - /// @return Duration in microseconds `frames` respresents. May be slightly inaccurate due to integer divison rounding + /// @return Duration in microseconds `frames` represents. May be slightly inaccurate due to integer division rounding /// for certain sample rates. uint32_t frames_to_microseconds(uint32_t frames) const; diff --git a/esphome/components/const/__init__.py b/esphome/components/const/__init__.py index 5b40545d89..18ef4d48b6 100644 --- a/esphome/components/const/__init__.py +++ b/esphome/components/const/__init__.py @@ -5,5 +5,6 @@ CODEOWNERS = ["@esphome/core"] CONF_BYTE_ORDER = "byte_order" CONF_COLOR_DEPTH = "color_depth" CONF_DRAW_ROUNDING = "draw_rounding" +CONF_ON_RECEIVE = "on_receive" CONF_ON_STATE_CHANGE = "on_state_change" CONF_REQUEST_HEADERS = "request_headers" diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index 68c06d28f2..89a0891b03 100644 --- a/esphome/components/http_request/http_request_idf.cpp +++ b/esphome/components/http_request/http_request_idf.cpp @@ -157,8 +157,8 @@ std::shared_ptr HttpRequestIDF::perform(std::string url, std::str container->status_code = esp_http_client_get_status_code(client); container->feed_wdt(); container->set_response_headers(user_data.response_headers); + container->duration_ms = millis() - start; if (is_success(container->status_code)) { - container->duration_ms = millis() - start; return container; } @@ -191,8 +191,8 @@ std::shared_ptr HttpRequestIDF::perform(std::string url, std::str container->feed_wdt(); container->status_code = esp_http_client_get_status_code(client); container->feed_wdt(); + container->duration_ms = millis() - start; if (is_success(container->status_code)) { - container->duration_ms = millis() - start; return container; } diff --git a/esphome/components/sgp4x/sensor.py b/esphome/components/sgp4x/sensor.py index 4f29248881..7c6fe580b2 100644 --- a/esphome/components/sgp4x/sensor.py +++ b/esphome/components/sgp4x/sensor.py @@ -139,7 +139,7 @@ async def to_code(config): ) ) cg.add_library( - None, + "Sensirion Gas Index Algorithm", None, "https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1", ) diff --git a/esphome/components/udp/__init__.py b/esphome/components/udp/__init__.py index ed405d7c22..6b1e4f8ed8 100644 --- a/esphome/components/udp/__init__.py +++ b/esphome/components/udp/__init__.py @@ -1,6 +1,7 @@ from esphome import automation from esphome.automation import Trigger import esphome.codegen as cg +from esphome.components.const import CONF_ON_RECEIVE from esphome.components.packet_transport import ( CONF_BINARY_SENSORS, CONF_ENCRYPTION, @@ -27,7 +28,6 @@ trigger_args = cg.std_vector.template(cg.uint8) CONF_ADDRESSES = "addresses" CONF_LISTEN_ADDRESS = "listen_address" CONF_UDP_ID = "udp_id" -CONF_ON_RECEIVE = "on_receive" CONF_LISTEN_PORT = "listen_port" CONF_BROADCAST_PORT = "broadcast_port" diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 873f342277..4fedd36120 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -55,6 +55,9 @@ void Application::setup() { return a->get_actual_setup_priority() > b->get_actual_setup_priority(); }); + // Initialize looping_components_ early so enable_pending_loops_() works during setup + this->calculate_looping_components_(); + for (uint32_t i = 0; i < this->components_.size(); i++) { Component *component = this->components_[i]; @@ -97,7 +100,6 @@ void Application::setup() { clear_setup_priority_overrides(); this->schedule_dump_config(); - this->calculate_looping_components_(); } void Application::loop() { uint8_t new_app_state = 0; @@ -269,24 +271,16 @@ void Application::calculate_looping_components_() { // Pre-reserve vector to avoid reallocations this->looping_components_.reserve(total_looping); - // First add all active components + // Add all components with loop override + // When called at start of setup, all components are in CONSTRUCTION state + // so none will be LOOP_DONE yet - they'll all go in the active section for (auto *obj : this->components_) { - if (obj->has_overridden_loop() && - (obj->get_component_state() & COMPONENT_STATE_MASK) != COMPONENT_STATE_LOOP_DONE) { + if (obj->has_overridden_loop()) { this->looping_components_.push_back(obj); } } this->looping_components_active_end_ = this->looping_components_.size(); - - // Then add all inactive (LOOP_DONE) components - // This handles components that called disable_loop() during setup, before this method runs - for (auto *obj : this->components_) { - if (obj->has_overridden_loop() && - (obj->get_component_state() & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP_DONE) { - this->looping_components_.push_back(obj); - } - } } void Application::disable_component_loop_(Component *component) { diff --git a/esphome/core/lock_free_queue.h b/esphome/core/lock_free_queue.h index de07b0ebba..68e2825d09 100644 --- a/esphome/core/lock_free_queue.h +++ b/esphome/core/lock_free_queue.h @@ -29,7 +29,7 @@ namespace esphome { // Base lock-free queue without task notification template class LockFreeQueue { public: - LockFreeQueue() : head_(0), tail_(0), dropped_count_(0) {} + LockFreeQueue() : dropped_count_(0), head_(0), tail_(0) {} bool push(T *element) { bool was_empty; diff --git a/script/build_language_schema.py b/script/build_language_schema.py index 4473ec1b5a..960c35ca0f 100755 --- a/script/build_language_schema.py +++ b/script/build_language_schema.py @@ -411,11 +411,16 @@ def add_referenced_recursive(referenced_schemas, config_var, path, eat_schema=Fa s1 = get_str_path_schema(k) p = k.split(".") - if len(p) == 3 and path[0] == f"{p[0]}.{p[1]}": - # special case for schema inside platforms - add_referenced_recursive( - referenced_schemas, s1, [path[0], "schemas", p[2]] - ) + if len(p) == 3: + if path[0] == f"{p[0]}.{p[1]}": + # special case for schema inside platforms + add_referenced_recursive( + referenced_schemas, s1, [path[0], "schemas", p[2]] + ) + else: + add_referenced_recursive( + referenced_schemas, s1, [f"{p[0]}.{p[1]}", "schemas", p[2]] + ) else: add_referenced_recursive( referenced_schemas, s1, [p[0], "schemas", p[1]]