Compare commits

..

24 Commits

Author SHA1 Message Date
Jesse Hills
ec26d31499 Merge pull request #8996 from esphome/bump-2025.5.2
2025.5.2
2025-06-03 20:45:09 +12:00
Jesse Hills
1bbc6db1c3 Bump version to 2025.5.2 2025-06-03 20:04:39 +12:00
J. Nick Koston
162472bdc2 Fix logger stack overflow (#8988) 2025-06-03 20:04:39 +12:00
Clyde Stubbs
aecac15809 [debug] Make sensors work without logger debug level (#8980) 2025-06-03 20:04:39 +12:00
Samuel Sieb
6554af21b9 [esp8266] fix isr pin (#8981)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2025-06-03 20:04:39 +12:00
Samuel Sieb
8583466c6a [rp2040] use low-level control for ISR gpio and add IRAM_ATTR (#8950)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2025-06-03 20:04:39 +12:00
Kevin Ahrendt
6666604069 [i2s-audio] ensure mic task isn't pinned to a core (#8879) 2025-06-03 20:04:39 +12:00
Jesse Hills
f74f89c6b5 Merge pull request #8913 from esphome/bump-2025.5.1
2025.5.1
2025-05-27 21:01:19 +12:00
Jesse Hills
42390faf4a Bump version to 2025.5.1 2025-05-27 14:31:38 +12:00
Jesse Hills
fdc6c4a219 [web_server] Fix download list where external_components has a substitution value (#8911)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-27 14:31:38 +12:00
Jesse Hills
6c08f5e343 [api] Fix crash with gcc compiler on host (#8902) 2025-05-27 14:31:38 +12:00
Keith Burzinski
e0e4ba9592 [esp32] Fix building on IDF 4 (#8892) 2025-05-27 14:31:38 +12:00
Jesse Hills
ad20825f31 [logger] Fix options in select (#8875)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2025-05-27 14:31:38 +12:00
Kevin Ahrendt
e4f3a952d5 [speaker] ensure the pipeline returns an error state before returning its stopped (#8878) 2025-05-27 14:31:38 +12:00
Kevin Ahrendt
90e3c5bba2 [micro_wake_word] avoid duplicated detections from same event (#8877) 2025-05-27 14:31:38 +12:00
Clyde Stubbs
b1d5ad27f3 [lvgl] Improve error messages from text validation (#8872) 2025-05-27 14:31:38 +12:00
Jesse Hills
5c54f75b7a [online_image] Allocate pngle manually to potentially use psram (#8354)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-05-27 14:31:38 +12:00
Cossid
a5f85b4437 [tuya_select] - Fix datapoint config error. (#8871) 2025-05-27 14:31:38 +12:00
Jesse Hills
da4e710249 [core] Add some missing includes (#8864) 2025-05-27 14:31:38 +12:00
Jesse Hills
cdcd1cd292 Merge pull request #8863 from esphome/bump-2025.5.0
2025.5.0
2025-05-21 20:32:40 +12:00
Jesse Hills
1cba22175f Bump version to 2025.5.0 2025-05-21 15:26:55 +12:00
Jesse Hills
801138da27 Merge pull request #8862 from esphome/bump-2025.5.0b6
2025.5.0b6
2025-05-21 13:09:04 +12:00
Jesse Hills
51740a2e99 Bump version to 2025.5.0b6 2025-05-21 11:54:08 +12:00
Jesse Hills
d68a391e67 [api-docs] Move netlify.toml to root (#8861) 2025-05-21 11:54:07 +12:00
25 changed files with 221 additions and 139 deletions

View File

@@ -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.5.0b5
PROJECT_NUMBER = 2025.5.2
# 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

View File

@@ -4,11 +4,11 @@
#include <cinttypes>
#include <utility>
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/version.h"
#include "esphome/core/application.h"
#ifdef USE_DEEP_SLEEP
#include "esphome/components/deep_sleep/deep_sleep_component.h"
@@ -153,7 +153,11 @@ void APIConnection::loop() {
} else {
this->last_traffic_ = App.get_loop_component_start_time();
// read a packet
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
if (buffer.data_len > 0) {
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
} else {
this->read_message(0, buffer.type, nullptr);
}
if (this->remove_)
return;
}

View File

@@ -15,10 +15,6 @@ namespace debug {
static const char *const TAG = "debug";
void DebugComponent::dump_config() {
#ifndef ESPHOME_LOG_HAS_DEBUG
return; // Can't log below if debug logging is disabled
#endif
ESP_LOGCONFIG(TAG, "Debug component:");
#ifdef USE_TEXT_SENSOR
LOG_TEXT_SENSOR(" ", "Device info", this->device_info_);

View File

@@ -15,8 +15,9 @@
#ifdef USE_ARDUINO
#include <Esp.h>
#else
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
#include <esp_clk_tree.h>
#endif
void setup();
void loop();
#endif
@@ -63,7 +64,13 @@ uint32_t arch_get_cpu_cycle_count() { return cpu_hal_get_cycle_count(); }
uint32_t arch_get_cpu_freq_hz() {
uint32_t freq = 0;
#ifdef USE_ESP_IDF
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_CPU, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &freq);
#else
rtc_cpu_freq_config_t config;
rtc_clk_cpu_freq_get_config(&config);
freq = config.freq_mhz * 1000000U;
#endif
#elif defined(USE_ARDUINO)
freq = ESP.getCpuFreqMHz() * 1000000;
#endif

View File

@@ -40,6 +40,7 @@ struct ISRPinArg {
volatile uint32_t *mode_set_reg;
volatile uint32_t *mode_clr_reg;
volatile uint32_t *func_reg;
volatile uint32_t *control_reg;
uint32_t mask;
};
@@ -54,6 +55,7 @@ ISRInternalGPIOPin ESP8266GPIOPin::to_isr() const {
arg->mode_set_reg = &GPES;
arg->mode_clr_reg = &GPEC;
arg->func_reg = &GPF(this->pin_);
arg->control_reg = &GPC(this->pin_);
arg->mask = 1 << this->pin_;
} else {
arg->in_reg = &GP16I;
@@ -62,6 +64,7 @@ ISRInternalGPIOPin ESP8266GPIOPin::to_isr() const {
arg->mode_set_reg = &GP16E;
arg->mode_clr_reg = nullptr;
arg->func_reg = &GPF16;
arg->control_reg = nullptr;
arg->mask = 1;
}
return ISRInternalGPIOPin((void *) arg);
@@ -143,11 +146,17 @@ void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
if (arg->pin < 16) {
if (flags & gpio::FLAG_OUTPUT) {
*arg->mode_set_reg = arg->mask;
} else {
if (flags & gpio::FLAG_OPEN_DRAIN) {
*arg->control_reg |= 1 << GPCD;
} else {
*arg->control_reg &= ~(1 << GPCD);
}
} else if (flags & gpio::FLAG_INPUT) {
*arg->mode_clr_reg = arg->mask;
}
if (flags & gpio::FLAG_PULLUP) {
*arg->func_reg |= 1 << GPFPU;
*arg->control_reg |= 1 << GPCD;
} else {
*arg->func_reg &= ~(1 << GPFPU);
}

View File

@@ -30,11 +30,11 @@ static const int32_t DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR = 1000;
static const char *const TAG = "i2s_audio.microphone";
enum MicrophoneEventGroupBits : uint32_t {
COMMAND_STOP = (1 << 0), // stops the microphone task
TASK_STARTING = (1 << 10),
TASK_RUNNING = (1 << 11),
TASK_STOPPING = (1 << 12),
TASK_STOPPED = (1 << 13),
COMMAND_STOP = (1 << 0), // stops the microphone task, set and cleared by ``loop``
TASK_STARTING = (1 << 10), // set by mic task, cleared by ``loop``
TASK_RUNNING = (1 << 11), // set by mic task, cleared by ``loop``
TASK_STOPPED = (1 << 13), // set by mic task, cleared by ``loop``
ALL_BITS = 0x00FFFFFF, // All valid FreeRTOS event group bits
};
@@ -151,24 +151,21 @@ bool I2SAudioMicrophone::start_driver_() {
config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN);
err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error installing I2S driver: %s", esp_err_to_name(err));
this->status_set_error();
ESP_LOGE(TAG, "Error installing I2S driver: %s", esp_err_to_name(err));
return false;
}
err = i2s_set_adc_mode(ADC_UNIT_1, this->adc_channel_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error setting ADC mode: %s", esp_err_to_name(err));
this->status_set_error();
return false;
}
err = i2s_adc_enable(this->parent_->get_port());
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error enabling ADC: %s", esp_err_to_name(err));
this->status_set_error();
ESP_LOGE(TAG, "Error setting ADC mode: %s", esp_err_to_name(err));
return false;
}
err = i2s_adc_enable(this->parent_->get_port());
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error enabling ADC: %s", esp_err_to_name(err));
return false;
}
} else
#endif
{
@@ -177,8 +174,7 @@ bool I2SAudioMicrophone::start_driver_() {
err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error installing I2S driver: %s", esp_err_to_name(err));
this->status_set_error();
ESP_LOGE(TAG, "Error installing I2S driver: %s", esp_err_to_name(err));
return false;
}
@@ -187,8 +183,7 @@ bool I2SAudioMicrophone::start_driver_() {
err = i2s_set_pin(this->parent_->get_port(), &pin_config);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error setting I2S pin: %s", esp_err_to_name(err));
this->status_set_error();
ESP_LOGE(TAG, "Error setting I2S pin: %s", esp_err_to_name(err));
return false;
}
}
@@ -203,8 +198,7 @@ bool I2SAudioMicrophone::start_driver_() {
/* Allocate a new RX channel and get the handle of this channel */
err = i2s_new_channel(&chan_cfg, NULL, &this->rx_handle_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error creating new I2S channel: %s", esp_err_to_name(err));
this->status_set_error();
ESP_LOGE(TAG, "Error creating new I2S channel: %s", esp_err_to_name(err));
return false;
}
@@ -276,22 +270,20 @@ bool I2SAudioMicrophone::start_driver_() {
err = i2s_channel_init_std_mode(this->rx_handle_, &std_cfg);
}
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error initializing I2S channel: %s", esp_err_to_name(err));
this->status_set_error();
ESP_LOGE(TAG, "Error initializing I2S channel: %s", esp_err_to_name(err));
return false;
}
/* Before reading data, start the RX channel first */
i2s_channel_enable(this->rx_handle_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error enabling I2S Microphone: %s", esp_err_to_name(err));
this->status_set_error();
ESP_LOGE(TAG, "Error enabling I2S Microphone: %s", esp_err_to_name(err));
return false;
}
#endif
this->status_clear_error();
this->configure_stream_settings_(); // redetermine the settings in case some settings were changed after compilation
return true;
}
@@ -303,71 +295,55 @@ void I2SAudioMicrophone::stop() {
}
void I2SAudioMicrophone::stop_driver_() {
// There is no harm continuing to unload the driver if an error is ever returned by the various functions. This
// ensures that we stop/unload the driver when it only partially starts.
esp_err_t err;
#ifdef USE_I2S_LEGACY
#if SOC_I2S_SUPPORTS_ADC
if (this->adc_) {
err = i2s_adc_disable(this->parent_->get_port());
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error disabling ADC: %s", esp_err_to_name(err));
this->status_set_error();
return;
ESP_LOGW(TAG, "Error disabling ADC - it may not have started: %s", esp_err_to_name(err));
}
}
#endif
err = i2s_stop(this->parent_->get_port());
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error stopping I2S microphone: %s", esp_err_to_name(err));
this->status_set_error();
return;
ESP_LOGW(TAG, "Error stopping I2S microphone - it may not have started: %s", esp_err_to_name(err));
}
err = i2s_driver_uninstall(this->parent_->get_port());
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error uninstalling I2S driver: %s", esp_err_to_name(err));
this->status_set_error();
return;
ESP_LOGW(TAG, "Error uninstalling I2S driver - it may not have started: %s", esp_err_to_name(err));
}
#else
/* Have to stop the channel before deleting it */
err = i2s_channel_disable(this->rx_handle_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error stopping I2S microphone: %s", esp_err_to_name(err));
this->status_set_error();
return;
ESP_LOGW(TAG, "Error stopping I2S microphone - it may not have started: %s", esp_err_to_name(err));
}
/* If the handle is not needed any more, delete it to release the channel resources */
err = i2s_del_channel(this->rx_handle_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error deleting I2S channel: %s", esp_err_to_name(err));
this->status_set_error();
return;
ESP_LOGW(TAG, "Error deleting I2S channel - it may not have started: %s", esp_err_to_name(err));
}
#endif
this->parent_->unlock();
this->status_clear_error();
}
void I2SAudioMicrophone::mic_task(void *params) {
I2SAudioMicrophone *this_microphone = (I2SAudioMicrophone *) params;
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_STARTING);
uint8_t start_counter = 0;
bool started = this_microphone->start_driver_();
while (!started && start_counter < 10) {
// Attempt to load the driver again in 100 ms. Doesn't slow down main loop since its in a task.
vTaskDelay(pdMS_TO_TICKS(100));
++start_counter;
started = this_microphone->start_driver_();
}
{ // Ensures the samples vector is freed when the task stops
if (started) {
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_RUNNING);
const size_t bytes_to_read = this_microphone->audio_stream_info_.ms_to_bytes(READ_DURATION_MS);
std::vector<uint8_t> samples;
samples.reserve(bytes_to_read);
while (!(xEventGroupGetBits(this_microphone->event_group_) & COMMAND_STOP)) {
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_RUNNING);
while (!(xEventGroupGetBits(this_microphone->event_group_) & MicrophoneEventGroupBits::COMMAND_STOP)) {
if (this_microphone->data_callbacks_.size() > 0) {
samples.resize(bytes_to_read);
size_t bytes_read = this_microphone->read_(samples.data(), bytes_to_read, 2 * pdMS_TO_TICKS(READ_DURATION_MS));
@@ -382,9 +358,6 @@ void I2SAudioMicrophone::mic_task(void *params) {
}
}
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_STOPPING);
this_microphone->stop_driver_();
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_STOPPED);
while (true) {
// Continuously delay until the loop method deletes the task
@@ -425,7 +398,10 @@ size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_w
#endif
if ((err != ESP_OK) && ((err != ESP_ERR_TIMEOUT) || (ticks_to_wait != 0))) {
// Ignore ESP_ERR_TIMEOUT if ticks_to_wait = 0, as it will read the data on the next call
ESP_LOGW(TAG, "Error reading from I2S microphone: %s", esp_err_to_name(err));
if (!this->status_has_warning()) {
// Avoid spamming the logs with the error message if its repeated
ESP_LOGW(TAG, "Error reading from I2S microphone: %s", esp_err_to_name(err));
}
this->status_set_warning();
return 0;
}
@@ -452,7 +428,7 @@ void I2SAudioMicrophone::loop() {
uint32_t event_group_bits = xEventGroupGetBits(this->event_group_);
if (event_group_bits & MicrophoneEventGroupBits::TASK_STARTING) {
ESP_LOGD(TAG, "Task has started, attempting to setup I2S audio driver");
ESP_LOGD(TAG, "Task started, attempting to allocate buffer");
xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_STARTING);
}
@@ -463,23 +439,25 @@ void I2SAudioMicrophone::loop() {
this->state_ = microphone::STATE_RUNNING;
}
if (event_group_bits & MicrophoneEventGroupBits::TASK_STOPPING) {
ESP_LOGD(TAG, "Task is stopping, attempting to unload the I2S audio driver");
xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_STOPPING);
}
if ((event_group_bits & MicrophoneEventGroupBits::TASK_STOPPED)) {
ESP_LOGD(TAG, "Task is finished, freeing resources");
ESP_LOGD(TAG, "Task finished, freeing resources and uninstalling I2S driver");
vTaskDelete(this->task_handle_);
this->task_handle_ = nullptr;
this->stop_driver_();
xEventGroupClearBits(this->event_group_, ALL_BITS);
this->status_clear_error();
this->state_ = microphone::STATE_STOPPED;
}
// Start the microphone if any semaphores are taken
if ((uxSemaphoreGetCount(this->active_listeners_semaphore_) < MAX_LISTENERS) &&
(this->state_ == microphone::STATE_STOPPED)) {
this->state_ = microphone::STATE_STARTING;
}
// Stop the microphone if all semaphores are returned
if ((uxSemaphoreGetCount(this->active_listeners_semaphore_) == MAX_LISTENERS) &&
(this->state_ == microphone::STATE_RUNNING)) {
this->state_ = microphone::STATE_STOPPING;
@@ -487,14 +465,26 @@ void I2SAudioMicrophone::loop() {
switch (this->state_) {
case microphone::STATE_STARTING:
if ((this->task_handle_ == nullptr) && !this->status_has_error()) {
if (this->status_has_error()) {
break;
}
if (!this->start_driver_()) {
this->status_momentary_error("I2S driver failed to start, unloading it and attempting again in 1 second", 1000);
this->stop_driver_(); // Stop/frees whatever possibly started
break;
}
if (this->task_handle_ == nullptr) {
xTaskCreate(I2SAudioMicrophone::mic_task, "mic_task", TASK_STACK_SIZE, (void *) this, TASK_PRIORITY,
&this->task_handle_);
if (this->task_handle_ == nullptr) {
this->status_momentary_error("Task failed to start, attempting again in 1 second", 1000);
this->stop_driver_(); // Stops the driver to return the lock; will be reloaded in next attempt
}
}
break;
case microphone::STATE_RUNNING:
break;

View File

@@ -43,7 +43,11 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
#endif
protected:
/// @brief Starts the I2S driver. Updates the ``audio_stream_info_`` member variable with the current setttings.
/// @return True if succesful, false otherwise
bool start_driver_();
/// @brief Stops the I2S driver.
void stop_driver_();
/// @brief Attempts to correct a microphone DC offset; e.g., a microphones silent level is offset from 0. Applies a

View File

@@ -24,6 +24,7 @@ from esphome.const import (
CONF_HARDWARE_UART,
CONF_ID,
CONF_LEVEL,
CONF_LOGGER,
CONF_LOGS,
CONF_ON_MESSAGE,
CONF_TAG,
@@ -247,6 +248,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
baud_rate = config[CONF_BAUD_RATE]
level = config[CONF_LEVEL]
CORE.data.setdefault(CONF_LOGGER, {})[CONF_LEVEL] = level
initial_level = LOG_LEVELS[config.get(CONF_INITIAL_LEVEL, level)]
log = cg.new_Pvariable(
config[CONF_ID],

View File

@@ -212,9 +212,9 @@ class Logger : public Component {
}
// Format string to explicit buffer with varargs
inline void printf_to_buffer_(const char *format, char *buffer, int *buffer_at, int buffer_size, ...) {
inline void printf_to_buffer_(char *buffer, int *buffer_at, int buffer_size, const char *format, ...) {
va_list arg;
va_start(arg, buffer_size);
va_start(arg, format);
this->format_body_to_buffer_(buffer, buffer_at, buffer_size, format, arg);
va_end(arg);
}
@@ -312,13 +312,13 @@ class Logger : public Component {
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
if (thread_name != nullptr) {
// Non-main task with thread name
this->printf_to_buffer_("%s[%s][%s:%03u]%s[%s]%s: ", buffer, buffer_at, buffer_size, color, letter, tag, line,
this->printf_to_buffer_(buffer, buffer_at, buffer_size, "%s[%s][%s:%03u]%s[%s]%s: ", color, letter, tag, line,
ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED), thread_name, color);
return;
}
#endif
// Main task or non ESP32/LibreTiny platform
this->printf_to_buffer_("%s[%s][%s:%03u]: ", buffer, buffer_at, buffer_size, color, letter, tag, line);
this->printf_to_buffer_(buffer, buffer_at, buffer_size, "%s[%s][%s:%03u]: ", color, letter, tag, line);
}
inline void HOT format_body_to_buffer_(char *buffer, int *buffer_at, int buffer_size, const char *format,

View File

@@ -5,7 +5,7 @@ from esphome.const import CONF_LEVEL, CONF_LOGGER, ENTITY_CATEGORY_CONFIG, ICON_
from esphome.core import CORE
from esphome.cpp_helpers import register_component, register_parented
from .. import CONF_LOGGER_ID, LOG_LEVEL_SEVERITY, Logger, logger_ns
from .. import CONF_LOGGER_ID, LOG_LEVELS, Logger, logger_ns
CODEOWNERS = ["@clydebarrow"]
@@ -21,9 +21,10 @@ CONFIG_SCHEMA = select.select_schema(
async def to_code(config):
levels = LOG_LEVEL_SEVERITY
index = levels.index(CORE.config[CONF_LOGGER][CONF_LEVEL])
parent = await cg.get_variable(config[CONF_LOGGER_ID])
levels = list(LOG_LEVELS)
index = levels.index(CORE.data[CONF_LOGGER][CONF_LEVEL])
levels = levels[: index + 1]
var = await select.new_select(config, options=levels)
await register_parented(var, config[CONF_LOGGER_ID])
await register_parented(var, parent)
await register_component(var, config)

View File

@@ -36,29 +36,43 @@ from .types import (
# this will be populated later, in __init__.py to avoid circular imports.
WIDGET_TYPES: dict = {}
TIME_TEXT_SCHEMA = cv.Schema(
{
cv.Required(CONF_TIME_FORMAT): cv.string,
cv.GenerateID(CONF_TIME): cv.templatable(cv.use_id(RealTimeClock)),
}
)
PRINTF_TEXT_SCHEMA = cv.All(
cv.Schema(
{
cv.Required(CONF_FORMAT): cv.string,
cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_),
},
),
validate_printf,
)
def _validate_text(value):
"""
Do some sanity checking of the format to get better error messages
than using cv.Any
"""
if value is None:
raise cv.Invalid("No text specified")
if isinstance(value, dict):
if CONF_TIME_FORMAT in value:
return TIME_TEXT_SCHEMA(value)
return PRINTF_TEXT_SCHEMA(value)
return cv.templatable(cv.string)(value)
# A schema for text properties
TEXT_SCHEMA = cv.Schema(
{
cv.Optional(CONF_TEXT): cv.Any(
cv.All(
cv.Schema(
{
cv.Required(CONF_FORMAT): cv.string,
cv.Optional(CONF_ARGS, default=list): cv.ensure_list(
cv.lambda_
),
},
),
validate_printf,
),
cv.Schema(
{
cv.Required(CONF_TIME_FORMAT): cv.string,
cv.GenerateID(CONF_TIME): cv.templatable(cv.use_id(RealTimeClock)),
}
),
cv.templatable(cv.string),
)
cv.Optional(CONF_TEXT): _validate_text,
}
)

View File

@@ -147,7 +147,11 @@ bool StreamingModel::perform_streaming_inference(const int8_t features[PREPROCES
this->recent_streaming_probabilities_[this->last_n_index_] = output->data.uint8[0]; // probability;
this->unprocessed_probability_status_ = true;
}
this->ignore_windows_ = std::min(this->ignore_windows_ + 1, 0);
if (this->recent_streaming_probabilities_[this->last_n_index_] < this->probability_cutoff_) {
// Only increment ignore windows if less than the probability cutoff; this forces the model to "cool-off" from a
// previous detection and calling ``reset_probabilities`` so it avoids duplicate detections
this->ignore_windows_ = std::min(this->ignore_windows_ + 1, 0);
}
}
return true;
}

View File

@@ -75,7 +75,7 @@ class PNGFormat(Format):
def actions(self):
cg.add_define("USE_ONLINE_IMAGE_PNG_SUPPORT")
cg.add_library("pngle", "1.0.2")
cg.add_library("pngle", "1.1.0")
IMAGE_FORMATS = {

View File

@@ -34,12 +34,32 @@ static void init_callback(pngle_t *pngle, uint32_t w, uint32_t h) {
* @param h The height of the rectangle to draw.
* @param rgba The color to paint the rectangle in.
*/
static void draw_callback(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint8_t rgba[4]) {
static void draw_callback(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, uint32_t h, const uint8_t rgba[4]) {
PngDecoder *decoder = (PngDecoder *) pngle_get_user_data(pngle);
Color color(rgba[0], rgba[1], rgba[2], rgba[3]);
decoder->draw(x, y, w, h, color);
}
PngDecoder::PngDecoder(OnlineImage *image) : ImageDecoder(image) {
{
pngle_t *pngle = this->allocator_.allocate(1, PNGLE_T_SIZE);
if (!pngle) {
ESP_LOGE(TAG, "Failed to allocate memory for PNGLE engine!");
return;
}
memset(pngle, 0, PNGLE_T_SIZE);
pngle_reset(pngle);
this->pngle_ = pngle;
}
}
PngDecoder::~PngDecoder() {
if (this->pngle_) {
pngle_reset(this->pngle_);
this->allocator_.deallocate(this->pngle_, PNGLE_T_SIZE);
}
}
int PngDecoder::prepare(size_t download_size) {
ImageDecoder::prepare(download_size);
if (!this->pngle_) {

View File

@@ -1,7 +1,8 @@
#pragma once
#include "image_decoder.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "image_decoder.h"
#ifdef USE_ONLINE_IMAGE_PNG_SUPPORT
#include <pngle.h>
@@ -18,13 +19,14 @@ class PngDecoder : public ImageDecoder {
*
* @param display The image to decode the stream into.
*/
PngDecoder(OnlineImage *image) : ImageDecoder(image), pngle_(pngle_new()) {}
~PngDecoder() override { pngle_destroy(this->pngle_); }
PngDecoder(OnlineImage *image);
~PngDecoder() override;
int prepare(size_t download_size) override;
int HOT decode(uint8_t *buffer, size_t size) override;
protected:
RAMAllocator<pngle_t> allocator_;
pngle_t *pngle_;
};

View File

@@ -8,7 +8,7 @@ namespace rp2040 {
static const char *const TAG = "rp2040";
static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) {
static int flags_to_mode(gpio::Flags flags, uint8_t pin) {
if (flags == gpio::FLAG_INPUT) { // NOLINT(bugprone-branch-clone)
return INPUT;
} else if (flags == gpio::FLAG_OUTPUT) {
@@ -25,14 +25,16 @@ static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) {
}
struct ISRPinArg {
uint32_t mask;
uint8_t pin;
bool inverted;
};
ISRInternalGPIOPin RP2040GPIOPin::to_isr() const {
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
arg->pin = pin_;
arg->inverted = inverted_;
arg->pin = this->pin_;
arg->inverted = this->inverted_;
arg->mask = 1 << this->pin_;
return ISRInternalGPIOPin((void *) arg);
}
@@ -81,21 +83,36 @@ void RP2040GPIOPin::detach_interrupt() const { detachInterrupt(pin_); }
using namespace rp2040;
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
return bool(digitalRead(arg->pin)) != arg->inverted; // NOLINT
auto *arg = reinterpret_cast<ISRPinArg *>(this->arg_);
return bool(sio_hw->gpio_in & arg->mask) != arg->inverted;
}
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
digitalWrite(arg->pin, value != arg->inverted ? 1 : 0); // NOLINT
auto *arg = reinterpret_cast<ISRPinArg *>(this->arg_);
if (value != arg->inverted) {
sio_hw->gpio_set = arg->mask;
} else {
sio_hw->gpio_clr = arg->mask;
}
}
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
// TODO: implement
// auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
// GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin);
}
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
pinMode(arg->pin, flags_to_mode(flags, arg->pin)); // NOLINT
auto *arg = reinterpret_cast<ISRPinArg *>(this->arg_);
if (flags & gpio::FLAG_OUTPUT) {
sio_hw->gpio_oe_set = arg->mask;
} else if (flags & gpio::FLAG_INPUT) {
sio_hw->gpio_oe_clr = arg->mask;
hw_write_masked(&padsbank0_hw->io[arg->pin],
(bool_to_bit(flags & gpio::FLAG_PULLUP) << PADS_BANK0_GPIO0_PUE_LSB) |
(bool_to_bit(flags & gpio::FLAG_PULLDOWN) << PADS_BANK0_GPIO0_PDE_LSB),
PADS_BANK0_GPIO0_PUE_BITS | PADS_BANK0_GPIO0_PDE_BITS);
}
}
} // namespace esphome

View File

@@ -174,6 +174,16 @@ AudioPipelineState AudioPipeline::process_state() {
}
}
if ((event_bits & EventGroupBits::READER_MESSAGE_ERROR)) {
xEventGroupClearBits(this->event_group_, EventGroupBits::READER_MESSAGE_ERROR);
return AudioPipelineState::ERROR_READING;
}
if ((event_bits & EventGroupBits::DECODER_MESSAGE_ERROR)) {
xEventGroupClearBits(this->event_group_, EventGroupBits::DECODER_MESSAGE_ERROR);
return AudioPipelineState::ERROR_DECODING;
}
if ((event_bits & EventGroupBits::READER_MESSAGE_FINISHED) &&
(!(event_bits & EventGroupBits::READER_MESSAGE_LOADED_MEDIA_TYPE) &&
(event_bits & EventGroupBits::DECODER_MESSAGE_FINISHED))) {
@@ -203,16 +213,6 @@ AudioPipelineState AudioPipeline::process_state() {
return AudioPipelineState::STOPPED;
}
if ((event_bits & EventGroupBits::READER_MESSAGE_ERROR)) {
xEventGroupClearBits(this->event_group_, EventGroupBits::READER_MESSAGE_ERROR);
return AudioPipelineState::ERROR_READING;
}
if ((event_bits & EventGroupBits::DECODER_MESSAGE_ERROR)) {
xEventGroupClearBits(this->event_group_, EventGroupBits::DECODER_MESSAGE_ERROR);
return AudioPipelineState::ERROR_DECODING;
}
if (this->pause_state_) {
return AudioPipelineState::PAUSED;
}

View File

@@ -54,8 +54,8 @@ async def to_code(config):
cg.add(var.set_select_mappings(list(options_map.keys())))
parent = await cg.get_variable(config[CONF_TUYA_ID])
cg.add(var.set_tuya_parent(parent))
if enum_datapoint := config.get(CONF_ENUM_DATAPOINT, None) is not None:
if (enum_datapoint := config.get(CONF_ENUM_DATAPOINT, None)) is not None:
cg.add(var.set_select_id(enum_datapoint, False))
if int_datapoint := config.get(CONF_INT_DATAPOINT, None) is not None:
if (int_datapoint := config.get(CONF_INT_DATAPOINT, None)) is not None:
cg.add(var.set_select_id(int_datapoint, True))
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))

View File

@@ -1,6 +1,6 @@
"""Constants used by esphome."""
__version__ = "2025.5.0b5"
__version__ = "2025.5.2"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = (

View File

@@ -24,6 +24,11 @@
#define PROGMEM ICACHE_RODATA_ATTR
#endif
#elif defined(USE_RP2040)
#define IRAM_ATTR __attribute__((noinline, long_call, section(".time_critical")))
#define PROGMEM
#else
#define IRAM_ATTR

View File

@@ -1,6 +1,7 @@
#pragma once
#include <cmath>
#include <cstdint>
#include <cstring>
#include <functional>
#include <limits>

View File

@@ -1,5 +1,6 @@
#pragma once
#include <cstdint>
#include <cstring>
#include <iterator>
#include <memory>

View File

@@ -601,10 +601,12 @@ class DownloadListRequestHandler(BaseHandler):
loop = asyncio.get_running_loop()
try:
downloads_json = await loop.run_in_executor(None, self._get, configuration)
except vol.Invalid:
except vol.Invalid as exc:
_LOGGER.exception("Error while fetching downloads", exc_info=exc)
self.send_error(404)
return
if downloads_json is None:
_LOGGER.error("Configuration %s not found", configuration)
self.send_error(404)
return
self.set_status(200)
@@ -618,14 +620,17 @@ class DownloadListRequestHandler(BaseHandler):
if storage_json is None:
return None
config = yaml_util.load_yaml(settings.rel_path(configuration))
try:
config = yaml_util.load_yaml(settings.rel_path(configuration))
if const.CONF_EXTERNAL_COMPONENTS in config:
from esphome.components.external_components import (
do_external_components_pass,
)
if const.CONF_EXTERNAL_COMPONENTS in config:
from esphome.components.external_components import (
do_external_components_pass,
)
do_external_components_pass(config)
do_external_components_pass(config)
except vol.Invalid:
_LOGGER.info("Could not parse `external_components`, skipping")
from esphome.components.esp32 import VARIANTS as ESP32_VARIANTS

View File

@@ -40,7 +40,7 @@ lib_deps =
wjtje/qr-code-generator-library@1.7.0 ; qr_code
functionpointer/arduino-MLX90393@1.0.2 ; mlx90393
pavlodn/HaierProtocol@0.9.31 ; haier
kikuchan98/pngle@1.0.2 ; online_image
kikuchan98/pngle@1.1.0 ; online_image
; Using the repository directly, otherwise ESP-IDF can't use the library
https://github.com/bitbank2/JPEGDEC.git#ca1e0f2 ; online_image
; This is using the repository until a new release is published to PlatformIO