Merge remote-tracking branch 'upstream/dev' into esp32-sram1-as-iram

# Conflicts:
#	esphome/components/esp32/__init__.py
This commit is contained in:
J. Nick Koston
2026-03-19 10:00:37 -10:00
337 changed files with 9059 additions and 4508 deletions
+56 -4
View File
@@ -14,11 +14,63 @@ include the relevant `.cpp` and `.h` test files there.
### Override component code generation for testing
When generating code for testing, ESPHome won't invoke the component's `to_code` function, since most components do not
need to generate configuration code for testing.
During C++ test builds, `to_code` is suppressed for every component by default — most components do not
need to generate configuration code for a unit test binary.
If you do need to generate code to for example configure compilation flags or add libraries,
add the component name to the `CPP_TESTING_CODEGEN_COMPONENTS` allowlist in `script/cpp_unit_test.py`.
#### Manifest overrides
If your component needs to customise code generation behavior for testing — for example to re-enable
`to_code`, supply a lightweight stub, add a test-only dependency, or change any other manifest attribute —
create an `__init__.py` in your component's test directory and define `override_manifest`:
**Top-level component** (`tests/components/<component>/__init__.py`):
```python
from tests.testing_helpers import ComponentManifestOverride
def override_manifest(manifest: ComponentManifestOverride) -> None:
# Re-enable the component's own to_code (needed when the component must
# emit C++ setup code that the test binary depends on at link time).
manifest.enable_codegen()
```
Or supply a lightweight stub instead of the real `to_code`:
```python
from tests.testing_helpers import ComponentManifestOverride
def override_manifest(manifest: ComponentManifestOverride) -> None:
async def to_code_testing(config):
# Only emit what the C++ tests actually need
pass
manifest.to_code = to_code_testing
manifest.dependencies = manifest.dependencies + ["some_test_only_dep"]
```
**Platform component** (`tests/components/<component>/<domain>/__init__.py`,
e.g. `tests/components/my_sensor/sensor/__init__.py`):
```python
from tests.testing_helpers import ComponentManifestOverride
def override_manifest(manifest: ComponentManifestOverride) -> None:
manifest.enable_codegen()
```
`override_manifest` receives a `ComponentManifestOverride` that wraps the real manifest.
Attribute assignments store an override; reads fall back to the real manifest when no
override is present.
Key methods:
| Method | Effect |
|---|---|
| `manifest.enable_codegen()` | Remove the `to_code` suppression, re-enabling code generation |
| `manifest.restore()` | Clear **all** overrides, reverting every attribute to the original |
The function is called after `to_code` has already been set to `None`, so calling
`enable_codegen()` is a deliberate opt-in.
## Running component unit tests
+8
View File
@@ -0,0 +1,8 @@
from tests.testing_helpers import ComponentManifestOverride
def override_manifest(manifest: ComponentManifestOverride) -> None:
# core (esphome/core/config.py) must run its to_code during C++ test builds
# because it bootstraps the fundamental application infrastructure that all
# components depend on (component registration, event loop, etc.).
manifest.enable_codegen()
@@ -1,5 +1,10 @@
esp32:
variant: esp32s3
partitions:
- name: my_data
type: data
subtype: spiffs
size: 0x1000
framework:
type: esp-idf
advanced:
+7
View File
@@ -0,0 +1,7 @@
from tests.testing_helpers import ComponentManifestOverride
def override_manifest(manifest: ComponentManifestOverride) -> None:
# host must run its to_code during C++ test builds because it sets up the
# host platform target, which is the execution environment for all unit tests.
manifest.enable_codegen()
@@ -1,16 +0,0 @@
substitutions:
i2s_bclk_pin: GPIO15
i2s_lrclk_pin: GPIO4
i2s_mclk_pin: GPIO5
<<: !include common.yaml
wifi:
ssid: test
password: test1234
media_player:
- platform: i2s_audio
name: Test Media Player
dac_type: internal
mode: stereo
+7
View File
@@ -0,0 +1,7 @@
from tests.testing_helpers import ComponentManifestOverride
def override_manifest(manifest: ComponentManifestOverride) -> None:
# logger must run its to_code during C++ test builds because it configures
# the logging subsystem used by ESP_LOG* macros throughout component code.
manifest.enable_codegen()
+57 -18
View File
@@ -42,6 +42,11 @@ lvgl:
disp_bg_color: color_id
disp_bg_image: cat_image
disp_bg_opa: cover
bottom_layer:
widgets:
- obj:
bg_color: 0x000000
bg_opa: cover
theme:
obj:
border_width: 1
@@ -49,12 +54,14 @@ lvgl:
gradients:
- id: color_bar
direction: hor
dither: err_diff
# dither: err_diff
stops:
- color: 0xFF0000
position: 0
opa: 100%
- color: 0xFFFF00
position: 42
opa: 80%
- color: 0x00FF00
position: 84
- color: 0x00FFFF
@@ -156,6 +163,16 @@ lvgl:
offset_x: !lambda return 20;
offset_y: !lambda return 20;
antialias: !lambda return true;
- id: msgbox_with_header_buttons
title: Header Buttons Test
body:
text: Testing header buttons
header_buttons:
- src: cat_image
on_click:
logger.log: Header button clicked
buttons:
- text: OK
- id: simple_msgbox
title: Simple
@@ -199,11 +216,11 @@ lvgl:
text: Unloaded
- lvgl.label.update:
id: msgbox_label
text: "" # Empty text
text: "" # Empty text
on_all_events:
logger.log:
format: "Event %s"
args: ['lv_event_code_name_for(event->code).c_str()']
args: ['lv_event_code_name_for(event).c_str()']
skip: true
layout:
type: Flex
@@ -241,7 +258,7 @@ lvgl:
on_all_events:
- logger.log:
format: "Event %s"
args: ['lv_event_code_name_for(event->code).c_str()']
args: ['lv_event_code_name_for(event).c_str()']
- lvgl.animimg.update:
id: anim_img
src: !lambda "return {dog_image, cat_image};"
@@ -306,7 +323,6 @@ lvgl:
anim_time: 1s
bg_color: light_blue
bg_grad_color: light_blue
bg_dither_mode: ordered
bg_grad_dir: hor
bg_grad_stop: 128
bg_image_opa: transp
@@ -354,10 +370,18 @@ lvgl:
text_line_space: 4
text_opa: cover
transform_angle: 180
transform_rotation: 90
transform_height: 100
transform_pivot_x: 50%
transform_pivot_y: 50%
transform_zoom: 0.5
transform_scale: 2.0
transform_scale_x: 1.5
transform_scale_y: 0.8
transform_skew_x: 10
transform_skew_y: 20
shadow_offset_x: 3
shadow_offset_y: 3
translate_x: 10
translate_y: 10
max_height: 100
@@ -549,14 +573,16 @@ lvgl:
arc_length: 120
spin_time: 2s
align: left_mid
- spinner:
align: right_mid
send_draw_task_events: true
- image:
id: lv_image
src: cat_image
align: top_left
y: "50"
mode: real
zoom: 2.0
angle: 45
scale: 2.0
rotation: 45
- tileview:
id: tileview_id
scrollbar_mode: active
@@ -661,8 +687,11 @@ lvgl:
src: cat_image
x: 100
y: 100
angle: 90
zoom: 2.0
rotation: 90
scale_x: 2.0
scale_y: 1.5
skew_x: 10
skew_y: 5
pivot_x: 25
pivot_y: 25
- lvgl.canvas.draw_line:
@@ -710,6 +739,9 @@ lvgl:
text:
format: "A string with a number %d"
args: ['(int)(random_uint32() % 1000)']
size: 120
dark_color: navy
light_color: white
- slider:
min_value: 0
@@ -928,7 +960,6 @@ lvgl:
grid_cell_row_pos: 0
grid_cell_column_pos: 0
src: !lambda return dog_image;
mode: virtual
on_click:
then:
- lvgl.tabview.select:
@@ -1023,10 +1054,18 @@ lvgl:
text_color: 0xFFFFFF
scales:
- ticks:
width: !lambda return 1;
width: 1
count: 61
length: 20%
radial_offset: 5
color: 0xFFFFFF
major:
stride: 5
width: 2
length: 8
color: 0xC0C0C0
radial_offset: 3
label_gap: 6
range_from: 0
range_to: 60
angle_range: 360
@@ -1037,15 +1076,15 @@ lvgl:
end_value: 60
color_start: 0x0000bd
color_end: 0xbd0000
width: !lambda return 1;
width: 1
- line:
opa: 50%
id: minute_hand
color: 0xFF0000
r_mod: !lambda return -1;
width: !lambda return 3;
-
angle_range: 330
length: 99%
radial_offset: 2
width: 1
- angle_range: 330
rotation: 300
range_from: 1
range_to: 12
@@ -1069,7 +1108,7 @@ lvgl:
value: 180
width: 4
color: 0xA0A0A0
r_mod: -20
length: 80%
opa: 0%
- id: page3
layout: Horizontal
+8 -8
View File
@@ -1,18 +1,18 @@
wifi:
ssid: MySSID
password: password1
i2s_audio:
i2s_lrclk_pin: 13
i2s_bclk_pin: 14
i2s_mclk_pin: 15
media_player:
speaker:
- platform: i2s_audio
name: None
dac_type: external
id: test_speaker
i2s_dout_pin: 18
mute_pin: 19
dac_type: external
media_player:
- platform: speaker
name: None
speaker: test_speaker
on_state:
- media_player.play:
- media_player.play_media: http://localhost/media.mp3
+9 -1
View File
@@ -6,7 +6,7 @@ i2s_audio:
microphone:
- platform: i2s_audio
id: mic_id_external
i2s_din_pin: ${i2s_din_pin}
i2s_din_pin: ${i2s_din_pin1}
adc_type: external
pdm: false
mclk_multiple: 384
@@ -15,7 +15,15 @@ microphone:
- if:
condition:
- microphone.is_muted:
id: mic_id_external
then:
- microphone.unmute:
id: mic_id_external
else:
- microphone.mute:
id: mic_id_external
- platform: i2s_audio
id: mic_id_pdm
i2s_din_pin: ${i2s_din_pin2}
adc_type: external
pdm: true
@@ -2,21 +2,7 @@ substitutions:
i2s_bclk_pin: GPIO15
i2s_lrclk_pin: GPIO4
i2s_mclk_pin: GPIO5
i2s_din_pin: GPIO33
i2s_din_pin1: GPIO33
i2s_din_pin2: GPIO34
i2s_audio:
i2s_bclk_pin: ${i2s_bclk_pin}
i2s_lrclk_pin: ${i2s_lrclk_pin}
i2s_mclk_pin: ${i2s_mclk_pin}
use_legacy: true
microphone:
- platform: i2s_audio
id: mic_id_external
i2s_din_pin: ${i2s_din_pin}
adc_type: external
pdm: false
- platform: i2s_audio
id: mic_id_adc
adc_pin: 32
adc_type: internal
<<: !include common.yaml
@@ -8,6 +8,11 @@ on_beo4:
- logger.log:
format: "on_beo4: %u %u"
args: ["x.source", "x.command"]
on_brennenstuhl:
then:
- logger.log:
format: "on_brennenstuhl: %u"
args: ["x.code"]
on_aeha:
then:
- logger.log:
@@ -14,6 +14,12 @@ button:
remote_transmitter.transmit_beo4:
source: 0x01
command: 0x0C
- platform: template
name: brennenstuhl
id: button_a_on
on_press:
remote_transmitter.transmit_brennenstuhl:
code: 0xBD2E2C
- platform: template
name: Dyson fan up
id: dyson_fan_up
+9
View File
@@ -0,0 +1,9 @@
import esphome.codegen as cg
from tests.testing_helpers import ComponentManifestOverride
def override_manifest(manifest: ComponentManifestOverride) -> None:
async def to_code(config):
cg.add_build_flag("-DUSE_TIME_TIMEZONE")
manifest.to_code = to_code