mirror of
https://github.com/esphome/esphome.git
synced 2026-10-01 17:00:21 +00:00
Merge branch 'dev' into copilot/fix-10045
This commit is contained in:
@@ -29,7 +29,7 @@ def test_binary_sensor_sets_mandatory_fields(generate_main):
|
||||
)
|
||||
|
||||
# Then
|
||||
assert 'bs_1->set_name("test bs1");' in main_cpp
|
||||
assert 'bs_1->set_name_and_object_id("test bs1", "test_bs1");' in main_cpp
|
||||
assert "bs_1->set_pin(" in main_cpp
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ def test_button_sets_mandatory_fields(generate_main):
|
||||
main_cpp = generate_main("tests/component_tests/button/test_button.yaml")
|
||||
|
||||
# Then
|
||||
assert 'wol_1->set_name("wol_test_1");' in main_cpp
|
||||
assert 'wol_1->set_name_and_object_id("wol_test_1", "wol_test_1");' in main_cpp
|
||||
assert "wol_2->set_macaddr(18, 52, 86, 120, 144, 171);" in main_cpp
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from collections.abc import Callable, Generator
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import Any
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -17,6 +18,7 @@ from esphome.const import (
|
||||
PlatformFramework,
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
from esphome.util import OrderedDict
|
||||
|
||||
# Add package root to python path
|
||||
here = Path(__file__).parent
|
||||
@@ -40,9 +42,9 @@ def config_path(request: pytest.FixtureRequest) -> Generator[None]:
|
||||
if config_dir.exists():
|
||||
# Set config_path to a dummy yaml file in the config directory
|
||||
# This ensures CORE.config_dir points to the config directory
|
||||
CORE.config_path = str(config_dir / "dummy.yaml")
|
||||
CORE.config_path = config_dir / "dummy.yaml"
|
||||
else:
|
||||
CORE.config_path = str(Path(request.fspath).parent / "dummy.yaml")
|
||||
CORE.config_path = Path(request.fspath).parent / "dummy.yaml"
|
||||
|
||||
yield
|
||||
CORE.config_path = original_path
|
||||
@@ -129,9 +131,35 @@ def generate_main() -> Generator[Callable[[str | Path], str]]:
|
||||
"""Generates the C++ main.cpp from a given yaml file and returns it in string form."""
|
||||
|
||||
def generator(path: str | Path) -> str:
|
||||
CORE.config_path = str(path)
|
||||
CORE.config_path = Path(path)
|
||||
CORE.config = read_config({})
|
||||
generate_cpp_contents(CORE.config)
|
||||
return CORE.cpp_main_section
|
||||
|
||||
yield generator
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_clone_or_update() -> Generator[Any]:
|
||||
"""Mock git.clone_or_update for testing."""
|
||||
with mock.patch("esphome.git.clone_or_update") as mock_func:
|
||||
# Default return value
|
||||
mock_func.return_value = (Path("/tmp/test"), None)
|
||||
yield mock_func
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_load_yaml() -> Generator[Any]:
|
||||
"""Mock yaml_util.load_yaml for testing."""
|
||||
|
||||
with mock.patch("esphome.yaml_util.load_yaml") as mock_func:
|
||||
# Default return value
|
||||
mock_func.return_value = OrderedDict({"sensor": []})
|
||||
yield mock_func
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_install_meta_finder() -> Generator[Any]:
|
||||
"""Mock loader.install_meta_finder for testing."""
|
||||
with mock.patch("esphome.loader.install_meta_finder") as mock_func:
|
||||
yield mock_func
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
"""Tests for the external_components skip_update functionality."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from esphome.components.external_components import do_external_components_pass
|
||||
from esphome.const import (
|
||||
CONF_EXTERNAL_COMPONENTS,
|
||||
CONF_REFRESH,
|
||||
CONF_SOURCE,
|
||||
CONF_URL,
|
||||
TYPE_GIT,
|
||||
)
|
||||
|
||||
|
||||
def test_external_components_skip_update_true(
|
||||
tmp_path: Path, mock_clone_or_update: MagicMock, mock_install_meta_finder: MagicMock
|
||||
) -> None:
|
||||
"""Test that external components don't update when skip_update=True."""
|
||||
# Create a components directory structure
|
||||
components_dir = tmp_path / "components"
|
||||
components_dir.mkdir()
|
||||
|
||||
# Create a test component
|
||||
test_component_dir = components_dir / "test_component"
|
||||
test_component_dir.mkdir()
|
||||
(test_component_dir / "__init__.py").write_text("# Test component")
|
||||
|
||||
# Set up mock to return our tmp_path
|
||||
mock_clone_or_update.return_value = (tmp_path, None)
|
||||
|
||||
config: dict[str, Any] = {
|
||||
CONF_EXTERNAL_COMPONENTS: [
|
||||
{
|
||||
CONF_SOURCE: {
|
||||
"type": TYPE_GIT,
|
||||
CONF_URL: "https://github.com/test/components",
|
||||
},
|
||||
CONF_REFRESH: "1d",
|
||||
"components": "all",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Call with skip_update=True
|
||||
do_external_components_pass(config, skip_update=True)
|
||||
|
||||
# Verify clone_or_update was called with NEVER_REFRESH
|
||||
mock_clone_or_update.assert_called_once()
|
||||
call_args = mock_clone_or_update.call_args
|
||||
from esphome import git
|
||||
|
||||
assert call_args.kwargs["refresh"] == git.NEVER_REFRESH
|
||||
|
||||
|
||||
def test_external_components_skip_update_false(
|
||||
tmp_path: Path, mock_clone_or_update: MagicMock, mock_install_meta_finder: MagicMock
|
||||
) -> None:
|
||||
"""Test that external components update when skip_update=False."""
|
||||
# Create a components directory structure
|
||||
components_dir = tmp_path / "components"
|
||||
components_dir.mkdir()
|
||||
|
||||
# Create a test component
|
||||
test_component_dir = components_dir / "test_component"
|
||||
test_component_dir.mkdir()
|
||||
(test_component_dir / "__init__.py").write_text("# Test component")
|
||||
|
||||
# Set up mock to return our tmp_path
|
||||
mock_clone_or_update.return_value = (tmp_path, None)
|
||||
|
||||
config: dict[str, Any] = {
|
||||
CONF_EXTERNAL_COMPONENTS: [
|
||||
{
|
||||
CONF_SOURCE: {
|
||||
"type": TYPE_GIT,
|
||||
CONF_URL: "https://github.com/test/components",
|
||||
},
|
||||
CONF_REFRESH: "1d",
|
||||
"components": "all",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Call with skip_update=False
|
||||
do_external_components_pass(config, skip_update=False)
|
||||
|
||||
# Verify clone_or_update was called with actual refresh value
|
||||
mock_clone_or_update.assert_called_once()
|
||||
call_args = mock_clone_or_update.call_args
|
||||
from esphome.core import TimePeriodSeconds
|
||||
|
||||
assert call_args.kwargs["refresh"] == TimePeriodSeconds(days=1)
|
||||
|
||||
|
||||
def test_external_components_default_no_skip(
|
||||
tmp_path: Path, mock_clone_or_update: MagicMock, mock_install_meta_finder: MagicMock
|
||||
) -> None:
|
||||
"""Test that external components update by default when skip_update not specified."""
|
||||
# Create a components directory structure
|
||||
components_dir = tmp_path / "components"
|
||||
components_dir.mkdir()
|
||||
|
||||
# Create a test component
|
||||
test_component_dir = components_dir / "test_component"
|
||||
test_component_dir.mkdir()
|
||||
(test_component_dir / "__init__.py").write_text("# Test component")
|
||||
|
||||
# Set up mock to return our tmp_path
|
||||
mock_clone_or_update.return_value = (tmp_path, None)
|
||||
|
||||
config: dict[str, Any] = {
|
||||
CONF_EXTERNAL_COMPONENTS: [
|
||||
{
|
||||
CONF_SOURCE: {
|
||||
"type": TYPE_GIT,
|
||||
CONF_URL: "https://github.com/test/components",
|
||||
},
|
||||
CONF_REFRESH: "1d",
|
||||
"components": "all",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Call without skip_update parameter
|
||||
do_external_components_pass(config)
|
||||
|
||||
# Verify clone_or_update was called with actual refresh value
|
||||
mock_clone_or_update.assert_called_once()
|
||||
call_args = mock_clone_or_update.call_args
|
||||
from esphome.core import TimePeriodSeconds
|
||||
|
||||
assert call_args.kwargs["refresh"] == TimePeriodSeconds(days=1)
|
||||
@@ -18,7 +18,8 @@ def test_gpio_binary_sensor_basic_setup(
|
||||
|
||||
assert "new gpio::GPIOBinarySensor();" in main_cpp
|
||||
assert "App.register_binary_sensor" in main_cpp
|
||||
assert "bs_gpio->set_use_interrupt(true);" in main_cpp
|
||||
# set_use_interrupt(true) should NOT be generated (uses C++ default)
|
||||
assert "bs_gpio->set_use_interrupt(true);" not in main_cpp
|
||||
assert "bs_gpio->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp
|
||||
|
||||
|
||||
@@ -51,8 +52,8 @@ def test_gpio_binary_sensor_esp8266_other_pins_use_interrupt(
|
||||
"tests/component_tests/gpio/test_gpio_binary_sensor_esp8266.yaml"
|
||||
)
|
||||
|
||||
# GPIO5 should still use interrupts
|
||||
assert "bs_gpio5->set_use_interrupt(true);" in main_cpp
|
||||
# GPIO5 should still use interrupts (default, so no setter call)
|
||||
assert "bs_gpio5->set_use_interrupt(true);" not in main_cpp
|
||||
assert "bs_gpio5->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp
|
||||
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ def run_schema_validation(config: ConfigType) -> None:
|
||||
{
|
||||
"id": "display_id",
|
||||
"model": "custom",
|
||||
"dimensions": {"width": 320, "height": 240},
|
||||
"dimensions": {"width": 260, "height": 260},
|
||||
"draw_rounding": 13,
|
||||
"init_sequence": [[0xA0, 0x01]],
|
||||
},
|
||||
@@ -220,7 +220,7 @@ def test_esp32s3_specific_errors(
|
||||
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32S3},
|
||||
platform_data={KEY_BOARD: "esp32-s3-devkitc-1", KEY_VARIANT: VARIANT_ESP32S3},
|
||||
)
|
||||
|
||||
with pytest.raises(cv.Invalid, match=error_match):
|
||||
@@ -250,7 +250,7 @@ def test_custom_model_with_all_options(
|
||||
"""Test custom model configuration with all available options."""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32S3},
|
||||
platform_data={KEY_BOARD: "esp32-s3-devkitc-1", KEY_VARIANT: VARIANT_ESP32S3},
|
||||
)
|
||||
|
||||
run_schema_validation(
|
||||
@@ -293,7 +293,7 @@ def test_all_predefined_models(
|
||||
"""Test all predefined display models validate successfully with appropriate defaults."""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32S3},
|
||||
platform_data={KEY_BOARD: "esp32-s3-devkitc-1", KEY_VARIANT: VARIANT_ESP32S3},
|
||||
)
|
||||
|
||||
# Enable PSRAM which is required for some models
|
||||
@@ -336,7 +336,7 @@ def test_native_generation(
|
||||
|
||||
main_cpp = generate_main(component_fixture_path("native.yaml"))
|
||||
assert (
|
||||
"mipi_spi::MipiSpiBuffer<uint16_t, mipi_spi::PIXEL_MODE_16, true, mipi_spi::PIXEL_MODE_16, mipi_spi::BUS_TYPE_QUAD, 360, 360, 0, 1, display::DISPLAY_ROTATION_0_DEGREES, 1>()"
|
||||
"mipi_spi::MipiSpiBuffer<uint16_t, mipi_spi::PIXEL_MODE_16, true, mipi_spi::PIXEL_MODE_16, mipi_spi::BUS_TYPE_QUAD, 360, 360, 0, 1, display::DISPLAY_ROTATION_0_DEGREES, 1, 1>()"
|
||||
in main_cpp
|
||||
)
|
||||
assert "set_init_sequence({240, 1, 8, 242" in main_cpp
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
"""Tests for the web_server OTA platform."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.web_server.ota import _web_server_ota_final_validate
|
||||
from esphome.const import CONF_ID, CONF_OTA, CONF_PLATFORM, CONF_WEB_SERVER
|
||||
from esphome.core import ID
|
||||
import esphome.final_validate as fv
|
||||
|
||||
|
||||
def test_web_server_ota_generated(generate_main: Callable[[str], str]) -> None:
|
||||
@@ -100,3 +112,144 @@ def test_web_server_ota_esp8266(generate_main: Callable[[str], str]) -> None:
|
||||
# Check web server OTA component is present
|
||||
assert "WebServerOTAComponent" in main_cpp
|
||||
assert "web_server::WebServerOTAComponent" in main_cpp
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("ota_configs", "expected_count", "warning_expected"),
|
||||
[
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
CONF_PLATFORM: CONF_WEB_SERVER,
|
||||
CONF_ID: ID("ota_web", is_manual=False),
|
||||
}
|
||||
],
|
||||
1,
|
||||
False,
|
||||
id="single_instance_no_merge",
|
||||
),
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
CONF_PLATFORM: CONF_WEB_SERVER,
|
||||
CONF_ID: ID("ota_web_1", is_manual=False),
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_WEB_SERVER,
|
||||
CONF_ID: ID("ota_web_2", is_manual=False),
|
||||
},
|
||||
],
|
||||
1,
|
||||
True,
|
||||
id="two_instances_merged",
|
||||
),
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
CONF_PLATFORM: CONF_WEB_SERVER,
|
||||
CONF_ID: ID("ota_web_1", is_manual=False),
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: "esphome",
|
||||
CONF_ID: ID("ota_esphome", is_manual=False),
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_WEB_SERVER,
|
||||
CONF_ID: ID("ota_web_2", is_manual=False),
|
||||
},
|
||||
],
|
||||
2,
|
||||
True,
|
||||
id="mixed_platforms_web_server_merged",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_web_server_ota_instance_merging(
|
||||
ota_configs: list[dict[str, Any]],
|
||||
expected_count: int,
|
||||
warning_expected: bool,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test web_server OTA instance merging behavior."""
|
||||
full_conf = {CONF_OTA: ota_configs.copy()}
|
||||
|
||||
token = fv.full_config.set(full_conf)
|
||||
try:
|
||||
with caplog.at_level(logging.WARNING):
|
||||
_web_server_ota_final_validate({})
|
||||
|
||||
updated_conf = fv.full_config.get()
|
||||
|
||||
# Verify total number of OTA platforms
|
||||
assert len(updated_conf[CONF_OTA]) == expected_count
|
||||
|
||||
# Verify warning
|
||||
if warning_expected:
|
||||
assert any(
|
||||
"Found and merged" in record.message
|
||||
and "web_server OTA" in record.message
|
||||
for record in caplog.records
|
||||
), "Expected merge warning not found in log"
|
||||
else:
|
||||
assert len(caplog.records) == 0, "Unexpected warnings logged"
|
||||
finally:
|
||||
fv.full_config.reset(token)
|
||||
|
||||
|
||||
def test_web_server_ota_consistent_manual_ids(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test that consistent manual IDs can be merged successfully."""
|
||||
ota_configs = [
|
||||
{
|
||||
CONF_PLATFORM: CONF_WEB_SERVER,
|
||||
CONF_ID: ID("ota_web", is_manual=True),
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_WEB_SERVER,
|
||||
CONF_ID: ID("ota_web", is_manual=True),
|
||||
},
|
||||
]
|
||||
|
||||
full_conf = {CONF_OTA: ota_configs}
|
||||
|
||||
token = fv.full_config.set(full_conf)
|
||||
try:
|
||||
with caplog.at_level(logging.WARNING):
|
||||
_web_server_ota_final_validate({})
|
||||
|
||||
updated_conf = fv.full_config.get()
|
||||
assert len(updated_conf[CONF_OTA]) == 1
|
||||
assert updated_conf[CONF_OTA][0][CONF_ID].id == "ota_web"
|
||||
assert any(
|
||||
"Found and merged" in record.message and "web_server OTA" in record.message
|
||||
for record in caplog.records
|
||||
)
|
||||
finally:
|
||||
fv.full_config.reset(token)
|
||||
|
||||
|
||||
def test_web_server_ota_inconsistent_manual_ids() -> None:
|
||||
"""Test that inconsistent manual IDs raise an error."""
|
||||
ota_configs = [
|
||||
{
|
||||
CONF_PLATFORM: CONF_WEB_SERVER,
|
||||
CONF_ID: ID("ota_web_1", is_manual=True),
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_WEB_SERVER,
|
||||
CONF_ID: ID("ota_web_2", is_manual=True),
|
||||
},
|
||||
]
|
||||
|
||||
full_conf = {CONF_OTA: ota_configs}
|
||||
|
||||
token = fv.full_config.set(full_conf)
|
||||
try:
|
||||
with pytest.raises(
|
||||
cv.Invalid,
|
||||
match="Found multiple web_server OTA configurations but id is inconsistent",
|
||||
):
|
||||
_web_server_ota_final_validate({})
|
||||
finally:
|
||||
fv.full_config.reset(token)
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
"""Tests for the packages component skip_update functionality."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from esphome.components.packages import do_packages_pass
|
||||
from esphome.const import CONF_FILES, CONF_PACKAGES, CONF_REFRESH, CONF_URL
|
||||
from esphome.util import OrderedDict
|
||||
|
||||
|
||||
def test_packages_skip_update_true(
|
||||
tmp_path: Path, mock_clone_or_update: MagicMock, mock_load_yaml: MagicMock
|
||||
) -> None:
|
||||
"""Test that packages don't update when skip_update=True."""
|
||||
# Set up mock to return our tmp_path
|
||||
mock_clone_or_update.return_value = (tmp_path, None)
|
||||
|
||||
# Create the test yaml file
|
||||
test_file = tmp_path / "test.yaml"
|
||||
test_file.write_text("sensor: []")
|
||||
|
||||
# Set mock_load_yaml to return some valid config
|
||||
mock_load_yaml.return_value = OrderedDict({"sensor": []})
|
||||
|
||||
config: dict[str, Any] = {
|
||||
CONF_PACKAGES: {
|
||||
"test_package": {
|
||||
CONF_URL: "https://github.com/test/repo",
|
||||
CONF_FILES: ["test.yaml"],
|
||||
CONF_REFRESH: "1d",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Call with skip_update=True
|
||||
do_packages_pass(config, skip_update=True)
|
||||
|
||||
# Verify clone_or_update was called with NEVER_REFRESH
|
||||
mock_clone_or_update.assert_called_once()
|
||||
call_args = mock_clone_or_update.call_args
|
||||
from esphome import git
|
||||
|
||||
assert call_args.kwargs["refresh"] == git.NEVER_REFRESH
|
||||
|
||||
|
||||
def test_packages_skip_update_false(
|
||||
tmp_path: Path, mock_clone_or_update: MagicMock, mock_load_yaml: MagicMock
|
||||
) -> None:
|
||||
"""Test that packages update when skip_update=False."""
|
||||
# Set up mock to return our tmp_path
|
||||
mock_clone_or_update.return_value = (tmp_path, None)
|
||||
|
||||
# Create the test yaml file
|
||||
test_file = tmp_path / "test.yaml"
|
||||
test_file.write_text("sensor: []")
|
||||
|
||||
# Set mock_load_yaml to return some valid config
|
||||
mock_load_yaml.return_value = OrderedDict({"sensor": []})
|
||||
|
||||
config: dict[str, Any] = {
|
||||
CONF_PACKAGES: {
|
||||
"test_package": {
|
||||
CONF_URL: "https://github.com/test/repo",
|
||||
CONF_FILES: ["test.yaml"],
|
||||
CONF_REFRESH: "1d",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Call with skip_update=False (default)
|
||||
do_packages_pass(config, skip_update=False)
|
||||
|
||||
# Verify clone_or_update was called with actual refresh value
|
||||
mock_clone_or_update.assert_called_once()
|
||||
call_args = mock_clone_or_update.call_args
|
||||
from esphome.core import TimePeriodSeconds
|
||||
|
||||
assert call_args.kwargs["refresh"] == TimePeriodSeconds(days=1)
|
||||
|
||||
|
||||
def test_packages_default_no_skip(
|
||||
tmp_path: Path, mock_clone_or_update: MagicMock, mock_load_yaml: MagicMock
|
||||
) -> None:
|
||||
"""Test that packages update by default when skip_update not specified."""
|
||||
# Set up mock to return our tmp_path
|
||||
mock_clone_or_update.return_value = (tmp_path, None)
|
||||
|
||||
# Create the test yaml file
|
||||
test_file = tmp_path / "test.yaml"
|
||||
test_file.write_text("sensor: []")
|
||||
|
||||
# Set mock_load_yaml to return some valid config
|
||||
mock_load_yaml.return_value = OrderedDict({"sensor": []})
|
||||
|
||||
config: dict[str, Any] = {
|
||||
CONF_PACKAGES: {
|
||||
"test_package": {
|
||||
CONF_URL: "https://github.com/test/repo",
|
||||
CONF_FILES: ["test.yaml"],
|
||||
CONF_REFRESH: "1d",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Call without skip_update parameter
|
||||
do_packages_pass(config)
|
||||
|
||||
# Verify clone_or_update was called with actual refresh value
|
||||
mock_clone_or_update.assert_called_once()
|
||||
call_args = mock_clone_or_update.call_args
|
||||
from esphome.core import TimePeriodSeconds
|
||||
|
||||
assert call_args.kwargs["refresh"] == TimePeriodSeconds(days=1)
|
||||
@@ -5,7 +5,8 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components.packages import do_packages_pass
|
||||
from esphome.components.packages import CONFIG_SCHEMA, do_packages_pass
|
||||
from esphome.config import resolve_extend_remove
|
||||
from esphome.config_helpers import Extend, Remove
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -64,13 +65,20 @@ def fixture_basic_esphome():
|
||||
return {CONF_NAME: TEST_DEVICE_NAME, CONF_PLATFORM: TEST_PLATFORM}
|
||||
|
||||
|
||||
def packages_pass(config):
|
||||
"""Wrapper around packages_pass that also resolves Extend and Remove."""
|
||||
config = do_packages_pass(config)
|
||||
resolve_extend_remove(config)
|
||||
return config
|
||||
|
||||
|
||||
def test_package_unused(basic_esphome, basic_wifi):
|
||||
"""
|
||||
Ensures do_package_pass does not change a config if packages aren't used.
|
||||
"""
|
||||
config = {CONF_ESPHOME: basic_esphome, CONF_WIFI: basic_wifi}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
actual = packages_pass(config)
|
||||
assert actual == config
|
||||
|
||||
|
||||
@@ -83,7 +91,51 @@ def test_package_invalid_dict(basic_esphome, basic_wifi):
|
||||
config = {CONF_ESPHOME: basic_esphome, CONF_PACKAGES: basic_wifi | {CONF_URL: ""}}
|
||||
|
||||
with pytest.raises(cv.Invalid):
|
||||
do_packages_pass(config)
|
||||
packages_pass(config)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"packages",
|
||||
[
|
||||
{"package1": "github://esphome/non-existant-repo/file1.yml@main"},
|
||||
{"package2": "github://esphome/non-existant-repo/file1.yml"},
|
||||
{"package3": "github://esphome/non-existant-repo/other-folder/file1.yml"},
|
||||
[
|
||||
"github://esphome/non-existant-repo/file1.yml@main",
|
||||
"github://esphome/non-existant-repo/file1.yml",
|
||||
"github://esphome/non-existant-repo/other-folder/file1.yml",
|
||||
],
|
||||
],
|
||||
)
|
||||
def test_package_shorthand(packages):
|
||||
CONFIG_SCHEMA(packages)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"packages",
|
||||
[
|
||||
# not github
|
||||
{"package1": "someplace://esphome/non-existant-repo/file1.yml@main"},
|
||||
# missing repo
|
||||
{"package2": "github://esphome/file1.yml"},
|
||||
# missing file
|
||||
{"package3": "github://esphome/non-existant-repo/@main"},
|
||||
{"a": "invalid string, not shorthand"},
|
||||
"some string",
|
||||
3,
|
||||
False,
|
||||
{"a": 8},
|
||||
["someplace://esphome/non-existant-repo/file1.yml@main"],
|
||||
["github://esphome/file1.yml"],
|
||||
["github://esphome/non-existant-repo/@main"],
|
||||
["some string"],
|
||||
[True],
|
||||
[3],
|
||||
],
|
||||
)
|
||||
def test_package_invalid(packages):
|
||||
with pytest.raises(cv.Invalid):
|
||||
CONFIG_SCHEMA(packages)
|
||||
|
||||
|
||||
def test_package_include(basic_wifi, basic_esphome):
|
||||
@@ -99,10 +151,37 @@ def test_package_include(basic_wifi, basic_esphome):
|
||||
|
||||
expected = {CONF_ESPHOME: basic_esphome, CONF_WIFI: basic_wifi}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
actual = packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_single_package(
|
||||
basic_esphome,
|
||||
basic_wifi,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
):
|
||||
"""
|
||||
Tests the simple case where a single package is added to the top-level config as is.
|
||||
In this test, the CONF_WIFI config is expected to be simply added to the top-level config.
|
||||
This tests the case where the user just put packages: !include package.yaml, not
|
||||
part of a list or mapping of packages.
|
||||
This behavior is deprecated, the test also checks if a warning is issued.
|
||||
"""
|
||||
config = {CONF_ESPHOME: basic_esphome, CONF_PACKAGES: {CONF_WIFI: basic_wifi}}
|
||||
|
||||
expected = {CONF_ESPHOME: basic_esphome, CONF_WIFI: basic_wifi}
|
||||
|
||||
with caplog.at_level("WARNING"):
|
||||
actual = packages_pass(config)
|
||||
|
||||
assert actual == expected
|
||||
|
||||
assert (
|
||||
"Including a single package under `packages:` is deprecated. Use a list instead."
|
||||
in caplog.text
|
||||
)
|
||||
|
||||
|
||||
def test_package_append(basic_wifi, basic_esphome):
|
||||
"""
|
||||
Tests the case where a key is present in both a package and top-level config.
|
||||
@@ -124,7 +203,7 @@ def test_package_append(basic_wifi, basic_esphome):
|
||||
},
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
actual = packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@@ -148,7 +227,7 @@ def test_package_override(basic_wifi, basic_esphome):
|
||||
},
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
actual = packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@@ -177,7 +256,7 @@ def test_multiple_package_order():
|
||||
},
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
actual = packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@@ -233,7 +312,7 @@ def test_package_list_merge():
|
||||
]
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
actual = packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@@ -311,7 +390,7 @@ def test_package_list_merge_by_id():
|
||||
]
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
actual = packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@@ -350,13 +429,13 @@ def test_package_merge_by_id_with_list():
|
||||
]
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
actual = packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_package_merge_by_missing_id():
|
||||
"""
|
||||
Ensures that components with missing IDs are not merged.
|
||||
Ensures that a validation error is thrown when trying to extend a missing ID.
|
||||
"""
|
||||
|
||||
config = {
|
||||
@@ -379,25 +458,15 @@ def test_package_merge_by_missing_id():
|
||||
],
|
||||
}
|
||||
|
||||
expected = {
|
||||
CONF_SENSOR: [
|
||||
{
|
||||
CONF_ID: TEST_SENSOR_ID_1,
|
||||
CONF_FILTERS: [{CONF_MULTIPLY: 42.0}],
|
||||
},
|
||||
{
|
||||
CONF_ID: TEST_SENSOR_ID_1,
|
||||
CONF_FILTERS: [{CONF_MULTIPLY: 10.0}],
|
||||
},
|
||||
{
|
||||
CONF_ID: Extend(TEST_SENSOR_ID_2),
|
||||
CONF_FILTERS: [{CONF_OFFSET: 146.0}],
|
||||
},
|
||||
]
|
||||
}
|
||||
error_raised = False
|
||||
try:
|
||||
packages_pass(config)
|
||||
assert False, "Expected validation error for missing ID"
|
||||
except cv.Invalid as err:
|
||||
error_raised = True
|
||||
assert err.path == [CONF_SENSOR, 2]
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
assert actual == expected
|
||||
assert error_raised
|
||||
|
||||
|
||||
def test_package_list_remove_by_id():
|
||||
@@ -447,7 +516,7 @@ def test_package_list_remove_by_id():
|
||||
]
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
actual = packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@@ -493,7 +562,7 @@ def test_multiple_package_list_remove_by_id():
|
||||
]
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
actual = packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@@ -514,7 +583,7 @@ def test_package_dict_remove_by_id(basic_wifi, basic_esphome):
|
||||
CONF_ESPHOME: basic_esphome,
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
actual = packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@@ -545,7 +614,6 @@ def test_package_remove_by_missing_id():
|
||||
}
|
||||
|
||||
expected = {
|
||||
"missing_key": Remove(),
|
||||
CONF_SENSOR: [
|
||||
{
|
||||
CONF_ID: TEST_SENSOR_ID_1,
|
||||
@@ -555,14 +623,10 @@ def test_package_remove_by_missing_id():
|
||||
CONF_ID: TEST_SENSOR_ID_1,
|
||||
CONF_FILTERS: [{CONF_MULTIPLY: 10.0}],
|
||||
},
|
||||
{
|
||||
CONF_ID: Remove(TEST_SENSOR_ID_2),
|
||||
CONF_FILTERS: [{CONF_OFFSET: 146.0}],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
actual = packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@@ -634,7 +698,7 @@ def test_remote_packages_with_files_list(
|
||||
]
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
actual = packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@@ -730,5 +794,5 @@ def test_remote_packages_with_files_and_vars(
|
||||
]
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
actual = packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
"""Tests for PSRAM component."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components.esp32.const import (
|
||||
KEY_VARIANT,
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32P4,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ESPHOME, PlatformFramework
|
||||
from tests.component_tests.types import SetCoreConfigCallable
|
||||
|
||||
UNSUPPORTED_PSRAM_VARIANTS = [
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
]
|
||||
|
||||
SUPPORTED_PSRAM_VARIANTS = [
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
VARIANT_ESP32P4,
|
||||
]
|
||||
SUPPORTED_PSRAM_MODES = {
|
||||
VARIANT_ESP32: ["quad"],
|
||||
VARIANT_ESP32S2: ["quad"],
|
||||
VARIANT_ESP32S3: ["quad", "octal"],
|
||||
VARIANT_ESP32P4: ["hex"],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("config", "error_match"),
|
||||
[
|
||||
pytest.param(
|
||||
{},
|
||||
r"PSRAM is not supported on this chip",
|
||||
id="psram_not_supported",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("variant", UNSUPPORTED_PSRAM_VARIANTS)
|
||||
def test_psram_configuration_errors_unsupported_variants(
|
||||
config: Any,
|
||||
error_match: str,
|
||||
variant: str,
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_VARIANT: variant},
|
||||
full_config={CONF_ESPHOME: {}},
|
||||
)
|
||||
"""Test detection of invalid PSRAM configuration on unsupported variants."""
|
||||
from esphome.components.psram import CONFIG_SCHEMA
|
||||
|
||||
with pytest.raises(cv.Invalid, match=error_match):
|
||||
CONFIG_SCHEMA(config)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("variant", SUPPORTED_PSRAM_VARIANTS)
|
||||
def test_psram_configuration_valid_supported_variants(
|
||||
variant: str,
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_VARIANT: variant},
|
||||
full_config={
|
||||
CONF_ESPHOME: {},
|
||||
"esp32": {
|
||||
"variant": variant,
|
||||
"cpu_frequency": "160MHz",
|
||||
"framework": {"type": "esp-idf"},
|
||||
},
|
||||
},
|
||||
)
|
||||
"""Test that PSRAM configuration is valid on supported variants."""
|
||||
from esphome.components.psram import CONFIG_SCHEMA, FINAL_VALIDATE_SCHEMA
|
||||
|
||||
# This should not raise an exception
|
||||
config = CONFIG_SCHEMA({"mode": SUPPORTED_PSRAM_MODES[variant][0]})
|
||||
FINAL_VALIDATE_SCHEMA(config)
|
||||
|
||||
|
||||
def _setup_psram_final_validation_test(
|
||||
esp32_config: dict,
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
set_component_config: Any,
|
||||
) -> str:
|
||||
"""Helper function to set up ESP32 configuration for PSRAM final validation tests."""
|
||||
# Use ESP32S3 for schema validation to allow all options, then override for final validation
|
||||
schema_variant = "ESP32S3"
|
||||
final_variant = esp32_config.get("variant", "ESP32S3")
|
||||
full_esp32_config = {
|
||||
"variant": final_variant,
|
||||
"cpu_frequency": esp32_config.get("cpu_frequency", "240MHz"),
|
||||
"framework": {"type": "esp-idf"},
|
||||
}
|
||||
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_VARIANT: schema_variant},
|
||||
full_config={
|
||||
CONF_ESPHOME: {},
|
||||
"esp32": full_esp32_config,
|
||||
},
|
||||
)
|
||||
set_component_config("esp32", full_esp32_config)
|
||||
|
||||
return final_variant
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("config", "esp32_config", "expect_error", "error_match"),
|
||||
[
|
||||
pytest.param(
|
||||
{"mode": "quad", "speed": "120MHz"},
|
||||
{"cpu_frequency": "160MHz"},
|
||||
True,
|
||||
r"PSRAM 120MHz requires 240MHz CPU frequency",
|
||||
id="120mhz_requires_240mhz_cpu",
|
||||
),
|
||||
pytest.param(
|
||||
{"mode": "octal"},
|
||||
{"variant": "ESP32"},
|
||||
True,
|
||||
r"Octal PSRAM is only supported on ESP32-S3",
|
||||
id="octal_mode_only_esp32s3",
|
||||
),
|
||||
pytest.param(
|
||||
{"mode": "quad", "enable_ecc": True},
|
||||
{},
|
||||
True,
|
||||
r"ECC is only available in octal mode",
|
||||
id="ecc_only_in_octal_mode",
|
||||
),
|
||||
pytest.param(
|
||||
{"mode": "quad", "speed": "120MHZ"},
|
||||
{"cpu_frequency": "240MHZ"},
|
||||
False,
|
||||
None,
|
||||
id="120mhz_with_240mhz_cpu",
|
||||
),
|
||||
pytest.param(
|
||||
{"mode": "octal"},
|
||||
{"variant": "ESP32S3"},
|
||||
False,
|
||||
None,
|
||||
id="octal_mode_on_esp32s3",
|
||||
),
|
||||
pytest.param(
|
||||
{"mode": "octal", "enable_ecc": True},
|
||||
{"variant": "ESP32S3"},
|
||||
False,
|
||||
None,
|
||||
id="ecc_in_octal_mode",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_psram_final_validation(
|
||||
config: Any,
|
||||
esp32_config: dict,
|
||||
expect_error: bool,
|
||||
error_match: str | None,
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
set_component_config: Any,
|
||||
) -> None:
|
||||
"""Test PSRAM final validation for both error and valid cases."""
|
||||
from esphome.components.psram import CONFIG_SCHEMA, FINAL_VALIDATE_SCHEMA
|
||||
from esphome.core import CORE
|
||||
|
||||
final_variant = _setup_psram_final_validation_test(
|
||||
esp32_config, set_core_config, set_component_config
|
||||
)
|
||||
|
||||
validated_config = CONFIG_SCHEMA(config)
|
||||
|
||||
# Update CORE variant for final validation
|
||||
CORE.data["esp32"][KEY_VARIANT] = final_variant
|
||||
|
||||
if expect_error:
|
||||
with pytest.raises(cv.Invalid, match=error_match):
|
||||
FINAL_VALIDATE_SCHEMA(validated_config)
|
||||
else:
|
||||
# This should not raise an exception
|
||||
FINAL_VALIDATE_SCHEMA(validated_config)
|
||||
@@ -0,0 +1 @@
|
||||
"""Tests for SNTP component."""
|
||||
@@ -0,0 +1,22 @@
|
||||
esphome:
|
||||
name: sntp-test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
wifi:
|
||||
ssid: "testssid"
|
||||
password: "testpassword"
|
||||
|
||||
# Test multiple SNTP instances that should be merged
|
||||
time:
|
||||
- platform: sntp
|
||||
servers:
|
||||
- 192.168.1.1
|
||||
- pool.ntp.org
|
||||
- platform: sntp
|
||||
servers:
|
||||
- pool.ntp.org
|
||||
- 192.168.1.2
|
||||
@@ -0,0 +1,238 @@
|
||||
"""Tests for SNTP time configuration validation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.sntp.time import CONF_SNTP, _sntp_final_validate
|
||||
from esphome.const import CONF_ID, CONF_PLATFORM, CONF_SERVERS, CONF_TIME
|
||||
from esphome.core import ID
|
||||
import esphome.final_validate as fv
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("time_configs", "expected_count", "expected_servers", "warning_messages"),
|
||||
[
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_ID: ID("sntp_time", is_manual=False),
|
||||
CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"],
|
||||
}
|
||||
],
|
||||
1,
|
||||
["192.168.1.1", "pool.ntp.org"],
|
||||
[],
|
||||
id="single_instance_no_merge",
|
||||
),
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_ID: ID("sntp_time_1", is_manual=False),
|
||||
CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"],
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_ID: ID("sntp_time_2", is_manual=False),
|
||||
CONF_SERVERS: ["192.168.1.2"],
|
||||
},
|
||||
],
|
||||
1,
|
||||
["192.168.1.1", "pool.ntp.org", "192.168.1.2"],
|
||||
["Found and merged 2 SNTP time configurations into one instance"],
|
||||
id="two_instances_merged",
|
||||
),
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_ID: ID("sntp_time_1", is_manual=False),
|
||||
CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"],
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_ID: ID("sntp_time_2", is_manual=False),
|
||||
CONF_SERVERS: ["pool.ntp.org", "192.168.1.2"],
|
||||
},
|
||||
],
|
||||
1,
|
||||
["192.168.1.1", "pool.ntp.org", "192.168.1.2"],
|
||||
["Found and merged 2 SNTP time configurations into one instance"],
|
||||
id="deduplication_preserves_order",
|
||||
),
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_ID: ID("sntp_time_1", is_manual=False),
|
||||
CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"],
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_ID: ID("sntp_time_2", is_manual=False),
|
||||
CONF_SERVERS: ["192.168.1.2", "pool2.ntp.org"],
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_ID: ID("sntp_time_3", is_manual=False),
|
||||
CONF_SERVERS: ["pool3.ntp.org"],
|
||||
},
|
||||
],
|
||||
1,
|
||||
["192.168.1.1", "pool.ntp.org", "192.168.1.2"],
|
||||
[
|
||||
"SNTP supports maximum 3 servers. Dropped excess server(s): ['pool2.ntp.org', 'pool3.ntp.org']",
|
||||
"Found and merged 3 SNTP time configurations into one instance",
|
||||
],
|
||||
id="three_instances_drops_excess_servers",
|
||||
),
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_ID: ID("sntp_time_1", is_manual=False),
|
||||
CONF_SERVERS: [
|
||||
"192.168.1.1",
|
||||
"pool.ntp.org",
|
||||
"pool.ntp.org",
|
||||
"192.168.1.1",
|
||||
],
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_ID: ID("sntp_time_2", is_manual=False),
|
||||
CONF_SERVERS: ["pool.ntp.org", "192.168.1.2"],
|
||||
},
|
||||
],
|
||||
1,
|
||||
["192.168.1.1", "pool.ntp.org", "192.168.1.2"],
|
||||
["Found and merged 2 SNTP time configurations into one instance"],
|
||||
id="deduplication_multiple_duplicates",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_sntp_instance_merging(
|
||||
time_configs: list[dict[str, Any]],
|
||||
expected_count: int,
|
||||
expected_servers: list[str],
|
||||
warning_messages: list[str],
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test SNTP instance merging behavior."""
|
||||
# Create a mock full config with time configs
|
||||
full_conf = {CONF_TIME: time_configs.copy()}
|
||||
|
||||
# Set the context var
|
||||
token = fv.full_config.set(full_conf)
|
||||
try:
|
||||
with caplog.at_level(logging.WARNING):
|
||||
_sntp_final_validate({})
|
||||
|
||||
# Get the updated config
|
||||
updated_conf = fv.full_config.get()
|
||||
|
||||
# Check if merging occurred
|
||||
if len(time_configs) > 1:
|
||||
# Verify only one SNTP instance remains
|
||||
sntp_instances = [
|
||||
tc
|
||||
for tc in updated_conf[CONF_TIME]
|
||||
if tc.get(CONF_PLATFORM) == CONF_SNTP
|
||||
]
|
||||
assert len(sntp_instances) == expected_count
|
||||
|
||||
# Verify server list
|
||||
assert sntp_instances[0][CONF_SERVERS] == expected_servers
|
||||
|
||||
# Verify warnings
|
||||
for expected_msg in warning_messages:
|
||||
assert any(
|
||||
expected_msg in record.message for record in caplog.records
|
||||
), f"Expected warning message '{expected_msg}' not found in log"
|
||||
else:
|
||||
# Single instance should not trigger merging or warnings
|
||||
assert len(caplog.records) == 0
|
||||
# Config should be unchanged
|
||||
assert updated_conf[CONF_TIME] == time_configs
|
||||
finally:
|
||||
fv.full_config.reset(token)
|
||||
|
||||
|
||||
def test_sntp_inconsistent_manual_ids() -> None:
|
||||
"""Test that inconsistent manual IDs raise an error."""
|
||||
# Create configs with manual IDs that are inconsistent
|
||||
time_configs = [
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_ID: ID("sntp_time_1", is_manual=True),
|
||||
CONF_SERVERS: ["192.168.1.1"],
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_ID: ID("sntp_time_2", is_manual=True),
|
||||
CONF_SERVERS: ["192.168.1.2"],
|
||||
},
|
||||
]
|
||||
|
||||
full_conf = {CONF_TIME: time_configs}
|
||||
|
||||
token = fv.full_config.set(full_conf)
|
||||
try:
|
||||
with pytest.raises(
|
||||
cv.Invalid,
|
||||
match="Found multiple SNTP configurations but id is inconsistent",
|
||||
):
|
||||
_sntp_final_validate({})
|
||||
finally:
|
||||
fv.full_config.reset(token)
|
||||
|
||||
|
||||
def test_sntp_with_other_time_platforms(caplog: pytest.LogCaptureFixture) -> None:
|
||||
"""Test that SNTP merging doesn't affect other time platforms."""
|
||||
time_configs = [
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_ID: ID("sntp_time_1", is_manual=False),
|
||||
CONF_SERVERS: ["192.168.1.1"],
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: "homeassistant",
|
||||
CONF_ID: ID("homeassistant_time", is_manual=False),
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_ID: ID("sntp_time_2", is_manual=False),
|
||||
CONF_SERVERS: ["192.168.1.2"],
|
||||
},
|
||||
]
|
||||
|
||||
full_conf = {CONF_TIME: time_configs.copy()}
|
||||
|
||||
token = fv.full_config.set(full_conf)
|
||||
try:
|
||||
with caplog.at_level(logging.WARNING):
|
||||
_sntp_final_validate({})
|
||||
|
||||
updated_conf = fv.full_config.get()
|
||||
|
||||
# Should have 2 time platforms: 1 merged SNTP + 1 homeassistant
|
||||
assert len(updated_conf[CONF_TIME]) == 2
|
||||
|
||||
# Find the platforms
|
||||
platforms = {tc[CONF_PLATFORM] for tc in updated_conf[CONF_TIME]}
|
||||
assert platforms == {CONF_SNTP, "homeassistant"}
|
||||
|
||||
# Verify SNTP was merged
|
||||
sntp_instances = [
|
||||
tc for tc in updated_conf[CONF_TIME] if tc[CONF_PLATFORM] == CONF_SNTP
|
||||
]
|
||||
assert len(sntp_instances) == 1
|
||||
assert sntp_instances[0][CONF_SERVERS] == ["192.168.1.1", "192.168.1.2"]
|
||||
finally:
|
||||
fv.full_config.reset(token)
|
||||
@@ -25,7 +25,7 @@ def test_text_sets_mandatory_fields(generate_main):
|
||||
main_cpp = generate_main("tests/component_tests/text/test_text.yaml")
|
||||
|
||||
# Then
|
||||
assert 'it_1->set_name("test 1 text");' in main_cpp
|
||||
assert 'it_1->set_name_and_object_id("test 1 text", "test_1_text");' in main_cpp
|
||||
|
||||
|
||||
def test_text_config_value_internal_set(generate_main):
|
||||
@@ -58,7 +58,7 @@ def test_text_config_value_mode_set(generate_main):
|
||||
|
||||
def test_text_config_lamda_is_set(generate_main):
|
||||
"""
|
||||
Test if lambda is set for lambda mode
|
||||
Test if lambda is set for lambda mode (optimized with stateless lambda)
|
||||
"""
|
||||
# Given
|
||||
|
||||
@@ -66,5 +66,5 @@ def test_text_config_lamda_is_set(generate_main):
|
||||
main_cpp = generate_main("tests/component_tests/text/test_text.yaml")
|
||||
|
||||
# Then
|
||||
assert "it_4->set_template([=]() -> esphome::optional<std::string> {" in main_cpp
|
||||
assert "it_4->set_template([]() -> esphome::optional<std::string> {" in main_cpp
|
||||
assert 'return std::string{"Hello"};' in main_cpp
|
||||
|
||||
@@ -25,9 +25,18 @@ def test_text_sensor_sets_mandatory_fields(generate_main):
|
||||
main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml")
|
||||
|
||||
# Then
|
||||
assert 'ts_1->set_name("Template Text Sensor 1");' in main_cpp
|
||||
assert 'ts_2->set_name("Template Text Sensor 2");' in main_cpp
|
||||
assert 'ts_3->set_name("Template Text Sensor 3");' in main_cpp
|
||||
assert (
|
||||
'ts_1->set_name_and_object_id("Template Text Sensor 1", "template_text_sensor_1");'
|
||||
in main_cpp
|
||||
)
|
||||
assert (
|
||||
'ts_2->set_name_and_object_id("Template Text Sensor 2", "template_text_sensor_2");'
|
||||
in main_cpp
|
||||
)
|
||||
assert (
|
||||
'ts_3->set_name_and_object_id("Template Text Sensor 3", "template_text_sensor_3");'
|
||||
in main_cpp
|
||||
)
|
||||
|
||||
|
||||
def test_text_sensor_config_value_internal_set(generate_main):
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Gitignore settings for ESPHome
|
||||
# This is an example and may include too much for your use-case.
|
||||
# You can modify this file to suit your needs.
|
||||
/.esphome/
|
||||
/secrets.yaml
|
||||
@@ -0,0 +1,32 @@
|
||||
# How to write C++ ESPHome unit tests
|
||||
|
||||
1. Locate the folder with your component or create a new one with the same name as the component.
|
||||
2. Write the tests. You can add as many `.cpp` and `.h` files as you need to organize your tests.
|
||||
|
||||
**IMPORTANT**: wrap all your testing code in a unique namespace to avoid linker collisions when compiling
|
||||
testing binaries that combine many components. By convention, this unique namespace is `esphome::component::testing`
|
||||
(where "component" is the component under test), for example: `esphome::uart::testing`.
|
||||
|
||||
|
||||
## Running component unit tests
|
||||
|
||||
(from the repository root)
|
||||
```bash
|
||||
./script/cpp_unit_test.py component1 component2 ...
|
||||
```
|
||||
|
||||
The above will compile and run the provided components and their tests.
|
||||
|
||||
To run all tests, you can invoke `cpp_unit_test.py` with the special `--all` flag:
|
||||
|
||||
```bash
|
||||
./script/cpp_unit_test.py --all
|
||||
```
|
||||
|
||||
To run a specific test suite, you can provide a Google Test filter:
|
||||
|
||||
```bash
|
||||
GTEST_FILTER='UART*' ./script/cpp_unit_test.py uart modbus
|
||||
```
|
||||
|
||||
The process will return `0` for success or nonzero for failure. In case of failure, the errors will be printed out to the console.
|
||||
@@ -1,11 +1,4 @@
|
||||
uart:
|
||||
- id: uart_a01nyub
|
||||
tx_pin: ${tx_pin}
|
||||
rx_pin: ${rx_pin}
|
||||
baud_rate: 9600
|
||||
|
||||
sensor:
|
||||
- platform: a01nyub
|
||||
id: a01nyub_sensor
|
||||
name: a01nyub Distance
|
||||
uart_id: uart_a01nyub
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO17
|
||||
rx_pin: GPIO16
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,5 +1,8 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO17
|
||||
rx_pin: GPIO16
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
uart:
|
||||
- id: uart_a02yyuw
|
||||
tx_pin: ${tx_pin}
|
||||
rx_pin: ${rx_pin}
|
||||
baud_rate: 9600
|
||||
|
||||
sensor:
|
||||
- platform: a02yyuw
|
||||
id: a02yyuw_sensor
|
||||
name: a02yyuw Distance
|
||||
uart_id: uart_a02yyuw
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO17
|
||||
rx_pin: GPIO16
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,5 +1,8 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO17
|
||||
rx_pin: GPIO16
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
substitutions:
|
||||
step_pin: GPIO22
|
||||
dir_pin: GPIO23
|
||||
sleep_pin: GPIO25
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,6 +0,0 @@
|
||||
substitutions:
|
||||
step_pin: GPIO2
|
||||
dir_pin: GPIO3
|
||||
sleep_pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,6 +0,0 @@
|
||||
substitutions:
|
||||
step_pin: GPIO2
|
||||
dir_pin: GPIO3
|
||||
sleep_pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,6 +1,6 @@
|
||||
substitutions:
|
||||
step_pin: GPIO22
|
||||
dir_pin: GPIO23
|
||||
dir_pin: GPIO4
|
||||
sleep_pin: GPIO25
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
substitutions:
|
||||
step_pin: GPIO1
|
||||
dir_pin: GPIO2
|
||||
sleep_pin: GPIO5
|
||||
sleep_pin: GPIO0
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -8,14 +8,12 @@ sensor:
|
||||
lambda: |-
|
||||
if (millis() > 10000) {
|
||||
return 0.6;
|
||||
} else {
|
||||
return 0.0;
|
||||
}
|
||||
return 0.0;
|
||||
- platform: template
|
||||
id: template_temperature
|
||||
lambda: |-
|
||||
if (millis() > 10000) {
|
||||
return 42.0;
|
||||
} else {
|
||||
return 0.0;
|
||||
}
|
||||
return 0.0;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
substitutions:
|
||||
gate_pin: GPIO18
|
||||
zero_cross_pin: GPIO19
|
||||
gate_pin: GPIO4
|
||||
zero_cross_pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
gate_pin: GPIO5
|
||||
zero_cross_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,5 +1,5 @@
|
||||
substitutions:
|
||||
gate_pin: GPIO5
|
||||
zero_cross_pin: GPIO4
|
||||
gate_pin: GPIO0
|
||||
zero_cross_pin: GPIO2
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
platform: adc
|
||||
name: ADC Test sensor
|
||||
update_interval: "1:01"
|
||||
attenuation: 2.5db
|
||||
unit_of_measurement: "°C"
|
||||
icon: "mdi:water-percent"
|
||||
accuracy_decimals: 5
|
||||
setup_priority: -100
|
||||
force_update: true
|
||||
@@ -1,7 +1,11 @@
|
||||
packages:
|
||||
base: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- id: !extend my_sensor
|
||||
- id: my_sensor
|
||||
platform: adc
|
||||
pin: P23
|
||||
attenuation: !remove
|
||||
name: ADC Test sensor
|
||||
update_interval: "1:01"
|
||||
unit_of_measurement: "°C"
|
||||
icon: "mdi:water-percent"
|
||||
accuracy_decimals: 5
|
||||
setup_priority: -100
|
||||
force_update: true
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
packages:
|
||||
base: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- id: !extend my_sensor
|
||||
pin: A0
|
||||
@@ -1,6 +0,0 @@
|
||||
packages:
|
||||
base: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- id: !extend my_sensor
|
||||
pin: 4
|
||||
@@ -1,6 +1,12 @@
|
||||
packages:
|
||||
base: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- id: !extend my_sensor
|
||||
pin: 4
|
||||
- id: my_sensor
|
||||
platform: adc
|
||||
pin: GPIO1
|
||||
name: ADC Test sensor
|
||||
update_interval: "1:01"
|
||||
attenuation: 2.5db
|
||||
unit_of_measurement: "°C"
|
||||
icon: "mdi:water-percent"
|
||||
accuracy_decimals: 5
|
||||
setup_priority: -100
|
||||
force_update: true
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
packages:
|
||||
base: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- id: !extend my_sensor
|
||||
- id: my_sensor
|
||||
platform: adc
|
||||
pin: A0
|
||||
name: ADC Test sensor
|
||||
update_interval: "1:01"
|
||||
attenuation: 2.5db
|
||||
unit_of_measurement: "°C"
|
||||
icon: "mdi:water-percent"
|
||||
accuracy_decimals: 5
|
||||
setup_priority: -100
|
||||
force_update: true
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
packages:
|
||||
base: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- id: !extend my_sensor
|
||||
pin: GPIO50
|
||||
- id: my_sensor
|
||||
platform: adc
|
||||
pin: GPIO16
|
||||
name: ADC Test sensor
|
||||
update_interval: "1:01"
|
||||
attenuation: 2.5db
|
||||
unit_of_measurement: "°C"
|
||||
icon: "mdi:water-percent"
|
||||
accuracy_decimals: 5
|
||||
setup_priority: -100
|
||||
force_update: true
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
packages:
|
||||
base: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- id: !extend my_sensor
|
||||
pin: 1
|
||||
@@ -1,6 +1,12 @@
|
||||
packages:
|
||||
base: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- id: !extend my_sensor
|
||||
pin: 1
|
||||
- id: my_sensor
|
||||
platform: adc
|
||||
pin: GPIO1
|
||||
name: ADC Test sensor
|
||||
update_interval: "1:01"
|
||||
attenuation: 2.5db
|
||||
unit_of_measurement: "°C"
|
||||
icon: "mdi:water-percent"
|
||||
accuracy_decimals: 5
|
||||
setup_priority: -100
|
||||
force_update: true
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
packages:
|
||||
base: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- id: !extend my_sensor
|
||||
pin: 1
|
||||
@@ -1,6 +1,12 @@
|
||||
packages:
|
||||
base: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- id: !extend my_sensor
|
||||
pin: 1
|
||||
- id: my_sensor
|
||||
platform: adc
|
||||
pin: GPIO1
|
||||
name: ADC Test sensor
|
||||
update_interval: "1:01"
|
||||
attenuation: 2.5db
|
||||
unit_of_measurement: "°C"
|
||||
icon: "mdi:water-percent"
|
||||
accuracy_decimals: 5
|
||||
setup_priority: -100
|
||||
force_update: true
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
packages:
|
||||
base: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- id: !extend my_sensor
|
||||
- id: my_sensor
|
||||
platform: adc
|
||||
pin: VCC
|
||||
attenuation: !remove
|
||||
name: ADC Test sensor
|
||||
update_interval: "1:01"
|
||||
unit_of_measurement: "°C"
|
||||
icon: "mdi:water-percent"
|
||||
accuracy_decimals: 5
|
||||
setup_priority: -100
|
||||
force_update: true
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
packages:
|
||||
base: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- id: !extend my_sensor
|
||||
pin: PA0
|
||||
attenuation: !remove
|
||||
- id: my_sensor
|
||||
platform: adc
|
||||
pin: A5
|
||||
name: ADC Test sensor
|
||||
update_interval: "1:01"
|
||||
unit_of_measurement: "°C"
|
||||
icon: "mdi:water-percent"
|
||||
accuracy_decimals: 5
|
||||
setup_priority: -100
|
||||
force_update: true
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
packages:
|
||||
base: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- id: !extend my_sensor
|
||||
- id: my_sensor
|
||||
platform: adc
|
||||
pin: VCC
|
||||
attenuation: !remove
|
||||
name: ADC Test sensor
|
||||
update_interval: "1:01"
|
||||
unit_of_measurement: "°C"
|
||||
icon: "mdi:water-percent"
|
||||
accuracy_decimals: 5
|
||||
setup_priority: -100
|
||||
force_update: true
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
spi:
|
||||
- id: spi_adc128s102
|
||||
clk_pin: ${clk_pin}
|
||||
mosi_pin: ${mosi_pin}
|
||||
miso_pin: ${miso_pin}
|
||||
|
||||
adc128s102:
|
||||
cs_pin: ${cs_pin}
|
||||
id: adc128s102_adc
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
substitutions:
|
||||
clk_pin: GPIO16
|
||||
mosi_pin: GPIO17
|
||||
miso_pin: GPIO15
|
||||
cs_pin: GPIO12
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,7 +0,0 @@
|
||||
substitutions:
|
||||
clk_pin: GPIO6
|
||||
mosi_pin: GPIO7
|
||||
miso_pin: GPIO5
|
||||
cs_pin: GPIO2
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,7 +0,0 @@
|
||||
substitutions:
|
||||
clk_pin: GPIO6
|
||||
mosi_pin: GPIO7
|
||||
miso_pin: GPIO5
|
||||
cs_pin: GPIO2
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,7 +1,7 @@
|
||||
substitutions:
|
||||
clk_pin: GPIO16
|
||||
mosi_pin: GPIO17
|
||||
miso_pin: GPIO15
|
||||
cs_pin: GPIO12
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
substitutions:
|
||||
clk_pin: GPIO14
|
||||
mosi_pin: GPIO13
|
||||
miso_pin: GPIO12
|
||||
clk_pin: GPIO0
|
||||
mosi_pin: GPIO2
|
||||
miso_pin: GPIO16
|
||||
cs_pin: GPIO15
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -4,4 +4,7 @@ substitutions:
|
||||
miso_pin: GPIO4
|
||||
cs_pin: GPIO5
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
substitutions:
|
||||
pin: GPIO2
|
||||
|
||||
<<: !include common-ard-esp32_rmt_led_strip.yaml
|
||||
@@ -1,4 +0,0 @@
|
||||
substitutions:
|
||||
pin: GPIO2
|
||||
|
||||
<<: !include common-ard-esp32_rmt_led_strip.yaml
|
||||
@@ -1,4 +0,0 @@
|
||||
substitutions:
|
||||
pin: GPIO2
|
||||
|
||||
<<: !include common-ard-fastled.yaml
|
||||
@@ -1,23 +1,18 @@
|
||||
i2c:
|
||||
- id: i2c_ade7880
|
||||
scl: ${scl_pin}
|
||||
sda: ${sda_pin}
|
||||
|
||||
sensor:
|
||||
- platform: ade7880
|
||||
i2c_id: i2c_ade7880
|
||||
i2c_id: i2c_bus
|
||||
irq0_pin: ${irq0_pin}
|
||||
irq1_pin: ${irq1_pin}
|
||||
reset_pin: ${reset_pin}
|
||||
frequency: 60Hz
|
||||
phase_a:
|
||||
name: Channel A
|
||||
voltage: Channel A Voltage
|
||||
current: Channel A Current
|
||||
active_power: Channel A Active Power
|
||||
power_factor: Channel A Power Factor
|
||||
forward_active_energy: Channel A Forward Active Energy
|
||||
reverse_active_energy: Channel A Reverse Active Energy
|
||||
voltage: Voltage
|
||||
current: Current
|
||||
active_power: Active Power
|
||||
power_factor: Power Factor
|
||||
forward_active_energy: Forward Active Energy
|
||||
reverse_active_energy: Reverse Active Energy
|
||||
calibration:
|
||||
current_gain: 3116628
|
||||
voltage_gain: -757178
|
||||
@@ -25,12 +20,12 @@ sensor:
|
||||
phase_angle: 188
|
||||
phase_b:
|
||||
name: Channel B
|
||||
voltage: Channel B Voltage
|
||||
current: Channel B Current
|
||||
active_power: Channel B Active Power
|
||||
power_factor: Channel B Power Factor
|
||||
forward_active_energy: Channel B Forward Active Energy
|
||||
reverse_active_energy: Channel B Reverse Active Energy
|
||||
voltage: Voltage
|
||||
current: Current
|
||||
active_power: Active Power
|
||||
power_factor: Power Factor
|
||||
forward_active_energy: Forward Active Energy
|
||||
reverse_active_energy: Reverse Active Energy
|
||||
calibration:
|
||||
current_gain: 3133655
|
||||
voltage_gain: -755235
|
||||
@@ -38,12 +33,12 @@ sensor:
|
||||
phase_angle: 188
|
||||
phase_c:
|
||||
name: Channel C
|
||||
voltage: Channel C Voltage
|
||||
current: Channel C Current
|
||||
active_power: Channel C Active Power
|
||||
power_factor: Channel C Power Factor
|
||||
forward_active_energy: Channel C Forward Active Energy
|
||||
reverse_active_energy: Channel C Reverse Active Energy
|
||||
voltage: Voltage
|
||||
current: Current
|
||||
active_power: Active Power
|
||||
power_factor: Power Factor
|
||||
forward_active_energy: Forward Active Energy
|
||||
reverse_active_energy: Reverse Active Energy
|
||||
calibration:
|
||||
current_gain: 3111158
|
||||
voltage_gain: -743813
|
||||
@@ -51,6 +46,6 @@ sensor:
|
||||
phase_angle: 180
|
||||
neutral:
|
||||
name: Neutral
|
||||
current: Neutral Current
|
||||
current: Current
|
||||
calibration:
|
||||
current_gain: 3189
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
irq0_pin: GPIO13
|
||||
irq1_pin: GPIO15
|
||||
reset_pin: GPIO16
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,8 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
irq0_pin: GPIO6
|
||||
irq1_pin: GPIO7
|
||||
reset_pin: GPIO10
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,8 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
irq0_pin: GPIO6
|
||||
irq1_pin: GPIO7
|
||||
reset_pin: GPIO10
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,8 +1,9 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
irq0_pin: GPIO13
|
||||
irq1_pin: GPIO15
|
||||
reset_pin: GPIO16
|
||||
reset_pin: GPIO12
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
irq0_pin: GPIO13
|
||||
irq0_pin: GPIO0
|
||||
irq1_pin: GPIO15
|
||||
reset_pin: GPIO16
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
irq0_pin: GPIO13
|
||||
irq1_pin: GPIO15
|
||||
reset_pin: GPIO16
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
i2c:
|
||||
- id: i2c_ade7953
|
||||
scl: ${scl_pin}
|
||||
sda: ${sda_pin}
|
||||
|
||||
sensor:
|
||||
- platform: ade7953_i2c
|
||||
i2c_id: i2c_bus
|
||||
irq_pin: ${irq_pin}
|
||||
voltage:
|
||||
name: ADE7953 Voltage
|
||||
id: ade7953_voltage
|
||||
id: ade7953_i2c_voltage
|
||||
current_a:
|
||||
name: ADE7953 Current A
|
||||
id: ade7953_current_a
|
||||
id: ade7953_i2c_current_a
|
||||
current_b:
|
||||
name: ADE7953 Current B
|
||||
id: ade7953_current_b
|
||||
id: ade7953_i2c_current_b
|
||||
power_factor_a:
|
||||
name: ADE7953 Power Factor A
|
||||
power_factor_b:
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
irq_pin: GPIO15
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,6 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
irq_pin: GPIO6
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,6 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
irq_pin: GPIO6
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,6 +1,7 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
irq_pin: GPIO15
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
irq_pin: GPIO15
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
irq_pin: GPIO6
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
spi:
|
||||
- id: spi_ade7953
|
||||
clk_pin: ${clk_pin}
|
||||
mosi_pin: ${mosi_pin}
|
||||
miso_pin: ${miso_pin}
|
||||
|
||||
sensor:
|
||||
- platform: ade7953_spi
|
||||
cs_pin: ${cs_pin}
|
||||
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,8 +0,0 @@
|
||||
substitutions:
|
||||
clk_pin: GPIO16
|
||||
mosi_pin: GPIO17
|
||||
miso_pin: GPIO15
|
||||
irq_pin: GPIO13
|
||||
cs_pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,8 +0,0 @@
|
||||
substitutions:
|
||||
clk_pin: GPIO6
|
||||
mosi_pin: GPIO7
|
||||
miso_pin: GPIO5
|
||||
irq_pin: GPIO9
|
||||
cs_pin: GPIO8
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,8 +0,0 @@
|
||||
substitutions:
|
||||
clk_pin: GPIO6
|
||||
mosi_pin: GPIO7
|
||||
miso_pin: GPIO5
|
||||
irq_pin: GPIO9
|
||||
cs_pin: GPIO8
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,8 +1,8 @@
|
||||
substitutions:
|
||||
clk_pin: GPIO16
|
||||
mosi_pin: GPIO17
|
||||
miso_pin: GPIO15
|
||||
irq_pin: GPIO13
|
||||
cs_pin: GPIO5
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
substitutions:
|
||||
clk_pin: GPIO14
|
||||
mosi_pin: GPIO13
|
||||
miso_pin: GPIO12
|
||||
clk_pin: GPIO0
|
||||
mosi_pin: GPIO2
|
||||
miso_pin: GPIO16
|
||||
irq_pin: GPIO5
|
||||
cs_pin: GPIO15
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -5,4 +5,7 @@ substitutions:
|
||||
irq_pin: GPIO5
|
||||
cs_pin: GPIO6
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
i2c:
|
||||
- id: i2c_ads1115
|
||||
scl: ${scl_pin}
|
||||
sda: ${sda_pin}
|
||||
|
||||
ads1115:
|
||||
i2c_id: i2c_bus
|
||||
address: 0x48
|
||||
|
||||
sensor:
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,5 +1,4 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,5 +1,4 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
i2c:
|
||||
- id: i2c_ags10
|
||||
scl: ${scl_pin}
|
||||
sda: ${sda_pin}
|
||||
frequency: 10kHz
|
||||
|
||||
sensor:
|
||||
- platform: ags10
|
||||
id: ags10_1
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,5 +1,4 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
packages:
|
||||
i2c_low_freq: !include ../../test_build_components/common/i2c_low_freq/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
packages:
|
||||
i2c_low_freq: !include ../../test_build_components/common/i2c_low_freq/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user