mirror of
https://github.com/esphome/esphome.git
synced 2025-07-28 14:16:40 +00:00
commit
50b7349fe0
@ -490,7 +490,7 @@ esphome/components/vbus/* @ssieb
|
|||||||
esphome/components/veml3235/* @kbx81
|
esphome/components/veml3235/* @kbx81
|
||||||
esphome/components/veml7700/* @latonita
|
esphome/components/veml7700/* @latonita
|
||||||
esphome/components/version/* @esphome/core
|
esphome/components/version/* @esphome/core
|
||||||
esphome/components/voice_assistant/* @jesserockz
|
esphome/components/voice_assistant/* @jesserockz @kahrendt
|
||||||
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
|
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
|
||||||
esphome/components/watchdog/* @oarcher
|
esphome/components/watchdog/* @oarcher
|
||||||
esphome/components/waveshare_epaper/* @clydebarrow
|
esphome/components/waveshare_epaper/* @clydebarrow
|
||||||
|
2
Doxyfile
2
Doxyfile
@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
|||||||
# could be handy for archiving the generated documentation or if some version
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 2025.6.1
|
PROJECT_NUMBER = 2025.6.2
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# 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
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
||||||
#include "esp_crt_bundle.h"
|
#include "esp_crt_bundle.h"
|
||||||
@ -16,13 +17,13 @@ namespace audio {
|
|||||||
static const uint32_t READ_WRITE_TIMEOUT_MS = 20;
|
static const uint32_t READ_WRITE_TIMEOUT_MS = 20;
|
||||||
|
|
||||||
static const uint32_t CONNECTION_TIMEOUT_MS = 5000;
|
static const uint32_t CONNECTION_TIMEOUT_MS = 5000;
|
||||||
|
static const uint8_t MAX_FETCHING_HEADER_ATTEMPTS = 6;
|
||||||
// The number of times the http read times out with no data before throwing an error
|
|
||||||
static const uint32_t ERROR_COUNT_NO_DATA_READ_TIMEOUT = 100;
|
|
||||||
|
|
||||||
static const size_t HTTP_STREAM_BUFFER_SIZE = 2048;
|
static const size_t HTTP_STREAM_BUFFER_SIZE = 2048;
|
||||||
|
|
||||||
static const uint8_t MAX_REDIRECTION = 5;
|
static const uint8_t MAX_REDIRECTIONS = 5;
|
||||||
|
|
||||||
|
static const char *const TAG = "audio_reader";
|
||||||
|
|
||||||
// Some common HTTP status codes - borrowed from http_request component accessed 20241224
|
// Some common HTTP status codes - borrowed from http_request component accessed 20241224
|
||||||
enum HttpStatus {
|
enum HttpStatus {
|
||||||
@ -94,7 +95,7 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) {
|
|||||||
client_config.url = uri.c_str();
|
client_config.url = uri.c_str();
|
||||||
client_config.cert_pem = nullptr;
|
client_config.cert_pem = nullptr;
|
||||||
client_config.disable_auto_redirect = false;
|
client_config.disable_auto_redirect = false;
|
||||||
client_config.max_redirection_count = 10;
|
client_config.max_redirection_count = MAX_REDIRECTIONS;
|
||||||
client_config.event_handler = http_event_handler;
|
client_config.event_handler = http_event_handler;
|
||||||
client_config.user_data = this;
|
client_config.user_data = this;
|
||||||
client_config.buffer_size = HTTP_STREAM_BUFFER_SIZE;
|
client_config.buffer_size = HTTP_STREAM_BUFFER_SIZE;
|
||||||
@ -116,12 +117,29 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) {
|
|||||||
esp_err_t err = esp_http_client_open(this->client_, 0);
|
esp_err_t err = esp_http_client_open(this->client_, 0);
|
||||||
|
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to open URL");
|
||||||
this->cleanup_connection_();
|
this->cleanup_connection_();
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t header_length = esp_http_client_fetch_headers(this->client_);
|
int64_t header_length = esp_http_client_fetch_headers(this->client_);
|
||||||
|
uint8_t reattempt_count = 0;
|
||||||
|
while ((header_length < 0) && (reattempt_count < MAX_FETCHING_HEADER_ATTEMPTS)) {
|
||||||
|
this->cleanup_connection_();
|
||||||
|
if (header_length != -ESP_ERR_HTTP_EAGAIN) {
|
||||||
|
// Serious error, no recovery
|
||||||
|
return ESP_FAIL;
|
||||||
|
} else {
|
||||||
|
// Reconnect from a fresh state to avoid a bug where it never reads the headers even if made available
|
||||||
|
this->client_ = esp_http_client_init(&client_config);
|
||||||
|
esp_http_client_open(this->client_, 0);
|
||||||
|
header_length = esp_http_client_fetch_headers(this->client_);
|
||||||
|
++reattempt_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (header_length < 0) {
|
if (header_length < 0) {
|
||||||
|
ESP_LOGE(TAG, "Failed to fetch headers");
|
||||||
this->cleanup_connection_();
|
this->cleanup_connection_();
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
@ -135,7 +153,7 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) {
|
|||||||
|
|
||||||
ssize_t redirect_count = 0;
|
ssize_t redirect_count = 0;
|
||||||
|
|
||||||
while ((esp_http_client_set_redirection(this->client_) == ESP_OK) && (redirect_count < MAX_REDIRECTION)) {
|
while ((esp_http_client_set_redirection(this->client_) == ESP_OK) && (redirect_count < MAX_REDIRECTIONS)) {
|
||||||
err = esp_http_client_open(this->client_, 0);
|
err = esp_http_client_open(this->client_, 0);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
this->cleanup_connection_();
|
this->cleanup_connection_();
|
||||||
@ -267,27 +285,29 @@ AudioReaderState AudioReader::http_read_() {
|
|||||||
return AudioReaderState::FINISHED;
|
return AudioReaderState::FINISHED;
|
||||||
}
|
}
|
||||||
} else if (this->output_transfer_buffer_->free() > 0) {
|
} else if (this->output_transfer_buffer_->free() > 0) {
|
||||||
size_t bytes_to_read = this->output_transfer_buffer_->free();
|
int received_len = esp_http_client_read(this->client_, (char *) this->output_transfer_buffer_->get_buffer_end(),
|
||||||
int received_len =
|
this->output_transfer_buffer_->free());
|
||||||
esp_http_client_read(this->client_, (char *) this->output_transfer_buffer_->get_buffer_end(), bytes_to_read);
|
|
||||||
|
|
||||||
if (received_len > 0) {
|
if (received_len > 0) {
|
||||||
this->output_transfer_buffer_->increase_buffer_length(received_len);
|
this->output_transfer_buffer_->increase_buffer_length(received_len);
|
||||||
this->last_data_read_ms_ = millis();
|
this->last_data_read_ms_ = millis();
|
||||||
} else if (received_len < 0) {
|
return AudioReaderState::READING;
|
||||||
|
} else if (received_len <= 0) {
|
||||||
// HTTP read error
|
// HTTP read error
|
||||||
this->cleanup_connection_();
|
if (received_len == -1) {
|
||||||
return AudioReaderState::FAILED;
|
// A true connection error occured, no chance at recovery
|
||||||
} else {
|
this->cleanup_connection_();
|
||||||
if (bytes_to_read > 0) {
|
return AudioReaderState::FAILED;
|
||||||
// Read timed out
|
|
||||||
if ((millis() - this->last_data_read_ms_) > CONNECTION_TIMEOUT_MS) {
|
|
||||||
this->cleanup_connection_();
|
|
||||||
return AudioReaderState::FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
delay(READ_WRITE_TIMEOUT_MS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read timed out, manually verify if it has been too long since the last successful read
|
||||||
|
if ((millis() - this->last_data_read_ms_) > MAX_FETCHING_HEADER_ATTEMPTS * CONNECTION_TIMEOUT_MS) {
|
||||||
|
ESP_LOGE(TAG, "Timed out");
|
||||||
|
this->cleanup_connection_();
|
||||||
|
return AudioReaderState::FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(READ_WRITE_TIMEOUT_MS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -605,7 +605,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
|||||||
CONF_ENABLE_LWIP_DHCP_SERVER, "wifi", default=False
|
CONF_ENABLE_LWIP_DHCP_SERVER, "wifi", default=False
|
||||||
): cv.boolean,
|
): cv.boolean,
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_ENABLE_LWIP_MDNS_QUERIES, default=False
|
CONF_ENABLE_LWIP_MDNS_QUERIES, default=True
|
||||||
): cv.boolean,
|
): cv.boolean,
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_ENABLE_LWIP_BRIDGE_INTERFACE, default=False
|
CONF_ENABLE_LWIP_BRIDGE_INTERFACE, default=False
|
||||||
@ -760,7 +760,7 @@ async def to_code(config):
|
|||||||
and not advanced[CONF_ENABLE_LWIP_DHCP_SERVER]
|
and not advanced[CONF_ENABLE_LWIP_DHCP_SERVER]
|
||||||
):
|
):
|
||||||
add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False)
|
add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False)
|
||||||
if not advanced.get(CONF_ENABLE_LWIP_MDNS_QUERIES, False):
|
if not advanced.get(CONF_ENABLE_LWIP_MDNS_QUERIES, True):
|
||||||
add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False)
|
add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False)
|
||||||
if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False):
|
if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False):
|
||||||
add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0)
|
add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0)
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
from esphome import pins
|
from esphome import pins
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import esp32
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ADDRESS,
|
CONF_ADDRESS,
|
||||||
@ -12,6 +15,8 @@ from esphome.const import (
|
|||||||
CONF_SCL,
|
CONF_SCL,
|
||||||
CONF_SDA,
|
CONF_SDA,
|
||||||
CONF_TIMEOUT,
|
CONF_TIMEOUT,
|
||||||
|
KEY_CORE,
|
||||||
|
KEY_FRAMEWORK_VERSION,
|
||||||
PLATFORM_ESP32,
|
PLATFORM_ESP32,
|
||||||
PLATFORM_ESP8266,
|
PLATFORM_ESP8266,
|
||||||
PLATFORM_RP2040,
|
PLATFORM_RP2040,
|
||||||
@ -19,6 +24,7 @@ from esphome.const import (
|
|||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
import esphome.final_validate as fv
|
import esphome.final_validate as fv
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
i2c_ns = cg.esphome_ns.namespace("i2c")
|
i2c_ns = cg.esphome_ns.namespace("i2c")
|
||||||
I2CBus = i2c_ns.class_("I2CBus")
|
I2CBus = i2c_ns.class_("I2CBus")
|
||||||
@ -40,6 +46,32 @@ def _bus_declare_type(value):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
def validate_config(config):
|
||||||
|
if (
|
||||||
|
config[CONF_SCAN]
|
||||||
|
and CORE.is_esp32
|
||||||
|
and CORE.using_esp_idf
|
||||||
|
and esp32.get_esp32_variant()
|
||||||
|
in [
|
||||||
|
esp32.const.VARIANT_ESP32C5,
|
||||||
|
esp32.const.VARIANT_ESP32C6,
|
||||||
|
esp32.const.VARIANT_ESP32P4,
|
||||||
|
]
|
||||||
|
):
|
||||||
|
version: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||||
|
if version.major == 5 and (
|
||||||
|
(version.minor == 3 and version.patch <= 3)
|
||||||
|
or (version.minor == 4 and version.patch <= 1)
|
||||||
|
):
|
||||||
|
LOGGER.warning(
|
||||||
|
"There is a bug in esp-idf version %s that breaks I2C scan, I2C scan "
|
||||||
|
"has been disabled, see https://github.com/esphome/issues/issues/7128",
|
||||||
|
str(version),
|
||||||
|
)
|
||||||
|
config[CONF_SCAN] = False
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
pin_with_input_and_output_support = pins.internal_gpio_pin_number(
|
pin_with_input_and_output_support = pins.internal_gpio_pin_number(
|
||||||
{CONF_OUTPUT: True, CONF_INPUT: True}
|
{CONF_OUTPUT: True, CONF_INPUT: True}
|
||||||
)
|
)
|
||||||
@ -65,6 +97,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]),
|
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]),
|
||||||
|
validate_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import esphome.config_validation as cv
|
|||||||
from esphome.const import CONF_SIZE, CONF_TEXT
|
from esphome.const import CONF_SIZE, CONF_TEXT
|
||||||
from esphome.cpp_generator import MockObjClass
|
from esphome.cpp_generator import MockObjClass
|
||||||
|
|
||||||
from ..defines import CONF_MAIN, literal
|
from ..defines import CONF_MAIN
|
||||||
from ..lv_validation import color, color_retmapper, lv_text
|
from ..lv_validation import color, color_retmapper, lv_text
|
||||||
from ..lvcode import LocalVariable, lv, lv_expr
|
from ..lvcode import LocalVariable, lv, lv_expr
|
||||||
from ..schemas import TEXT_SCHEMA
|
from ..schemas import TEXT_SCHEMA
|
||||||
@ -34,7 +34,7 @@ class QrCodeType(WidgetType):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_uses(self):
|
def get_uses(self):
|
||||||
return ("canvas", "img")
|
return ("canvas", "img", "label")
|
||||||
|
|
||||||
def obj_creator(self, parent: MockObjClass, config: dict):
|
def obj_creator(self, parent: MockObjClass, config: dict):
|
||||||
dark_color = color_retmapper(config[CONF_DARK_COLOR])
|
dark_color = color_retmapper(config[CONF_DARK_COLOR])
|
||||||
@ -45,10 +45,8 @@ class QrCodeType(WidgetType):
|
|||||||
async def to_code(self, w: Widget, config):
|
async def to_code(self, w: Widget, config):
|
||||||
if (value := config.get(CONF_TEXT)) is not None:
|
if (value := config.get(CONF_TEXT)) is not None:
|
||||||
value = await lv_text.process(value)
|
value = await lv_text.process(value)
|
||||||
with LocalVariable(
|
with LocalVariable("qr_text", cg.std_string, value, modifier="") as str_obj:
|
||||||
"qr_text", cg.const_char_ptr, value, modifier=""
|
lv.qrcode_update(w.obj, str_obj.c_str(), str_obj.size())
|
||||||
) as str_obj:
|
|
||||||
lv.qrcode_update(w.obj, str_obj, literal(f"strlen({str_obj})"))
|
|
||||||
|
|
||||||
|
|
||||||
qr_code_spec = QrCodeType()
|
qr_code_spec = QrCodeType()
|
||||||
|
@ -6,7 +6,11 @@ namespace mcp23xxx_base {
|
|||||||
|
|
||||||
float MCP23XXXBase::get_setup_priority() const { return setup_priority::IO; }
|
float MCP23XXXBase::get_setup_priority() const { return setup_priority::IO; }
|
||||||
|
|
||||||
void MCP23XXXGPIOPin::setup() { pin_mode(flags_); }
|
void MCP23XXXGPIOPin::setup() {
|
||||||
|
pin_mode(flags_);
|
||||||
|
this->parent_->pin_interrupt_mode(this->pin_, this->interrupt_mode_);
|
||||||
|
}
|
||||||
|
|
||||||
void MCP23XXXGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
|
void MCP23XXXGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
|
||||||
bool MCP23XXXGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
|
bool MCP23XXXGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
|
||||||
void MCP23XXXGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
|
void MCP23XXXGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
|
||||||
|
@ -343,13 +343,12 @@ void AudioPipeline::read_task(void *params) {
|
|||||||
xEventGroupSetBits(this_pipeline->event_group_, EventGroupBits::READER_MESSAGE_FINISHED);
|
xEventGroupSetBits(this_pipeline->event_group_, EventGroupBits::READER_MESSAGE_FINISHED);
|
||||||
|
|
||||||
// Wait until the pipeline notifies us the source of the media file
|
// Wait until the pipeline notifies us the source of the media file
|
||||||
EventBits_t event_bits =
|
EventBits_t event_bits = xEventGroupWaitBits(
|
||||||
xEventGroupWaitBits(this_pipeline->event_group_,
|
this_pipeline->event_group_,
|
||||||
EventGroupBits::READER_COMMAND_INIT_FILE | EventGroupBits::READER_COMMAND_INIT_HTTP |
|
EventGroupBits::READER_COMMAND_INIT_FILE | EventGroupBits::READER_COMMAND_INIT_HTTP, // Bit message to read
|
||||||
EventGroupBits::PIPELINE_COMMAND_STOP, // Bit message to read
|
pdFALSE, // Clear the bit on exit
|
||||||
pdFALSE, // Clear the bit on exit
|
pdFALSE, // Wait for all the bits,
|
||||||
pdFALSE, // Wait for all the bits,
|
portMAX_DELAY); // Block indefinitely until bit is set
|
||||||
portMAX_DELAY); // Block indefinitely until bit is set
|
|
||||||
|
|
||||||
if (!(event_bits & EventGroupBits::PIPELINE_COMMAND_STOP)) {
|
if (!(event_bits & EventGroupBits::PIPELINE_COMMAND_STOP)) {
|
||||||
xEventGroupClearBits(this_pipeline->event_group_, EventGroupBits::READER_MESSAGE_FINISHED |
|
xEventGroupClearBits(this_pipeline->event_group_, EventGroupBits::READER_MESSAGE_FINISHED |
|
||||||
@ -434,12 +433,12 @@ void AudioPipeline::decode_task(void *params) {
|
|||||||
xEventGroupSetBits(this_pipeline->event_group_, EventGroupBits::DECODER_MESSAGE_FINISHED);
|
xEventGroupSetBits(this_pipeline->event_group_, EventGroupBits::DECODER_MESSAGE_FINISHED);
|
||||||
|
|
||||||
// Wait until the reader notifies us that the media type is available
|
// Wait until the reader notifies us that the media type is available
|
||||||
EventBits_t event_bits = xEventGroupWaitBits(this_pipeline->event_group_,
|
EventBits_t event_bits =
|
||||||
EventGroupBits::READER_MESSAGE_LOADED_MEDIA_TYPE |
|
xEventGroupWaitBits(this_pipeline->event_group_,
|
||||||
EventGroupBits::PIPELINE_COMMAND_STOP, // Bit message to read
|
EventGroupBits::READER_MESSAGE_LOADED_MEDIA_TYPE, // Bit message to read
|
||||||
pdFALSE, // Clear the bit on exit
|
pdFALSE, // Clear the bit on exit
|
||||||
pdFALSE, // Wait for all the bits,
|
pdFALSE, // Wait for all the bits,
|
||||||
portMAX_DELAY); // Block indefinitely until bit is set
|
portMAX_DELAY); // Block indefinitely until bit is set
|
||||||
|
|
||||||
xEventGroupClearBits(this_pipeline->event_group_,
|
xEventGroupClearBits(this_pipeline->event_group_,
|
||||||
EventGroupBits::DECODER_MESSAGE_FINISHED | EventGroupBits::READER_MESSAGE_LOADED_MEDIA_TYPE);
|
EventGroupBits::DECODER_MESSAGE_FINISHED | EventGroupBits::READER_MESSAGE_LOADED_MEDIA_TYPE);
|
||||||
|
@ -17,10 +17,11 @@ from esphome.const import (
|
|||||||
AUTO_LOAD = ["socket"]
|
AUTO_LOAD = ["socket"]
|
||||||
DEPENDENCIES = ["api", "microphone"]
|
DEPENDENCIES = ["api", "microphone"]
|
||||||
|
|
||||||
CODEOWNERS = ["@jesserockz"]
|
CODEOWNERS = ["@jesserockz", "@kahrendt"]
|
||||||
|
|
||||||
CONF_ON_END = "on_end"
|
CONF_ON_END = "on_end"
|
||||||
CONF_ON_INTENT_END = "on_intent_end"
|
CONF_ON_INTENT_END = "on_intent_end"
|
||||||
|
CONF_ON_INTENT_PROGRESS = "on_intent_progress"
|
||||||
CONF_ON_INTENT_START = "on_intent_start"
|
CONF_ON_INTENT_START = "on_intent_start"
|
||||||
CONF_ON_LISTENING = "on_listening"
|
CONF_ON_LISTENING = "on_listening"
|
||||||
CONF_ON_START = "on_start"
|
CONF_ON_START = "on_start"
|
||||||
@ -136,6 +137,9 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_ON_INTENT_START): automation.validate_automation(
|
cv.Optional(CONF_ON_INTENT_START): automation.validate_automation(
|
||||||
single=True
|
single=True
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_ON_INTENT_PROGRESS): automation.validate_automation(
|
||||||
|
single=True
|
||||||
|
),
|
||||||
cv.Optional(CONF_ON_INTENT_END): automation.validate_automation(
|
cv.Optional(CONF_ON_INTENT_END): automation.validate_automation(
|
||||||
single=True
|
single=True
|
||||||
),
|
),
|
||||||
@ -282,6 +286,13 @@ async def to_code(config):
|
|||||||
config[CONF_ON_INTENT_START],
|
config[CONF_ON_INTENT_START],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if CONF_ON_INTENT_PROGRESS in config:
|
||||||
|
await automation.build_automation(
|
||||||
|
var.get_intent_progress_trigger(),
|
||||||
|
[(cg.std_string, "x")],
|
||||||
|
config[CONF_ON_INTENT_PROGRESS],
|
||||||
|
)
|
||||||
|
|
||||||
if CONF_ON_INTENT_END in config:
|
if CONF_ON_INTENT_END in config:
|
||||||
await automation.build_automation(
|
await automation.build_automation(
|
||||||
var.get_intent_end_trigger(),
|
var.get_intent_end_trigger(),
|
||||||
|
@ -555,7 +555,7 @@ void VoiceAssistant::request_stop() {
|
|||||||
break;
|
break;
|
||||||
case State::AWAITING_RESPONSE:
|
case State::AWAITING_RESPONSE:
|
||||||
this->signal_stop_();
|
this->signal_stop_();
|
||||||
break;
|
// Fallthrough intended to stop a streaming TTS announcement that has potentially started
|
||||||
case State::STREAMING_RESPONSE:
|
case State::STREAMING_RESPONSE:
|
||||||
#ifdef USE_MEDIA_PLAYER
|
#ifdef USE_MEDIA_PLAYER
|
||||||
// Stop any ongoing media player announcement
|
// Stop any ongoing media player announcement
|
||||||
@ -599,6 +599,14 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
|
|||||||
switch (msg.event_type) {
|
switch (msg.event_type) {
|
||||||
case api::enums::VOICE_ASSISTANT_RUN_START:
|
case api::enums::VOICE_ASSISTANT_RUN_START:
|
||||||
ESP_LOGD(TAG, "Assist Pipeline running");
|
ESP_LOGD(TAG, "Assist Pipeline running");
|
||||||
|
#ifdef USE_MEDIA_PLAYER
|
||||||
|
this->started_streaming_tts_ = false;
|
||||||
|
for (auto arg : msg.data) {
|
||||||
|
if (arg.name == "url") {
|
||||||
|
this->tts_response_url_ = std::move(arg.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
this->defer([this]() { this->start_trigger_->trigger(); });
|
this->defer([this]() { this->start_trigger_->trigger(); });
|
||||||
break;
|
break;
|
||||||
case api::enums::VOICE_ASSISTANT_WAKE_WORD_START:
|
case api::enums::VOICE_ASSISTANT_WAKE_WORD_START:
|
||||||
@ -622,6 +630,8 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
|
|||||||
if (text.empty()) {
|
if (text.empty()) {
|
||||||
ESP_LOGW(TAG, "No text in STT_END event");
|
ESP_LOGW(TAG, "No text in STT_END event");
|
||||||
return;
|
return;
|
||||||
|
} else if (text.length() > 500) {
|
||||||
|
text = text.substr(0, 497) + "...";
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "Speech recognised as: \"%s\"", text.c_str());
|
ESP_LOGD(TAG, "Speech recognised as: \"%s\"", text.c_str());
|
||||||
this->defer([this, text]() { this->stt_end_trigger_->trigger(text); });
|
this->defer([this, text]() { this->stt_end_trigger_->trigger(text); });
|
||||||
@ -631,6 +641,27 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
|
|||||||
ESP_LOGD(TAG, "Intent started");
|
ESP_LOGD(TAG, "Intent started");
|
||||||
this->defer([this]() { this->intent_start_trigger_->trigger(); });
|
this->defer([this]() { this->intent_start_trigger_->trigger(); });
|
||||||
break;
|
break;
|
||||||
|
case api::enums::VOICE_ASSISTANT_INTENT_PROGRESS: {
|
||||||
|
ESP_LOGD(TAG, "Intent progress");
|
||||||
|
std::string tts_url_for_trigger = "";
|
||||||
|
#ifdef USE_MEDIA_PLAYER
|
||||||
|
if (this->media_player_ != nullptr) {
|
||||||
|
for (const auto &arg : msg.data) {
|
||||||
|
if ((arg.name == "tts_start_streaming") && (arg.value == "1") && !this->tts_response_url_.empty()) {
|
||||||
|
this->media_player_->make_call().set_media_url(this->tts_response_url_).set_announcement(true).perform();
|
||||||
|
|
||||||
|
this->media_player_wait_for_announcement_start_ = true;
|
||||||
|
this->media_player_wait_for_announcement_end_ = false;
|
||||||
|
this->started_streaming_tts_ = true;
|
||||||
|
tts_url_for_trigger = this->tts_response_url_;
|
||||||
|
this->tts_response_url_.clear(); // Reset streaming URL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
this->defer([this, tts_url_for_trigger]() { this->intent_progress_trigger_->trigger(tts_url_for_trigger); });
|
||||||
|
break;
|
||||||
|
}
|
||||||
case api::enums::VOICE_ASSISTANT_INTENT_END: {
|
case api::enums::VOICE_ASSISTANT_INTENT_END: {
|
||||||
for (auto arg : msg.data) {
|
for (auto arg : msg.data) {
|
||||||
if (arg.name == "conversation_id") {
|
if (arg.name == "conversation_id") {
|
||||||
@ -653,6 +684,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
|
|||||||
ESP_LOGW(TAG, "No text in TTS_START event");
|
ESP_LOGW(TAG, "No text in TTS_START event");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (text.length() > 500) {
|
||||||
|
text = text.substr(0, 497) + "...";
|
||||||
|
}
|
||||||
ESP_LOGD(TAG, "Response: \"%s\"", text.c_str());
|
ESP_LOGD(TAG, "Response: \"%s\"", text.c_str());
|
||||||
this->defer([this, text]() {
|
this->defer([this, text]() {
|
||||||
this->tts_start_trigger_->trigger(text);
|
this->tts_start_trigger_->trigger(text);
|
||||||
@ -678,7 +712,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
|
|||||||
ESP_LOGD(TAG, "Response URL: \"%s\"", url.c_str());
|
ESP_LOGD(TAG, "Response URL: \"%s\"", url.c_str());
|
||||||
this->defer([this, url]() {
|
this->defer([this, url]() {
|
||||||
#ifdef USE_MEDIA_PLAYER
|
#ifdef USE_MEDIA_PLAYER
|
||||||
if (this->media_player_ != nullptr) {
|
if ((this->media_player_ != nullptr) && (!this->started_streaming_tts_)) {
|
||||||
this->media_player_->make_call().set_media_url(url).set_announcement(true).perform();
|
this->media_player_->make_call().set_media_url(url).set_announcement(true).perform();
|
||||||
|
|
||||||
this->media_player_wait_for_announcement_start_ = true;
|
this->media_player_wait_for_announcement_start_ = true;
|
||||||
|
@ -177,6 +177,7 @@ class VoiceAssistant : public Component {
|
|||||||
|
|
||||||
Trigger<> *get_intent_end_trigger() const { return this->intent_end_trigger_; }
|
Trigger<> *get_intent_end_trigger() const { return this->intent_end_trigger_; }
|
||||||
Trigger<> *get_intent_start_trigger() const { return this->intent_start_trigger_; }
|
Trigger<> *get_intent_start_trigger() const { return this->intent_start_trigger_; }
|
||||||
|
Trigger<std::string> *get_intent_progress_trigger() const { return this->intent_progress_trigger_; }
|
||||||
Trigger<> *get_listening_trigger() const { return this->listening_trigger_; }
|
Trigger<> *get_listening_trigger() const { return this->listening_trigger_; }
|
||||||
Trigger<> *get_end_trigger() const { return this->end_trigger_; }
|
Trigger<> *get_end_trigger() const { return this->end_trigger_; }
|
||||||
Trigger<> *get_start_trigger() const { return this->start_trigger_; }
|
Trigger<> *get_start_trigger() const { return this->start_trigger_; }
|
||||||
@ -233,6 +234,7 @@ class VoiceAssistant : public Component {
|
|||||||
Trigger<> *tts_stream_start_trigger_ = new Trigger<>();
|
Trigger<> *tts_stream_start_trigger_ = new Trigger<>();
|
||||||
Trigger<> *tts_stream_end_trigger_ = new Trigger<>();
|
Trigger<> *tts_stream_end_trigger_ = new Trigger<>();
|
||||||
#endif
|
#endif
|
||||||
|
Trigger<std::string> *intent_progress_trigger_ = new Trigger<std::string>();
|
||||||
Trigger<> *wake_word_detected_trigger_ = new Trigger<>();
|
Trigger<> *wake_word_detected_trigger_ = new Trigger<>();
|
||||||
Trigger<std::string> *stt_end_trigger_ = new Trigger<std::string>();
|
Trigger<std::string> *stt_end_trigger_ = new Trigger<std::string>();
|
||||||
Trigger<std::string> *tts_end_trigger_ = new Trigger<std::string>();
|
Trigger<std::string> *tts_end_trigger_ = new Trigger<std::string>();
|
||||||
@ -268,6 +270,8 @@ class VoiceAssistant : public Component {
|
|||||||
#endif
|
#endif
|
||||||
#ifdef USE_MEDIA_PLAYER
|
#ifdef USE_MEDIA_PLAYER
|
||||||
media_player::MediaPlayer *media_player_{nullptr};
|
media_player::MediaPlayer *media_player_{nullptr};
|
||||||
|
std::string tts_response_url_{""};
|
||||||
|
bool started_streaming_tts_{false};
|
||||||
bool media_player_wait_for_announcement_start_{false};
|
bool media_player_wait_for_announcement_start_{false};
|
||||||
bool media_player_wait_for_announcement_end_{false};
|
bool media_player_wait_for_announcement_end_{false};
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Constants used by esphome."""
|
"""Constants used by esphome."""
|
||||||
|
|
||||||
__version__ = "2025.6.1"
|
__version__ = "2025.6.2"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||||
|
@ -646,7 +646,9 @@ lvgl:
|
|||||||
on_click:
|
on_click:
|
||||||
lvgl.qrcode.update:
|
lvgl.qrcode.update:
|
||||||
id: lv_qr
|
id: lv_qr
|
||||||
text: homeassistant.io
|
text:
|
||||||
|
format: "A string with a number %d"
|
||||||
|
args: ['(int)(random_uint32() % 1000)']
|
||||||
|
|
||||||
- slider:
|
- slider:
|
||||||
min_value: 0
|
min_value: 0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user