Merge pull request #9122 from esphome/bump-2025.6.0b3

2025.6.0b3
This commit is contained in:
Jesse Hills 2025-06-18 12:37:25 +12:00 committed by GitHub
commit 16a0f9db97
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 121 additions and 66 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.6.0b2
PROJECT_NUMBER = 2025.6.0b3
# 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

@ -18,7 +18,7 @@ void I2SAudioComponent::setup() {
static i2s_port_t next_port_num = I2S_NUM_0;
if (next_port_num >= I2S_NUM_MAX) {
ESP_LOGE(TAG, "Too many I2S Audio components");
ESP_LOGE(TAG, "Too many components");
this->mark_failed();
return;
}

View File

@ -45,7 +45,7 @@ void I2SAudioMicrophone::setup() {
#if SOC_I2S_SUPPORTS_ADC
if (this->adc_) {
if (this->parent_->get_port() != I2S_NUM_0) {
ESP_LOGE(TAG, "Internal ADC only works on I2S0!");
ESP_LOGE(TAG, "Internal ADC only works on I2S0");
this->mark_failed();
return;
}
@ -55,7 +55,7 @@ void I2SAudioMicrophone::setup() {
{
if (this->pdm_) {
if (this->parent_->get_port() != I2S_NUM_0) {
ESP_LOGE(TAG, "PDM only works on I2S0!");
ESP_LOGE(TAG, "PDM only works on I2S0");
this->mark_failed();
return;
}
@ -64,14 +64,14 @@ void I2SAudioMicrophone::setup() {
this->active_listeners_semaphore_ = xSemaphoreCreateCounting(MAX_LISTENERS, MAX_LISTENERS);
if (this->active_listeners_semaphore_ == nullptr) {
ESP_LOGE(TAG, "Failed to create semaphore");
ESP_LOGE(TAG, "Creating semaphore failed");
this->mark_failed();
return;
}
this->event_group_ = xEventGroupCreate();
if (this->event_group_ == nullptr) {
ESP_LOGE(TAG, "Failed to create event group");
ESP_LOGE(TAG, "Creating event group failed");
this->mark_failed();
return;
}
@ -79,6 +79,15 @@ void I2SAudioMicrophone::setup() {
this->configure_stream_settings_();
}
void I2SAudioMicrophone::dump_config() {
ESP_LOGCONFIG(TAG,
"Microphone:\n"
" Pin: %d\n"
" PDM: %s\n"
" DC offset correction: %s",
static_cast<int8_t>(this->din_pin_), YESNO(this->pdm_), YESNO(this->correct_dc_offset_));
}
void I2SAudioMicrophone::configure_stream_settings_() {
uint8_t channel_count = 1;
#ifdef USE_I2S_LEGACY
@ -127,6 +136,7 @@ bool I2SAudioMicrophone::start_driver_() {
if (!this->parent_->try_lock()) {
return false; // Waiting for another i2s to return lock
}
this->locked_driver_ = true;
esp_err_t err;
#ifdef USE_I2S_LEGACY
@ -151,7 +161,7 @@ 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_LOGE(TAG, "Error installing I2S driver: %s", esp_err_to_name(err));
ESP_LOGE(TAG, "Error installing driver: %s", esp_err_to_name(err));
return false;
}
@ -174,7 +184,7 @@ bool I2SAudioMicrophone::start_driver_() {
err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error installing I2S driver: %s", esp_err_to_name(err));
ESP_LOGE(TAG, "Error installing driver: %s", esp_err_to_name(err));
return false;
}
@ -183,7 +193,7 @@ bool I2SAudioMicrophone::start_driver_() {
err = i2s_set_pin(this->parent_->get_port(), &pin_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error setting I2S pin: %s", esp_err_to_name(err));
ESP_LOGE(TAG, "Error setting pin: %s", esp_err_to_name(err));
return false;
}
}
@ -198,7 +208,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_LOGE(TAG, "Error creating new I2S channel: %s", esp_err_to_name(err));
ESP_LOGE(TAG, "Error creating channel: %s", esp_err_to_name(err));
return false;
}
@ -270,14 +280,14 @@ bool I2SAudioMicrophone::start_driver_() {
err = i2s_channel_init_std_mode(this->rx_handle_, &std_cfg);
}
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error initializing I2S channel: %s", esp_err_to_name(err));
ESP_LOGE(TAG, "Error initializing 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_LOGE(TAG, "Error enabling I2S Microphone: %s", esp_err_to_name(err));
ESP_LOGE(TAG, "Enabling failed: %s", esp_err_to_name(err));
return false;
}
#endif
@ -304,34 +314,37 @@ void I2SAudioMicrophone::stop_driver_() {
if (this->adc_) {
err = i2s_adc_disable(this->parent_->get_port());
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error disabling ADC - it may not have started: %s", esp_err_to_name(err));
ESP_LOGW(TAG, "Error disabling ADC: %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 - it may not have started: %s", esp_err_to_name(err));
ESP_LOGW(TAG, "Error stopping: %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 - it may not have started: %s", esp_err_to_name(err));
ESP_LOGW(TAG, "Error uninstalling driver: %s", esp_err_to_name(err));
}
#else
if (this->rx_handle_ != nullptr) {
/* 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 - it may not have started: %s", esp_err_to_name(err));
ESP_LOGW(TAG, "Error stopping: %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 - it may not have started: %s", esp_err_to_name(err));
ESP_LOGW(TAG, "Error deleting channel: %s", esp_err_to_name(err));
}
this->rx_handle_ = nullptr;
}
#endif
this->parent_->unlock();
if (this->locked_driver_) {
this->parent_->unlock();
this->locked_driver_ = false;
}
}
void I2SAudioMicrophone::mic_task(void *params) {
@ -403,7 +416,7 @@ size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_w
// Ignore ESP_ERR_TIMEOUT if ticks_to_wait = 0, as it will read the data on the next call
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));
ESP_LOGW(TAG, "Read error: %s", esp_err_to_name(err));
}
this->status_set_warning();
return 0;
@ -431,19 +444,19 @@ void I2SAudioMicrophone::loop() {
uint32_t event_group_bits = xEventGroupGetBits(this->event_group_);
if (event_group_bits & MicrophoneEventGroupBits::TASK_STARTING) {
ESP_LOGD(TAG, "Task started, attempting to allocate buffer");
ESP_LOGV(TAG, "Task started, attempting to allocate buffer");
xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_STARTING);
}
if (event_group_bits & MicrophoneEventGroupBits::TASK_RUNNING) {
ESP_LOGD(TAG, "Task is running and reading data");
ESP_LOGV(TAG, "Task is running and reading data");
xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_RUNNING);
this->state_ = microphone::STATE_RUNNING;
}
if ((event_group_bits & MicrophoneEventGroupBits::TASK_STOPPED)) {
ESP_LOGD(TAG, "Task finished, freeing resources and uninstalling I2S driver");
ESP_LOGV(TAG, "Task finished, freeing resources and uninstalling driver");
vTaskDelete(this->task_handle_);
this->task_handle_ = nullptr;
@ -473,7 +486,8 @@ void I2SAudioMicrophone::loop() {
}
if (!this->start_driver_()) {
this->status_momentary_error("I2S driver failed to start, unloading it and attempting again in 1 second", 1000);
ESP_LOGE(TAG, "Driver failed to start; retrying in 1 second");
this->status_momentary_error("driver_fail", 1000);
this->stop_driver_(); // Stop/frees whatever possibly started
break;
}
@ -483,7 +497,8 @@ void I2SAudioMicrophone::loop() {
&this->task_handle_);
if (this->task_handle_ == nullptr) {
this->status_momentary_error("Task failed to start, attempting again in 1 second", 1000);
ESP_LOGE(TAG, "Task failed to start, retrying in 1 second");
this->status_momentary_error("task_fail", 1000);
this->stop_driver_(); // Stops the driver to return the lock; will be reloaded in next attempt
}
}

View File

@ -18,6 +18,7 @@ namespace i2s_audio {
class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, public Component {
public:
void setup() override;
void dump_config() override;
void start() override;
void stop() override;
@ -80,6 +81,7 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
bool pdm_{false};
bool correct_dc_offset_;
bool locked_driver_{false};
int32_t dc_offset_{0};
};

View File

@ -110,29 +110,48 @@ void I2SAudioSpeaker::setup() {
}
}
void I2SAudioSpeaker::dump_config() {
ESP_LOGCONFIG(TAG,
"Speaker:\n"
" Pin: %d\n"
" Buffer duration: %" PRIu32,
static_cast<int8_t>(this->dout_pin_), this->buffer_duration_ms_);
if (this->timeout_.has_value()) {
ESP_LOGCONFIG(TAG, " Timeout: %" PRIu32 " ms", this->timeout_.value());
}
#ifdef USE_I2S_LEGACY
#if SOC_I2S_SUPPORTS_DAC
ESP_LOGCONFIG(TAG, " Internal DAC mode: %d", static_cast<int8_t>(this->internal_dac_mode_));
#endif
ESP_LOGCONFIG(TAG, " Communication format: %d", static_cast<int8_t>(this->i2s_comm_fmt_));
#else
ESP_LOGCONFIG(TAG, " Communication format: %s", this->i2s_comm_fmt_.c_str());
#endif
}
void I2SAudioSpeaker::loop() {
uint32_t event_group_bits = xEventGroupGetBits(this->event_group_);
if (event_group_bits & SpeakerEventGroupBits::STATE_STARTING) {
ESP_LOGD(TAG, "Starting Speaker");
ESP_LOGD(TAG, "Starting");
this->state_ = speaker::STATE_STARTING;
xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_STARTING);
}
if (event_group_bits & SpeakerEventGroupBits::STATE_RUNNING) {
ESP_LOGD(TAG, "Started Speaker");
ESP_LOGD(TAG, "Started");
this->state_ = speaker::STATE_RUNNING;
xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_RUNNING);
this->status_clear_warning();
this->status_clear_error();
}
if (event_group_bits & SpeakerEventGroupBits::STATE_STOPPING) {
ESP_LOGD(TAG, "Stopping Speaker");
ESP_LOGD(TAG, "Stopping");
this->state_ = speaker::STATE_STOPPING;
xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_STOPPING);
}
if (event_group_bits & SpeakerEventGroupBits::STATE_STOPPED) {
if (!this->task_created_) {
ESP_LOGD(TAG, "Stopped Speaker");
ESP_LOGD(TAG, "Stopped");
this->state_ = speaker::STATE_STOPPED;
xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ALL_BITS);
this->speaker_task_handle_ = nullptr;
@ -140,20 +159,19 @@ void I2SAudioSpeaker::loop() {
}
if (event_group_bits & SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START) {
this->status_set_error("Failed to start speaker task");
this->status_set_error("Failed to start task");
xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START);
}
if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) {
uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS;
ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits)));
ESP_LOGW(TAG, "Writing failed: %s", esp_err_to_name(err_bit_to_esp_err(error_bits)));
this->status_set_warning();
}
if (event_group_bits & SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED) {
this->status_set_error("Failed to adjust I2S bus to match the incoming audio");
ESP_LOGE(TAG,
"Incompatible audio format: sample rate = %" PRIu32 ", channels = %" PRIu8 ", bits per sample = %" PRIu8,
this->status_set_error("Failed to adjust bus to match incoming audio");
ESP_LOGE(TAG, "Incompatible audio format: sample rate = %" PRIu32 ", channels = %u, bits per sample = %u",
this->audio_stream_info_.get_sample_rate(), this->audio_stream_info_.get_channels(),
this->audio_stream_info_.get_bits_per_sample());
}
@ -202,7 +220,7 @@ void I2SAudioSpeaker::set_mute_state(bool mute_state) {
size_t I2SAudioSpeaker::play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) {
if (this->is_failed()) {
ESP_LOGE(TAG, "Cannot play audio, speaker failed to setup");
ESP_LOGE(TAG, "Setup failed; cannot play audio");
return 0;
}
if (this->state_ != speaker::STATE_RUNNING && this->state_ != speaker::STATE_STARTING) {

View File

@ -24,6 +24,7 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp
float get_setup_priority() const override { return esphome::setup_priority::PROCESSOR; }
void setup() override;
void dump_config() override;
void loop() override;
void set_buffer_duration(uint32_t buffer_duration_ms) { this->buffer_duration_ms_ = buffer_duration_ms; }

View File

@ -17,7 +17,7 @@ namespace light {
class LightOutput;
enum LightRestoreMode {
enum LightRestoreMode : uint8_t {
LIGHT_RESTORE_DEFAULT_OFF,
LIGHT_RESTORE_DEFAULT_ON,
LIGHT_ALWAYS_OFF,
@ -212,12 +212,18 @@ class LightState : public EntityBase, public Component {
/// Store the output to allow effects to have more access.
LightOutput *output_;
/// Value for storing the index of the currently active effect. 0 if no effect is active
uint32_t active_effect_index_{};
/// The currently active transformer for this light (transition/flash).
std::unique_ptr<LightTransformer> transformer_{nullptr};
/// Whether the light value should be written in the next cycle.
bool next_write_{true};
/// List of effects for this light.
std::vector<LightEffect *> effects_;
/// Value for storing the index of the currently active effect. 0 if no effect is active
uint32_t active_effect_index_{};
/// Default transition length for all transitions in ms.
uint32_t default_transition_length_{};
/// Transition length to use for flash transitions.
uint32_t flash_transition_length_{};
/// Gamma correction factor for the light.
float gamma_correct_{};
/// Object used to store the persisted values of the light.
ESPPreferenceObject rtc_;
@ -236,19 +242,13 @@ class LightState : public EntityBase, public Component {
*/
CallbackManager<void()> target_state_reached_callback_{};
/// Default transition length for all transitions in ms.
uint32_t default_transition_length_{};
/// Transition length to use for flash transitions.
uint32_t flash_transition_length_{};
/// Gamma correction factor for the light.
float gamma_correct_{};
/// Restore mode of the light.
LightRestoreMode restore_mode_;
/// Initial state of the light.
optional<LightStateRTCState> initial_state_{};
/// List of effects for this light.
std::vector<LightEffect *> effects_;
/// Restore mode of the light.
LightRestoreMode restore_mode_;
/// Whether the light value should be written in the next cycle.
bool next_write_{true};
// for effects, true if a transformer (transition) is active.
bool is_transformer_active_ = false;
};

View File

@ -3,7 +3,6 @@
namespace esphome {
namespace spi {
#ifdef USE_ARDUINO
static const char *const TAG = "spi-esp-arduino";
@ -38,17 +37,31 @@ class SPIDelegateHw : public SPIDelegate {
void write16(uint16_t data) override { this->channel_->transfer16(data); }
#ifdef USE_RP2040
void write_array(const uint8_t *ptr, size_t length) override {
// avoid overwriting the supplied buffer
uint8_t *rxbuf = new uint8_t[length]; // NOLINT(cppcoreguidelines-owning-memory)
memcpy(rxbuf, ptr, length);
this->channel_->transfer((void *) rxbuf, length);
delete[] rxbuf; // NOLINT(cppcoreguidelines-owning-memory)
}
if (length == 1) {
this->channel_->transfer(*ptr);
return;
}
#ifdef USE_RP2040
// avoid overwriting the supplied buffer. Use vector for automatic deallocation
auto rxbuf = std::vector<uint8_t>(length);
memcpy(rxbuf.data(), ptr, length);
this->channel_->transfer((void *) rxbuf.data(), length);
#elif defined(USE_ESP8266)
// ESP8266 SPI library requires the pointer to be word aligned, but the data may not be
// so we need to copy the data to a temporary buffer
if (reinterpret_cast<uintptr_t>(ptr) & 0x3) {
ESP_LOGVV(TAG, "SPI write buffer not word aligned, copying to temporary buffer");
auto txbuf = std::vector<uint8_t>(length);
memcpy(txbuf.data(), ptr, length);
this->channel_->writeBytes(txbuf.data(), length);
} else {
this->channel_->writeBytes(ptr, length);
}
#else
void write_array(const uint8_t *ptr, size_t length) override { this->channel_->writeBytes(ptr, length); }
this->channel_->writeBytes(ptr, length);
#endif
}
void read_array(uint8_t *ptr, size_t length) override { this->channel_->transfer(ptr, length); }

View File

@ -21,7 +21,7 @@ const int RESTORE_MODE_PERSISTENT_MASK = 0x02;
const int RESTORE_MODE_INVERTED_MASK = 0x04;
const int RESTORE_MODE_DISABLED_MASK = 0x08;
enum SwitchRestoreMode {
enum SwitchRestoreMode : uint8_t {
SWITCH_ALWAYS_OFF = !RESTORE_MODE_ON_MASK,
SWITCH_ALWAYS_ON = RESTORE_MODE_ON_MASK,
SWITCH_RESTORE_DEFAULT_OFF = RESTORE_MODE_PERSISTENT_MASK,
@ -49,12 +49,12 @@ class Switch : public EntityBase, public EntityBase_DeviceClass {
*/
void publish_state(bool state);
/// The current reported state of the binary sensor.
bool state;
/// Indicates whether or not state is to be retrieved from flash and how
SwitchRestoreMode restore_mode{SWITCH_RESTORE_DEFAULT_OFF};
/// The current reported state of the binary sensor.
bool state;
/** Turn this switch on. This is called by the front-end.
*
* For implementing switches, please override write_state.
@ -123,10 +123,16 @@ class Switch : public EntityBase, public EntityBase_DeviceClass {
*/
virtual void write_state(bool state) = 0;
CallbackManager<void(bool)> state_callback_{};
bool inverted_{false};
Deduplicator<bool> publish_dedup_;
// Pointer first (4 bytes)
ESPPreferenceObject rtc_;
// CallbackManager (12 bytes on 32-bit - contains vector)
CallbackManager<void(bool)> state_callback_{};
// Small types grouped together
Deduplicator<bool> publish_dedup_; // 2 bytes (bool has_value_ + bool last_value_)
bool inverted_{false}; // 1 byte
// Total: 3 bytes, 1 byte padding
};
#define LOG_SWITCH(prefix, type, obj) log_switch((TAG), (prefix), LOG_STR_LITERAL(type), (obj))

View File

@ -1,6 +1,6 @@
"""Constants used by esphome."""
__version__ = "2025.6.0b2"
__version__ = "2025.6.0b3"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = (