mirror of
https://github.com/esphome/esphome.git
synced 2025-10-16 15:37:51 +02:00
[ci] Merge components with different buses to reduce CI time (#11251)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
f2e0a412db
commit
14d76e9e4e
62
.github/workflows/ci.yml
vendored
62
.github/workflows/ci.yml
vendored
@ -379,7 +379,16 @@ jobs:
|
||||
|
||||
# Use intelligent splitter that groups components with same bus configs
|
||||
components='${{ needs.determine-jobs.outputs.changed-components-with-tests }}'
|
||||
directly_changed='${{ needs.determine-jobs.outputs.directly-changed-components-with-tests }}'
|
||||
|
||||
# Only isolate directly changed components when targeting dev branch
|
||||
# For beta/release branches, group everything for faster CI
|
||||
if [[ "${{ github.base_ref }}" == beta* ]] || [[ "${{ github.base_ref }}" == release* ]]; then
|
||||
directly_changed='[]'
|
||||
echo "Target branch: ${{ github.base_ref }} - grouping all components"
|
||||
else
|
||||
directly_changed='${{ needs.determine-jobs.outputs.directly-changed-components-with-tests }}'
|
||||
echo "Target branch: ${{ github.base_ref }} - isolating directly changed components"
|
||||
fi
|
||||
|
||||
echo "Splitting components intelligently..."
|
||||
output=$(python3 script/split_components_for_ci.py --components "$components" --directly-changed "$directly_changed" --batch-size 40 --output github)
|
||||
@ -396,7 +405,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: ${{ (github.base_ref == 'beta' || github.base_ref == 'release') && 8 || 4 }}
|
||||
max-parallel: ${{ (startsWith(github.base_ref, 'beta') || startsWith(github.base_ref, 'release')) && 8 || 4 }}
|
||||
matrix:
|
||||
components: ${{ fromJson(needs.test-build-components-splitter.outputs.matrix) }}
|
||||
steps:
|
||||
@ -424,18 +433,31 @@ jobs:
|
||||
- name: Validate and compile components with intelligent grouping
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
# Use /mnt for build files (70GB available vs ~29GB on /)
|
||||
# Bind mount PlatformIO directory to /mnt (tools, packages, build cache all go there)
|
||||
sudo mkdir -p /mnt/platformio
|
||||
sudo chown $USER:$USER /mnt/platformio
|
||||
mkdir -p ~/.platformio
|
||||
sudo mount --bind /mnt/platformio ~/.platformio
|
||||
|
||||
# Bind mount test build directory to /mnt
|
||||
sudo mkdir -p /mnt/test_build_components_build
|
||||
sudo chown $USER:$USER /mnt/test_build_components_build
|
||||
mkdir -p tests/test_build_components/build
|
||||
sudo mount --bind /mnt/test_build_components_build tests/test_build_components/build
|
||||
# Check if /mnt has more free space than / before bind mounting
|
||||
# Extract available space in KB for comparison
|
||||
root_avail=$(df -k / | awk 'NR==2 {print $4}')
|
||||
mnt_avail=$(df -k /mnt 2>/dev/null | awk 'NR==2 {print $4}')
|
||||
|
||||
echo "Available space: / has ${root_avail}KB, /mnt has ${mnt_avail}KB"
|
||||
|
||||
# Only use /mnt if it has more space than /
|
||||
if [ -n "$mnt_avail" ] && [ "$mnt_avail" -gt "$root_avail" ]; then
|
||||
echo "Using /mnt for build files (more space available)"
|
||||
# Bind mount PlatformIO directory to /mnt (tools, packages, build cache all go there)
|
||||
sudo mkdir -p /mnt/platformio
|
||||
sudo chown $USER:$USER /mnt/platformio
|
||||
mkdir -p ~/.platformio
|
||||
sudo mount --bind /mnt/platformio ~/.platformio
|
||||
|
||||
# Bind mount test build directory to /mnt
|
||||
sudo mkdir -p /mnt/test_build_components_build
|
||||
sudo chown $USER:$USER /mnt/test_build_components_build
|
||||
mkdir -p tests/test_build_components/build
|
||||
sudo mount --bind /mnt/test_build_components_build tests/test_build_components/build
|
||||
else
|
||||
echo "Using / for build files (more space available than /mnt or /mnt unavailable)"
|
||||
fi
|
||||
|
||||
# Convert space-separated components to comma-separated for Python script
|
||||
components_csv=$(echo "${{ matrix.components }}" | tr ' ' ',')
|
||||
@ -448,7 +470,7 @@ jobs:
|
||||
# - This catches pin conflicts and other issues in directly changed code
|
||||
# - Grouped tests use --testing-mode to allow config merging (disables some checks)
|
||||
# - Dependencies are safe to group since they weren't modified in this PR
|
||||
if [ "${{ github.base_ref }}" = "beta" ] || [ "${{ github.base_ref }}" = "release" ]; then
|
||||
if [[ "${{ github.base_ref }}" == beta* ]] || [[ "${{ github.base_ref }}" == release* ]]; then
|
||||
directly_changed_csv=""
|
||||
echo "Testing components: $components_csv"
|
||||
echo "Target branch: ${{ github.base_ref }} - grouping all components"
|
||||
@ -459,6 +481,11 @@ jobs:
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Show disk space before validation (after bind mounts setup)
|
||||
echo "Disk space before config validation:"
|
||||
df -h
|
||||
echo ""
|
||||
|
||||
# Run config validation with grouping and isolation
|
||||
python3 script/test_build_components.py -e config -c "$components_csv" -f --isolate "$directly_changed_csv"
|
||||
|
||||
@ -466,6 +493,11 @@ jobs:
|
||||
echo "Config validation passed! Starting compilation..."
|
||||
echo ""
|
||||
|
||||
# Show disk space before compilation
|
||||
echo "Disk space before compilation:"
|
||||
df -h
|
||||
echo ""
|
||||
|
||||
# Run compilation with grouping and isolation
|
||||
python3 script/test_build_components.py -e compile -c "$components_csv" -f --isolate "$directly_changed_csv"
|
||||
|
||||
@ -474,7 +506,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- common
|
||||
if: github.event_name == 'pull_request' && github.base_ref != 'beta' && github.base_ref != 'release'
|
||||
if: github.event_name == 'pull_request' && !startsWith(github.base_ref, 'beta') && !startsWith(github.base_ref, 'release')
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
@ -190,7 +190,7 @@ async def to_code(config):
|
||||
cg.add_define("ESPHOME_VARIANT", "ESP8266")
|
||||
cg.add_define(ThreadModel.SINGLE)
|
||||
|
||||
cg.add_platformio_option("extra_scripts", ["post:post_build.py"])
|
||||
cg.add_platformio_option("extra_scripts", ["pre:iram_fix.py", "post:post_build.py"])
|
||||
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
@ -230,6 +230,12 @@ async def to_code(config):
|
||||
# For cases where nullptrs can be handled, use nothrow: `new (std::nothrow) T;`
|
||||
cg.add_build_flag("-DNEW_OOM_ABORT")
|
||||
|
||||
# In testing mode, fake a larger IRAM to allow linking grouped component tests
|
||||
# Real ESP8266 hardware only has 32KB IRAM, but for CI testing we pretend it has 2MB
|
||||
# This is done via a pre-build script that generates a custom linker script
|
||||
if CORE.testing_mode:
|
||||
cg.add_build_flag("-DESPHOME_TESTING_MODE")
|
||||
|
||||
cg.add_platformio_option("board_build.flash_mode", config[CONF_BOARD_FLASH_MODE])
|
||||
|
||||
ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||
@ -265,3 +271,8 @@ def copy_files():
|
||||
post_build_file,
|
||||
CORE.relative_build_path("post_build.py"),
|
||||
)
|
||||
iram_fix_file = dir / "iram_fix.py.script"
|
||||
copy_file_if_changed(
|
||||
iram_fix_file,
|
||||
CORE.relative_build_path("iram_fix.py"),
|
||||
)
|
||||
|
44
esphome/components/esp8266/iram_fix.py.script
Normal file
44
esphome/components/esp8266/iram_fix.py.script
Normal file
@ -0,0 +1,44 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
# pylint: disable=E0602
|
||||
Import("env") # noqa
|
||||
|
||||
|
||||
def patch_linker_script_after_preprocess(source, target, env):
|
||||
"""Patch the local linker script after PlatformIO preprocesses it."""
|
||||
# Check if we're in testing mode by looking for the define
|
||||
build_flags = env.get("BUILD_FLAGS", [])
|
||||
testing_mode = any("-DESPHOME_TESTING_MODE" in flag for flag in build_flags)
|
||||
|
||||
if not testing_mode:
|
||||
return
|
||||
|
||||
# Get the local linker script path
|
||||
build_dir = env.subst("$BUILD_DIR")
|
||||
local_ld = os.path.join(build_dir, "ld", "local.eagle.app.v6.common.ld")
|
||||
|
||||
if not os.path.exists(local_ld):
|
||||
return
|
||||
|
||||
# Read the linker script
|
||||
with open(local_ld, "r") as f:
|
||||
content = f.read()
|
||||
|
||||
# Replace IRAM size from 0x8000 (32KB) to 0x200000 (2MB)
|
||||
# The line looks like: iram1_0_seg : org = 0x40100000, len = 0x8000
|
||||
updated = re.sub(
|
||||
r"(iram1_0_seg\s*:\s*org\s*=\s*0x40100000\s*,\s*len\s*=\s*)0x8000",
|
||||
r"\g<1>0x200000",
|
||||
content,
|
||||
)
|
||||
|
||||
if updated != content:
|
||||
with open(local_ld, "w") as f:
|
||||
f.write(updated)
|
||||
print("ESPHome: Patched IRAM size to 2MB for testing mode")
|
||||
|
||||
|
||||
# Hook into the build process right before linking
|
||||
# This runs after PlatformIO has already preprocessed the linker scripts
|
||||
env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", patch_linker_script_after_preprocess)
|
@ -56,6 +56,10 @@ DIRECT_BUS_TYPES = ("i2c", "spi", "uart", "modbus")
|
||||
# These components can be merged with any other group
|
||||
NO_BUSES_SIGNATURE = "no_buses"
|
||||
|
||||
# Prefix for isolated component signatures
|
||||
# Isolated components have unique signatures and cannot be merged with others
|
||||
ISOLATED_SIGNATURE_PREFIX = "isolated_"
|
||||
|
||||
# Base bus components - these ARE the bus implementations and should not
|
||||
# be flagged as needing migration since they are the platform/base components
|
||||
BASE_BUS_COMPONENTS = {
|
||||
@ -75,6 +79,7 @@ ISOLATED_COMPONENTS = {
|
||||
"ethernet": "Defines ethernet: which conflicts with wifi: used by most components",
|
||||
"ethernet_info": "Related to ethernet component which conflicts with wifi",
|
||||
"lvgl": "Defines multiple SDL displays on host platform that conflict when merged with other display configs",
|
||||
"mapping": "Uses dict format for image/display sections incompatible with standard list format - ESPHome merge_config cannot handle",
|
||||
"openthread": "Conflicts with wifi: used by most components",
|
||||
"openthread_info": "Conflicts with wifi: used by most components",
|
||||
"matrix_keypad": "Needs isolation due to keypad",
|
||||
@ -368,6 +373,143 @@ def analyze_all_components(
|
||||
return components, non_groupable, direct_bus_components
|
||||
|
||||
|
||||
@lru_cache(maxsize=256)
|
||||
def _get_bus_configs(buses: tuple[str, ...]) -> frozenset[tuple[str, str]]:
|
||||
"""Map bus type to set of configs for that type.
|
||||
|
||||
Args:
|
||||
buses: Tuple of bus package names (e.g., ("uart_9600", "i2c"))
|
||||
|
||||
Returns:
|
||||
Frozenset of (base_type, full_config) tuples
|
||||
Example: frozenset({("uart", "uart_9600"), ("i2c", "i2c")})
|
||||
"""
|
||||
# Split on underscore to get base type: "uart_9600" -> "uart", "i2c" -> "i2c"
|
||||
return frozenset((bus.split("_", 1)[0], bus) for bus in buses)
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def are_buses_compatible(buses1: tuple[str, ...], buses2: tuple[str, ...]) -> bool:
|
||||
"""Check if two bus tuples are compatible for merging.
|
||||
|
||||
Two bus lists are compatible if they don't have conflicting configurations
|
||||
for the same bus type. For example:
|
||||
- ("ble", "uart") and ("i2c",) are compatible (different buses)
|
||||
- ("uart_9600",) and ("uart_19200",) are NOT compatible (same bus, different configs)
|
||||
- ("uart_9600",) and ("uart_9600",) are compatible (same bus, same config)
|
||||
|
||||
Args:
|
||||
buses1: First tuple of bus package names
|
||||
buses2: Second tuple of bus package names
|
||||
|
||||
Returns:
|
||||
True if buses can be merged without conflicts
|
||||
"""
|
||||
configs1 = _get_bus_configs(buses1)
|
||||
configs2 = _get_bus_configs(buses2)
|
||||
|
||||
# Group configs by base type
|
||||
bus_types1: dict[str, set[str]] = {}
|
||||
for base_type, full_config in configs1:
|
||||
if base_type not in bus_types1:
|
||||
bus_types1[base_type] = set()
|
||||
bus_types1[base_type].add(full_config)
|
||||
|
||||
bus_types2: dict[str, set[str]] = {}
|
||||
for base_type, full_config in configs2:
|
||||
if base_type not in bus_types2:
|
||||
bus_types2[base_type] = set()
|
||||
bus_types2[base_type].add(full_config)
|
||||
|
||||
# Check for conflicts: same bus type with different configs
|
||||
for bus_type, configs in bus_types1.items():
|
||||
if bus_type not in bus_types2:
|
||||
continue # No conflict - different bus types
|
||||
# Same bus type - check if configs match
|
||||
if configs != bus_types2[bus_type]:
|
||||
return False # Conflict - same bus type, different configs
|
||||
|
||||
return True # No conflicts found
|
||||
|
||||
|
||||
def merge_compatible_bus_groups(
|
||||
grouped_components: dict[tuple[str, str], list[str]],
|
||||
) -> dict[tuple[str, str], list[str]]:
|
||||
"""Merge groups with compatible (non-conflicting) buses.
|
||||
|
||||
This function takes groups keyed by (platform, bus_signature) and merges
|
||||
groups that share the same platform and have compatible bus configurations.
|
||||
Two groups can be merged if their buses don't conflict - meaning they don't
|
||||
have different configurations for the same bus type.
|
||||
|
||||
For example:
|
||||
- ["ble"] + ["uart"] = compatible (different buses)
|
||||
- ["uart_9600"] + ["uart_19200"] = incompatible (same bus, different configs)
|
||||
- ["uart_9600"] + ["uart_9600"] = compatible (same bus, same config)
|
||||
|
||||
Args:
|
||||
grouped_components: Dictionary mapping (platform, signature) to list of component names
|
||||
|
||||
Returns:
|
||||
Dictionary with same structure but with compatible groups merged
|
||||
"""
|
||||
merged_groups: dict[tuple[str, str], list[str]] = {}
|
||||
processed_keys: set[tuple[str, str]] = set()
|
||||
|
||||
for (platform1, sig1), comps1 in sorted(grouped_components.items()):
|
||||
if (platform1, sig1) in processed_keys:
|
||||
continue
|
||||
|
||||
# Skip NO_BUSES_SIGNATURE - kept separate for flexible batch distribution
|
||||
# These components have no bus requirements and can be added to any batch
|
||||
# as "fillers" for load balancing across CI runners
|
||||
if sig1 == NO_BUSES_SIGNATURE:
|
||||
merged_groups[(platform1, sig1)] = comps1
|
||||
processed_keys.add((platform1, sig1))
|
||||
continue
|
||||
|
||||
# Skip isolated components - they can't be merged with others
|
||||
if sig1.startswith(ISOLATED_SIGNATURE_PREFIX):
|
||||
merged_groups[(platform1, sig1)] = comps1
|
||||
processed_keys.add((platform1, sig1))
|
||||
continue
|
||||
|
||||
# Start with this group's components
|
||||
merged_comps: list[str] = list(comps1)
|
||||
merged_sig: str = sig1
|
||||
processed_keys.add((platform1, sig1))
|
||||
|
||||
# Get buses for this group as tuple for caching
|
||||
buses1: tuple[str, ...] = tuple(sorted(sig1.split("+")))
|
||||
|
||||
# Try to merge with other groups on same platform
|
||||
for (platform2, sig2), comps2 in sorted(grouped_components.items()):
|
||||
if (platform2, sig2) in processed_keys:
|
||||
continue
|
||||
if platform2 != platform1:
|
||||
continue # Different platforms can't be merged
|
||||
if sig2 == NO_BUSES_SIGNATURE:
|
||||
continue # Keep separate for flexible batch distribution
|
||||
if sig2.startswith(ISOLATED_SIGNATURE_PREFIX):
|
||||
continue # Isolated components can't be merged
|
||||
|
||||
# Check if buses are compatible
|
||||
buses2: tuple[str, ...] = tuple(sorted(sig2.split("+")))
|
||||
if are_buses_compatible(buses1, buses2):
|
||||
# Compatible! Merge this group
|
||||
merged_comps.extend(comps2)
|
||||
processed_keys.add((platform2, sig2))
|
||||
# Update merged signature to include all unique buses
|
||||
all_buses: set[str] = set(buses1) | set(buses2)
|
||||
merged_sig = "+".join(sorted(all_buses))
|
||||
buses1 = tuple(sorted(all_buses)) # Update for next iteration
|
||||
|
||||
# Store merged group
|
||||
merged_groups[(platform1, merged_sig)] = merged_comps
|
||||
|
||||
return merged_groups
|
||||
|
||||
|
||||
def create_grouping_signature(
|
||||
platform_buses: dict[str, list[str]], platform: str
|
||||
) -> str:
|
||||
|
@ -185,17 +185,20 @@ def main():
|
||||
"-c",
|
||||
"--changed",
|
||||
action="store_true",
|
||||
help="List all components required for testing based on changes (includes dependencies)",
|
||||
help="List all components with dependencies (used by clang-tidy). "
|
||||
"When base test infrastructure changes, returns ALL components.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--changed-direct",
|
||||
action="store_true",
|
||||
help="List only directly changed components (without dependencies)",
|
||||
help="List only directly changed components, ignoring infrastructure changes "
|
||||
"(used by CI for isolation decisions)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--changed-with-deps",
|
||||
action="store_true",
|
||||
help="Output JSON with both directly changed and all changed components",
|
||||
help="Output JSON with both directly changed and all changed components "
|
||||
"(with dependencies), ignoring infrastructure changes (used by CI for test determination)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-b", "--branch", help="Branch to compare changed files against"
|
||||
@ -213,12 +216,34 @@ def main():
|
||||
# When --changed* is passed, only get the changed files
|
||||
changed = changed_files(args.branch)
|
||||
|
||||
# If any base test file(s) changed, there's no need to filter out components
|
||||
if any("tests/test_build_components" in file for file in changed):
|
||||
# Need to get all component files
|
||||
# If any base test file(s) changed, we need to check all components
|
||||
# BUT only for --changed (used by clang-tidy for comprehensive checking)
|
||||
# NOT for --changed-direct or --changed-with-deps (used by CI for targeted testing)
|
||||
#
|
||||
# Flag usage:
|
||||
# - --changed: Used by clang-tidy (script/helpers.py get_changed_components)
|
||||
# Returns: All components with dependencies when base test files change
|
||||
# Reason: Test infrastructure changes may affect any component
|
||||
#
|
||||
# - --changed-direct: Used by CI isolation (script/determine-jobs.py)
|
||||
# Returns: Only components with actual code changes (not infrastructure)
|
||||
# Reason: Only directly changed components need isolated testing
|
||||
#
|
||||
# - --changed-with-deps: Used by CI test determination (script/determine-jobs.py)
|
||||
# Returns: Components with code changes + their dependencies (not infrastructure)
|
||||
# Reason: CI needs to test changed components and their dependents
|
||||
base_test_changed = any(
|
||||
"tests/test_build_components" in file for file in changed
|
||||
)
|
||||
|
||||
if base_test_changed and not args.changed_direct and not args.changed_with_deps:
|
||||
# Base test infrastructure changed - load all component files
|
||||
# This is for --changed (clang-tidy) which needs comprehensive checking
|
||||
files = get_all_component_files()
|
||||
else:
|
||||
# Only look at changed component files
|
||||
# Only look at changed component files (ignore infrastructure changes)
|
||||
# For --changed-direct: only actual component code changes matter (for isolation)
|
||||
# For --changed-with-deps: only actual component code changes matter (for testing)
|
||||
files = [f for f in changed if filter_component_files(f)]
|
||||
else:
|
||||
# Get all component files
|
||||
|
@ -16,6 +16,7 @@ The merger handles:
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
@ -28,6 +29,10 @@ from esphome import yaml_util
|
||||
from esphome.config_helpers import merge_config
|
||||
from script.analyze_component_buses import PACKAGE_DEPENDENCIES, get_common_bus_packages
|
||||
|
||||
# Prefix for dependency markers in package tracking
|
||||
# Used to mark packages that are included transitively (e.g., uart via modbus)
|
||||
DEPENDENCY_MARKER_PREFIX = "_dep_"
|
||||
|
||||
|
||||
def load_yaml_file(yaml_file: Path) -> dict:
|
||||
"""Load YAML file using ESPHome's YAML loader.
|
||||
@ -44,6 +49,34 @@ def load_yaml_file(yaml_file: Path) -> dict:
|
||||
return yaml_util.load_yaml(yaml_file)
|
||||
|
||||
|
||||
@lru_cache(maxsize=256)
|
||||
def get_component_packages(
|
||||
component_name: str, platform: str, tests_dir_str: str
|
||||
) -> dict:
|
||||
"""Get packages dict from a component's test file with caching.
|
||||
|
||||
This function is cached to avoid re-loading and re-parsing the same file
|
||||
multiple times when extracting packages during cross-bus merging.
|
||||
|
||||
Args:
|
||||
component_name: Name of the component
|
||||
platform: Platform name (e.g., "esp32-idf")
|
||||
tests_dir_str: String path to tests/components directory (must be string for cache hashability)
|
||||
|
||||
Returns:
|
||||
Dictionary with 'packages' key containing the raw packages dict from the YAML,
|
||||
or empty dict if no packages section exists
|
||||
"""
|
||||
tests_dir = Path(tests_dir_str)
|
||||
test_file = tests_dir / component_name / f"test.{platform}.yaml"
|
||||
comp_data = load_yaml_file(test_file)
|
||||
|
||||
if "packages" not in comp_data or not isinstance(comp_data["packages"], dict):
|
||||
return {}
|
||||
|
||||
return comp_data["packages"]
|
||||
|
||||
|
||||
def extract_packages_from_yaml(data: dict) -> dict[str, str]:
|
||||
"""Extract COMMON BUS package includes from parsed YAML.
|
||||
|
||||
@ -82,7 +115,7 @@ def extract_packages_from_yaml(data: dict) -> dict[str, str]:
|
||||
if dep not in common_bus_packages:
|
||||
continue
|
||||
# Mark as included via dependency
|
||||
packages[f"_dep_{dep}"] = f"(included via {name})"
|
||||
packages[f"{DEPENDENCY_MARKER_PREFIX}{dep}"] = f"(included via {name})"
|
||||
|
||||
return packages
|
||||
|
||||
@ -195,6 +228,9 @@ def merge_component_configs(
|
||||
# Start with empty config
|
||||
merged_config_data = {}
|
||||
|
||||
# Convert tests_dir to string for caching
|
||||
tests_dir_str = str(tests_dir)
|
||||
|
||||
# Process each component
|
||||
for comp_name in component_names:
|
||||
comp_dir = tests_dir / comp_name
|
||||
@ -206,26 +242,29 @@ def merge_component_configs(
|
||||
# Load the component's test file
|
||||
comp_data = load_yaml_file(test_file)
|
||||
|
||||
# Validate packages are compatible
|
||||
# Components with no packages (no_buses) can merge with any group
|
||||
# Merge packages from all components (cross-bus merging)
|
||||
# Components can have different packages (e.g., one with ble, another with uart)
|
||||
# as long as they don't conflict (checked by are_buses_compatible before calling this)
|
||||
comp_packages = extract_packages_from_yaml(comp_data)
|
||||
|
||||
if all_packages is None:
|
||||
# First component - set the baseline
|
||||
all_packages = comp_packages
|
||||
elif not comp_packages:
|
||||
# This component has no packages (no_buses) - it can merge with any group
|
||||
pass
|
||||
elif not all_packages:
|
||||
# Previous components had no packages, but this one does - adopt these packages
|
||||
all_packages = comp_packages
|
||||
elif comp_packages != all_packages:
|
||||
# Both have packages but they differ - this is an error
|
||||
raise ValueError(
|
||||
f"Component {comp_name} has different packages than previous components. "
|
||||
f"Expected: {all_packages}, Got: {comp_packages}. "
|
||||
f"All components must use the same common bus configs to be merged."
|
||||
)
|
||||
# First component - initialize package dict
|
||||
all_packages = comp_packages if comp_packages else {}
|
||||
elif comp_packages:
|
||||
# Merge packages - combine all unique package types
|
||||
# If both have the same package type, verify they're identical
|
||||
for pkg_name, pkg_config in comp_packages.items():
|
||||
if pkg_name in all_packages:
|
||||
# Same package type - verify config matches
|
||||
if all_packages[pkg_name] != pkg_config:
|
||||
raise ValueError(
|
||||
f"Component {comp_name} has conflicting config for package '{pkg_name}'. "
|
||||
f"Expected: {all_packages[pkg_name]}, Got: {pkg_config}. "
|
||||
f"Components with conflicting bus configs cannot be merged."
|
||||
)
|
||||
else:
|
||||
# New package type - add it
|
||||
all_packages[pkg_name] = pkg_config
|
||||
|
||||
# Handle $component_dir by replacing with absolute path
|
||||
# This allows components that use local file references to be grouped
|
||||
@ -287,26 +326,51 @@ def merge_component_configs(
|
||||
# merge_config handles list merging with ID-based deduplication automatically
|
||||
merged_config_data = merge_config(merged_config_data, comp_data)
|
||||
|
||||
# Add packages back (only once, since they're identical)
|
||||
# IMPORTANT: Only re-add common bus packages (spi, i2c, uart, etc.)
|
||||
# Add merged packages back (union of all component packages)
|
||||
# IMPORTANT: Only include common bus packages (spi, i2c, uart, etc.)
|
||||
# Do NOT re-add component-specific packages as they contain unprefixed $component_dir refs
|
||||
if all_packages:
|
||||
first_comp_data = load_yaml_file(
|
||||
tests_dir / component_names[0] / f"test.{platform}.yaml"
|
||||
)
|
||||
if "packages" in first_comp_data and isinstance(
|
||||
first_comp_data["packages"], dict
|
||||
):
|
||||
# Filter to only include common bus packages
|
||||
# Only dict format can contain common bus packages
|
||||
common_bus_packages = get_common_bus_packages()
|
||||
filtered_packages = {
|
||||
name: value
|
||||
for name, value in first_comp_data["packages"].items()
|
||||
if name in common_bus_packages
|
||||
}
|
||||
if filtered_packages:
|
||||
merged_config_data["packages"] = filtered_packages
|
||||
# Build packages dict from merged all_packages
|
||||
# all_packages is a dict mapping package_name -> str(package_value)
|
||||
# We need to reconstruct the actual package values by loading them from any component
|
||||
# Since packages with the same name must have identical configs (verified above),
|
||||
# we can load the package value from the first component that has each package
|
||||
common_bus_packages = get_common_bus_packages()
|
||||
merged_packages: dict[str, Any] = {}
|
||||
|
||||
# Collect packages that are included as dependencies
|
||||
# If modbus is present, uart is included via modbus.packages.uart
|
||||
packages_to_skip: set[str] = set()
|
||||
for pkg_name in all_packages:
|
||||
if pkg_name.startswith(DEPENDENCY_MARKER_PREFIX):
|
||||
# Extract the actual package name (remove _dep_ prefix)
|
||||
dep_name = pkg_name[len(DEPENDENCY_MARKER_PREFIX) :]
|
||||
packages_to_skip.add(dep_name)
|
||||
|
||||
for pkg_name in all_packages:
|
||||
# Skip dependency markers
|
||||
if pkg_name.startswith(DEPENDENCY_MARKER_PREFIX):
|
||||
continue
|
||||
# Skip non-common-bus packages
|
||||
if pkg_name not in common_bus_packages:
|
||||
continue
|
||||
# Skip packages that are included as dependencies of other packages
|
||||
# This prevents duplicate definitions (e.g., uart via modbus + uart separately)
|
||||
if pkg_name in packages_to_skip:
|
||||
continue
|
||||
|
||||
# Find a component that has this package and extract its value
|
||||
# Uses cached lookup to avoid re-loading the same files
|
||||
for comp_name in component_names:
|
||||
comp_packages = get_component_packages(
|
||||
comp_name, platform, tests_dir_str
|
||||
)
|
||||
if pkg_name in comp_packages:
|
||||
merged_packages[pkg_name] = comp_packages[pkg_name]
|
||||
break
|
||||
|
||||
if merged_packages:
|
||||
merged_config_data["packages"] = merged_packages
|
||||
|
||||
# Deduplicate items with same ID (keeps first occurrence)
|
||||
merged_config_data = deduplicate_by_id(merged_config_data)
|
||||
|
@ -22,9 +22,11 @@ sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from script.analyze_component_buses import (
|
||||
ISOLATED_COMPONENTS,
|
||||
ISOLATED_SIGNATURE_PREFIX,
|
||||
NO_BUSES_SIGNATURE,
|
||||
analyze_all_components,
|
||||
create_grouping_signature,
|
||||
merge_compatible_bus_groups,
|
||||
)
|
||||
|
||||
# Weighting for batch creation
|
||||
@ -33,6 +35,10 @@ from script.analyze_component_buses import (
|
||||
ISOLATED_WEIGHT = 10
|
||||
GROUPABLE_WEIGHT = 1
|
||||
|
||||
# Platform used for batching (platform-agnostic batching)
|
||||
# Batches are split across CI runners and each runner tests all platforms
|
||||
ALL_PLATFORMS = "all"
|
||||
|
||||
|
||||
def has_test_files(component_name: str, tests_dir: Path) -> bool:
|
||||
"""Check if a component has test files.
|
||||
@ -57,7 +63,7 @@ def create_intelligent_batches(
|
||||
tests_dir: Path,
|
||||
batch_size: int = 40,
|
||||
directly_changed: set[str] | None = None,
|
||||
) -> list[list[str]]:
|
||||
) -> tuple[list[list[str]], dict[tuple[str, str], list[str]]]:
|
||||
"""Create batches optimized for component grouping.
|
||||
|
||||
Args:
|
||||
@ -67,7 +73,9 @@ def create_intelligent_batches(
|
||||
directly_changed: Set of directly changed components (for logging only)
|
||||
|
||||
Returns:
|
||||
List of component batches (lists of component names)
|
||||
Tuple of (batches, signature_groups) where:
|
||||
- batches: List of component batches (lists of component names)
|
||||
- signature_groups: Dict mapping (platform, signature) to component lists
|
||||
"""
|
||||
# Filter out components without test files
|
||||
# Platform components like 'climate' and 'climate_ir' don't have test files
|
||||
@ -91,8 +99,9 @@ def create_intelligent_batches(
|
||||
|
||||
# Group components by their bus signature ONLY (ignore platform)
|
||||
# All platforms will be tested by test_build_components.py for each batch
|
||||
# Key: signature, Value: list of components
|
||||
signature_groups: dict[str, list[str]] = defaultdict(list)
|
||||
# Key: (platform, signature), Value: list of components
|
||||
# We use ALL_PLATFORMS since batching is platform-agnostic
|
||||
signature_groups: dict[tuple[str, str], list[str]] = defaultdict(list)
|
||||
|
||||
for component in components_with_tests:
|
||||
# Components that can't be grouped get unique signatures
|
||||
@ -107,7 +116,9 @@ def create_intelligent_batches(
|
||||
or (directly_changed and component in directly_changed)
|
||||
)
|
||||
if is_isolated:
|
||||
signature_groups[f"isolated_{component}"].append(component)
|
||||
signature_groups[
|
||||
(ALL_PLATFORMS, f"{ISOLATED_SIGNATURE_PREFIX}{component}")
|
||||
].append(component)
|
||||
continue
|
||||
|
||||
# Get signature from any platform (they should all have the same buses)
|
||||
@ -117,11 +128,17 @@ def create_intelligent_batches(
|
||||
if buses:
|
||||
signature = create_grouping_signature({platform: buses}, platform)
|
||||
# Group by signature only - platform doesn't matter for batching
|
||||
signature_groups[signature].append(component)
|
||||
# Use ALL_PLATFORMS since we're batching across all platforms
|
||||
signature_groups[(ALL_PLATFORMS, signature)].append(component)
|
||||
break # Only use first platform for grouping
|
||||
else:
|
||||
# No buses found for any platform - can be grouped together
|
||||
signature_groups[NO_BUSES_SIGNATURE].append(component)
|
||||
signature_groups[(ALL_PLATFORMS, NO_BUSES_SIGNATURE)].append(component)
|
||||
|
||||
# Merge compatible bus groups (cross-bus optimization)
|
||||
# This allows components with different buses (ble + uart) to be batched together
|
||||
# improving the efficiency of test_build_components.py grouping
|
||||
signature_groups = merge_compatible_bus_groups(signature_groups)
|
||||
|
||||
# Create batches by keeping signature groups together
|
||||
# Components with the same signature stay in the same batches
|
||||
@ -132,8 +149,8 @@ def create_intelligent_batches(
|
||||
# 2. Sort groupable signatures by size (largest first)
|
||||
# 3. "no_buses" components CAN be grouped together
|
||||
def sort_key(item):
|
||||
signature, components = item
|
||||
is_isolated = signature.startswith("isolated_")
|
||||
(_platform, signature), components = item
|
||||
is_isolated = signature.startswith(ISOLATED_SIGNATURE_PREFIX)
|
||||
# Put "isolated_*" last (1), groupable first (0)
|
||||
# Within each category, sort by size (largest first)
|
||||
return (is_isolated, -len(components))
|
||||
@ -149,8 +166,8 @@ def create_intelligent_batches(
|
||||
current_batch = []
|
||||
current_weight = 0
|
||||
|
||||
for signature, group_components in sorted_groups:
|
||||
is_isolated = signature.startswith("isolated_")
|
||||
for (_platform, signature), group_components in sorted_groups:
|
||||
is_isolated = signature.startswith(ISOLATED_SIGNATURE_PREFIX)
|
||||
weight_per_component = ISOLATED_WEIGHT if is_isolated else GROUPABLE_WEIGHT
|
||||
|
||||
for component in group_components:
|
||||
@ -169,7 +186,7 @@ def create_intelligent_batches(
|
||||
if current_batch:
|
||||
batches.append(current_batch)
|
||||
|
||||
return batches
|
||||
return batches, signature_groups
|
||||
|
||||
|
||||
def main() -> int:
|
||||
@ -231,7 +248,7 @@ def main() -> int:
|
||||
return 1
|
||||
|
||||
# Create intelligent batches
|
||||
batches = create_intelligent_batches(
|
||||
batches, signature_groups = create_intelligent_batches(
|
||||
components=components,
|
||||
tests_dir=args.tests_dir,
|
||||
batch_size=args.batch_size,
|
||||
@ -256,6 +273,58 @@ def main() -> int:
|
||||
# Re-analyze to get isolated component counts for summary
|
||||
_, non_groupable, _ = analyze_all_components(args.tests_dir)
|
||||
|
||||
# Show grouping details
|
||||
print("\n=== Component Grouping Details ===", file=sys.stderr)
|
||||
# Sort groups by signature for readability
|
||||
groupable_groups = []
|
||||
isolated_groups = []
|
||||
for (platform, signature), group_comps in sorted(signature_groups.items()):
|
||||
if signature.startswith(ISOLATED_SIGNATURE_PREFIX):
|
||||
isolated_groups.append((signature, group_comps))
|
||||
else:
|
||||
groupable_groups.append((signature, group_comps))
|
||||
|
||||
if groupable_groups:
|
||||
print(
|
||||
f"\nGroupable signatures ({len(groupable_groups)} merged groups after cross-bus optimization):",
|
||||
file=sys.stderr,
|
||||
)
|
||||
for signature, group_comps in sorted(
|
||||
groupable_groups, key=lambda x: (-len(x[1]), x[0])
|
||||
):
|
||||
# Check if this is a merged signature (contains +)
|
||||
is_merged = "+" in signature and signature != NO_BUSES_SIGNATURE
|
||||
# Special handling for no_buses components
|
||||
if signature == NO_BUSES_SIGNATURE:
|
||||
print(
|
||||
f" [{signature}]: {len(group_comps)} components (used as fillers across batches)",
|
||||
file=sys.stderr,
|
||||
)
|
||||
else:
|
||||
merge_indicator = " [MERGED]" if is_merged else ""
|
||||
print(
|
||||
f" [{signature}]{merge_indicator}: {len(group_comps)} components",
|
||||
file=sys.stderr,
|
||||
)
|
||||
# Show first few components as examples
|
||||
examples = ", ".join(sorted(group_comps)[:8])
|
||||
if len(group_comps) > 8:
|
||||
examples += f", ... (+{len(group_comps) - 8} more)"
|
||||
print(f" → {examples}", file=sys.stderr)
|
||||
|
||||
if isolated_groups:
|
||||
print(
|
||||
f"\nIsolated components ({len(isolated_groups)} components - tested individually):",
|
||||
file=sys.stderr,
|
||||
)
|
||||
isolated_names = sorted(
|
||||
[comp for _, comps in isolated_groups for comp in comps]
|
||||
)
|
||||
# Group isolated components for compact display
|
||||
for i in range(0, len(isolated_names), 10):
|
||||
chunk = isolated_names[i : i + 10]
|
||||
print(f" {', '.join(chunk)}", file=sys.stderr)
|
||||
|
||||
# Count isolated vs groupable components
|
||||
all_batched_components = [comp for batch in batches for comp in batch]
|
||||
isolated_count = sum(
|
||||
|
@ -17,11 +17,13 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
# Add esphome to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
@ -34,32 +36,49 @@ from script.analyze_component_buses import (
|
||||
analyze_all_components,
|
||||
create_grouping_signature,
|
||||
is_platform_component,
|
||||
merge_compatible_bus_groups,
|
||||
uses_local_file_references,
|
||||
)
|
||||
from script.merge_component_configs import merge_component_configs
|
||||
|
||||
# Platform-specific maximum group sizes
|
||||
# ESP8266 has limited IRAM and can't handle large component groups
|
||||
PLATFORM_MAX_GROUP_SIZE = {
|
||||
"esp8266-ard": 10, # ESP8266 Arduino has limited IRAM
|
||||
"esp8266-idf": 10, # ESP8266 IDF also has limited IRAM
|
||||
# BK72xx now uses BK7252 board (1.62MB flash vs 1.03MB) - no limit needed
|
||||
# Other platforms can handle larger groups
|
||||
}
|
||||
|
||||
@dataclass
|
||||
class TestResult:
|
||||
"""Store information about a single test run."""
|
||||
|
||||
test_id: str
|
||||
components: list[str]
|
||||
platform: str
|
||||
success: bool
|
||||
duration: float
|
||||
command: str = ""
|
||||
test_type: str = "compile" # "config" or "compile"
|
||||
|
||||
|
||||
def show_disk_space_if_ci(esphome_command: str) -> None:
|
||||
"""Show disk space usage if running in CI during compile.
|
||||
|
||||
Only shows output during compilation (not config validation) since
|
||||
disk space is only relevant when actually building firmware.
|
||||
|
||||
Args:
|
||||
esphome_command: The esphome command being run (config/compile/clean)
|
||||
"""
|
||||
if os.environ.get("GITHUB_ACTIONS") and esphome_command == "compile":
|
||||
print("\n" + "=" * 80)
|
||||
print("Disk Space After Build:")
|
||||
print("=" * 80)
|
||||
subprocess.run(["df", "-h"], check=False)
|
||||
print("=" * 80 + "\n")
|
||||
# Only show disk space during compilation in CI
|
||||
# Config validation doesn't build anything so disk space isn't relevant
|
||||
if not os.environ.get("GITHUB_ACTIONS"):
|
||||
return
|
||||
if esphome_command != "compile":
|
||||
return
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("Disk Space After Build:")
|
||||
print("=" * 80)
|
||||
# Use sys.stdout.flush() to ensure output appears immediately
|
||||
sys.stdout.flush()
|
||||
subprocess.run(["df", "-h"], check=False, stdout=sys.stdout, stderr=sys.stderr)
|
||||
print("=" * 80 + "\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def find_component_tests(
|
||||
@ -128,6 +147,140 @@ def get_platform_base_files(base_dir: Path) -> dict[str, list[Path]]:
|
||||
return dict(platform_files)
|
||||
|
||||
|
||||
def group_components_by_platform(
|
||||
failed_results: list[TestResult],
|
||||
) -> dict[tuple[str, str], list[str]]:
|
||||
"""Group failed components by platform and test type for simplified reproduction commands.
|
||||
|
||||
Args:
|
||||
failed_results: List of failed test results
|
||||
|
||||
Returns:
|
||||
Dictionary mapping (platform, test_type) to list of component names
|
||||
"""
|
||||
platform_components: dict[tuple[str, str], list[str]] = {}
|
||||
for result in failed_results:
|
||||
key = (result.platform, result.test_type)
|
||||
if key not in platform_components:
|
||||
platform_components[key] = []
|
||||
platform_components[key].extend(result.components)
|
||||
|
||||
# Remove duplicates and sort for each platform
|
||||
return {
|
||||
key: sorted(set(components)) for key, components in platform_components.items()
|
||||
}
|
||||
|
||||
|
||||
def format_github_summary(test_results: list[TestResult]) -> str:
|
||||
"""Format test results as GitHub Actions job summary markdown.
|
||||
|
||||
Args:
|
||||
test_results: List of all test results
|
||||
|
||||
Returns:
|
||||
Markdown formatted summary string
|
||||
"""
|
||||
# Separate results into passed and failed
|
||||
passed_results = [r for r in test_results if r.success]
|
||||
failed_results = [r for r in test_results if not r.success]
|
||||
|
||||
lines = []
|
||||
|
||||
# Header with emoji based on success/failure
|
||||
if failed_results:
|
||||
lines.append("## :x: Component Tests Failed\n")
|
||||
else:
|
||||
lines.append("## :white_check_mark: Component Tests Passed\n")
|
||||
|
||||
# Summary statistics
|
||||
total_time = sum(r.duration for r in test_results)
|
||||
# Determine test type from results (all should be the same)
|
||||
test_type = test_results[0].test_type if test_results else "unknown"
|
||||
lines.append(
|
||||
f"**Results:** {len(passed_results)} passed, {len(failed_results)} failed\n"
|
||||
)
|
||||
lines.append(f"**Total time:** {total_time:.1f}s\n")
|
||||
lines.append(f"**Test type:** `{test_type}`\n")
|
||||
|
||||
# Show failed tests if any
|
||||
if failed_results:
|
||||
lines.append("### Failed Tests\n")
|
||||
lines.append("| Test | Components | Platform | Duration |\n")
|
||||
lines.append("|------|-----------|----------|----------|\n")
|
||||
for result in failed_results:
|
||||
components_str = ", ".join(result.components)
|
||||
lines.append(
|
||||
f"| `{result.test_id}` | {components_str} | {result.platform} | {result.duration:.1f}s |\n"
|
||||
)
|
||||
lines.append("\n")
|
||||
|
||||
# Show simplified commands to reproduce failures
|
||||
# Group all failed components by platform for a single command per platform
|
||||
lines.append("<details>\n")
|
||||
lines.append("<summary>Commands to reproduce failures</summary>\n\n")
|
||||
lines.append("```bash\n")
|
||||
|
||||
# Generate one command per platform and test type
|
||||
platform_components = group_components_by_platform(failed_results)
|
||||
for platform, test_type in sorted(platform_components.keys()):
|
||||
components_csv = ",".join(platform_components[(platform, test_type)])
|
||||
lines.append(
|
||||
f"script/test_build_components.py -c {components_csv} -t {platform} -e {test_type}\n"
|
||||
)
|
||||
|
||||
lines.append("```\n")
|
||||
lines.append("</details>\n")
|
||||
|
||||
# Show passed tests
|
||||
if passed_results:
|
||||
lines.append("### Passed Tests\n\n")
|
||||
lines.append(f"{len(passed_results)} tests passed successfully\n")
|
||||
|
||||
# Separate grouped and individual tests
|
||||
grouped_results = [r for r in passed_results if len(r.components) > 1]
|
||||
individual_results = [r for r in passed_results if len(r.components) == 1]
|
||||
|
||||
if grouped_results:
|
||||
lines.append("#### Grouped Tests\n")
|
||||
lines.append("| Components | Platform | Count | Duration |\n")
|
||||
lines.append("|-----------|----------|-------|----------|\n")
|
||||
for result in grouped_results:
|
||||
components_str = ", ".join(result.components)
|
||||
lines.append(
|
||||
f"| {components_str} | {result.platform} | {len(result.components)} | {result.duration:.1f}s |\n"
|
||||
)
|
||||
lines.append("\n")
|
||||
|
||||
if individual_results:
|
||||
lines.append("#### Individual Tests\n")
|
||||
# Show first 10 individual tests with timing
|
||||
if len(individual_results) <= 10:
|
||||
lines.extend(
|
||||
f"- `{result.test_id}` - {result.duration:.1f}s\n"
|
||||
for result in individual_results
|
||||
)
|
||||
else:
|
||||
lines.extend(
|
||||
f"- `{result.test_id}` - {result.duration:.1f}s\n"
|
||||
for result in individual_results[:10]
|
||||
)
|
||||
lines.append(f"\n...and {len(individual_results) - 10} more\n")
|
||||
lines.append("\n")
|
||||
|
||||
return "".join(lines)
|
||||
|
||||
|
||||
def write_github_summary(test_results: list[TestResult]) -> None:
|
||||
"""Write GitHub Actions job summary with test results and timing.
|
||||
|
||||
Args:
|
||||
test_results: List of all test results
|
||||
"""
|
||||
summary_content = format_github_summary(test_results)
|
||||
with open(os.environ["GITHUB_STEP_SUMMARY"], "a", encoding="utf-8") as f:
|
||||
f.write(summary_content)
|
||||
|
||||
|
||||
def extract_platform_with_version(base_file: Path) -> str:
|
||||
"""Extract platform with version from base filename.
|
||||
|
||||
@ -151,7 +304,7 @@ def run_esphome_test(
|
||||
esphome_command: str,
|
||||
continue_on_fail: bool,
|
||||
use_testing_mode: bool = False,
|
||||
) -> tuple[bool, str]:
|
||||
) -> TestResult:
|
||||
"""Run esphome test for a single component.
|
||||
|
||||
Args:
|
||||
@ -166,7 +319,7 @@ def run_esphome_test(
|
||||
use_testing_mode: Whether to use --testing-mode flag
|
||||
|
||||
Returns:
|
||||
Tuple of (success status, command string)
|
||||
TestResult object with test details and timing
|
||||
"""
|
||||
test_name = test_file.stem.split(".")[0]
|
||||
|
||||
@ -221,9 +374,13 @@ def run_esphome_test(
|
||||
if use_testing_mode:
|
||||
print(" (using --testing-mode)")
|
||||
|
||||
start_time = time.time()
|
||||
test_id = f"{component}.{test_name}.{platform_with_version}"
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, check=False)
|
||||
success = result.returncode == 0
|
||||
duration = time.time() - start_time
|
||||
|
||||
# Show disk space after build in CI during compile
|
||||
show_disk_space_if_ci(esphome_command)
|
||||
@ -236,12 +393,30 @@ def run_esphome_test(
|
||||
print(cmd_str)
|
||||
print()
|
||||
raise subprocess.CalledProcessError(result.returncode, cmd)
|
||||
return success, cmd_str
|
||||
|
||||
return TestResult(
|
||||
test_id=test_id,
|
||||
components=[component],
|
||||
platform=platform_with_version,
|
||||
success=success,
|
||||
duration=duration,
|
||||
command=cmd_str,
|
||||
test_type=esphome_command,
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
duration = time.time() - start_time
|
||||
# Re-raise if we're not continuing on fail
|
||||
if not continue_on_fail:
|
||||
raise
|
||||
return False, cmd_str
|
||||
return TestResult(
|
||||
test_id=test_id,
|
||||
components=[component],
|
||||
platform=platform_with_version,
|
||||
success=False,
|
||||
duration=duration,
|
||||
command=cmd_str,
|
||||
test_type=esphome_command,
|
||||
)
|
||||
|
||||
|
||||
def run_grouped_test(
|
||||
@ -253,7 +428,7 @@ def run_grouped_test(
|
||||
tests_dir: Path,
|
||||
esphome_command: str,
|
||||
continue_on_fail: bool,
|
||||
) -> tuple[bool, str]:
|
||||
) -> TestResult:
|
||||
"""Run esphome test for a group of components with shared bus configs.
|
||||
|
||||
Args:
|
||||
@ -267,7 +442,7 @@ def run_grouped_test(
|
||||
continue_on_fail: Whether to continue on failure
|
||||
|
||||
Returns:
|
||||
Tuple of (success status, command string)
|
||||
TestResult object with test details and timing
|
||||
"""
|
||||
# Create merged config
|
||||
group_name = "_".join(components[:3]) # Use first 3 components for name
|
||||
@ -294,8 +469,17 @@ def run_grouped_test(
|
||||
print(f"Error merging configs for {components}: {e}")
|
||||
if not continue_on_fail:
|
||||
raise
|
||||
# Return empty command string since we failed before building the command
|
||||
return False, f"# Failed during config merge: {e}"
|
||||
# Return TestResult for merge failure
|
||||
test_id = f"GROUPED[{','.join(components)}].{platform_with_version}"
|
||||
return TestResult(
|
||||
test_id=test_id,
|
||||
components=components,
|
||||
platform=platform_with_version,
|
||||
success=False,
|
||||
duration=0.0,
|
||||
command=f"# Failed during config merge: {e}",
|
||||
test_type=esphome_command,
|
||||
)
|
||||
|
||||
# Create test file that includes merged config
|
||||
output_file = build_dir / f"test_{group_name}.{platform_with_version}.yaml"
|
||||
@ -334,9 +518,13 @@ def run_grouped_test(
|
||||
print(f"> [GROUPED: {components_str}] [{platform_with_version}]")
|
||||
print(" (using --testing-mode)")
|
||||
|
||||
start_time = time.time()
|
||||
test_id = f"GROUPED[{','.join(components)}].{platform_with_version}"
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, check=False)
|
||||
success = result.returncode == 0
|
||||
duration = time.time() - start_time
|
||||
|
||||
# Show disk space after build in CI during compile
|
||||
show_disk_space_if_ci(esphome_command)
|
||||
@ -349,12 +537,30 @@ def run_grouped_test(
|
||||
print(cmd_str)
|
||||
print()
|
||||
raise subprocess.CalledProcessError(result.returncode, cmd)
|
||||
return success, cmd_str
|
||||
|
||||
return TestResult(
|
||||
test_id=test_id,
|
||||
components=components,
|
||||
platform=platform_with_version,
|
||||
success=success,
|
||||
duration=duration,
|
||||
command=cmd_str,
|
||||
test_type=esphome_command,
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
duration = time.time() - start_time
|
||||
# Re-raise if we're not continuing on fail
|
||||
if not continue_on_fail:
|
||||
raise
|
||||
return False, cmd_str
|
||||
return TestResult(
|
||||
test_id=test_id,
|
||||
components=components,
|
||||
platform=platform_with_version,
|
||||
success=False,
|
||||
duration=duration,
|
||||
command=cmd_str,
|
||||
test_type=esphome_command,
|
||||
)
|
||||
|
||||
|
||||
def run_grouped_component_tests(
|
||||
@ -366,7 +572,7 @@ def run_grouped_component_tests(
|
||||
esphome_command: str,
|
||||
continue_on_fail: bool,
|
||||
additional_isolated: set[str] | None = None,
|
||||
) -> tuple[set[tuple[str, str]], list[str], list[str], dict[str, str]]:
|
||||
) -> tuple[set[tuple[str, str]], list[TestResult]]:
|
||||
"""Run grouped component tests.
|
||||
|
||||
Args:
|
||||
@ -380,12 +586,10 @@ def run_grouped_component_tests(
|
||||
additional_isolated: Additional components to treat as isolated (not grouped)
|
||||
|
||||
Returns:
|
||||
Tuple of (tested_components, passed_tests, failed_tests, failed_commands)
|
||||
Tuple of (tested_components, test_results)
|
||||
"""
|
||||
tested_components = set()
|
||||
passed_tests = []
|
||||
failed_tests = []
|
||||
failed_commands = {} # Map test_id to command string
|
||||
test_results = []
|
||||
|
||||
# Group components by platform and bus signature
|
||||
grouped_components: dict[tuple[str, str], list[str]] = defaultdict(list)
|
||||
@ -462,6 +666,11 @@ def run_grouped_component_tests(
|
||||
if signature:
|
||||
grouped_components[(platform, signature)].append(component)
|
||||
|
||||
# Merge groups with compatible buses (cross-bus grouping optimization)
|
||||
# This allows mixing components with different buses (e.g., ble + uart)
|
||||
# as long as they don't have conflicting configurations for the same bus type
|
||||
grouped_components = merge_compatible_bus_groups(grouped_components)
|
||||
|
||||
# Print detailed grouping plan
|
||||
print("\nGrouping Plan:")
|
||||
print("-" * 80)
|
||||
@ -560,28 +769,6 @@ def run_grouped_component_tests(
|
||||
# No other groups for this platform - keep no_buses components together
|
||||
grouped_components[(platform, NO_BUSES_SIGNATURE)] = no_buses_comps
|
||||
|
||||
# Split groups that exceed platform-specific maximum sizes
|
||||
# ESP8266 has limited IRAM and can't handle large component groups
|
||||
split_groups = {}
|
||||
for (platform, signature), components in list(grouped_components.items()):
|
||||
max_size = PLATFORM_MAX_GROUP_SIZE.get(platform)
|
||||
if max_size and len(components) > max_size:
|
||||
# Split this group into smaller groups
|
||||
print(
|
||||
f"\n ℹ️ Splitting {platform} group (signature: {signature}) "
|
||||
f"from {len(components)} to max {max_size} components per group"
|
||||
)
|
||||
# Remove original group
|
||||
del grouped_components[(platform, signature)]
|
||||
# Create split groups
|
||||
for i in range(0, len(components), max_size):
|
||||
split_components = components[i : i + max_size]
|
||||
# Create unique signature for each split group
|
||||
split_signature = f"{signature}_split{i // max_size + 1}"
|
||||
split_groups[(platform, split_signature)] = split_components
|
||||
# Add split groups back
|
||||
grouped_components.update(split_groups)
|
||||
|
||||
groups_to_test = []
|
||||
individual_tests = set() # Use set to avoid duplicates
|
||||
|
||||
@ -672,7 +859,7 @@ def run_grouped_component_tests(
|
||||
continue
|
||||
|
||||
# Run grouped test
|
||||
success, cmd_str = run_grouped_test(
|
||||
test_result = run_grouped_test(
|
||||
components=components_to_group,
|
||||
platform=platform,
|
||||
platform_with_version=platform_with_version,
|
||||
@ -687,17 +874,10 @@ def run_grouped_component_tests(
|
||||
for comp in components_to_group:
|
||||
tested_components.add((comp, platform_with_version))
|
||||
|
||||
# Record result for each component - show all components in grouped tests
|
||||
test_id = (
|
||||
f"GROUPED[{','.join(components_to_group)}].{platform_with_version}"
|
||||
)
|
||||
if success:
|
||||
passed_tests.append(test_id)
|
||||
else:
|
||||
failed_tests.append(test_id)
|
||||
failed_commands[test_id] = cmd_str
|
||||
# Store test result
|
||||
test_results.append(test_result)
|
||||
|
||||
return tested_components, passed_tests, failed_tests, failed_commands
|
||||
return tested_components, test_results
|
||||
|
||||
|
||||
def run_individual_component_test(
|
||||
@ -710,9 +890,7 @@ def run_individual_component_test(
|
||||
esphome_command: str,
|
||||
continue_on_fail: bool,
|
||||
tested_components: set[tuple[str, str]],
|
||||
passed_tests: list[str],
|
||||
failed_tests: list[str],
|
||||
failed_commands: dict[str, str],
|
||||
test_results: list[TestResult],
|
||||
) -> None:
|
||||
"""Run an individual component test if not already tested in a group.
|
||||
|
||||
@ -726,16 +904,13 @@ def run_individual_component_test(
|
||||
esphome_command: ESPHome command
|
||||
continue_on_fail: Whether to continue on failure
|
||||
tested_components: Set of already tested components
|
||||
passed_tests: List to append passed test IDs
|
||||
failed_tests: List to append failed test IDs
|
||||
failed_commands: Dict to store failed test commands
|
||||
test_results: List to append test results
|
||||
"""
|
||||
# Skip if already tested in a group
|
||||
if (component, platform_with_version) in tested_components:
|
||||
return
|
||||
|
||||
test_name = test_file.stem.split(".")[0]
|
||||
success, cmd_str = run_esphome_test(
|
||||
test_result = run_esphome_test(
|
||||
component=component,
|
||||
test_file=test_file,
|
||||
platform=platform,
|
||||
@ -745,12 +920,7 @@ def run_individual_component_test(
|
||||
esphome_command=esphome_command,
|
||||
continue_on_fail=continue_on_fail,
|
||||
)
|
||||
test_id = f"{component}.{test_name}.{platform_with_version}"
|
||||
if success:
|
||||
passed_tests.append(test_id)
|
||||
else:
|
||||
failed_tests.append(test_id)
|
||||
failed_commands[test_id] = cmd_str
|
||||
test_results.append(test_result)
|
||||
|
||||
|
||||
def test_components(
|
||||
@ -799,19 +969,12 @@ def test_components(
|
||||
print(f"Found {len(all_tests)} components to test")
|
||||
|
||||
# Run tests
|
||||
failed_tests = []
|
||||
passed_tests = []
|
||||
test_results = []
|
||||
tested_components = set() # Track which components were tested in groups
|
||||
failed_commands = {} # Track commands for failed tests
|
||||
|
||||
# First, run grouped tests if grouping is enabled
|
||||
if enable_grouping:
|
||||
(
|
||||
tested_components,
|
||||
passed_tests,
|
||||
failed_tests,
|
||||
failed_commands,
|
||||
) = run_grouped_component_tests(
|
||||
tested_components, grouped_results = run_grouped_component_tests(
|
||||
all_tests=all_tests,
|
||||
platform_filter=platform_filter,
|
||||
platform_bases=platform_bases,
|
||||
@ -821,6 +984,7 @@ def test_components(
|
||||
continue_on_fail=continue_on_fail,
|
||||
additional_isolated=isolated_components,
|
||||
)
|
||||
test_results.extend(grouped_results)
|
||||
|
||||
# Then run individual tests for components not in groups
|
||||
for component, test_files in sorted(all_tests.items()):
|
||||
@ -846,9 +1010,7 @@ def test_components(
|
||||
esphome_command=esphome_command,
|
||||
continue_on_fail=continue_on_fail,
|
||||
tested_components=tested_components,
|
||||
passed_tests=passed_tests,
|
||||
failed_tests=failed_tests,
|
||||
failed_commands=failed_commands,
|
||||
test_results=test_results,
|
||||
)
|
||||
else:
|
||||
# Platform-specific test
|
||||
@ -880,31 +1042,40 @@ def test_components(
|
||||
esphome_command=esphome_command,
|
||||
continue_on_fail=continue_on_fail,
|
||||
tested_components=tested_components,
|
||||
passed_tests=passed_tests,
|
||||
failed_tests=failed_tests,
|
||||
failed_commands=failed_commands,
|
||||
test_results=test_results,
|
||||
)
|
||||
|
||||
# Separate results into passed and failed
|
||||
passed_results = [r for r in test_results if r.success]
|
||||
failed_results = [r for r in test_results if not r.success]
|
||||
|
||||
# Print summary
|
||||
print("\n" + "=" * 80)
|
||||
print(f"Test Summary: {len(passed_tests)} passed, {len(failed_tests)} failed")
|
||||
print(f"Test Summary: {len(passed_results)} passed, {len(failed_results)} failed")
|
||||
print("=" * 80)
|
||||
|
||||
if failed_tests:
|
||||
if failed_results:
|
||||
print("\nFailed tests:")
|
||||
for test in failed_tests:
|
||||
print(f" - {test}")
|
||||
for result in failed_results:
|
||||
print(f" - {result.test_id}")
|
||||
|
||||
# Print failed commands at the end for easy copy-paste from CI logs
|
||||
# Print simplified commands grouped by platform and test type for easy copy-paste
|
||||
print("\n" + "=" * 80)
|
||||
print("Failed test commands (copy-paste to reproduce locally):")
|
||||
print("Commands to reproduce failures (copy-paste to reproduce locally):")
|
||||
print("=" * 80)
|
||||
for test in failed_tests:
|
||||
if test in failed_commands:
|
||||
print(f"\n# {test}")
|
||||
print(failed_commands[test])
|
||||
platform_components = group_components_by_platform(failed_results)
|
||||
for platform, test_type in sorted(platform_components.keys()):
|
||||
components_csv = ",".join(platform_components[(platform, test_type)])
|
||||
print(
|
||||
f"script/test_build_components.py -c {components_csv} -t {platform} -e {test_type}"
|
||||
)
|
||||
print()
|
||||
|
||||
# Write GitHub Actions job summary if in CI
|
||||
if os.environ.get("GITHUB_STEP_SUMMARY"):
|
||||
write_github_summary(test_results)
|
||||
|
||||
if failed_results:
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
@ -1,5 +1,5 @@
|
||||
substitutions:
|
||||
irq0_pin: GPIO13
|
||||
irq0_pin: GPIO0
|
||||
irq1_pin: GPIO15
|
||||
reset_pin: GPIO16
|
||||
|
||||
|
@ -4,10 +4,13 @@ sensor:
|
||||
irq_pin: ${irq_pin}
|
||||
voltage:
|
||||
name: ADE7953 Voltage
|
||||
id: ade7953_i2c_voltage
|
||||
current_a:
|
||||
name: ADE7953 Current A
|
||||
id: ade7953_i2c_current_a
|
||||
current_b:
|
||||
name: ADE7953 Current B
|
||||
id: ade7953_i2c_current_b
|
||||
power_factor_a:
|
||||
name: ADE7953 Power Factor A
|
||||
power_factor_b:
|
||||
|
@ -4,13 +4,13 @@ sensor:
|
||||
irq_pin: ${irq_pin}
|
||||
voltage:
|
||||
name: ADE7953 Voltage
|
||||
id: ade7953_voltage
|
||||
id: ade7953_spi_voltage
|
||||
current_a:
|
||||
name: ADE7953 Current A
|
||||
id: ade7953_current_a
|
||||
id: ade7953_spi_current_a
|
||||
current_b:
|
||||
name: ADE7953 Current B
|
||||
id: ade7953_current_b
|
||||
id: ade7953_spi_current_b
|
||||
power_factor_a:
|
||||
name: ADE7953 Power Factor A
|
||||
power_factor_b:
|
||||
|
@ -1,13 +1,16 @@
|
||||
as3935_i2c:
|
||||
id: as3935_i2c_id
|
||||
i2c_id: i2c_bus
|
||||
irq_pin: ${irq_pin}
|
||||
|
||||
binary_sensor:
|
||||
- platform: as3935
|
||||
as3935_id: as3935_i2c_id
|
||||
name: Storm Alert
|
||||
|
||||
sensor:
|
||||
- platform: as3935
|
||||
as3935_id: as3935_i2c_id
|
||||
lightning_energy:
|
||||
name: Lightning Energy
|
||||
distance:
|
||||
|
@ -1,13 +1,16 @@
|
||||
as3935_spi:
|
||||
id: as3935_spi_id
|
||||
cs_pin: ${cs_pin}
|
||||
irq_pin: ${irq_pin}
|
||||
|
||||
binary_sensor:
|
||||
- platform: as3935
|
||||
as3935_id: as3935_spi_id
|
||||
name: Storm Alert
|
||||
|
||||
sensor:
|
||||
- platform: as3935
|
||||
as3935_id: as3935_spi_id
|
||||
lightning_energy:
|
||||
name: Lightning Energy
|
||||
distance:
|
||||
|
@ -1,7 +1,7 @@
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: ssd1306_display
|
||||
id: ssd1306_i2c_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: 19
|
||||
pages:
|
||||
@ -13,6 +13,6 @@ touchscreen:
|
||||
- platform: axs15231
|
||||
i2c_id: i2c_bus
|
||||
id: axs15231_touchscreen
|
||||
display: ssd1306_display
|
||||
display: ssd1306_i2c_display
|
||||
interrupt_pin: 20
|
||||
reset_pin: 18
|
||||
|
@ -3,12 +3,12 @@ sensor:
|
||||
i2c_id: i2c_bus
|
||||
address: 0x76
|
||||
temperature:
|
||||
id: bme280_temperature
|
||||
id: bme280_i2c_temperature
|
||||
name: BME280 Temperature
|
||||
humidity:
|
||||
id: bme280_humidity
|
||||
id: bme280_i2c_humidity
|
||||
name: BME280 Humidity
|
||||
pressure:
|
||||
id: bme280_pressure
|
||||
id: bme280_i2c_pressure
|
||||
name: BME280 Pressure
|
||||
update_interval: 15s
|
||||
|
@ -2,12 +2,12 @@ sensor:
|
||||
- platform: bme280_spi
|
||||
cs_pin: ${cs_pin}
|
||||
temperature:
|
||||
id: bme280_temperature
|
||||
id: bme280_spi_temperature
|
||||
name: BME280 Temperature
|
||||
humidity:
|
||||
id: bme280_humidity
|
||||
id: bme280_spi_humidity
|
||||
name: BME280 Humidity
|
||||
pressure:
|
||||
id: bme280_pressure
|
||||
id: bme280_spi_pressure
|
||||
name: BME280 Pressure
|
||||
update_interval: 15s
|
||||
|
@ -3,10 +3,10 @@ sensor:
|
||||
i2c_id: i2c_bus
|
||||
address: 0x77
|
||||
temperature:
|
||||
id: bmp280_temperature
|
||||
id: bmp280_i2c_temperature
|
||||
name: Outside Temperature
|
||||
pressure:
|
||||
name: Outside Pressure
|
||||
id: bmp280_pressure
|
||||
id: bmp280_i2c_pressure
|
||||
iir_filter: 16x
|
||||
update_interval: 15s
|
||||
|
@ -2,10 +2,10 @@ sensor:
|
||||
- platform: bmp280_spi
|
||||
cs_pin: ${cs_pin}
|
||||
temperature:
|
||||
id: bmp280_temperature
|
||||
id: bmp280_spi_temperature
|
||||
name: Outside Temperature
|
||||
pressure:
|
||||
name: Outside Pressure
|
||||
id: bmp280_pressure
|
||||
id: bmp280_spi_pressure
|
||||
iir_filter: 16x
|
||||
update_interval: 15s
|
||||
|
@ -3,8 +3,10 @@ sensor:
|
||||
i2c_id: i2c_bus
|
||||
address: 0x77
|
||||
temperature:
|
||||
id: bmp3xx_i2c_temperature
|
||||
name: BMP Temperature
|
||||
oversampling: 16x
|
||||
pressure:
|
||||
id: bmp3xx_i2c_pressure
|
||||
name: BMP Pressure
|
||||
iir_filter: 2X
|
||||
|
@ -2,8 +2,10 @@ sensor:
|
||||
- platform: bmp3xx_spi
|
||||
cs_pin: ${cs_pin}
|
||||
temperature:
|
||||
id: bmp3xx_spi_temperature
|
||||
name: BMP Temperature
|
||||
oversampling: 16x
|
||||
pressure:
|
||||
id: bmp3xx_spi_pressure
|
||||
name: BMP Pressure
|
||||
iir_filter: 2X
|
||||
|
@ -1,4 +1,4 @@
|
||||
packages:
|
||||
camera: !include ../../test_build_components/common/camera/esp32-idf.yaml
|
||||
i2c_camera: !include ../../test_build_components/common/i2c_camera/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -1,4 +1,4 @@
|
||||
packages:
|
||||
camera: !include ../../test_build_components/common/camera/esp32-idf.yaml
|
||||
i2c_camera: !include ../../test_build_components/common/i2c_camera/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -4,6 +4,7 @@ packages:
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
spi_id: spi_bus
|
||||
id: ili9xxx_display
|
||||
model: GC9A01A
|
||||
invert_colors: True
|
||||
@ -16,5 +17,6 @@ display:
|
||||
|
||||
touchscreen:
|
||||
- platform: chsc6x
|
||||
i2c_id: i2c_bus
|
||||
display: ili9xxx_display
|
||||
interrupt_pin: 20
|
||||
|
@ -1,7 +1,7 @@
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: ssd1306_display
|
||||
id: ssd1306_i2c_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: ${display_reset_pin}
|
||||
pages:
|
||||
@ -15,7 +15,7 @@ touchscreen:
|
||||
id: ektf2232_touchscreen
|
||||
interrupt_pin: ${interrupt_pin}
|
||||
reset_pin: ${touch_reset_pin}
|
||||
display: ssd1306_display
|
||||
display: ssd1306_i2c_display
|
||||
on_touch:
|
||||
- logger.log:
|
||||
format: Touch at (%d, %d)
|
||||
|
@ -3,8 +3,11 @@ sensor:
|
||||
i2c_id: i2c_bus
|
||||
address: 0x53
|
||||
eco2:
|
||||
id: ens160_i2c_eco2
|
||||
name: "ENS160 eCO2"
|
||||
tvoc:
|
||||
id: ens160_i2c_tvoc
|
||||
name: "ENS160 Total Volatile Organic Compounds"
|
||||
aqi:
|
||||
id: ens160_i2c_aqi
|
||||
name: "ENS160 Air Quality Index"
|
||||
|
@ -2,8 +2,11 @@ sensor:
|
||||
- platform: ens160_spi
|
||||
cs_pin: ${cs_pin}
|
||||
eco2:
|
||||
id: ens160_spi_eco2
|
||||
name: "ENS160 eCO2"
|
||||
tvoc:
|
||||
id: ens160_spi_tvoc
|
||||
name: "ENS160 Total Volatile Organic Compounds"
|
||||
aqi:
|
||||
id: ens160_spi_aqi
|
||||
name: "ENS160 Air Quality Index"
|
||||
|
@ -1,4 +1,4 @@
|
||||
packages:
|
||||
camera: !include ../../test_build_components/common/camera/esp32-idf.yaml
|
||||
i2c_camera: !include ../../test_build_components/common/i2c_camera/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -1,4 +1,4 @@
|
||||
packages:
|
||||
camera: !include ../../test_build_components/common/camera/esp32-idf.yaml
|
||||
i2c_camera: !include ../../test_build_components/common/i2c_camera/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -49,6 +49,7 @@ font:
|
||||
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: ssd1306_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: ${display_reset_pin}
|
||||
|
@ -1,5 +1,5 @@
|
||||
substitutions:
|
||||
interrupt_pin: GPIO12
|
||||
interrupt_pin: GPIO0
|
||||
reset_pin: GPIO16
|
||||
|
||||
packages:
|
||||
|
@ -11,6 +11,7 @@ graph:
|
||||
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: ssd1306_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: ${reset_pin}
|
||||
|
@ -1,6 +1,6 @@
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
id: ssd1306_display
|
||||
id: ssd1306_i2c_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: ${reset_pin}
|
||||
pages:
|
||||
@ -36,7 +36,7 @@ switch:
|
||||
|
||||
graphical_display_menu:
|
||||
id: test_graphical_display_menu
|
||||
display: ssd1306_display
|
||||
display: ssd1306_i2c_display
|
||||
font: roboto
|
||||
active: false
|
||||
mode: rotary
|
||||
|
@ -1,7 +1,7 @@
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: ssd1306_display
|
||||
id: ssd1306_i2c_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: ${display_reset_pin}
|
||||
pages:
|
||||
@ -13,7 +13,7 @@ touchscreen:
|
||||
- platform: gt911
|
||||
i2c_id: i2c_bus
|
||||
id: gt911_touchscreen
|
||||
display: ssd1306_display
|
||||
display: ssd1306_i2c_display
|
||||
interrupt_pin: ${interrupt_pin}
|
||||
reset_pin: ${reset_pin}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
substitutions:
|
||||
clk_pin: GPIO4
|
||||
dout_pin: GPIO5
|
||||
clk_pin: GPIO0
|
||||
dout_pin: GPIO2
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -7,9 +7,21 @@ sensor:
|
||||
max_current: 40 A
|
||||
adc_range: 1
|
||||
temperature_coefficient: 50
|
||||
shunt_voltage: "INA2xx Shunt Voltage"
|
||||
bus_voltage: "INA2xx Bus Voltage"
|
||||
current: "INA2xx Current"
|
||||
power: "INA2xx Power"
|
||||
energy: "INA2xx Energy"
|
||||
charge: "INA2xx Charge"
|
||||
shunt_voltage:
|
||||
id: ina2xx_i2c_shunt_voltage
|
||||
name: "INA2xx Shunt Voltage"
|
||||
bus_voltage:
|
||||
id: ina2xx_i2c_bus_voltage
|
||||
name: "INA2xx Bus Voltage"
|
||||
current:
|
||||
id: ina2xx_i2c_current
|
||||
name: "INA2xx Current"
|
||||
power:
|
||||
id: ina2xx_i2c_power
|
||||
name: "INA2xx Power"
|
||||
energy:
|
||||
id: ina2xx_i2c_energy
|
||||
name: "INA2xx Energy"
|
||||
charge:
|
||||
id: ina2xx_i2c_charge
|
||||
name: "INA2xx Charge"
|
||||
|
@ -6,9 +6,21 @@ sensor:
|
||||
max_current: 40 A
|
||||
adc_range: 1
|
||||
temperature_coefficient: 50
|
||||
shunt_voltage: "INA2xx Shunt Voltage"
|
||||
bus_voltage: "INA2xx Bus Voltage"
|
||||
current: "INA2xx Current"
|
||||
power: "INA2xx Power"
|
||||
energy: "INA2xx Energy"
|
||||
charge: "INA2xx Charge"
|
||||
shunt_voltage:
|
||||
id: ina2xx_spi_shunt_voltage
|
||||
name: "INA2xx Shunt Voltage"
|
||||
bus_voltage:
|
||||
id: ina2xx_spi_bus_voltage
|
||||
name: "INA2xx Bus Voltage"
|
||||
current:
|
||||
id: ina2xx_spi_current
|
||||
name: "INA2xx Current"
|
||||
power:
|
||||
id: ina2xx_spi_power
|
||||
name: "INA2xx Power"
|
||||
energy:
|
||||
id: ina2xx_spi_energy
|
||||
name: "INA2xx Energy"
|
||||
charge:
|
||||
id: ina2xx_spi_charge
|
||||
name: "INA2xx Charge"
|
||||
|
@ -1,7 +1,7 @@
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: ssd1306_display
|
||||
id: ssd1306_i2c_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: ${reset_pin}
|
||||
pages:
|
||||
@ -14,7 +14,7 @@ touchscreen:
|
||||
i2c_id: i2c_bus
|
||||
id: lilygo_touchscreen
|
||||
interrupt_pin: ${interrupt_pin}
|
||||
display: ssd1306_display
|
||||
display: ssd1306_i2c_display
|
||||
on_touch:
|
||||
- logger.log:
|
||||
format: Touch at (%d, %d)
|
||||
|
@ -1,9 +1,9 @@
|
||||
pn532_i2c:
|
||||
i2c_id: i2c_bus
|
||||
id: pn532_nfcc
|
||||
id: pn532_nfcc_i2c
|
||||
|
||||
binary_sensor:
|
||||
- platform: pn532
|
||||
pn532_id: pn532_nfcc
|
||||
pn532_id: pn532_nfcc_i2c
|
||||
name: PN532 NFC Tag
|
||||
uid: 74-10-37-94
|
||||
|
@ -1,9 +1,9 @@
|
||||
pn532_spi:
|
||||
id: pn532_nfcc
|
||||
id: pn532_nfcc_spi
|
||||
cs_pin: ${cs_pin}
|
||||
|
||||
binary_sensor:
|
||||
- platform: pn532
|
||||
pn532_id: pn532_nfcc
|
||||
pn532_id: pn532_nfcc_spi
|
||||
name: PN532 NFC Tag
|
||||
uid: 74-10-37-94
|
||||
|
@ -1,23 +1,23 @@
|
||||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- tag.set_clean_mode: nfcc_pn7160
|
||||
- tag.set_format_mode: nfcc_pn7160
|
||||
- tag.set_read_mode: nfcc_pn7160
|
||||
- tag.set_clean_mode: nfcc_pn7160_i2c
|
||||
- tag.set_format_mode: nfcc_pn7160_i2c
|
||||
- tag.set_read_mode: nfcc_pn7160_i2c
|
||||
- tag.set_write_message:
|
||||
message: https://www.home-assistant.io/tag/pulse
|
||||
include_android_app_record: false
|
||||
- tag.set_write_mode: nfcc_pn7160
|
||||
- tag.set_write_mode: nfcc_pn7160_i2c
|
||||
- tag.set_emulation_message:
|
||||
message: https://www.home-assistant.io/tag/pulse
|
||||
include_android_app_record: false
|
||||
- tag.emulation_off: nfcc_pn7160
|
||||
- tag.emulation_on: nfcc_pn7160
|
||||
- tag.polling_off: nfcc_pn7160
|
||||
- tag.polling_on: nfcc_pn7160
|
||||
- tag.emulation_off: nfcc_pn7160_i2c
|
||||
- tag.emulation_on: nfcc_pn7160_i2c
|
||||
- tag.polling_off: nfcc_pn7160_i2c
|
||||
- tag.polling_on: nfcc_pn7160_i2c
|
||||
|
||||
pn7150_i2c:
|
||||
id: nfcc_pn7160
|
||||
id: nfcc_pn7160_i2c
|
||||
i2c_id: i2c_bus
|
||||
irq_pin: ${irq_pin}
|
||||
ven_pin: ${ven_pin}
|
||||
|
@ -1,23 +1,23 @@
|
||||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- tag.set_clean_mode: nfcc_pn7160
|
||||
- tag.set_format_mode: nfcc_pn7160
|
||||
- tag.set_read_mode: nfcc_pn7160
|
||||
- tag.set_clean_mode: nfcc_pn7160_spi
|
||||
- tag.set_format_mode: nfcc_pn7160_spi
|
||||
- tag.set_read_mode: nfcc_pn7160_spi
|
||||
- tag.set_write_message:
|
||||
message: https://www.home-assistant.io/tag/pulse
|
||||
include_android_app_record: false
|
||||
- tag.set_write_mode: nfcc_pn7160
|
||||
- tag.set_write_mode: nfcc_pn7160_spi
|
||||
- tag.set_emulation_message:
|
||||
message: https://www.home-assistant.io/tag/pulse
|
||||
include_android_app_record: false
|
||||
- tag.emulation_off: nfcc_pn7160
|
||||
- tag.emulation_on: nfcc_pn7160
|
||||
- tag.polling_off: nfcc_pn7160
|
||||
- tag.polling_on: nfcc_pn7160
|
||||
- tag.emulation_off: nfcc_pn7160_spi
|
||||
- tag.emulation_on: nfcc_pn7160_spi
|
||||
- tag.polling_off: nfcc_pn7160_spi
|
||||
- tag.polling_on: nfcc_pn7160_spi
|
||||
|
||||
pn7160_spi:
|
||||
id: nfcc_pn7160
|
||||
id: nfcc_pn7160_spi
|
||||
cs_pin: ${cs_pin}
|
||||
irq_pin: ${irq_pin}
|
||||
ven_pin: ${ven_pin}
|
||||
|
@ -1,5 +1,5 @@
|
||||
rc522_i2c:
|
||||
- id: rc522_nfcc
|
||||
- id: rc522_nfcc_i2c
|
||||
i2c_id: i2c_bus
|
||||
update_interval: 1s
|
||||
on_tag:
|
||||
@ -8,6 +8,6 @@ rc522_i2c:
|
||||
|
||||
binary_sensor:
|
||||
- platform: rc522
|
||||
rc522_id: rc522_nfcc
|
||||
rc522_id: rc522_nfcc_i2c
|
||||
name: RC522 NFC Tag
|
||||
uid: 74-10-37-94
|
||||
|
@ -1,9 +1,9 @@
|
||||
rc522_spi:
|
||||
id: rc522_nfcc
|
||||
id: rc522_nfcc_spi
|
||||
cs_pin: ${cs_pin}
|
||||
|
||||
binary_sensor:
|
||||
- platform: rc522
|
||||
rc522_id: rc522_nfcc
|
||||
name: PN532 NFC Tag
|
||||
rc522_id: rc522_nfcc_spi
|
||||
name: RC522 NFC Tag
|
||||
uid: 74-10-37-94
|
||||
|
@ -1,7 +1,7 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO0
|
||||
rx_pin: GPIO2
|
||||
flow_control_pin: GPIO4
|
||||
flow_control_pin: GPIO15
|
||||
|
||||
packages:
|
||||
modbus: !include ../../test_build_components/common/modbus/esp8266-ard.yaml
|
||||
|
@ -2,8 +2,8 @@ packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml
|
||||
|
||||
substitutions:
|
||||
clock_pin: GPIO5
|
||||
data_pin: GPIO4
|
||||
clock_pin: GPIO15
|
||||
data_pin: GPIO16
|
||||
latch_pin1: GPIO2
|
||||
oe_pin1: GPIO0
|
||||
latch_pin2: GPIO3
|
||||
|
@ -4,7 +4,7 @@ display:
|
||||
model: SSD1306_128X64
|
||||
reset_pin: ${reset_pin}
|
||||
address: 0x3C
|
||||
id: display1
|
||||
id: ssd1306_i2c_display
|
||||
contrast: 60%
|
||||
pages:
|
||||
- id: ssd1306_i2c_page1
|
||||
|
@ -1,5 +1,6 @@
|
||||
display:
|
||||
- platform: ssd1306_spi
|
||||
id: ssd1306_spi_display
|
||||
model: SSD1306 128x64
|
||||
cs_pin: ${cs_pin}
|
||||
dc_pin: ${dc_pin}
|
||||
|
@ -4,7 +4,7 @@ display:
|
||||
model: SSD1327_128x128
|
||||
reset_pin: ${reset_pin}
|
||||
address: 0x3C
|
||||
id: display1
|
||||
id: ssd1327_i2c_display
|
||||
pages:
|
||||
- id: ssd1327_i2c_page1
|
||||
lambda: |-
|
||||
|
@ -1,5 +1,6 @@
|
||||
display:
|
||||
- platform: ssd1327_spi
|
||||
id: ssd1327_spi_display
|
||||
model: SSD1327 128x128
|
||||
cs_pin: ${cs_pin}
|
||||
dc_pin: ${dc_pin}
|
||||
|
@ -3,7 +3,7 @@ display:
|
||||
i2c_id: i2c_bus
|
||||
reset_pin: ${reset_pin}
|
||||
address: 0x3C
|
||||
id: display1
|
||||
id: st7567_i2c_display
|
||||
pages:
|
||||
- id: st7567_i2c_page1
|
||||
lambda: |-
|
||||
|
@ -1,5 +1,6 @@
|
||||
display:
|
||||
- platform: st7567_spi
|
||||
id: st7567_spi_display
|
||||
cs_pin: ${cs_pin}
|
||||
dc_pin: ${dc_pin}
|
||||
reset_pin: ${reset_pin}
|
||||
|
@ -6,7 +6,8 @@ udp:
|
||||
addresses: ["239.0.60.53"]
|
||||
|
||||
time:
|
||||
platform: host
|
||||
- platform: host
|
||||
id: host_time
|
||||
|
||||
syslog:
|
||||
port: 514
|
||||
|
@ -1,7 +1,7 @@
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: ssd1306_display
|
||||
id: ssd1306_i2c_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: ${disp_reset_pin}
|
||||
pages:
|
||||
@ -13,7 +13,7 @@ touchscreen:
|
||||
- platform: tt21100
|
||||
i2c_id: i2c_bus
|
||||
id: tt21100_touchscreen
|
||||
display: ssd1306_display
|
||||
display: ssd1306_i2c_display
|
||||
interrupt_pin: ${interrupt_pin}
|
||||
reset_pin: ${reset_pin}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
uart_bridge_2: !include ../../test_build_components/common/uart_bridge_2/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -4,5 +4,6 @@ substitutions:
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml
|
||||
uart_bridge_2: !include ../../test_build_components/common/uart_bridge_2/esp32-s3-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -1,20 +1,20 @@
|
||||
wk2132_spi:
|
||||
- id: wk2132_spi_id
|
||||
- id: wk2132_spi_bridge
|
||||
cs_pin: ${cs_pin}
|
||||
crystal: 11059200
|
||||
data_rate: 1MHz
|
||||
uart:
|
||||
- id: wk2132_spi_id0
|
||||
- id: wk2132_spi_uart0
|
||||
channel: 0
|
||||
baud_rate: 115200
|
||||
stop_bits: 1
|
||||
parity: none
|
||||
- id: wk2132_spi_id1
|
||||
- id: wk2132_spi_uart1
|
||||
channel: 1
|
||||
baud_rate: 9600
|
||||
|
||||
# Ensures a sensor doesn't break validation
|
||||
sensor:
|
||||
- platform: a02yyuw
|
||||
uart_id: wk2132_spi_id1
|
||||
uart_id: wk2132_spi_uart1
|
||||
id: distance_sensor
|
||||
|
@ -3,5 +3,6 @@ substitutions:
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-idf.yaml
|
||||
uart_bridge_2: !include ../../test_build_components/common/uart_bridge_2/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -6,5 +6,6 @@ substitutions:
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-s3-idf.yaml
|
||||
uart_bridge_2: !include ../../test_build_components/common/uart_bridge_2/esp32-s3-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -1,4 +1,5 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -4,5 +4,6 @@ substitutions:
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml
|
||||
uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -3,5 +3,6 @@ substitutions:
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-idf.yaml
|
||||
uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -6,5 +6,6 @@ substitutions:
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-s3-idf.yaml
|
||||
uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -1,4 +1,5 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -4,5 +4,6 @@ substitutions:
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml
|
||||
uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -1,28 +1,28 @@
|
||||
wk2204_spi:
|
||||
- id: wk2204_spi_id
|
||||
- id: wk2204_spi_bridge
|
||||
cs_pin: ${cs_pin}
|
||||
crystal: 11059200
|
||||
data_rate: 1MHz
|
||||
uart:
|
||||
- id: wk2204_spi_id0
|
||||
- id: wk2204_spi_uart0
|
||||
channel: 0
|
||||
baud_rate: 115200
|
||||
stop_bits: 1
|
||||
parity: none
|
||||
- id: wk2204_spi_id1
|
||||
- id: wk2204_spi_uart1
|
||||
channel: 1
|
||||
baud_rate: 921600
|
||||
- id: wk2204_spi_id2
|
||||
- id: wk2204_spi_uart2
|
||||
channel: 2
|
||||
baud_rate: 115200
|
||||
stop_bits: 1
|
||||
parity: none
|
||||
- id: wk2204_spi_id3
|
||||
- id: wk2204_spi_uart3
|
||||
channel: 3
|
||||
baud_rate: 9600
|
||||
|
||||
# Ensures a sensor doesn't break validation
|
||||
sensor:
|
||||
- platform: a02yyuw
|
||||
uart_id: wk2204_spi_id3
|
||||
uart_id: wk2204_spi_uart3
|
||||
id: distance_sensor
|
||||
|
@ -3,5 +3,6 @@ substitutions:
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-idf.yaml
|
||||
uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -6,5 +6,6 @@ substitutions:
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-s3-idf.yaml
|
||||
uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -1,4 +1,5 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -4,5 +4,6 @@ substitutions:
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml
|
||||
uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -3,5 +3,6 @@ substitutions:
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-idf.yaml
|
||||
uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -6,5 +6,6 @@ substitutions:
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-s3-idf.yaml
|
||||
uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -3,9 +3,13 @@ esphome:
|
||||
friendly_name: $component_name
|
||||
|
||||
esp32:
|
||||
board: nodemcu-32s
|
||||
# Use board with 8MB flash for testing large component groups
|
||||
board: esp32-pico-devkitm-2
|
||||
framework:
|
||||
type: esp-idf
|
||||
# Use custom partition table with larger app partitions (3MB each)
|
||||
# Default IDF partitions only allow 1.75MB which is too small for grouped tests
|
||||
partitions: ../partitions_testing.csv
|
||||
|
||||
logger:
|
||||
level: VERY_VERBOSE
|
||||
|
@ -1,3 +1,10 @@
|
||||
# I2C bus for camera sensor
|
||||
i2c:
|
||||
- id: i2c_camera_bus
|
||||
sda: 25
|
||||
scl: 23
|
||||
frequency: 400kHz
|
||||
|
||||
esp32_camera:
|
||||
name: ESP32 Camera
|
||||
data_pins:
|
||||
@ -15,9 +22,7 @@ esp32_camera:
|
||||
external_clock:
|
||||
pin: 27
|
||||
frequency: 20MHz
|
||||
i2c_pins:
|
||||
sda: 25
|
||||
scl: 23
|
||||
i2c_id: i2c_camera_bus
|
||||
reset_pin: 15
|
||||
power_down_pin: 1
|
||||
resolution: 640x480
|
@ -0,0 +1,11 @@
|
||||
# Common configuration for 2-channel UART bridge/expander chips
|
||||
# Used by components like wk2132 that create 2 UART channels
|
||||
# Defines standardized UART IDs: uart_id_0, uart_id_1
|
||||
|
||||
substitutions:
|
||||
# These will be overridden by component-specific values
|
||||
uart_bridge_address: "0x70"
|
||||
|
||||
# Note: The actual UART instances are created by the bridge component
|
||||
# This package just ensures all bridge components use the same ID naming convention
|
||||
# so they can be grouped together without conflicts
|
@ -0,0 +1,11 @@
|
||||
# Common configuration for 2-channel UART bridge/expander chips
|
||||
# Used by components like wk2132 that create 2 UART channels
|
||||
# Defines standardized UART IDs: uart_id_0, uart_id_1
|
||||
|
||||
substitutions:
|
||||
# These will be overridden by component-specific values
|
||||
uart_bridge_address: "0x70"
|
||||
|
||||
# Note: The actual UART instances are created by the bridge component
|
||||
# This package just ensures all bridge components use the same ID naming convention
|
||||
# so they can be grouped together without conflicts
|
@ -0,0 +1,11 @@
|
||||
# Common configuration for 4-channel UART bridge/expander chips
|
||||
# Used by components like wk2168, wk2204, wk2212 that create 4 UART channels
|
||||
# Defines standardized UART IDs: uart_id_0, uart_id_1, uart_id_2, uart_id_3
|
||||
|
||||
substitutions:
|
||||
# These will be overridden by component-specific values
|
||||
uart_bridge_address: "0x70"
|
||||
|
||||
# Note: The actual UART instances are created by the bridge component
|
||||
# This package just ensures all bridge components use the same ID naming convention
|
||||
# so they can be grouped together without conflicts
|
@ -0,0 +1,11 @@
|
||||
# Common configuration for 4-channel UART bridge/expander chips
|
||||
# Used by components like wk2168, wk2204, wk2212 that create 4 UART channels
|
||||
# Defines standardized UART IDs: uart_id_0, uart_id_1, uart_id_2, uart_id_3
|
||||
|
||||
substitutions:
|
||||
# These will be overridden by component-specific values
|
||||
uart_bridge_address: "0x70"
|
||||
|
||||
# Note: The actual UART instances are created by the bridge component
|
||||
# This package just ensures all bridge components use the same ID naming convention
|
||||
# so they can be grouped together without conflicts
|
10
tests/test_build_components/partitions_testing.csv
Normal file
10
tests/test_build_components/partitions_testing.csv
Normal file
@ -0,0 +1,10 @@
|
||||
# ESP-IDF Partition Table for ESPHome Component Testing
|
||||
# Single app partition to maximize space for large component group testing
|
||||
# Fits in 4MB flash
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x4000,
|
||||
otadata, data, ota, , 0x2000,
|
||||
phy_init, data, phy, , 0x1000,
|
||||
factory, app, factory, 0x10000, 0x300000,
|
||||
nvs_key, data, nvs_keys,, 0x1000,
|
||||
coredump, data, coredump,, 0xEB000,
|
|
Loading…
x
Reference in New Issue
Block a user