mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 15:10:51 +00:00
[psram] Consolidate task stack in PSRAM handling (#16628)
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import audio, esp32, media_source, psram
|
||||
from esphome.components import audio, media_source, psram
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_TASK_STACK_IN_PSRAM
|
||||
from esphome.types import ConfigType
|
||||
@@ -21,19 +19,13 @@ def _request_micro_decoder(config: ConfigType) -> ConfigType:
|
||||
return config
|
||||
|
||||
|
||||
def _validate_task_stack_in_psram(value: Any) -> bool:
|
||||
if value := cv.boolean(value):
|
||||
return cv.requires_component(psram.DOMAIN)(value)
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
media_source.media_source_schema(
|
||||
AudioFileMediaSource,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(CONF_TASK_STACK_IN_PSRAM): _validate_task_stack_in_psram,
|
||||
cv.Optional(CONF_TASK_STACK_IN_PSRAM): psram.validate_task_stack_in_psram,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
@@ -49,6 +41,4 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
if config.get(CONF_TASK_STACK_IN_PSRAM):
|
||||
cg.add(var.set_task_stack_in_psram(True))
|
||||
esp32.add_idf_sdkconfig_option(
|
||||
"CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True
|
||||
)
|
||||
psram.request_external_task_stack()
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import audio, esp32, media_source, psram
|
||||
from esphome.components import audio, media_source, psram
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BUFFER_SIZE, CONF_ID, CONF_TASK_STACK_IN_PSRAM
|
||||
from esphome.types import ConfigType
|
||||
@@ -20,14 +18,6 @@ def _request_micro_decoder(config: ConfigType) -> ConfigType:
|
||||
return config
|
||||
|
||||
|
||||
def _validate_task_stack_in_psram(value: Any) -> bool:
|
||||
# Only require the psram component when actually enabling PSRAM stacks; validating
|
||||
# the boolean first means `false` doesn't trigger the requires_component check.
|
||||
if value := cv.boolean(value):
|
||||
return cv.requires_component(psram.DOMAIN)(value)
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
media_source.media_source_schema(
|
||||
AudioHTTPMediaSource,
|
||||
@@ -37,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_BUFFER_SIZE, default=50000): cv.int_range(
|
||||
min=5000, max=1000000
|
||||
),
|
||||
cv.Optional(CONF_TASK_STACK_IN_PSRAM): _validate_task_stack_in_psram,
|
||||
cv.Optional(CONF_TASK_STACK_IN_PSRAM): psram.validate_task_stack_in_psram,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
@@ -53,7 +43,5 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
if config.get(CONF_TASK_STACK_IN_PSRAM):
|
||||
cg.add(var.set_task_stack_in_psram(True))
|
||||
esp32.add_idf_sdkconfig_option(
|
||||
"CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True
|
||||
)
|
||||
psram.request_external_task_stack()
|
||||
cg.add(var.set_buffer_size(config[CONF_BUFFER_SIZE]))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import audio, esp32, speaker
|
||||
from esphome.components import audio, psram, speaker
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BITS_PER_SAMPLE,
|
||||
@@ -93,7 +93,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_BITS_PER_SAMPLE): cv.one_of(8, 16, 24, 32, int=True),
|
||||
cv.Optional(CONF_NUM_CHANNELS): cv.int_range(min=1, max=2),
|
||||
cv.Optional(CONF_QUEUE_MODE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_TASK_STACK_IN_PSRAM, default=False): cv.boolean,
|
||||
cv.Optional(CONF_TASK_STACK_IN_PSRAM): psram.validate_task_stack_in_psram,
|
||||
}
|
||||
),
|
||||
cv.only_on([PLATFORM_ESP32]),
|
||||
@@ -123,12 +123,9 @@ async def to_code(config):
|
||||
cg.add(var.set_output_speaker(spkr))
|
||||
cg.add(var.set_queue_mode(config[CONF_QUEUE_MODE]))
|
||||
|
||||
if task_stack_in_psram := config.get(CONF_TASK_STACK_IN_PSRAM):
|
||||
cg.add(var.set_task_stack_in_psram(task_stack_in_psram))
|
||||
if task_stack_in_psram and config[CONF_TASK_STACK_IN_PSRAM]:
|
||||
esp32.add_idf_sdkconfig_option(
|
||||
"CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True
|
||||
)
|
||||
if config.get(CONF_TASK_STACK_IN_PSRAM):
|
||||
cg.add(var.set_task_stack_in_psram(True))
|
||||
psram.request_external_task_stack()
|
||||
|
||||
# Initialize FixedVector with exact count of source speakers
|
||||
cg.add(var.init_source_speakers(len(config[CONF_SOURCE_SPEAKERS])))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
import textwrap
|
||||
from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_IGNORE_NOT_FOUND
|
||||
@@ -94,6 +95,27 @@ def is_guaranteed() -> bool:
|
||||
return CORE.data.get(KEY_PSRAM_GUARANTEED, False)
|
||||
|
||||
|
||||
def request_external_task_stack() -> None:
|
||||
"""Allow FreeRTOS task stacks to be allocated in external RAM (PSRAM).
|
||||
|
||||
Components that expose a ``task_stack_in_psram`` option should call this from their
|
||||
``to_code`` when the option is enabled. The sdkconfig option only permits external
|
||||
stacks; it does not move any stack into PSRAM on its own, so it stays opt-in per task.
|
||||
"""
|
||||
add_idf_sdkconfig_option("CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True)
|
||||
|
||||
|
||||
def validate_task_stack_in_psram(value: Any) -> bool:
|
||||
"""Validate a ``task_stack_in_psram`` boolean, requiring the psram component only when enabled.
|
||||
|
||||
Validating the boolean first means an explicit ``false`` does not pull in the psram
|
||||
requirement, so the option can still be set to false on devices without PSRAM.
|
||||
"""
|
||||
if value := cv.boolean(value):
|
||||
return cv.requires_component(DOMAIN)(value)
|
||||
return value
|
||||
|
||||
|
||||
def validate_psram_mode(config):
|
||||
esp32_config = fv.full_config.get()[PLATFORM_ESP32]
|
||||
if config[CONF_SPEED] == "120MHZ":
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import audio, esp32, speaker
|
||||
from esphome.components import audio, psram, speaker
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BITS_PER_SAMPLE,
|
||||
@@ -63,7 +63,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(
|
||||
CONF_BUFFER_DURATION, default="100ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_TASK_STACK_IN_PSRAM, default=False): cv.boolean,
|
||||
cv.Optional(CONF_TASK_STACK_IN_PSRAM): psram.validate_task_stack_in_psram,
|
||||
cv.Optional(CONF_FILTERS, default=16): cv.int_range(min=2, max=1024),
|
||||
cv.Optional(CONF_TAPS, default=16): _validate_taps,
|
||||
}
|
||||
@@ -88,9 +88,7 @@ async def to_code(config):
|
||||
|
||||
if config.get(CONF_TASK_STACK_IN_PSRAM):
|
||||
cg.add(var.set_task_stack_in_psram(True))
|
||||
esp32.add_idf_sdkconfig_option(
|
||||
"CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True
|
||||
)
|
||||
psram.request_external_task_stack()
|
||||
|
||||
cg.add(var.set_target_bits_per_sample(config[CONF_BITS_PER_SAMPLE]))
|
||||
cg.add(var.set_target_sample_rate(config[CONF_SAMPLE_RATE]))
|
||||
|
||||
@@ -121,13 +121,6 @@ def register_player_config(config: ConfigType) -> None:
|
||||
data.player_config = config
|
||||
|
||||
|
||||
def _validate_task_stack_in_psram(value):
|
||||
value = cv.boolean(value)
|
||||
if value:
|
||||
return cv.requires_component(psram.DOMAIN)(value)
|
||||
return value
|
||||
|
||||
|
||||
def _request_high_performance_networking(config: ConfigType) -> ConfigType:
|
||||
"""Request high performance networking for Sendspin streaming.
|
||||
|
||||
@@ -152,7 +145,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SendspinHub),
|
||||
cv.Optional(CONF_TASK_STACK_IN_PSRAM): _validate_task_stack_in_psram,
|
||||
cv.Optional(CONF_TASK_STACK_IN_PSRAM): psram.validate_task_stack_in_psram,
|
||||
}
|
||||
),
|
||||
cv.only_on_esp32,
|
||||
@@ -201,9 +194,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
if config.get(CONF_TASK_STACK_IN_PSRAM):
|
||||
cg.add(var.set_task_stack_in_psram(True))
|
||||
esp32.add_idf_sdkconfig_option(
|
||||
"CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True
|
||||
)
|
||||
psram.request_external_task_stack()
|
||||
|
||||
# sendspin-cpp library
|
||||
esp32.add_idf_component(name="sendspin/sendspin-cpp", ref="0.6.1")
|
||||
@@ -261,9 +252,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
psram_stack = player_cfg.get(CONF_TASK_STACK_IN_PSRAM, False)
|
||||
if psram_stack:
|
||||
esp32.add_idf_sdkconfig_option(
|
||||
"CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True
|
||||
)
|
||||
psram.request_external_task_stack()
|
||||
|
||||
# Library defaults: priority 18 (one above httpd_priority 17 so the decoder is not
|
||||
# starved by the HTTP server during the initial encoded-audio burst at stream start),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import media_source
|
||||
from esphome.components import media_source, psram
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BUFFER_SIZE,
|
||||
@@ -19,7 +19,6 @@ from .. import (
|
||||
CONF_SENDSPIN_ID,
|
||||
MEMORY_LOCATIONS,
|
||||
SendspinHub,
|
||||
_validate_task_stack_in_psram,
|
||||
register_player_config,
|
||||
request_controller_support,
|
||||
sendspin_ns,
|
||||
@@ -71,7 +70,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
).extend(
|
||||
{
|
||||
cv.GenerateID(CONF_SENDSPIN_ID): cv.use_id(SendspinHub),
|
||||
cv.Optional(CONF_TASK_STACK_IN_PSRAM): _validate_task_stack_in_psram,
|
||||
cv.Optional(CONF_TASK_STACK_IN_PSRAM): psram.validate_task_stack_in_psram,
|
||||
cv.Optional(CONF_BUFFER_SIZE, default=1000000): cv.int_range(min=25000),
|
||||
cv.Optional(CONF_INITIAL_STATIC_DELAY, default="0ms"): cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
|
||||
@@ -7,7 +7,6 @@ import esphome.codegen as cg
|
||||
from esphome.components import (
|
||||
audio,
|
||||
audio_file,
|
||||
esp32,
|
||||
media_player,
|
||||
network,
|
||||
ota,
|
||||
@@ -155,9 +154,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
# Remove before 2026.10.0
|
||||
cv.Optional(CONF_CODEC_SUPPORT_ENABLED): cv.Any(cv.boolean, cv.string),
|
||||
cv.Optional(CONF_FILES): audio_file.audio_files_schema(),
|
||||
cv.Optional(CONF_TASK_STACK_IN_PSRAM): cv.All(
|
||||
cv.boolean, cv.requires_component(psram.DOMAIN)
|
||||
),
|
||||
cv.Optional(CONF_TASK_STACK_IN_PSRAM): psram.validate_task_stack_in_psram,
|
||||
cv.Optional(CONF_VOLUME_INCREMENT, default=0.05): cv.percentage,
|
||||
cv.Optional(CONF_VOLUME_INITIAL, default=0.5): cv.percentage,
|
||||
cv.Optional(CONF_VOLUME_MAX, default=1.0): cv.percentage,
|
||||
@@ -198,9 +195,7 @@ async def to_code(config):
|
||||
|
||||
if config.get(CONF_TASK_STACK_IN_PSRAM):
|
||||
cg.add(var.set_task_stack_in_psram(True))
|
||||
esp32.add_idf_sdkconfig_option(
|
||||
"CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True
|
||||
)
|
||||
psram.request_external_task_stack()
|
||||
|
||||
cg.add(var.set_volume_increment(config[CONF_VOLUME_INCREMENT]))
|
||||
cg.add(var.set_volume_initial(config[CONF_VOLUME_INITIAL]))
|
||||
|
||||
11
tests/components/audio_file/validate.esp32-idf.yaml
Normal file
11
tests/components/audio_file/validate.esp32-idf.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
audio_file:
|
||||
- id: test_audio
|
||||
file:
|
||||
type: local
|
||||
path: $component_dir/test.wav
|
||||
|
||||
media_source:
|
||||
- platform: audio_file
|
||||
id: audio_file_source
|
||||
# task_stack_in_psram: false must validate without a psram: component
|
||||
task_stack_in_psram: false
|
||||
Reference in New Issue
Block a user