mirror of
https://github.com/esphome/esphome.git
synced 2026-10-06 19:06:37 +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
|
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
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID, CONF_TASK_STACK_IN_PSRAM
|
from esphome.const import CONF_ID, CONF_TASK_STACK_IN_PSRAM
|
||||||
from esphome.types import ConfigType
|
from esphome.types import ConfigType
|
||||||
@@ -21,19 +19,13 @@ def _request_micro_decoder(config: ConfigType) -> ConfigType:
|
|||||||
return config
|
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(
|
CONFIG_SCHEMA = cv.All(
|
||||||
media_source.media_source_schema(
|
media_source.media_source_schema(
|
||||||
AudioFileMediaSource,
|
AudioFileMediaSource,
|
||||||
)
|
)
|
||||||
.extend(
|
.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),
|
.extend(cv.COMPONENT_SCHEMA),
|
||||||
@@ -49,6 +41,4 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
|
|
||||||
if config.get(CONF_TASK_STACK_IN_PSRAM):
|
if config.get(CONF_TASK_STACK_IN_PSRAM):
|
||||||
cg.add(var.set_task_stack_in_psram(True))
|
cg.add(var.set_task_stack_in_psram(True))
|
||||||
esp32.add_idf_sdkconfig_option(
|
psram.request_external_task_stack()
|
||||||
"CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from typing import Any
|
|
||||||
|
|
||||||
import esphome.codegen as cg
|
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
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_BUFFER_SIZE, CONF_ID, CONF_TASK_STACK_IN_PSRAM
|
from esphome.const import CONF_BUFFER_SIZE, CONF_ID, CONF_TASK_STACK_IN_PSRAM
|
||||||
from esphome.types import ConfigType
|
from esphome.types import ConfigType
|
||||||
@@ -20,14 +18,6 @@ def _request_micro_decoder(config: ConfigType) -> ConfigType:
|
|||||||
return config
|
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(
|
CONFIG_SCHEMA = cv.All(
|
||||||
media_source.media_source_schema(
|
media_source.media_source_schema(
|
||||||
AudioHTTPMediaSource,
|
AudioHTTPMediaSource,
|
||||||
@@ -37,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_BUFFER_SIZE, default=50000): cv.int_range(
|
cv.Optional(CONF_BUFFER_SIZE, default=50000): cv.int_range(
|
||||||
min=5000, max=1000000
|
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),
|
.extend(cv.COMPONENT_SCHEMA),
|
||||||
@@ -53,7 +43,5 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
|
|
||||||
if config.get(CONF_TASK_STACK_IN_PSRAM):
|
if config.get(CONF_TASK_STACK_IN_PSRAM):
|
||||||
cg.add(var.set_task_stack_in_psram(True))
|
cg.add(var.set_task_stack_in_psram(True))
|
||||||
esp32.add_idf_sdkconfig_option(
|
psram.request_external_task_stack()
|
||||||
"CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True
|
|
||||||
)
|
|
||||||
cg.add(var.set_buffer_size(config[CONF_BUFFER_SIZE]))
|
cg.add(var.set_buffer_size(config[CONF_BUFFER_SIZE]))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from esphome import automation
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
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
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_BITS_PER_SAMPLE,
|
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_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_NUM_CHANNELS): cv.int_range(min=1, max=2),
|
||||||
cv.Optional(CONF_QUEUE_MODE, default=False): cv.boolean,
|
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]),
|
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_output_speaker(spkr))
|
||||||
cg.add(var.set_queue_mode(config[CONF_QUEUE_MODE]))
|
cg.add(var.set_queue_mode(config[CONF_QUEUE_MODE]))
|
||||||
|
|
||||||
if task_stack_in_psram := config.get(CONF_TASK_STACK_IN_PSRAM):
|
if config.get(CONF_TASK_STACK_IN_PSRAM):
|
||||||
cg.add(var.set_task_stack_in_psram(task_stack_in_psram))
|
cg.add(var.set_task_stack_in_psram(True))
|
||||||
if task_stack_in_psram and config[CONF_TASK_STACK_IN_PSRAM]:
|
psram.request_external_task_stack()
|
||||||
esp32.add_idf_sdkconfig_option(
|
|
||||||
"CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Initialize FixedVector with exact count of source speakers
|
# Initialize FixedVector with exact count of source speakers
|
||||||
cg.add(var.init_source_speakers(len(config[CONF_SOURCE_SPEAKERS])))
|
cg.add(var.init_source_speakers(len(config[CONF_SOURCE_SPEAKERS])))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import textwrap
|
import textwrap
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components.const import CONF_IGNORE_NOT_FOUND
|
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)
|
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):
|
def validate_psram_mode(config):
|
||||||
esp32_config = fv.full_config.get()[PLATFORM_ESP32]
|
esp32_config = fv.full_config.get()[PLATFORM_ESP32]
|
||||||
if config[CONF_SPEED] == "120MHZ":
|
if config[CONF_SPEED] == "120MHZ":
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import esphome.codegen as cg
|
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
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_BITS_PER_SAMPLE,
|
CONF_BITS_PER_SAMPLE,
|
||||||
@@ -63,7 +63,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_BUFFER_DURATION, default="100ms"
|
CONF_BUFFER_DURATION, default="100ms"
|
||||||
): cv.positive_time_period_milliseconds,
|
): 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_FILTERS, default=16): cv.int_range(min=2, max=1024),
|
||||||
cv.Optional(CONF_TAPS, default=16): _validate_taps,
|
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):
|
if config.get(CONF_TASK_STACK_IN_PSRAM):
|
||||||
cg.add(var.set_task_stack_in_psram(True))
|
cg.add(var.set_task_stack_in_psram(True))
|
||||||
esp32.add_idf_sdkconfig_option(
|
psram.request_external_task_stack()
|
||||||
"CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True
|
|
||||||
)
|
|
||||||
|
|
||||||
cg.add(var.set_target_bits_per_sample(config[CONF_BITS_PER_SAMPLE]))
|
cg.add(var.set_target_bits_per_sample(config[CONF_BITS_PER_SAMPLE]))
|
||||||
cg.add(var.set_target_sample_rate(config[CONF_SAMPLE_RATE]))
|
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
|
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:
|
def _request_high_performance_networking(config: ConfigType) -> ConfigType:
|
||||||
"""Request high performance networking for Sendspin streaming.
|
"""Request high performance networking for Sendspin streaming.
|
||||||
|
|
||||||
@@ -152,7 +145,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(SendspinHub),
|
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,
|
cv.only_on_esp32,
|
||||||
@@ -201,9 +194,7 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
|
|
||||||
if config.get(CONF_TASK_STACK_IN_PSRAM):
|
if config.get(CONF_TASK_STACK_IN_PSRAM):
|
||||||
cg.add(var.set_task_stack_in_psram(True))
|
cg.add(var.set_task_stack_in_psram(True))
|
||||||
esp32.add_idf_sdkconfig_option(
|
psram.request_external_task_stack()
|
||||||
"CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True
|
|
||||||
)
|
|
||||||
|
|
||||||
# sendspin-cpp library
|
# sendspin-cpp library
|
||||||
esp32.add_idf_component(name="sendspin/sendspin-cpp", ref="0.6.1")
|
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)
|
psram_stack = player_cfg.get(CONF_TASK_STACK_IN_PSRAM, False)
|
||||||
if psram_stack:
|
if psram_stack:
|
||||||
esp32.add_idf_sdkconfig_option(
|
psram.request_external_task_stack()
|
||||||
"CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Library defaults: priority 18 (one above httpd_priority 17 so the decoder is not
|
# 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),
|
# starved by the HTTP server during the initial encoded-audio burst at stream start),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from esphome import automation
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import media_source
|
from esphome.components import media_source, psram
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_BUFFER_SIZE,
|
CONF_BUFFER_SIZE,
|
||||||
@@ -19,7 +19,6 @@ from .. import (
|
|||||||
CONF_SENDSPIN_ID,
|
CONF_SENDSPIN_ID,
|
||||||
MEMORY_LOCATIONS,
|
MEMORY_LOCATIONS,
|
||||||
SendspinHub,
|
SendspinHub,
|
||||||
_validate_task_stack_in_psram,
|
|
||||||
register_player_config,
|
register_player_config,
|
||||||
request_controller_support,
|
request_controller_support,
|
||||||
sendspin_ns,
|
sendspin_ns,
|
||||||
@@ -71,7 +70,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
).extend(
|
).extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_SENDSPIN_ID): cv.use_id(SendspinHub),
|
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_BUFFER_SIZE, default=1000000): cv.int_range(min=25000),
|
||||||
cv.Optional(CONF_INITIAL_STATIC_DELAY, default="0ms"): cv.All(
|
cv.Optional(CONF_INITIAL_STATIC_DELAY, default="0ms"): cv.All(
|
||||||
cv.positive_time_period_milliseconds,
|
cv.positive_time_period_milliseconds,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import esphome.codegen as cg
|
|||||||
from esphome.components import (
|
from esphome.components import (
|
||||||
audio,
|
audio,
|
||||||
audio_file,
|
audio_file,
|
||||||
esp32,
|
|
||||||
media_player,
|
media_player,
|
||||||
network,
|
network,
|
||||||
ota,
|
ota,
|
||||||
@@ -155,9 +154,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
# Remove before 2026.10.0
|
# Remove before 2026.10.0
|
||||||
cv.Optional(CONF_CODEC_SUPPORT_ENABLED): cv.Any(cv.boolean, cv.string),
|
cv.Optional(CONF_CODEC_SUPPORT_ENABLED): cv.Any(cv.boolean, cv.string),
|
||||||
cv.Optional(CONF_FILES): audio_file.audio_files_schema(),
|
cv.Optional(CONF_FILES): audio_file.audio_files_schema(),
|
||||||
cv.Optional(CONF_TASK_STACK_IN_PSRAM): cv.All(
|
cv.Optional(CONF_TASK_STACK_IN_PSRAM): psram.validate_task_stack_in_psram,
|
||||||
cv.boolean, cv.requires_component(psram.DOMAIN)
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_VOLUME_INCREMENT, default=0.05): cv.percentage,
|
cv.Optional(CONF_VOLUME_INCREMENT, default=0.05): cv.percentage,
|
||||||
cv.Optional(CONF_VOLUME_INITIAL, default=0.5): cv.percentage,
|
cv.Optional(CONF_VOLUME_INITIAL, default=0.5): cv.percentage,
|
||||||
cv.Optional(CONF_VOLUME_MAX, default=1.0): 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):
|
if config.get(CONF_TASK_STACK_IN_PSRAM):
|
||||||
cg.add(var.set_task_stack_in_psram(True))
|
cg.add(var.set_task_stack_in_psram(True))
|
||||||
esp32.add_idf_sdkconfig_option(
|
psram.request_external_task_stack()
|
||||||
"CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True
|
|
||||||
)
|
|
||||||
|
|
||||||
cg.add(var.set_volume_increment(config[CONF_VOLUME_INCREMENT]))
|
cg.add(var.set_volume_increment(config[CONF_VOLUME_INCREMENT]))
|
||||||
cg.add(var.set_volume_initial(config[CONF_VOLUME_INITIAL]))
|
cg.add(var.set_volume_initial(config[CONF_VOLUME_INITIAL]))
|
||||||
|
|||||||
@@ -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