53
.github/workflows/release.yml
vendored
53
.github/workflows/release.yml
vendored
@@ -219,10 +219,19 @@ jobs:
|
||||
- init
|
||||
- deploy-manifest
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
owner: esphome
|
||||
repositories: home-assistant-addon
|
||||
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
let description = "ESPHome";
|
||||
if (context.eventName == "release") {
|
||||
@@ -245,10 +254,19 @@ jobs:
|
||||
needs: [init]
|
||||
environment: ${{ needs.init.outputs.deploy_env }}
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
owner: esphome
|
||||
repositories: esphome-schema
|
||||
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
github.rest.actions.createWorkflowDispatch({
|
||||
owner: "esphome",
|
||||
@@ -259,3 +277,34 @@ jobs:
|
||||
version: "${{ needs.init.outputs.tag }}",
|
||||
}
|
||||
})
|
||||
|
||||
version-notifier:
|
||||
if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false'
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- init
|
||||
- deploy-manifest
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
owner: esphome
|
||||
repositories: version-notifier
|
||||
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
github.rest.actions.createWorkflowDispatch({
|
||||
owner: "esphome",
|
||||
repo: "version-notifier",
|
||||
workflow_id: "notify.yml",
|
||||
ref: "main",
|
||||
inputs: {
|
||||
version: "${{ needs.init.outputs.tag }}",
|
||||
}
|
||||
})
|
||||
|
||||
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2025.11.3
|
||||
PROJECT_NUMBER = 2025.11.4
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
@@ -22,7 +22,6 @@ ES8311_BITS_PER_SAMPLE_ENUM = {
|
||||
|
||||
es8311_mic_gain = es8311_ns.enum("ES8311MicGain")
|
||||
ES8311_MIC_GAIN_ENUM = {
|
||||
"MIN": es8311_mic_gain.ES8311_MIC_GAIN_MIN,
|
||||
"0DB": es8311_mic_gain.ES8311_MIC_GAIN_0DB,
|
||||
"6DB": es8311_mic_gain.ES8311_MIC_GAIN_6DB,
|
||||
"12DB": es8311_mic_gain.ES8311_MIC_GAIN_12DB,
|
||||
@@ -31,7 +30,6 @@ ES8311_MIC_GAIN_ENUM = {
|
||||
"30DB": es8311_mic_gain.ES8311_MIC_GAIN_30DB,
|
||||
"36DB": es8311_mic_gain.ES8311_MIC_GAIN_36DB,
|
||||
"42DB": es8311_mic_gain.ES8311_MIC_GAIN_42DB,
|
||||
"MAX": es8311_mic_gain.ES8311_MIC_GAIN_MAX,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -860,6 +860,7 @@ async def to_code(config):
|
||||
)
|
||||
cg.set_cpp_standard("gnu++20")
|
||||
cg.add_build_flag("-DUSE_ESP32")
|
||||
cg.add_build_flag("-Wl,-z,noexecstack")
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
variant = config[CONF_VARIANT]
|
||||
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{variant}")
|
||||
|
||||
@@ -93,9 +93,9 @@ async def to_code(config):
|
||||
framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||
os.environ["ESP_IDF_VERSION"] = f"{framework_ver.major}.{framework_ver.minor}"
|
||||
if framework_ver >= cv.Version(5, 5, 0):
|
||||
esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.1.5")
|
||||
esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.2.2")
|
||||
esp32.add_idf_component(name="espressif/eppp_link", ref="1.1.3")
|
||||
esp32.add_idf_component(name="espressif/esp_hosted", ref="2.6.1")
|
||||
esp32.add_idf_component(name="espressif/esp_hosted", ref="2.7.0")
|
||||
else:
|
||||
esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="0.13.0")
|
||||
esp32.add_idf_component(name="espressif/eppp_link", ref="0.2.0")
|
||||
|
||||
@@ -205,8 +205,10 @@ void LD2420Component::dump_config() {
|
||||
LOG_BUTTON(" ", "Factory Reset:", this->factory_reset_button_);
|
||||
LOG_BUTTON(" ", "Restart Module:", this->restart_module_button_);
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
ESP_LOGCONFIG(TAG, "Select:");
|
||||
LOG_SELECT(" ", "Operating Mode", this->operating_selector_);
|
||||
#endif
|
||||
if (ld2420::get_firmware_int(this->firmware_ver_) < CALIBRATE_VERSION_MIN) {
|
||||
ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->firmware_ver_);
|
||||
}
|
||||
@@ -238,12 +240,20 @@ void LD2420Component::setup() {
|
||||
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
|
||||
if (ld2420::get_firmware_int(this->firmware_ver_) < CALIBRATE_VERSION_MIN) {
|
||||
this->set_operating_mode(OP_SIMPLE_MODE_STRING);
|
||||
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
|
||||
#ifdef USE_SELECT
|
||||
if (this->operating_selector_ != nullptr) {
|
||||
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
|
||||
}
|
||||
#endif
|
||||
this->set_mode_(CMD_SYSTEM_MODE_SIMPLE);
|
||||
ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->firmware_ver_);
|
||||
} else {
|
||||
this->set_mode_(CMD_SYSTEM_MODE_ENERGY);
|
||||
this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING);
|
||||
#ifdef USE_SELECT
|
||||
if (this->operating_selector_ != nullptr) {
|
||||
this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#ifdef USE_NUMBER
|
||||
this->init_gate_config_numbers();
|
||||
@@ -383,8 +393,12 @@ void LD2420Component::set_operating_mode(const char *state) {
|
||||
// If unsupported firmware ignore mode select
|
||||
if (ld2420::get_firmware_int(firmware_ver_) >= CALIBRATE_VERSION_MIN) {
|
||||
this->current_operating_mode = find_uint8(OP_MODE_BY_STR, state);
|
||||
// Entering Auto Calibrate we need to clear the privoiuos data collection
|
||||
this->operating_selector_->publish_state(state);
|
||||
// Entering Auto Calibrate we need to clear the previous data collection
|
||||
#ifdef USE_SELECT
|
||||
if (this->operating_selector_ != nullptr) {
|
||||
this->operating_selector_->publish_state(state);
|
||||
}
|
||||
#endif
|
||||
if (current_operating_mode == OP_CALIBRATE_MODE) {
|
||||
this->set_calibration_(true);
|
||||
for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
|
||||
@@ -404,7 +418,11 @@ void LD2420Component::set_operating_mode(const char *state) {
|
||||
}
|
||||
} else {
|
||||
this->current_operating_mode = OP_SIMPLE_MODE;
|
||||
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
|
||||
#ifdef USE_SELECT
|
||||
if (this->operating_selector_ != nullptr) {
|
||||
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -740,9 +740,10 @@ def has_at_most_one_key(*keys):
|
||||
if not isinstance(obj, dict):
|
||||
raise Invalid("expected dictionary")
|
||||
|
||||
number = sum(k in keys for k in obj)
|
||||
if number > 1:
|
||||
raise Invalid(f"Cannot specify more than one of {', '.join(keys)}.")
|
||||
used = set(obj) & set(keys)
|
||||
if len(used) > 1:
|
||||
msg = "Cannot specify more than one of '" + "', '".join(used) + "'."
|
||||
raise MultipleInvalid([Invalid(msg, path=[k]) for k in used])
|
||||
return obj
|
||||
|
||||
return validate
|
||||
|
||||
@@ -4,7 +4,7 @@ from enum import Enum
|
||||
|
||||
from esphome.enum import StrEnum
|
||||
|
||||
__version__ = "2025.11.3"
|
||||
__version__ = "2025.11.4"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
||||
@@ -315,7 +315,7 @@ void Scheduler::full_cleanup_removed_items_() {
|
||||
valid_items.push_back(std::move(item));
|
||||
} else {
|
||||
// Recycle removed items
|
||||
this->recycle_item_(std::move(item));
|
||||
this->recycle_item_main_loop_(std::move(item));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,7 +400,7 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
// Don't run on failed components
|
||||
if (item->component != nullptr && item->component->is_failed()) {
|
||||
LockGuard guard{this->lock_};
|
||||
this->recycle_item_(this->pop_raw_locked_());
|
||||
this->recycle_item_main_loop_(this->pop_raw_locked_());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -413,7 +413,7 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
{
|
||||
LockGuard guard{this->lock_};
|
||||
if (is_item_removed_(item.get())) {
|
||||
this->recycle_item_(this->pop_raw_locked_());
|
||||
this->recycle_item_main_loop_(this->pop_raw_locked_());
|
||||
this->to_remove_--;
|
||||
continue;
|
||||
}
|
||||
@@ -422,7 +422,7 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
// Single-threaded or multi-threaded with atomics: can check without lock
|
||||
if (is_item_removed_(item.get())) {
|
||||
LockGuard guard{this->lock_};
|
||||
this->recycle_item_(this->pop_raw_locked_());
|
||||
this->recycle_item_main_loop_(this->pop_raw_locked_());
|
||||
this->to_remove_--;
|
||||
continue;
|
||||
}
|
||||
@@ -449,7 +449,7 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
if (executed_item->remove) {
|
||||
// We were removed/cancelled in the function call, recycle and continue
|
||||
this->to_remove_--;
|
||||
this->recycle_item_(std::move(executed_item));
|
||||
this->recycle_item_main_loop_(std::move(executed_item));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -460,7 +460,7 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
this->to_add_.push_back(std::move(executed_item));
|
||||
} else {
|
||||
// Timeout completed - recycle it
|
||||
this->recycle_item_(std::move(executed_item));
|
||||
this->recycle_item_main_loop_(std::move(executed_item));
|
||||
}
|
||||
|
||||
has_added_items |= !this->to_add_.empty();
|
||||
@@ -475,7 +475,7 @@ void HOT Scheduler::process_to_add() {
|
||||
for (auto &it : this->to_add_) {
|
||||
if (is_item_removed_(it.get())) {
|
||||
// Recycle cancelled items
|
||||
this->recycle_item_(std::move(it));
|
||||
this->recycle_item_main_loop_(std::move(it));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -509,7 +509,7 @@ size_t HOT Scheduler::cleanup_() {
|
||||
if (!item->remove)
|
||||
break;
|
||||
this->to_remove_--;
|
||||
this->recycle_item_(this->pop_raw_locked_());
|
||||
this->recycle_item_main_loop_(this->pop_raw_locked_());
|
||||
}
|
||||
return this->items_.size();
|
||||
}
|
||||
@@ -562,20 +562,15 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
||||
#endif /* not ESPHOME_THREAD_SINGLE */
|
||||
|
||||
// Cancel items in the main heap
|
||||
// Special case: if the last item in the heap matches, we can remove it immediately
|
||||
// (removing the last element doesn't break heap structure)
|
||||
// We only mark items for removal here - never recycle directly.
|
||||
// The main loop may be executing an item's callback right now, and recycling
|
||||
// would destroy the callback while it's running (use-after-free).
|
||||
// Only the main loop in call() should recycle items after execution completes.
|
||||
if (!this->items_.empty()) {
|
||||
auto &last_item = this->items_.back();
|
||||
if (this->matches_item_locked_(last_item, component, name_cstr, type, match_retry)) {
|
||||
this->recycle_item_(std::move(this->items_.back()));
|
||||
this->items_.pop_back();
|
||||
total_cancelled++;
|
||||
}
|
||||
// For other items in heap, we can only mark for removal (can't remove from middle of heap)
|
||||
size_t heap_cancelled =
|
||||
this->mark_matching_items_removed_locked_(this->items_, component, name_cstr, type, match_retry);
|
||||
total_cancelled += heap_cancelled;
|
||||
this->to_remove_ += heap_cancelled; // Track removals for heap items
|
||||
this->to_remove_ += heap_cancelled;
|
||||
}
|
||||
|
||||
// Cancel items in to_add_
|
||||
@@ -749,7 +744,7 @@ bool HOT Scheduler::SchedulerItem::cmp(const std::unique_ptr<SchedulerItem> &a,
|
||||
: (a->next_execution_high_ > b->next_execution_high_);
|
||||
}
|
||||
|
||||
void Scheduler::recycle_item_(std::unique_ptr<SchedulerItem> item) {
|
||||
void Scheduler::recycle_item_main_loop_(std::unique_ptr<SchedulerItem> item) {
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
|
||||
@@ -272,8 +272,10 @@ class Scheduler {
|
||||
return is_item_removed_(item) || (item->component != nullptr && item->component->is_failed());
|
||||
}
|
||||
|
||||
// Helper to recycle a SchedulerItem
|
||||
void recycle_item_(std::unique_ptr<SchedulerItem> item);
|
||||
// Helper to recycle a SchedulerItem back to the pool.
|
||||
// IMPORTANT: Only call from main loop context! Recycling clears the callback,
|
||||
// so calling from another thread while the callback is executing causes use-after-free.
|
||||
void recycle_item_main_loop_(std::unique_ptr<SchedulerItem> item);
|
||||
|
||||
// Helper to perform full cleanup when too many items are cancelled
|
||||
void full_cleanup_removed_items_();
|
||||
@@ -329,7 +331,7 @@ class Scheduler {
|
||||
now = this->execute_item_(item.get(), now);
|
||||
}
|
||||
// Recycle the defer item after execution
|
||||
this->recycle_item_(std::move(item));
|
||||
this->recycle_item_main_loop_(std::move(item));
|
||||
}
|
||||
|
||||
// If we've consumed all items up to the snapshot point, clean up the dead space
|
||||
|
||||
Reference in New Issue
Block a user