mirror of
https://github.com/esphome/esphome.git
synced 2025-08-10 20:29:24 +00:00
Compare commits
24 Commits
2025.5.0b5
...
2025.5.2
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ec26d31499 | ||
![]() |
1bbc6db1c3 | ||
![]() |
162472bdc2 | ||
![]() |
aecac15809 | ||
![]() |
6554af21b9 | ||
![]() |
8583466c6a | ||
![]() |
6666604069 | ||
![]() |
f74f89c6b5 | ||
![]() |
42390faf4a | ||
![]() |
fdc6c4a219 | ||
![]() |
6c08f5e343 | ||
![]() |
e0e4ba9592 | ||
![]() |
ad20825f31 | ||
![]() |
e4f3a952d5 | ||
![]() |
90e3c5bba2 | ||
![]() |
b1d5ad27f3 | ||
![]() |
5c54f75b7a | ||
![]() |
a5f85b4437 | ||
![]() |
da4e710249 | ||
![]() |
cdcd1cd292 | ||
![]() |
1cba22175f | ||
![]() |
801138da27 | ||
![]() |
51740a2e99 | ||
![]() |
d68a391e67 |
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.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
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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_);
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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],
|
||||
|
@@ -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,
|
||||
|
@@ -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)
|
||||
|
@@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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 = {
|
||||
|
@@ -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_) {
|
||||
|
@@ -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_;
|
||||
};
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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]))
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"""Constants used by esphome."""
|
||||
|
||||
__version__ = "2025.5.0b5"
|
||||
__version__ = "2025.5.2"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
@@ -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
|
||||
|
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user