mirror of
https://github.com/esphome/esphome.git
synced 2026-02-08 16:51:52 +00:00
Merge branch 'error_logstr' into integration
This commit is contained in:
@@ -87,7 +87,7 @@ void AbsoluteHumidityComponent::loop() {
|
||||
break;
|
||||
default:
|
||||
this->publish_state(NAN);
|
||||
this->status_set_error("Invalid saturation vapor pressure equation selection!");
|
||||
this->status_set_error(LOG_STR("Invalid saturation vapor pressure equation selection!"));
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);
|
||||
|
||||
@@ -83,7 +83,7 @@ void AHT10Component::setup() {
|
||||
void AHT10Component::restart_read_() {
|
||||
if (this->read_count_ == AHT10_ATTEMPTS) {
|
||||
this->read_count_ = 0;
|
||||
this->status_set_error("Reading timed out");
|
||||
this->status_set_error(LOG_STR("Reading timed out"));
|
||||
return;
|
||||
}
|
||||
this->read_count_++;
|
||||
|
||||
@@ -23,7 +23,7 @@ void BH1900NUXSensor::setup() {
|
||||
i2c::ErrorCode result_code =
|
||||
this->write_register(SOFT_RESET_REG, &SOFT_RESET_PAYLOAD, 1); // Software Reset to check communication
|
||||
if (result_code != i2c::ERROR_OK) {
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,18 +100,18 @@ void BME280Component::setup() {
|
||||
|
||||
if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
return;
|
||||
}
|
||||
if (chip_id != 0x60) {
|
||||
this->error_code_ = WRONG_CHIP_ID;
|
||||
this->mark_failed(BME280_ERROR_WRONG_CHIP_ID);
|
||||
this->mark_failed(LOG_STR(BME280_ERROR_WRONG_CHIP_ID));
|
||||
return;
|
||||
}
|
||||
|
||||
// Send a soft reset.
|
||||
if (!this->write_byte(BME280_REGISTER_RESET, BME280_SOFT_RESET)) {
|
||||
this->mark_failed("Reset failed");
|
||||
this->mark_failed(LOG_STR("Reset failed"));
|
||||
return;
|
||||
}
|
||||
// Wait until the NVM data has finished loading.
|
||||
@@ -120,12 +120,12 @@ void BME280Component::setup() {
|
||||
do { // NOLINT
|
||||
delay(2);
|
||||
if (!this->read_byte(BME280_REGISTER_STATUS, &status)) {
|
||||
this->mark_failed("Error reading status register");
|
||||
this->mark_failed(LOG_STR("Error reading status register"));
|
||||
return;
|
||||
}
|
||||
} while ((status & BME280_STATUS_IM_UPDATE) && (--retry));
|
||||
if (status & BME280_STATUS_IM_UPDATE) {
|
||||
this->mark_failed("Timeout loading NVM");
|
||||
this->mark_failed(LOG_STR("Timeout loading NVM"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -153,26 +153,26 @@ void BME280Component::setup() {
|
||||
|
||||
uint8_t humid_control_val = 0;
|
||||
if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) {
|
||||
this->mark_failed("Read humidity control");
|
||||
this->mark_failed(LOG_STR("Read humidity control"));
|
||||
return;
|
||||
}
|
||||
humid_control_val &= ~0b00000111;
|
||||
humid_control_val |= this->humidity_oversampling_ & 0b111;
|
||||
if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) {
|
||||
this->mark_failed("Write humidity control");
|
||||
this->mark_failed(LOG_STR("Write humidity control"));
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t config_register = 0;
|
||||
if (!this->read_byte(BME280_REGISTER_CONFIG, &config_register)) {
|
||||
this->mark_failed("Read config");
|
||||
this->mark_failed(LOG_STR("Read config"));
|
||||
return;
|
||||
}
|
||||
config_register &= ~0b11111100;
|
||||
config_register |= 0b101 << 5; // 1000 ms standby time
|
||||
config_register |= (this->iir_filter_ & 0b111) << 2;
|
||||
if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) {
|
||||
this->mark_failed("Write config");
|
||||
this->mark_failed(LOG_STR("Write config"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,23 +65,23 @@ void BMP280Component::setup() {
|
||||
// https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855
|
||||
if (!this->bmp_read_byte(0xD0, &chip_id)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
return;
|
||||
}
|
||||
if (!this->bmp_read_byte(0xD0, &chip_id)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
return;
|
||||
}
|
||||
if (chip_id != 0x58) {
|
||||
this->error_code_ = WRONG_CHIP_ID;
|
||||
this->mark_failed(BMP280_ERROR_WRONG_CHIP_ID);
|
||||
this->mark_failed(LOG_STR(BMP280_ERROR_WRONG_CHIP_ID));
|
||||
return;
|
||||
}
|
||||
|
||||
// Send a soft reset.
|
||||
if (!this->bmp_write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
|
||||
this->mark_failed("Reset failed");
|
||||
this->mark_failed(LOG_STR("Reset failed"));
|
||||
return;
|
||||
}
|
||||
// Wait until the NVM data has finished loading.
|
||||
@@ -90,12 +90,12 @@ void BMP280Component::setup() {
|
||||
do {
|
||||
delay(2);
|
||||
if (!this->bmp_read_byte(BMP280_REGISTER_STATUS, &status)) {
|
||||
this->mark_failed("Error reading status register");
|
||||
this->mark_failed(LOG_STR("Error reading status register"));
|
||||
return;
|
||||
}
|
||||
} while ((status & BMP280_STATUS_IM_UPDATE) && (--retry));
|
||||
if (status & BMP280_STATUS_IM_UPDATE) {
|
||||
this->mark_failed("Timeout loading NVM");
|
||||
this->mark_failed(LOG_STR("Timeout loading NVM"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -116,14 +116,14 @@ void BMP280Component::setup() {
|
||||
|
||||
uint8_t config_register = 0;
|
||||
if (!this->bmp_read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
|
||||
this->mark_failed("Read config");
|
||||
this->mark_failed(LOG_STR("Read config"));
|
||||
return;
|
||||
}
|
||||
config_register &= ~0b11111100;
|
||||
config_register |= 0b000 << 5; // 0.5 ms standby time
|
||||
config_register |= (this->iir_filter_ & 0b111) << 2;
|
||||
if (!this->bmp_write_byte(BMP280_REGISTER_CONFIG, config_register)) {
|
||||
this->mark_failed("Write config");
|
||||
this->mark_failed(LOG_STR("Write config"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ Camera *Camera::global_camera = nullptr;
|
||||
|
||||
Camera::Camera() {
|
||||
if (global_camera != nullptr) {
|
||||
this->status_set_error("Multiple cameras are configured, but only one is supported.");
|
||||
this->status_set_error(LOG_STR("Multiple cameras are configured, but only one is supported."));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,13 +20,13 @@ void CST816Touchscreen::continue_setup_() {
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unknown chip ID: 0x%02X", this->chip_id_);
|
||||
this->status_set_error("Unknown chip ID");
|
||||
this->status_set_error(LOG_STR("Unknown chip ID"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION);
|
||||
} else if (!this->skip_probe_) {
|
||||
this->status_set_error("Failed to read chip id");
|
||||
this->status_set_error(LOG_STR("Failed to read chip id"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ const char *EPaperBase::epaper_state_to_string_() {
|
||||
|
||||
void EPaperBase::setup() {
|
||||
if (!this->init_buffer_(this->buffer_length_)) {
|
||||
this->mark_failed("Failed to initialise buffer");
|
||||
this->mark_failed(LOG_STR("Failed to initialise buffer"));
|
||||
return;
|
||||
}
|
||||
this->setup_pins_();
|
||||
@@ -246,7 +246,7 @@ void EPaperBase::initialise_() {
|
||||
auto length = this->init_sequence_length_;
|
||||
while (index != length) {
|
||||
if (length - index < 2) {
|
||||
this->mark_failed("Malformed init sequence");
|
||||
this->mark_failed(LOG_STR("Malformed init sequence"));
|
||||
return;
|
||||
}
|
||||
const uint8_t cmd = sequence[index++];
|
||||
|
||||
@@ -88,7 +88,7 @@ void Esp32HostedUpdate::perform(bool force) {
|
||||
hasher.add(this->firmware_data_, this->firmware_size_);
|
||||
hasher.calculate();
|
||||
if (!hasher.equals_bytes(this->firmware_sha256_.data())) {
|
||||
this->status_set_error("SHA256 verification failed");
|
||||
this->status_set_error(LOG_STR("SHA256 verification failed"));
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ void Esp32HostedUpdate::perform(bool force) {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to begin OTA: %s", esp_err_to_name(err));
|
||||
this->state_ = prev_state;
|
||||
this->status_set_error("Failed to begin OTA");
|
||||
this->status_set_error(LOG_STR("Failed to begin OTA"));
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
@@ -121,7 +121,7 @@ void Esp32HostedUpdate::perform(bool force) {
|
||||
ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err));
|
||||
esp_hosted_slave_ota_end(); // NOLINT
|
||||
this->state_ = prev_state;
|
||||
this->status_set_error("Failed to write OTA data");
|
||||
this->status_set_error(LOG_STR("Failed to write OTA data"));
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
@@ -134,7 +134,7 @@ void Esp32HostedUpdate::perform(bool force) {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(err));
|
||||
this->state_ = prev_state;
|
||||
this->status_set_error("Failed to end OTA");
|
||||
this->status_set_error(LOG_STR("Failed to end OTA"));
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
@@ -144,7 +144,7 @@ void Esp32HostedUpdate::perform(bool force) {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to activate OTA: %s", esp_err_to_name(err));
|
||||
this->state_ = prev_state;
|
||||
this->status_set_error("Failed to activate OTA");
|
||||
this->status_set_error(LOG_STR("Failed to activate OTA"));
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ void EspLdo::setup() {
|
||||
config.flags.adjustable = this->adjustable_;
|
||||
auto err = esp_ldo_acquire_channel(&config, &this->handle_);
|
||||
if (err != ESP_OK) {
|
||||
auto msg = str_sprintf("Failed to acquire LDO channel %d with voltage %fV", this->channel_, this->voltage_);
|
||||
this->mark_failed(msg.c_str());
|
||||
ESP_LOGE(TAG, "Failed to acquire LDO channel %d with voltage %fV", this->channel_, this->voltage_);
|
||||
this->mark_failed(LOG_STR("Failed to acquire LDO channel"));
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Acquired LDO channel %d with voltage %fV", this->channel_, this->voltage_);
|
||||
}
|
||||
|
||||
@@ -36,20 +36,20 @@ void GDK101Component::setup() {
|
||||
uint8_t data[2];
|
||||
// first, reset the sensor
|
||||
if (!this->reset_sensor_(data)) {
|
||||
this->status_set_error("Reset failed!");
|
||||
this->status_set_error(LOG_STR("Reset failed!"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// sensor should acknowledge success of the reset procedure
|
||||
if (data[0] != 1) {
|
||||
this->status_set_error("Reset not acknowledged!");
|
||||
this->status_set_error(LOG_STR("Reset not acknowledged!"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
delay(10);
|
||||
// read firmware version
|
||||
if (!this->read_fw_version_(data)) {
|
||||
this->status_set_error("Failed to read firmware version");
|
||||
this->status_set_error(LOG_STR("Failed to read firmware version"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -79,13 +79,13 @@ void GT911Touchscreen::setup_internal_() {
|
||||
}
|
||||
}
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->mark_failed("Calibration error");
|
||||
this->mark_failed(LOG_STR("Calibration error"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
return;
|
||||
}
|
||||
this->setup_done_ = true;
|
||||
|
||||
@@ -29,7 +29,7 @@ void HttpRequestUpdate::setup() {
|
||||
this->publish_state();
|
||||
} else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) {
|
||||
this->state_ = update::UPDATE_STATE_AVAILABLE;
|
||||
this->status_set_error("Failed to install firmware");
|
||||
this->status_set_error(LOG_STR("Failed to install firmware"));
|
||||
this->publish_state();
|
||||
}
|
||||
});
|
||||
@@ -51,7 +51,7 @@ void HttpRequestUpdate::update_task(void *params) {
|
||||
if (container == nullptr || container->status_code != HTTP_STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Failed to fetch manifest from %s", this_update->source_url_.c_str());
|
||||
// Defer to main loop to avoid race condition on component_state_ read-modify-write
|
||||
this_update->defer([this_update]() { this_update->status_set_error("Failed to fetch manifest"); });
|
||||
this_update->defer([this_update]() { this_update->status_set_error(LOG_STR("Failed to fetch manifest")); });
|
||||
UPDATE_RETURN;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,8 @@ void HttpRequestUpdate::update_task(void *params) {
|
||||
if (data == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to allocate %zu bytes for manifest", container->content_length);
|
||||
// Defer to main loop to avoid race condition on component_state_ read-modify-write
|
||||
this_update->defer([this_update]() { this_update->status_set_error("Failed to allocate memory for manifest"); });
|
||||
this_update->defer(
|
||||
[this_update]() { this_update->status_set_error(LOG_STR("Failed to allocate memory for manifest")); });
|
||||
container->end();
|
||||
UPDATE_RETURN;
|
||||
}
|
||||
@@ -123,7 +124,7 @@ void HttpRequestUpdate::update_task(void *params) {
|
||||
if (!valid) {
|
||||
ESP_LOGE(TAG, "Failed to parse JSON from %s", this_update->source_url_.c_str());
|
||||
// Defer to main loop to avoid race condition on component_state_ read-modify-write
|
||||
this_update->defer([this_update]() { this_update->status_set_error("Failed to parse manifest JSON"); });
|
||||
this_update->defer([this_update]() { this_update->status_set_error(LOG_STR("Failed to parse manifest JSON")); });
|
||||
UPDATE_RETURN;
|
||||
}
|
||||
|
||||
|
||||
@@ -466,7 +466,7 @@ void LvglComponent::setup() {
|
||||
buffer = lv_custom_mem_alloc(buf_bytes); // NOLINT
|
||||
}
|
||||
if (buffer == nullptr) {
|
||||
this->status_set_error("Memory allocation failure");
|
||||
this->status_set_error(LOG_STR("Memory allocation failure"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -479,7 +479,7 @@ void LvglComponent::setup() {
|
||||
if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) {
|
||||
this->rotate_buf_ = static_cast<lv_color_t *>(lv_custom_mem_alloc(buf_bytes)); // NOLINT
|
||||
if (this->rotate_buf_ == nullptr) {
|
||||
this->status_set_error("Memory allocation failure");
|
||||
this->status_set_error(LOG_STR("Memory allocation failure"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -57,14 +57,14 @@ void MAX17043Component::setup() {
|
||||
|
||||
if (config_reg != MAX17043_CONFIG_POWER_UP_DEFAULT) {
|
||||
ESP_LOGE(TAG, "Device does not appear to be a MAX17043");
|
||||
this->status_set_error("unrecognised");
|
||||
this->status_set_error(LOG_STR("unrecognised"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// need to write back to config register to reset the sleep bit
|
||||
if (!this->write_byte_16(MAX17043_CONFIG, MAX17043_CONFIG_POWER_UP_DEFAULT)) {
|
||||
this->status_set_error("sleep reset failed");
|
||||
this->status_set_error(LOG_STR("sleep reset failed"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@ static bool notify_refresh_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel
|
||||
xSemaphoreGiveFromISR(sem, &need_yield);
|
||||
return (need_yield == pdTRUE);
|
||||
}
|
||||
|
||||
void MIPI_DSI::smark_failed(const LogString *message, esp_err_t err) {
|
||||
ESP_LOGE(TAG, "%s: %s", LOG_STR_ARG(message), esp_err_to_name(err));
|
||||
this->mark_failed(message);
|
||||
}
|
||||
|
||||
void MIPI_DSI::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Running Setup");
|
||||
|
||||
@@ -31,7 +37,7 @@ void MIPI_DSI::setup() {
|
||||
};
|
||||
auto err = esp_lcd_new_dsi_bus(&bus_config, &this->bus_handle_);
|
||||
if (err != ESP_OK) {
|
||||
this->smark_failed("lcd_new_dsi_bus failed", err);
|
||||
this->smark_failed(LOG_STR("lcd_new_dsi_bus failed"), err);
|
||||
return;
|
||||
}
|
||||
esp_lcd_dbi_io_config_t dbi_config = {
|
||||
@@ -41,7 +47,7 @@ void MIPI_DSI::setup() {
|
||||
};
|
||||
err = esp_lcd_new_panel_io_dbi(this->bus_handle_, &dbi_config, &this->io_handle_);
|
||||
if (err != ESP_OK) {
|
||||
this->smark_failed("new_panel_io_dbi failed", err);
|
||||
this->smark_failed(LOG_STR("new_panel_io_dbi failed"), err);
|
||||
return;
|
||||
}
|
||||
auto pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565;
|
||||
@@ -69,7 +75,7 @@ void MIPI_DSI::setup() {
|
||||
}};
|
||||
err = esp_lcd_new_panel_dpi(this->bus_handle_, &dpi_config, &this->handle_);
|
||||
if (err != ESP_OK) {
|
||||
this->smark_failed("esp_lcd_new_panel_dpi failed", err);
|
||||
this->smark_failed(LOG_STR("esp_lcd_new_panel_dpi failed"), err);
|
||||
return;
|
||||
}
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
@@ -86,14 +92,14 @@ void MIPI_DSI::setup() {
|
||||
auto when = millis() + 120;
|
||||
err = esp_lcd_panel_init(this->handle_);
|
||||
if (err != ESP_OK) {
|
||||
this->smark_failed("esp_lcd_init failed", err);
|
||||
this->smark_failed(LOG_STR("esp_lcd_init failed"), err);
|
||||
return;
|
||||
}
|
||||
size_t index = 0;
|
||||
auto &vec = this->init_sequence_;
|
||||
while (index != vec.size()) {
|
||||
if (vec.size() - index < 2) {
|
||||
this->mark_failed("Malformed init sequence");
|
||||
this->mark_failed(LOG_STR("Malformed init sequence"));
|
||||
return;
|
||||
}
|
||||
uint8_t cmd = vec[index++];
|
||||
@@ -104,7 +110,7 @@ void MIPI_DSI::setup() {
|
||||
} else {
|
||||
uint8_t num_args = x & 0x7F;
|
||||
if (vec.size() - index < num_args) {
|
||||
this->mark_failed("Malformed init sequence");
|
||||
this->mark_failed(LOG_STR("Malformed init sequence"));
|
||||
return;
|
||||
}
|
||||
if (cmd == SLEEP_OUT) {
|
||||
@@ -119,7 +125,7 @@ void MIPI_DSI::setup() {
|
||||
format_hex_pretty(ptr, num_args, '.', false).c_str());
|
||||
err = esp_lcd_panel_io_tx_param(this->io_handle_, cmd, ptr, num_args);
|
||||
if (err != ESP_OK) {
|
||||
this->smark_failed("lcd_panel_io_tx_param failed", err);
|
||||
this->smark_failed(LOG_STR("lcd_panel_io_tx_param failed"), err);
|
||||
return;
|
||||
}
|
||||
index += num_args;
|
||||
@@ -134,7 +140,7 @@ void MIPI_DSI::setup() {
|
||||
|
||||
err = (esp_lcd_dpi_panel_register_event_callbacks(this->handle_, &cbs, this->io_lock_));
|
||||
if (err != ESP_OK) {
|
||||
this->smark_failed("Failed to register callbacks", err);
|
||||
this->smark_failed(LOG_STR("Failed to register callbacks"), err);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -216,7 +222,7 @@ bool MIPI_DSI::check_buffer_() {
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
this->buffer_ = allocator.allocate(this->height_ * this->width_ * bytes_per_pixel);
|
||||
if (this->buffer_ == nullptr) {
|
||||
this->mark_failed("Could not allocate buffer for display!");
|
||||
this->mark_failed(LOG_STR("Could not allocate buffer for display!"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -62,10 +62,7 @@ class MIPI_DSI : public display::Display {
|
||||
void set_lanes(uint8_t lanes) { this->lanes_ = lanes; }
|
||||
void set_madctl(uint8_t madctl) { this->madctl_ = madctl; }
|
||||
|
||||
void smark_failed(const char *message, esp_err_t err) {
|
||||
auto str = str_sprintf("Setup failed: %s: %s", message, esp_err_to_name(err));
|
||||
this->mark_failed(str.c_str());
|
||||
}
|
||||
void smark_failed(const LogString *message, esp_err_t err);
|
||||
|
||||
void update() override;
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ void MipiRgbSpi::write_init_sequence_() {
|
||||
auto &vec = this->init_sequence_;
|
||||
while (index != vec.size()) {
|
||||
if (vec.size() - index < 2) {
|
||||
this->mark_failed("Malformed init sequence");
|
||||
this->mark_failed(LOG_STR("Malformed init sequence"));
|
||||
return;
|
||||
}
|
||||
uint8_t cmd = vec[index++];
|
||||
@@ -84,7 +84,7 @@ void MipiRgbSpi::write_init_sequence_() {
|
||||
} else {
|
||||
uint8_t num_args = x & 0x7F;
|
||||
if (vec.size() - index < num_args) {
|
||||
this->mark_failed("Malformed init sequence");
|
||||
this->mark_failed(LOG_STR("Malformed init sequence"));
|
||||
return;
|
||||
}
|
||||
if (cmd == SLEEP_OUT) {
|
||||
@@ -164,8 +164,8 @@ void MipiRgb::common_setup_() {
|
||||
if (err == ESP_OK)
|
||||
err = esp_lcd_panel_init(this->handle_);
|
||||
if (err != ESP_OK) {
|
||||
auto msg = str_sprintf("lcd setup failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed(msg.c_str());
|
||||
ESP_LOGE(TAG, "lcd setup failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed(LOG_STR("lcd setup failed"));
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "MipiRgb setup complete");
|
||||
}
|
||||
@@ -249,7 +249,7 @@ bool MipiRgb::check_buffer_() {
|
||||
RAMAllocator<uint16_t> allocator;
|
||||
this->buffer_ = allocator.allocate(this->height_ * this->width_);
|
||||
if (this->buffer_ == nullptr) {
|
||||
this->mark_failed("Could not allocate buffer for display!");
|
||||
this->mark_failed(LOG_STR("Could not allocate buffer for display!"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -478,7 +478,7 @@ class MipiSpiBuffer : public MipiSpi<BUFFERTYPE, BUFFERPIXEL, IS_BIG_ENDIAN, DIS
|
||||
RAMAllocator<BUFFERTYPE> allocator{};
|
||||
this->buffer_ = allocator.allocate(BUFFER_WIDTH * BUFFER_HEIGHT / FRACTION);
|
||||
if (this->buffer_ == nullptr) {
|
||||
this->mark_failed("Buffer allocation failed");
|
||||
this->mark_failed(LOG_STR("Buffer allocation failed"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,19 +78,20 @@ void SourceSpeaker::loop() {
|
||||
} else {
|
||||
switch (err) {
|
||||
case ESP_ERR_NO_MEM:
|
||||
this->status_set_error("Failed to start mixer: not enough memory");
|
||||
this->status_set_error(LOG_STR("Failed to start mixer: not enough memory"));
|
||||
break;
|
||||
case ESP_ERR_NOT_SUPPORTED:
|
||||
this->status_set_error("Failed to start mixer: unsupported bits per sample");
|
||||
this->status_set_error(LOG_STR("Failed to start mixer: unsupported bits per sample"));
|
||||
break;
|
||||
case ESP_ERR_INVALID_ARG:
|
||||
this->status_set_error("Failed to start mixer: audio stream isn't compatible with the other audio stream.");
|
||||
this->status_set_error(
|
||||
LOG_STR("Failed to start mixer: audio stream isn't compatible with the other audio stream."));
|
||||
break;
|
||||
case ESP_ERR_INVALID_STATE:
|
||||
this->status_set_error("Failed to start mixer: mixer task failed to start");
|
||||
this->status_set_error(LOG_STR("Failed to start mixer: mixer task failed to start"));
|
||||
break;
|
||||
default:
|
||||
this->status_set_error("Failed to start mixer");
|
||||
this->status_set_error(LOG_STR("Failed to start mixer"));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -317,7 +318,7 @@ void MixerSpeaker::loop() {
|
||||
xEventGroupClearBits(this->event_group_, MixerEventGroupBits::STATE_STARTING);
|
||||
}
|
||||
if (event_group_bits & MixerEventGroupBits::ERR_ESP_NO_MEM) {
|
||||
this->status_set_error("Failed to allocate the mixer's internal buffer");
|
||||
this->status_set_error(LOG_STR("Failed to allocate the mixer's internal buffer"));
|
||||
xEventGroupClearBits(this->event_group_, MixerEventGroupBits::ERR_ESP_NO_MEM);
|
||||
}
|
||||
if (event_group_bits & MixerEventGroupBits::STATE_RUNNING) {
|
||||
|
||||
@@ -278,7 +278,7 @@ void NAU7802Sensor::loop() {
|
||||
this->set_calibration_failure_(true);
|
||||
this->state_ = CalibrationState::INACTIVE;
|
||||
ESP_LOGE(TAG, "Failed to calibrate sensor");
|
||||
this->status_set_error("Calibration Failed");
|
||||
this->status_set_error(LOG_STR("Calibration Failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ static void add(std::vector<uint8_t> &vec, const char *str) {
|
||||
void PacketTransport::setup() {
|
||||
this->name_ = App.get_name().c_str();
|
||||
if (strlen(this->name_) > 255) {
|
||||
this->status_set_error("Device name exceeds 255 chars");
|
||||
this->status_set_error(LOG_STR("Device name exceeds 255 chars"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ void QMP6988Component::calculate_pressure_() {
|
||||
|
||||
void QMP6988Component::setup() {
|
||||
if (!this->device_check_()) {
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,17 +66,17 @@ void ResamplerSpeaker::loop() {
|
||||
}
|
||||
|
||||
if (event_group_bits & ResamplingEventGroupBits::ERR_ESP_NO_MEM) {
|
||||
this->status_set_error("Resampler task failed to allocate the internal buffers");
|
||||
this->status_set_error(LOG_STR("Resampler task failed to allocate the internal buffers"));
|
||||
xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ERR_ESP_NO_MEM);
|
||||
this->state_ = speaker::STATE_STOPPING;
|
||||
}
|
||||
if (event_group_bits & ResamplingEventGroupBits::ERR_ESP_NOT_SUPPORTED) {
|
||||
this->status_set_error("Cannot resample due to an unsupported audio stream");
|
||||
this->status_set_error(LOG_STR("Cannot resample due to an unsupported audio stream"));
|
||||
xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ERR_ESP_NOT_SUPPORTED);
|
||||
this->state_ = speaker::STATE_STOPPING;
|
||||
}
|
||||
if (event_group_bits & ResamplingEventGroupBits::ERR_ESP_FAIL) {
|
||||
this->status_set_error("Resampler task failed");
|
||||
this->status_set_error(LOG_STR("Resampler task failed"));
|
||||
xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ERR_ESP_FAIL);
|
||||
this->state_ = speaker::STATE_STOPPING;
|
||||
}
|
||||
@@ -106,12 +106,12 @@ void ResamplerSpeaker::loop() {
|
||||
} else {
|
||||
switch (err) {
|
||||
case ESP_ERR_INVALID_STATE:
|
||||
this->status_set_error("Failed to start resampler: resampler task failed to start");
|
||||
this->status_set_error(LOG_STR("Failed to start resampler: resampler task failed to start"));
|
||||
break;
|
||||
case ESP_ERR_NO_MEM:
|
||||
this->status_set_error("Failed to start resampler: not enough memory for task stack");
|
||||
this->status_set_error(LOG_STR("Failed to start resampler: not enough memory for task stack"));
|
||||
default:
|
||||
this->status_set_error("Failed to start resampler");
|
||||
this->status_set_error(LOG_STR("Failed to start resampler"));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <forward_list>
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -290,10 +290,10 @@ template<class C, typename... Ts> class ScriptWaitAction : public Action<Ts...>,
|
||||
}
|
||||
|
||||
// Store parameters for later execution
|
||||
this->param_queue_.emplace_front(x...);
|
||||
// Enable loop now that we have work to do
|
||||
this->param_queue_.emplace_back(x...);
|
||||
// Enable loop now that we have work to do - don't call loop() synchronously!
|
||||
// Let the event loop call it to avoid reentrancy issues
|
||||
this->enable_loop();
|
||||
this->loop();
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
@@ -303,13 +303,17 @@ template<class C, typename... Ts> class ScriptWaitAction : public Action<Ts...>,
|
||||
if (this->script_->is_running())
|
||||
return;
|
||||
|
||||
while (!this->param_queue_.empty()) {
|
||||
// Only process ONE queued item per loop iteration
|
||||
// Processing all items in a while loop causes infinite loops because
|
||||
// play_next_() can trigger more items to be queued
|
||||
if (!this->param_queue_.empty()) {
|
||||
auto ¶ms = this->param_queue_.front();
|
||||
this->play_next_tuple_(params, std::make_index_sequence<sizeof...(Ts)>{});
|
||||
this->param_queue_.pop_front();
|
||||
} else {
|
||||
// Queue is now empty - disable loop until next play_complex
|
||||
this->disable_loop();
|
||||
}
|
||||
// Queue is now empty - disable loop until next play_complex
|
||||
this->disable_loop();
|
||||
}
|
||||
|
||||
void play(const Ts &...x) override { /* ignore - see play_complex */
|
||||
@@ -326,7 +330,7 @@ template<class C, typename... Ts> class ScriptWaitAction : public Action<Ts...>,
|
||||
}
|
||||
|
||||
C *script_;
|
||||
std::forward_list<std::tuple<Ts...>> param_queue_;
|
||||
std::list<std::tuple<Ts...>> param_queue_;
|
||||
};
|
||||
|
||||
} // namespace script
|
||||
|
||||
@@ -13,7 +13,7 @@ void SHT4XComponent::start_heater_() {
|
||||
|
||||
ESP_LOGD(TAG, "Heater turning on");
|
||||
if (this->write(cmd, 1) != i2c::ERROR_OK) {
|
||||
this->status_set_error("Failed to turn on heater");
|
||||
this->status_set_error(LOG_STR("Failed to turn on heater"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ static const float SENSOR_SCALE = 0.01f; // Sensor resolution in degrees Celsiu
|
||||
void STTS22HComponent::setup() {
|
||||
// Check if device is a STTS22H
|
||||
if (!this->is_stts22h_sensor_()) {
|
||||
this->mark_failed("Device is not a STTS22H sensor");
|
||||
this->mark_failed(LOG_STR("Device is not a STTS22H sensor"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -61,12 +61,12 @@ float STTS22HComponent::read_temperature_() {
|
||||
bool STTS22HComponent::is_stts22h_sensor_() {
|
||||
uint8_t whoami_value;
|
||||
if (this->read_register(WHOAMI_REG, &whoami_value, 1) != i2c::NO_ERROR) {
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (whoami_value != WHOAMI_STTS22H_IDENTIFICATION) {
|
||||
this->mark_failed("Unexpected WHOAMI identifier. Sensor is not a STTS22H");
|
||||
this->mark_failed(LOG_STR("Unexpected WHOAMI identifier. Sensor is not a STTS22H"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ void STTS22HComponent::initialize_sensor_() {
|
||||
// Read current CTRL_REG configuration
|
||||
uint8_t ctrl_value;
|
||||
if (this->read_register(CTRL_REG, &ctrl_value, 1) != i2c::NO_ERROR) {
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -86,14 +86,14 @@ void STTS22HComponent::initialize_sensor_() {
|
||||
// FREERUN bit must be cleared (see sensor documentation)
|
||||
ctrl_value &= ~FREERUN_CTRL_ENABLE_FLAG; // Clear FREERUN bit
|
||||
if (this->write_register(CTRL_REG, &ctrl_value, 1) != i2c::NO_ERROR) {
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable LOW ODR mode and ADD_INC
|
||||
ctrl_value |= LOW_ODR_CTRL_ENABLE_FLAG | ADD_INC_ENABLE_FLAG; // Set LOW ODR bit and ADD_INC bit
|
||||
if (this->write_register(CTRL_REG, &ctrl_value, 1) != i2c::NO_ERROR) {
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ void UDPComponent::setup() {
|
||||
if (this->should_broadcast_) {
|
||||
this->broadcast_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (this->broadcast_socket_ == nullptr) {
|
||||
this->status_set_error("Could not create socket");
|
||||
this->status_set_error(LOG_STR("Could not create socket"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -41,14 +41,14 @@ void UDPComponent::setup() {
|
||||
if (this->should_listen_) {
|
||||
this->listen_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (this->listen_socket_ == nullptr) {
|
||||
this->status_set_error("Could not create socket");
|
||||
this->status_set_error(LOG_STR("Could not create socket"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
auto err = this->listen_socket_->setblocking(false);
|
||||
if (err < 0) {
|
||||
ESP_LOGE(TAG, "Unable to set nonblocking: errno %d", errno);
|
||||
this->status_set_error("Unable to set nonblocking");
|
||||
this->status_set_error(LOG_STR("Unable to set nonblocking"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -73,7 +73,7 @@ void UDPComponent::setup() {
|
||||
err = this->listen_socket_->setsockopt(IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(imreq));
|
||||
if (err < 0) {
|
||||
ESP_LOGE(TAG, "Failed to set IP_ADD_MEMBERSHIP. Error %d", errno);
|
||||
this->status_set_error("Failed to set IP_ADD_MEMBERSHIP");
|
||||
this->status_set_error(LOG_STR("Failed to set IP_ADD_MEMBERSHIP"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -82,7 +82,7 @@ void UDPComponent::setup() {
|
||||
err = this->listen_socket_->bind((struct sockaddr *) &server, sizeof(server));
|
||||
if (err != 0) {
|
||||
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
|
||||
this->status_set_error("Unable to bind socket");
|
||||
this->status_set_error(LOG_STR("Unable to bind socket"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ void USBClient::setup() {
|
||||
auto err = usb_host_client_register(&config, &this->handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "client register failed: %s", esp_err_to_name(err));
|
||||
this->status_set_error("Client register failed");
|
||||
this->status_set_error(LOG_STR("Client register failed"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ void USBHost::setup() {
|
||||
usb_host_config_t config{};
|
||||
|
||||
if (usb_host_install(&config) != ESP_OK) {
|
||||
this->status_set_error("usb_host_install failed");
|
||||
this->status_set_error(LOG_STR("usb_host_install failed"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -320,7 +320,7 @@ static void fix_mps(const usb_ep_desc_t *ep) {
|
||||
void USBUartTypeCdcAcm::on_connected() {
|
||||
auto cdc_devs = this->parse_descriptors(this->device_handle_);
|
||||
if (cdc_devs.empty()) {
|
||||
this->status_set_error("No CDC-ACM device found");
|
||||
this->status_set_error(LOG_STR("No CDC-ACM device found"));
|
||||
this->disconnect();
|
||||
return;
|
||||
}
|
||||
@@ -341,7 +341,7 @@ void USBUartTypeCdcAcm::on_connected() {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "usb_host_interface_claim failed: %s, channel=%d, intf=%d", esp_err_to_name(err), channel->index_,
|
||||
channel->cdc_dev_.bulk_interface_number);
|
||||
this->status_set_error("usb_host_interface_claim failed");
|
||||
this->status_set_error(LOG_STR("usb_host_interface_claim failed"));
|
||||
this->disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ void VoiceAssistant::loop() {
|
||||
case State::START_MICROPHONE: {
|
||||
ESP_LOGD(TAG, "Starting Microphone");
|
||||
if (!this->allocate_buffers_()) {
|
||||
this->status_set_error("Failed to allocate buffers");
|
||||
this->status_set_error(LOG_STR("Failed to allocate buffers"));
|
||||
return;
|
||||
}
|
||||
if (this->status_has_error()) {
|
||||
|
||||
@@ -67,7 +67,7 @@ void WakeOnLanButton::setup() {
|
||||
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
this->broadcast_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (this->broadcast_socket_ == nullptr) {
|
||||
this->status_set_error("Could not create socket");
|
||||
this->status_set_error(LOG_STR("Could not create socket"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <forward_list>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -445,9 +445,10 @@ template<typename... Ts> class WaitUntilAction : public Action<Ts...>, public Co
|
||||
// Store for later processing
|
||||
auto now = millis();
|
||||
auto timeout = this->timeout_value_.optional_value(x...);
|
||||
this->var_queue_.emplace_front(now, timeout, std::make_tuple(x...));
|
||||
this->var_queue_.emplace_back(now, timeout, std::make_tuple(x...));
|
||||
|
||||
// Do immediate check with fresh timestamp
|
||||
// Do immediate check with fresh timestamp - don't call loop() synchronously!
|
||||
// Let the event loop call it to avoid reentrancy issues
|
||||
if (this->process_queue_(now)) {
|
||||
// Only enable loop if we still have pending items
|
||||
this->enable_loop();
|
||||
@@ -499,7 +500,7 @@ template<typename... Ts> class WaitUntilAction : public Action<Ts...>, public Co
|
||||
}
|
||||
|
||||
Condition<Ts...> *condition_;
|
||||
std::forward_list<std::tuple<uint32_t, optional<uint32_t>, std::tuple<Ts...>>> var_queue_{};
|
||||
std::list<std::tuple<uint32_t, optional<uint32_t>, std::tuple<Ts...>>> var_queue_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class UpdateComponentAction : public Action<Ts...> {
|
||||
|
||||
@@ -36,6 +36,9 @@ namespace {
|
||||
struct ComponentErrorMessage {
|
||||
const Component *component;
|
||||
const char *message;
|
||||
// Track if message is flash pointer (needs LOG_STR_ARG) or RAM pointer
|
||||
// Remove before 2026.6.0 when deprecated const char* API is removed
|
||||
bool is_flash_ptr;
|
||||
};
|
||||
|
||||
struct ComponentPriorityOverride {
|
||||
@@ -49,6 +52,25 @@ std::unique_ptr<std::vector<ComponentErrorMessage>> component_error_messages;
|
||||
// Setup priority overrides - freed after setup completes
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
std::unique_ptr<std::vector<ComponentPriorityOverride>> setup_priority_overrides;
|
||||
|
||||
// Helper to store error messages - reduces duplication between deprecated and new API
|
||||
// Remove before 2026.6.0 when deprecated const char* API is removed
|
||||
void store_component_error_message(const Component *component, const char *message, bool is_flash_ptr) {
|
||||
// Lazy allocate the error messages vector if needed
|
||||
if (!component_error_messages) {
|
||||
component_error_messages = std::make_unique<std::vector<ComponentErrorMessage>>();
|
||||
}
|
||||
// Check if this component already has an error message
|
||||
for (auto &entry : *component_error_messages) {
|
||||
if (entry.component == component) {
|
||||
entry.message = message;
|
||||
entry.is_flash_ptr = is_flash_ptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Add new error message
|
||||
component_error_messages->emplace_back(ComponentErrorMessage{component, message, is_flash_ptr});
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace setup_priority {
|
||||
@@ -143,16 +165,20 @@ void Component::call_dump_config() {
|
||||
if (this->is_failed()) {
|
||||
// Look up error message from global vector
|
||||
const char *error_msg = nullptr;
|
||||
bool is_flash_ptr = false;
|
||||
if (component_error_messages) {
|
||||
for (const auto &entry : *component_error_messages) {
|
||||
if (entry.component == this) {
|
||||
error_msg = entry.message;
|
||||
is_flash_ptr = entry.is_flash_ptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Log with appropriate format based on pointer type
|
||||
ESP_LOGE(TAG, " %s is marked FAILED: %s", LOG_STR_ARG(this->get_component_log_str()),
|
||||
error_msg ? error_msg : LOG_STR_LITERAL("unspecified"));
|
||||
error_msg ? (is_flash_ptr ? LOG_STR_ARG((const LogString *) error_msg) : error_msg)
|
||||
: LOG_STR_LITERAL("unspecified"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,19 +341,19 @@ void Component::status_set_error(const char *message) {
|
||||
ESP_LOGE(TAG, "%s set Error flag: %s", LOG_STR_ARG(this->get_component_log_str()),
|
||||
message ? message : LOG_STR_LITERAL("unspecified"));
|
||||
if (message != nullptr) {
|
||||
// Lazy allocate the error messages vector if needed
|
||||
if (!component_error_messages) {
|
||||
component_error_messages = std::make_unique<std::vector<ComponentErrorMessage>>();
|
||||
}
|
||||
// Check if this component already has an error message
|
||||
for (auto &entry : *component_error_messages) {
|
||||
if (entry.component == this) {
|
||||
entry.message = message;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Add new error message
|
||||
component_error_messages->emplace_back(ComponentErrorMessage{this, message});
|
||||
store_component_error_message(this, message, false);
|
||||
}
|
||||
}
|
||||
void Component::status_set_error(const LogString *message) {
|
||||
if ((this->component_state_ & STATUS_LED_ERROR) != 0)
|
||||
return;
|
||||
this->component_state_ |= STATUS_LED_ERROR;
|
||||
App.app_state_ |= STATUS_LED_ERROR;
|
||||
ESP_LOGE(TAG, "%s set Error flag: %s", LOG_STR_ARG(this->get_component_log_str()),
|
||||
message ? LOG_STR_ARG(message) : LOG_STR_LITERAL("unspecified"));
|
||||
if (message != nullptr) {
|
||||
// Store the LogString pointer directly (safe because LogString is always in flash/static memory)
|
||||
store_component_error_message(this, LOG_STR_ARG(message), true);
|
||||
}
|
||||
}
|
||||
void Component::status_clear_warning() {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/optional.h"
|
||||
|
||||
@@ -157,11 +158,18 @@ class Component {
|
||||
*/
|
||||
virtual void mark_failed();
|
||||
|
||||
// Remove before 2026.6.0
|
||||
ESPDEPRECATED("Use mark_failed(LOG_STR(message)) instead. Will stop working in 2026.6.0", "2025.12.0")
|
||||
void mark_failed(const char *message) {
|
||||
this->status_set_error(message);
|
||||
this->mark_failed();
|
||||
}
|
||||
|
||||
void mark_failed(const LogString *message) {
|
||||
this->status_set_error(message);
|
||||
this->mark_failed();
|
||||
}
|
||||
|
||||
/** Disable this component's loop. The loop() method will no longer be called.
|
||||
*
|
||||
* This is useful for components that only need to run for a certain period of time
|
||||
@@ -216,7 +224,10 @@ class Component {
|
||||
void status_set_warning(const char *message = nullptr);
|
||||
void status_set_warning(const LogString *message);
|
||||
|
||||
// Remove before 2026.6.0
|
||||
ESPDEPRECATED("Use status_set_error(LOG_STR(message)) instead. Will stop working in 2026.6.0", "2025.12.0")
|
||||
void status_set_error(const char *message = nullptr);
|
||||
void status_set_error(const LogString *message);
|
||||
|
||||
void status_clear_warning();
|
||||
|
||||
|
||||
131
tests/integration/fixtures/script_delay_with_params.yaml
Normal file
131
tests/integration/fixtures/script_delay_with_params.yaml
Normal file
@@ -0,0 +1,131 @@
|
||||
esphome:
|
||||
name: test-script-delay-params
|
||||
|
||||
host:
|
||||
|
||||
api:
|
||||
actions:
|
||||
# Test case from issue #12044: parent script with repeat calling child with delay
|
||||
- action: test_repeat_with_delay
|
||||
then:
|
||||
- logger.log: "=== TEST: Repeat loop calling script with delay and parameters ==="
|
||||
- script.execute: father_script
|
||||
|
||||
# Test case from issue #12043: script.wait with delayed child script
|
||||
- action: test_script_wait
|
||||
then:
|
||||
- logger.log: "=== TEST: script.wait with delayed child script ==="
|
||||
- script.execute: show_start_page
|
||||
- script.wait: show_start_page
|
||||
- logger.log: "After wait: script completed successfully"
|
||||
|
||||
# Test: Delay with different parameter types
|
||||
- action: test_delay_param_types
|
||||
then:
|
||||
- logger.log: "=== TEST: Delay with various parameter types ==="
|
||||
- script.execute:
|
||||
id: delay_with_int
|
||||
val: 42
|
||||
- delay: 50ms
|
||||
- script.execute:
|
||||
id: delay_with_string
|
||||
msg: "test message"
|
||||
- delay: 50ms
|
||||
- script.execute:
|
||||
id: delay_with_float
|
||||
num: 3.14
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
||||
script:
|
||||
# Reproduces issue #12044: child script with conditional delay
|
||||
- id: son_script
|
||||
mode: single
|
||||
parameters:
|
||||
iteration: int
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Son script started with iteration %d"
|
||||
args: ['iteration']
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return iteration >= 5;'
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Son script delaying for iteration %d"
|
||||
args: ['iteration']
|
||||
- delay: 100ms
|
||||
- logger.log:
|
||||
format: "Son script finished with iteration %d"
|
||||
args: ['iteration']
|
||||
|
||||
# Reproduces issue #12044: parent script with repeat loop
|
||||
- id: father_script
|
||||
mode: single
|
||||
then:
|
||||
- repeat:
|
||||
count: 10
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Father iteration %d: calling son"
|
||||
args: ['iteration']
|
||||
- script.execute:
|
||||
id: son_script
|
||||
iteration: !lambda 'return iteration;'
|
||||
- script.wait: son_script
|
||||
- logger.log:
|
||||
format: "Father iteration %d: son finished, wait returned"
|
||||
args: ['iteration']
|
||||
|
||||
# Reproduces issue #12043: script.wait hangs
|
||||
- id: show_start_page
|
||||
mode: single
|
||||
then:
|
||||
- logger.log: "Start page: beginning"
|
||||
- delay: 100ms
|
||||
- logger.log: "Start page: after delay"
|
||||
- delay: 100ms
|
||||
- logger.log: "Start page: completed"
|
||||
|
||||
# Test delay with int parameter
|
||||
- id: delay_with_int
|
||||
mode: single
|
||||
parameters:
|
||||
val: int
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Int test: before delay, val=%d"
|
||||
args: ['val']
|
||||
- delay: 50ms
|
||||
- logger.log:
|
||||
format: "Int test: after delay, val=%d"
|
||||
args: ['val']
|
||||
|
||||
# Test delay with string parameter
|
||||
- id: delay_with_string
|
||||
mode: single
|
||||
parameters:
|
||||
msg: string
|
||||
then:
|
||||
- logger.log:
|
||||
format: "String test: before delay, msg=%s"
|
||||
args: ['msg.c_str()']
|
||||
- delay: 50ms
|
||||
- logger.log:
|
||||
format: "String test: after delay, msg=%s"
|
||||
args: ['msg.c_str()']
|
||||
|
||||
# Test delay with float parameter
|
||||
- id: delay_with_float
|
||||
mode: single
|
||||
parameters:
|
||||
num: float
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Float test: before delay, num=%.2f"
|
||||
args: ['num']
|
||||
- delay: 50ms
|
||||
- logger.log:
|
||||
format: "Float test: after delay, num=%.2f"
|
||||
args: ['num']
|
||||
82
tests/integration/fixtures/wait_until_fifo_ordering.yaml
Normal file
82
tests/integration/fixtures/wait_until_fifo_ordering.yaml
Normal file
@@ -0,0 +1,82 @@
|
||||
esphome:
|
||||
name: test-wait-until-ordering
|
||||
|
||||
host:
|
||||
|
||||
api:
|
||||
actions:
|
||||
- action: test_wait_until_fifo
|
||||
then:
|
||||
- logger.log: "=== TEST: wait_until should execute in FIFO order ==="
|
||||
- globals.set:
|
||||
id: gate_open
|
||||
value: 'false'
|
||||
- delay: 100ms
|
||||
# Start multiple parallel executions of coordinator script
|
||||
# Each will call the shared waiter script, queueing in same wait_until
|
||||
- script.execute: coordinator_0
|
||||
- script.execute: coordinator_1
|
||||
- script.execute: coordinator_2
|
||||
- script.execute: coordinator_3
|
||||
- script.execute: coordinator_4
|
||||
# Give scripts time to reach wait_until and queue
|
||||
- delay: 200ms
|
||||
- logger.log: "Opening gate - all wait_until should complete now"
|
||||
- globals.set:
|
||||
id: gate_open
|
||||
value: 'true'
|
||||
- delay: 500ms
|
||||
- logger.log: "Test complete"
|
||||
|
||||
globals:
|
||||
- id: gate_open
|
||||
type: bool
|
||||
initial_value: 'false'
|
||||
|
||||
script:
|
||||
# Shared waiter with single wait_until action (all coordinators call this)
|
||||
- id: waiter
|
||||
mode: parallel
|
||||
parameters:
|
||||
iter: int
|
||||
then:
|
||||
- lambda: 'ESP_LOGD("main", "Queueing iteration %d", iter);'
|
||||
- wait_until:
|
||||
condition:
|
||||
lambda: 'return id(gate_open);'
|
||||
timeout: 5s
|
||||
- lambda: 'ESP_LOGD("main", "Completed iteration %d", iter);'
|
||||
|
||||
# Coordinator scripts - each calls shared waiter with different iteration number
|
||||
- id: coordinator_0
|
||||
then:
|
||||
- script.execute:
|
||||
id: waiter
|
||||
iter: 0
|
||||
|
||||
- id: coordinator_1
|
||||
then:
|
||||
- script.execute:
|
||||
id: waiter
|
||||
iter: 1
|
||||
|
||||
- id: coordinator_2
|
||||
then:
|
||||
- script.execute:
|
||||
id: waiter
|
||||
iter: 2
|
||||
|
||||
- id: coordinator_3
|
||||
then:
|
||||
- script.execute:
|
||||
id: waiter
|
||||
iter: 3
|
||||
|
||||
- id: coordinator_4
|
||||
then:
|
||||
- script.execute:
|
||||
id: waiter
|
||||
iter: 4
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
121
tests/integration/test_script_delay_params.py
Normal file
121
tests/integration/test_script_delay_params.py
Normal file
@@ -0,0 +1,121 @@
|
||||
"""Integration test for script.wait FIFO ordering (issues #12043, #12044).
|
||||
|
||||
This test verifies that ScriptWaitAction processes queued items in FIFO order.
|
||||
|
||||
PR #7972 introduced bugs in ScriptWaitAction:
|
||||
- Used emplace_front() causing LIFO ordering instead of FIFO
|
||||
- Called loop() synchronously causing reentrancy issues
|
||||
- Used while loop processing entire queue causing infinite loops
|
||||
|
||||
These bugs manifested as:
|
||||
- Scripts becoming "zombies" (stuck in running state)
|
||||
- script.wait hanging forever
|
||||
- Incorrect execution order
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_script_delay_with_params(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Test that script.wait processes queued items in FIFO order.
|
||||
|
||||
This reproduces issues #12043 and #12044 where scripts would hang or become
|
||||
zombies due to LIFO ordering bugs in ScriptWaitAction from PR #7972.
|
||||
"""
|
||||
test_complete = asyncio.Event()
|
||||
|
||||
# Patterns to match in logs
|
||||
father_calling_pattern = re.compile(r"Father iteration (\d+): calling son")
|
||||
son_started_pattern = re.compile(r"Son script started with iteration (\d+)")
|
||||
son_delaying_pattern = re.compile(r"Son script delaying for iteration (\d+)")
|
||||
son_finished_pattern = re.compile(r"Son script finished with iteration (\d+)")
|
||||
father_wait_returned_pattern = re.compile(
|
||||
r"Father iteration (\d+): son finished, wait returned"
|
||||
)
|
||||
|
||||
# Track which iterations completed
|
||||
father_calling = set()
|
||||
son_started = set()
|
||||
son_delaying = set()
|
||||
son_finished = set()
|
||||
wait_returned = set()
|
||||
|
||||
def check_output(line: str) -> None:
|
||||
"""Check log output for expected messages."""
|
||||
if test_complete.is_set():
|
||||
return
|
||||
|
||||
if mo := father_calling_pattern.search(line):
|
||||
father_calling.add(int(mo.group(1)))
|
||||
elif mo := son_started_pattern.search(line):
|
||||
son_started.add(int(mo.group(1)))
|
||||
elif mo := son_delaying_pattern.search(line):
|
||||
son_delaying.add(int(mo.group(1)))
|
||||
elif mo := son_finished_pattern.search(line):
|
||||
son_finished.add(int(mo.group(1)))
|
||||
elif mo := father_wait_returned_pattern.search(line):
|
||||
iteration = int(mo.group(1))
|
||||
wait_returned.add(iteration)
|
||||
# Test completes when iteration 9 finishes
|
||||
if iteration == 9:
|
||||
test_complete.set()
|
||||
|
||||
# Run with log monitoring
|
||||
async with (
|
||||
run_compiled(yaml_config, line_callback=check_output),
|
||||
api_client_connected() as client,
|
||||
):
|
||||
# Verify device info
|
||||
device_info = await client.device_info()
|
||||
assert device_info is not None
|
||||
assert device_info.name == "test-script-delay-params"
|
||||
|
||||
# Get services
|
||||
_, services = await client.list_entities_services()
|
||||
test_service = next(
|
||||
(s for s in services if s.name == "test_repeat_with_delay"), None
|
||||
)
|
||||
assert test_service is not None, "test_repeat_with_delay service not found"
|
||||
|
||||
# Execute the test
|
||||
client.execute_service(test_service, {})
|
||||
|
||||
# Wait for test to complete (10 iterations * ~100ms each + margin)
|
||||
try:
|
||||
await asyncio.wait_for(test_complete.wait(), timeout=5.0)
|
||||
except TimeoutError:
|
||||
pytest.fail(
|
||||
f"Test timed out. Completed iterations: {sorted(wait_returned)}. "
|
||||
f"This likely indicates the script became a zombie (issue #12044)."
|
||||
)
|
||||
|
||||
# Verify all 10 iterations completed successfully
|
||||
expected_iterations = set(range(10))
|
||||
assert father_calling == expected_iterations, "Not all iterations started"
|
||||
assert son_started == expected_iterations, (
|
||||
"Son script not started for all iterations"
|
||||
)
|
||||
assert son_finished == expected_iterations, (
|
||||
"Son script not finished for all iterations"
|
||||
)
|
||||
assert wait_returned == expected_iterations, (
|
||||
"script.wait did not return for all iterations"
|
||||
)
|
||||
|
||||
# Verify delays were triggered for iterations >= 5
|
||||
expected_delays = set(range(5, 10))
|
||||
assert son_delaying == expected_delays, (
|
||||
"Delays not triggered for iterations >= 5"
|
||||
)
|
||||
90
tests/integration/test_wait_until_ordering.py
Normal file
90
tests/integration/test_wait_until_ordering.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""Integration test for wait_until FIFO ordering.
|
||||
|
||||
This test verifies that when multiple wait_until actions are queued,
|
||||
they execute in FIFO (First In First Out) order, not LIFO.
|
||||
|
||||
PR #7972 introduced a bug where emplace_front() was used, causing
|
||||
LIFO ordering which is incorrect.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wait_until_fifo_ordering(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Test that wait_until executes queued items in FIFO order.
|
||||
|
||||
With the bug (using emplace_front), the order would be 4,3,2,1,0 (LIFO).
|
||||
With the fix (using emplace_back), the order should be 0,1,2,3,4 (FIFO).
|
||||
"""
|
||||
test_complete = asyncio.Event()
|
||||
|
||||
# Track completion order
|
||||
completed_order = []
|
||||
|
||||
# Patterns to match
|
||||
queuing_pattern = re.compile(r"Queueing iteration (\d+)")
|
||||
completed_pattern = re.compile(r"Completed iteration (\d+)")
|
||||
|
||||
def check_output(line: str) -> None:
|
||||
"""Check log output for completion order."""
|
||||
if test_complete.is_set():
|
||||
return
|
||||
|
||||
if mo := queuing_pattern.search(line):
|
||||
iteration = int(mo.group(1))
|
||||
|
||||
elif mo := completed_pattern.search(line):
|
||||
iteration = int(mo.group(1))
|
||||
completed_order.append(iteration)
|
||||
|
||||
# Test completes when all 5 have completed
|
||||
if len(completed_order) == 5:
|
||||
test_complete.set()
|
||||
|
||||
# Run with log monitoring
|
||||
async with (
|
||||
run_compiled(yaml_config, line_callback=check_output),
|
||||
api_client_connected() as client,
|
||||
):
|
||||
# Verify device info
|
||||
device_info = await client.device_info()
|
||||
assert device_info is not None
|
||||
assert device_info.name == "test-wait-until-ordering"
|
||||
|
||||
# Get services
|
||||
_, services = await client.list_entities_services()
|
||||
test_service = next(
|
||||
(s for s in services if s.name == "test_wait_until_fifo"), None
|
||||
)
|
||||
assert test_service is not None, "test_wait_until_fifo service not found"
|
||||
|
||||
# Execute the test
|
||||
client.execute_service(test_service, {})
|
||||
|
||||
# Wait for test to complete
|
||||
try:
|
||||
await asyncio.wait_for(test_complete.wait(), timeout=5.0)
|
||||
except TimeoutError:
|
||||
pytest.fail(
|
||||
f"Test timed out. Completed order: {completed_order}. "
|
||||
f"Expected 5 completions but got {len(completed_order)}."
|
||||
)
|
||||
|
||||
# Verify FIFO order
|
||||
expected_order = [0, 1, 2, 3, 4]
|
||||
assert completed_order == expected_order, (
|
||||
f"Unexpected order: {completed_order}. "
|
||||
f"Expected FIFO order: {expected_order}"
|
||||
)
|
||||
Reference in New Issue
Block a user