mirror of
https://github.com/esphome/esphome.git
synced 2026-09-30 16:30:22 +00:00
Merge branch 'dev' into store-yaml-firmware
This commit is contained in:
@@ -4,77 +4,145 @@ from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components.const import BYTE_ORDER_BIG, BYTE_ORDER_LITTLE
|
||||
from esphome.components.display import (
|
||||
DisplayMetaData,
|
||||
add_metadata,
|
||||
get_all_display_metadata,
|
||||
get_display_metadata,
|
||||
)
|
||||
from esphome.cpp_generator import MockObj
|
||||
from esphome.config import Config
|
||||
from esphome.core import ID
|
||||
from esphome.final_validate import full_config
|
||||
|
||||
|
||||
def test_add_metadata_with_string_id():
|
||||
"""Test adding metadata with a plain string ID."""
|
||||
def test_add_metadata_basic():
|
||||
"""Test adding metadata with an ID object."""
|
||||
with patch("esphome.components.display.CORE.data", {}):
|
||||
add_metadata("my_display", 320, 240, True)
|
||||
meta = get_display_metadata("my_display")
|
||||
add_metadata(ID("my_display"), 320, 240)
|
||||
meta = get_display_metadata(ID("my_display"))
|
||||
assert meta == DisplayMetaData(
|
||||
width=320, height=240, has_writer=True, has_hardware_rotation=False
|
||||
width=320,
|
||||
height=240,
|
||||
has_hardware_rotation=False,
|
||||
byte_order=BYTE_ORDER_BIG,
|
||||
)
|
||||
|
||||
|
||||
def test_add_metadata_with_mockobj_id():
|
||||
"""Test adding metadata with a MockObj ID (converted via str())."""
|
||||
def test_add_metadata_with_all_fields():
|
||||
"""Test adding metadata with all fields set."""
|
||||
with patch("esphome.components.display.CORE.data", {}):
|
||||
mock_id = MockObj("my_display_obj")
|
||||
add_metadata(mock_id, 480, 320, False, has_hardware_rotation=True)
|
||||
meta = get_display_metadata("my_display_obj")
|
||||
add_metadata(
|
||||
ID("my_display"),
|
||||
480,
|
||||
320,
|
||||
has_hardware_rotation=True,
|
||||
byte_order=BYTE_ORDER_LITTLE,
|
||||
)
|
||||
meta = get_display_metadata(ID("my_display"))
|
||||
assert meta == DisplayMetaData(
|
||||
width=480, height=320, has_writer=False, has_hardware_rotation=True
|
||||
width=480,
|
||||
height=320,
|
||||
has_hardware_rotation=True,
|
||||
byte_order=BYTE_ORDER_LITTLE,
|
||||
)
|
||||
|
||||
|
||||
def test_add_metadata_hardware_rotation_default():
|
||||
"""Test that has_hardware_rotation defaults to False."""
|
||||
with patch("esphome.components.display.CORE.data", {}):
|
||||
add_metadata("disp", 128, 64, False)
|
||||
meta = get_display_metadata("disp")
|
||||
add_metadata(ID("disp"), 128, 64)
|
||||
meta = get_display_metadata(ID("disp"))
|
||||
assert meta.has_hardware_rotation is False
|
||||
assert meta.byte_order == BYTE_ORDER_BIG
|
||||
|
||||
|
||||
def test_get_display_metadata_missing_returns_none():
|
||||
"""Test that querying a non-existent ID returns None."""
|
||||
def test_add_metadata_with_byte_order():
|
||||
"""Test adding metadata with explicit byte_order."""
|
||||
with patch("esphome.components.display.CORE.data", {}):
|
||||
data = get_display_metadata("no_such_display")
|
||||
assert data.width == 0
|
||||
assert data.height == 0
|
||||
assert data.has_writer is False
|
||||
add_metadata(ID("disp"), 240, 320, byte_order=BYTE_ORDER_LITTLE)
|
||||
meta = get_display_metadata(ID("disp"))
|
||||
assert meta.byte_order == BYTE_ORDER_LITTLE
|
||||
|
||||
|
||||
def test_get_display_metadata_missing_reads_raw_config():
|
||||
"""Querying a non-existent ID falls back to raw config lookup."""
|
||||
with patch("esphome.components.display.CORE.data", {}):
|
||||
# Set up a minimal full_config with a display entry so the fallback
|
||||
# path in get_display_metadata can find the display config.
|
||||
fc = Config()
|
||||
fc["display"] = [
|
||||
{
|
||||
"id": ID("no_such_display", True),
|
||||
"auto_clear_enabled": True,
|
||||
"dimensions": {"width": 320, "height": 240},
|
||||
"byte_order": BYTE_ORDER_LITTLE,
|
||||
"rotation": 90,
|
||||
},
|
||||
{
|
||||
"id": ID("other_display", True),
|
||||
"auto_clear_enabled": "undefined",
|
||||
"dimensions": (1024, 600),
|
||||
},
|
||||
]
|
||||
fc.declare_ids.append((ID("no_such_display", True), ["display", 0, "id"]))
|
||||
fc.declare_ids.append((ID("other_display", True), ["display", 1, "id"]))
|
||||
full_config.set(fc)
|
||||
data = get_display_metadata(ID("no_such_display"))
|
||||
assert data.width == 320
|
||||
assert data.height == 240
|
||||
assert data.has_hardware_rotation is False
|
||||
assert data.has_writer is True
|
||||
assert data.byte_order == BYTE_ORDER_LITTLE
|
||||
assert data.rotation == 90
|
||||
|
||||
data = get_display_metadata(ID("other_display"))
|
||||
assert data.width == 1024
|
||||
assert data.height == 600
|
||||
assert data.has_writer is False
|
||||
|
||||
|
||||
def test_add_multiple_displays():
|
||||
"""Test adding metadata for multiple displays."""
|
||||
with patch("esphome.components.display.CORE.data", {}):
|
||||
add_metadata("disp_a", 320, 240, True)
|
||||
add_metadata("disp_b", 128, 64, False, has_hardware_rotation=True)
|
||||
add_metadata(ID("disp_a"), 320, 240)
|
||||
add_metadata(ID("disp_b"), 128, 64, has_hardware_rotation=True)
|
||||
|
||||
all_meta = get_all_display_metadata()
|
||||
assert len(all_meta) == 2
|
||||
assert all_meta["disp_a"] == DisplayMetaData(320, 240, True, False)
|
||||
assert all_meta["disp_b"] == DisplayMetaData(128, 64, False, True)
|
||||
assert all_meta["disp_a"] == DisplayMetaData(320, 240, False)
|
||||
assert all_meta["disp_b"] == DisplayMetaData(128, 64, True, BYTE_ORDER_BIG)
|
||||
|
||||
|
||||
def test_add_metadata_overwrites_existing():
|
||||
"""Test that adding metadata for the same ID overwrites the previous entry."""
|
||||
def test_add_duplicate_id_asserts():
|
||||
"""Adding metadata for the same ID object twice should assert."""
|
||||
with patch("esphome.components.display.CORE.data", {}):
|
||||
add_metadata("disp", 320, 240, True)
|
||||
add_metadata("disp", 640, 480, False, has_hardware_rotation=True)
|
||||
meta = get_display_metadata("disp")
|
||||
assert meta == DisplayMetaData(640, 480, False, True)
|
||||
id_obj = ID("disp")
|
||||
add_metadata(id_obj, 320, 240)
|
||||
with pytest.raises(AssertionError, match="Duplicate"):
|
||||
add_metadata(id_obj, 640, 480)
|
||||
|
||||
|
||||
def test_metadata_is_frozen():
|
||||
"""Test that DisplayMetaData instances are immutable (frozen dataclass)."""
|
||||
meta = DisplayMetaData(320, 240, True, False)
|
||||
meta = DisplayMetaData(320, 240, False, BYTE_ORDER_BIG)
|
||||
with pytest.raises(AttributeError):
|
||||
meta.width = 640
|
||||
with pytest.raises(AttributeError):
|
||||
meta.byte_order = BYTE_ORDER_LITTLE
|
||||
|
||||
|
||||
def test_get_all_metadata_asserts_on_unresolved_id():
|
||||
"""get_all_display_metadata should assert if any ID has id=None."""
|
||||
with patch("esphome.components.display.CORE.data", {}):
|
||||
add_metadata(ID(None), 320, 240)
|
||||
with pytest.raises(AssertionError, match="resolved"):
|
||||
get_all_display_metadata()
|
||||
|
||||
|
||||
def test_get_metadata_asserts_on_unresolved_id():
|
||||
"""get_display_metadata should assert if any ID has id=None."""
|
||||
with patch("esphome.components.display.CORE.data", {}):
|
||||
add_metadata(ID(None), 320, 240)
|
||||
with pytest.raises(AssertionError, match="resolved"):
|
||||
get_display_metadata(ID("anything"))
|
||||
|
||||
@@ -45,11 +45,20 @@ def test_esp32_config(
|
||||
config = CONFIG_SCHEMA(config)
|
||||
assert config["variant"] == VARIANT_ESP32
|
||||
|
||||
# Check that defining a variant sets the board name correctly
|
||||
# Check that defining a variant sets the board name correctly.
|
||||
# Run under the ESP-IDF toolchain so variants without an entry in
|
||||
# STANDARD_BOARDS (S31, H4, H21) still derive a board name from
|
||||
# VARIANT_FRIENDLY rather than failing with cv.Invalid. CORE.toolchain
|
||||
# gets pinned by the first CONFIG_SCHEMA() call above (via
|
||||
# _resolve_toolchain) and that pinned value wins over the dict's
|
||||
# CONF_TOOLCHAIN, so clear it between iterations to mirror a fresh
|
||||
# config run.
|
||||
for variant in VARIANTS:
|
||||
CORE.toolchain = None
|
||||
config = CONFIG_SCHEMA(
|
||||
{
|
||||
"variant": variant,
|
||||
"toolchain": Toolchain.ESP_IDF.value,
|
||||
}
|
||||
)
|
||||
assert VARIANT_FRIENDLY[variant].lower() in config["board"]
|
||||
@@ -73,6 +82,11 @@ def test_esp32_config(
|
||||
r"Option 'variant' does not match selected board. @ data\['variant'\]",
|
||||
id="mismatched_board_variant_config",
|
||||
),
|
||||
pytest.param(
|
||||
{"variant": "esp32s31", "toolchain": Toolchain.PLATFORMIO.value},
|
||||
r"No default board is known for ESP32S31\. Please specify the `board:` option explicitly\. @ data\['variant'\]",
|
||||
id="variant_without_default_board_requires_explicit_board_under_platformio",
|
||||
),
|
||||
pytest.param(
|
||||
{
|
||||
"variant": "esp32s2",
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Tests for esp32_ble_server configuration helpers."""
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components.esp32_ble_server import (
|
||||
CCCD_DESCRIPTOR_UUID,
|
||||
CUD_DESCRIPTOR_UUID,
|
||||
DEVICE_INFORMATION_SERVICE_UUID,
|
||||
uuid_is,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"uuid",
|
||||
[
|
||||
DEVICE_INFORMATION_SERVICE_UUID, # int form (cv.hex_uint32_t)
|
||||
"180A", # 16 bit short form (bt_uuid)
|
||||
"180a", # lowercase is normalized by bt_uuid but guard anyway
|
||||
"0000180A", # 32 bit form
|
||||
"0000180A-0000-1000-8000-00805F9B34FB", # full 128 bit form
|
||||
],
|
||||
)
|
||||
def test_uuid_is_matches_all_representations(uuid) -> None:
|
||||
"""All representations of the same 16 bit UUID must compare equal."""
|
||||
assert uuid_is(uuid, DEVICE_INFORMATION_SERVICE_UUID)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"uuid",
|
||||
[
|
||||
0x1818, # Cycling Power Service (different int)
|
||||
"1818", # different 16 bit short form
|
||||
"0000180B", # adjacent UUID
|
||||
"0000180A-0000-1000-8000-00805F9B34FC", # wrong base UUID suffix
|
||||
],
|
||||
)
|
||||
def test_uuid_is_rejects_other_uuids(uuid) -> None:
|
||||
"""A different UUID must not be mistaken for the device information service."""
|
||||
assert not uuid_is(uuid, DEVICE_INFORMATION_SERVICE_UUID)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("uuid16", [CUD_DESCRIPTOR_UUID, CCCD_DESCRIPTOR_UUID])
|
||||
def test_uuid_is_matches_descriptor_short_strings(uuid16) -> None:
|
||||
"""Reserved descriptor UUIDs match whether given as int or short string."""
|
||||
assert uuid_is(uuid16, uuid16)
|
||||
assert uuid_is(f"{uuid16:04X}", uuid16)
|
||||
assert uuid_is(f"{uuid16:08X}", uuid16)
|
||||
@@ -0,0 +1,177 @@
|
||||
"""Tests for LVGL final_validation display metadata checks."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components.const import BYTE_ORDER_BIG, BYTE_ORDER_LITTLE, CONF_BYTE_ORDER
|
||||
from esphome.components.display import add_metadata
|
||||
from esphome.components.lvgl import final_validation
|
||||
from esphome.config import Config
|
||||
from esphome.config_validation import Invalid
|
||||
from esphome.const import KEY_CORE, KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM
|
||||
from esphome.core import CORE, ID
|
||||
from esphome.final_validate import full_config
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _setup_core():
|
||||
"""Ensure CORE.data has enough context for final_validation."""
|
||||
CORE.data[KEY_CORE] = {
|
||||
KEY_TARGET_PLATFORM: "host",
|
||||
KEY_TARGET_FRAMEWORK: "",
|
||||
}
|
||||
full_config.set(Config())
|
||||
yield
|
||||
CORE.reset()
|
||||
|
||||
|
||||
def _register_displays(*display_ids: str) -> None:
|
||||
"""Register display IDs in full_config so get_path_for_id works."""
|
||||
fc = full_config.get()
|
||||
display_list = [{"id": ID(d, True)} for d in display_ids]
|
||||
fc["display"] = display_list
|
||||
for i, disp_id in enumerate(display_ids):
|
||||
fc.declare_ids.append((ID(disp_id, True), ["display", i, "id"]))
|
||||
|
||||
|
||||
def _make_lvgl_config(
|
||||
display_ids: list[str],
|
||||
byte_order: str | None = None,
|
||||
) -> dict:
|
||||
"""Build a minimal LVGL config dict for final_validation."""
|
||||
_register_displays(*display_ids)
|
||||
config = {
|
||||
"displays": [ID(d, True) for d in display_ids],
|
||||
"log_level": "WARN",
|
||||
"color_depth": 16,
|
||||
"transparency_key": 0x000400,
|
||||
"draw_rounding": 2,
|
||||
"buffer_size": 0,
|
||||
}
|
||||
if byte_order is not None:
|
||||
config[CONF_BYTE_ORDER] = byte_order
|
||||
return config
|
||||
|
||||
|
||||
class TestByteOrderAutoConfig:
|
||||
"""Test that LVGL auto-configures byte_order from display metadata."""
|
||||
|
||||
def test_inherits_big_endian_from_display(self) -> None:
|
||||
"""LVGL should inherit big_endian from display metadata."""
|
||||
add_metadata(ID("my_disp"), 320, 240, byte_order=BYTE_ORDER_BIG)
|
||||
configs = [_make_lvgl_config(["my_disp"])]
|
||||
final_validation(configs)
|
||||
assert configs[0][CONF_BYTE_ORDER] == BYTE_ORDER_BIG
|
||||
|
||||
def test_inherits_little_endian_from_display(self) -> None:
|
||||
"""LVGL should inherit little_endian from display metadata."""
|
||||
add_metadata(ID("my_disp"), 320, 240, byte_order=BYTE_ORDER_LITTLE)
|
||||
configs = [_make_lvgl_config(["my_disp"])]
|
||||
final_validation(configs)
|
||||
assert configs[0][CONF_BYTE_ORDER] == BYTE_ORDER_LITTLE
|
||||
|
||||
def test_defaults_to_big_endian_when_no_metadata(self) -> None:
|
||||
"""LVGL should default to big_endian when display has no metadata."""
|
||||
configs = [_make_lvgl_config(["my_disp"])]
|
||||
final_validation(configs)
|
||||
assert configs[0][CONF_BYTE_ORDER] == BYTE_ORDER_BIG
|
||||
|
||||
|
||||
class TestByteOrderExplicitMismatchError:
|
||||
"""Test that LVGL rejects explicit byte_order mismatch with display."""
|
||||
|
||||
def test_raises_on_mismatch(self) -> None:
|
||||
"""Explicit LVGL byte_order different from display should raise."""
|
||||
add_metadata(ID("my_disp"), 320, 240, byte_order=BYTE_ORDER_LITTLE)
|
||||
configs = [_make_lvgl_config(["my_disp"], byte_order=BYTE_ORDER_BIG)]
|
||||
with pytest.raises(
|
||||
Invalid, match="LVGL byte order must match the display byte order"
|
||||
):
|
||||
final_validation(configs)
|
||||
|
||||
def test_no_error_when_matching(self) -> None:
|
||||
"""Explicit LVGL byte_order matching display should pass."""
|
||||
add_metadata(ID("my_disp"), 320, 240, byte_order=BYTE_ORDER_BIG)
|
||||
configs = [_make_lvgl_config(["my_disp"], byte_order=BYTE_ORDER_BIG)]
|
||||
final_validation(configs)
|
||||
|
||||
|
||||
class TestByteOrderMultipleDisplays:
|
||||
"""Test byte_order validation with multiple displays."""
|
||||
|
||||
def test_consistent_displays_inherit(self) -> None:
|
||||
"""All displays with same byte_order should set LVGL byte_order."""
|
||||
add_metadata(ID("disp_a"), 320, 240, byte_order=BYTE_ORDER_LITTLE)
|
||||
add_metadata(ID("disp_b"), 128, 64, byte_order=BYTE_ORDER_LITTLE)
|
||||
configs = [_make_lvgl_config(["disp_a", "disp_b"])]
|
||||
final_validation(configs)
|
||||
assert configs[0][CONF_BYTE_ORDER] == BYTE_ORDER_LITTLE
|
||||
|
||||
def test_inconsistent_displays_raises(self) -> None:
|
||||
"""Displays with different byte_order should raise an error."""
|
||||
add_metadata(ID("disp_a"), 320, 240, byte_order=BYTE_ORDER_BIG)
|
||||
add_metadata(ID("disp_b"), 128, 64, byte_order=BYTE_ORDER_LITTLE)
|
||||
configs = [_make_lvgl_config(["disp_a", "disp_b"])]
|
||||
with pytest.raises(Invalid, match="same byte_order"):
|
||||
final_validation(configs)
|
||||
|
||||
|
||||
class TestHasWriterCheck:
|
||||
"""Test that LVGL rejects displays with has_writer set."""
|
||||
|
||||
def test_display_with_writer_raises(self) -> None:
|
||||
"""Display with lambda/pages/auto_clear should be rejected."""
|
||||
add_metadata(ID("my_disp"), 320, 240, has_writer=True)
|
||||
configs = [_make_lvgl_config(["my_disp"])]
|
||||
with pytest.raises(Invalid, match="not compatible with LVGL"):
|
||||
final_validation(configs)
|
||||
|
||||
def test_display_without_writer_passes(self) -> None:
|
||||
"""Display without writer should pass."""
|
||||
add_metadata(ID("my_disp"), 320, 240, has_writer=False)
|
||||
configs = [_make_lvgl_config(["my_disp"])]
|
||||
final_validation(configs)
|
||||
|
||||
|
||||
class TestRotationCheck:
|
||||
"""Test that LVGL rejects displays with non-zero rotation."""
|
||||
|
||||
def test_display_with_rotation_raises(self) -> None:
|
||||
"""Display with rotation should be rejected."""
|
||||
add_metadata(ID("my_disp"), 320, 240, rotation=90)
|
||||
configs = [_make_lvgl_config(["my_disp"])]
|
||||
with pytest.raises(Invalid, match="rotation.*not compatible with LVGL"):
|
||||
final_validation(configs)
|
||||
|
||||
def test_display_without_rotation_passes(self) -> None:
|
||||
"""Display with rotation=0 should pass."""
|
||||
add_metadata(ID("my_disp"), 320, 240, rotation=0)
|
||||
configs = [_make_lvgl_config(["my_disp"])]
|
||||
final_validation(configs)
|
||||
|
||||
|
||||
class TestDrawRoundingMerge:
|
||||
"""Test that display draw_rounding is merged into LVGL config."""
|
||||
|
||||
def test_display_draw_rounding_overrides_lower(self) -> None:
|
||||
"""Display draw_rounding higher than LVGL default should win."""
|
||||
add_metadata(ID("my_disp"), 320, 240, draw_rounding=8)
|
||||
configs = [_make_lvgl_config(["my_disp"])]
|
||||
final_validation(configs)
|
||||
assert configs[0]["draw_rounding"] == 8
|
||||
|
||||
def test_display_draw_rounding_does_not_lower(self) -> None:
|
||||
"""Display draw_rounding lower than LVGL config should not reduce it."""
|
||||
add_metadata(ID("my_disp"), 320, 240, draw_rounding=1)
|
||||
configs = [_make_lvgl_config(["my_disp"])]
|
||||
configs[0]["draw_rounding"] = 4
|
||||
final_validation(configs)
|
||||
assert configs[0]["draw_rounding"] == 4
|
||||
|
||||
def test_zero_draw_rounding_no_change(self) -> None:
|
||||
"""Display with draw_rounding=0 should not affect LVGL config."""
|
||||
add_metadata(ID("my_disp"), 320, 240, draw_rounding=0)
|
||||
configs = [_make_lvgl_config(["my_disp"])]
|
||||
final_validation(configs)
|
||||
assert configs[0]["draw_rounding"] == 2
|
||||
@@ -124,15 +124,15 @@ def test_code_generation(
|
||||
|
||||
main_cpp = generate_main(component_fixture_path("mipi_dsi.yaml"))
|
||||
assert (
|
||||
"alignas(mipi_dsi::MIPI_DSI) static unsigned char mipi_dsi__p4_nano__pstorage[sizeof(mipi_dsi::MIPI_DSI)];"
|
||||
"alignas(mipi_dsi::MipiDsi) static unsigned char mipi_dsi__p4_nano__pstorage[sizeof(mipi_dsi::MipiDsi)];"
|
||||
in main_cpp
|
||||
)
|
||||
assert (
|
||||
"static mipi_dsi::MIPI_DSI *const p4_nano = reinterpret_cast<mipi_dsi::MIPI_DSI *>(mipi_dsi__p4_nano__pstorage);"
|
||||
"static mipi_dsi::MipiDsi *const p4_nano = reinterpret_cast<mipi_dsi::MipiDsi *>(mipi_dsi__p4_nano__pstorage);"
|
||||
in main_cpp
|
||||
)
|
||||
assert (
|
||||
"new(p4_nano) mipi_dsi::MIPI_DSI(800, 1280, display::COLOR_BITNESS_565, 16);"
|
||||
"new(p4_nano) mipi_dsi::MipiDsi(800, 1280, display::COLOR_BITNESS_565, 16);"
|
||||
in main_cpp
|
||||
)
|
||||
assert "set_init_sequence({224, 1, 0, 225, 1, 147, 226, 1," in main_cpp
|
||||
|
||||
@@ -3,22 +3,15 @@
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
from esphome.components.display import (
|
||||
DisplayMetaData,
|
||||
get_all_display_metadata,
|
||||
get_display_metadata,
|
||||
)
|
||||
from esphome.components.const import BYTE_ORDER_BIG
|
||||
from esphome.components.display import get_all_display_metadata, get_display_metadata
|
||||
from esphome.components.esp32 import (
|
||||
KEY_BOARD,
|
||||
KEY_VARIANT,
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
from esphome.components.mipi_spi.display import (
|
||||
CONFIG_SCHEMA,
|
||||
FINAL_VALIDATE_SCHEMA,
|
||||
get_instance,
|
||||
)
|
||||
from esphome.components.mipi_spi.display import CONFIG_SCHEMA, FINAL_VALIDATE_SCHEMA
|
||||
from esphome.const import PlatformFramework
|
||||
from tests.component_tests.types import SetCoreConfigCallable
|
||||
|
||||
@@ -38,38 +31,32 @@ def test_metadata_native_quad_default_test_card(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32-s3-devkitc-1", KEY_VARIANT: VARIANT_ESP32S3},
|
||||
)
|
||||
config = validated_config({"model": "JC3636W518"})
|
||||
get_instance(config)
|
||||
meta = get_display_metadata(str(config["id"]))
|
||||
config = CONFIG_SCHEMA({"model": "JC3636W518", "id": "jc3232w518"})
|
||||
meta = get_display_metadata(config["id"])
|
||||
assert meta is not None
|
||||
assert meta.width == 360
|
||||
assert meta.height == 360
|
||||
# final validation auto-enables show_test_card when no drawing methods are configured
|
||||
assert meta.has_writer is True
|
||||
assert meta.has_hardware_rotation is True
|
||||
assert meta.byte_order == BYTE_ORDER_BIG
|
||||
|
||||
|
||||
def test_metadata_single_mode_with_dc_pin(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
"""A single-mode display with no explicit drawing gets a test card from final validation."""
|
||||
"""A single-mode display with no explicit drawing gets metadata from schema validation."""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
|
||||
)
|
||||
config = validated_config(
|
||||
{
|
||||
"model": "ST7735",
|
||||
"dc_pin": 18,
|
||||
}
|
||||
config = CONFIG_SCHEMA(
|
||||
{"model": "ST7735", "dc_pin": 18, "id": "single_mode_with_dc_pin"}
|
||||
)
|
||||
get_instance(config)
|
||||
meta = get_display_metadata(str(config["id"]))
|
||||
meta = get_display_metadata(config["id"])
|
||||
assert meta is not None
|
||||
assert meta.width == 128
|
||||
assert meta.height == 160
|
||||
assert meta.has_writer is True
|
||||
assert meta.has_hardware_rotation is True
|
||||
assert meta.byte_order == BYTE_ORDER_BIG
|
||||
|
||||
|
||||
def test_metadata_custom_dimensions(
|
||||
@@ -80,47 +67,22 @@ def test_metadata_custom_dimensions(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
|
||||
)
|
||||
config = validated_config(
|
||||
config = CONFIG_SCHEMA(
|
||||
{
|
||||
"model": "custom",
|
||||
"dc_pin": 18,
|
||||
"dimensions": {"width": 480, "height": 320},
|
||||
"init_sequence": [[0xA0, 0x01]],
|
||||
"id": "custom_dimensions",
|
||||
}
|
||||
)
|
||||
get_instance(config)
|
||||
meta = get_display_metadata(str(config["id"]))
|
||||
meta = get_display_metadata(config["id"])
|
||||
assert meta is not None
|
||||
assert meta.width == 480
|
||||
assert meta.height == 320
|
||||
# final validation auto-enables show_test_card
|
||||
assert meta.has_writer is True
|
||||
assert meta.has_hardware_rotation is True
|
||||
|
||||
|
||||
def test_metadata_with_test_card_has_writer(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
"""When show_test_card is enabled, has_writer should be True."""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
|
||||
)
|
||||
config = validated_config(
|
||||
{
|
||||
"model": "custom",
|
||||
"dc_pin": 18,
|
||||
"dimensions": {"width": 240, "height": 240},
|
||||
"init_sequence": [[0xA0, 0x01]],
|
||||
"show_test_card": True,
|
||||
}
|
||||
)
|
||||
get_instance(config)
|
||||
meta = get_display_metadata(str(config["id"]))
|
||||
assert meta is not None
|
||||
assert meta.has_writer is True
|
||||
|
||||
|
||||
def test_metadata_no_swap_xy_not_full_hardware_rotation(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
@@ -130,9 +92,8 @@ def test_metadata_no_swap_xy_not_full_hardware_rotation(
|
||||
platform_data={KEY_BOARD: "esp32-s3-devkitc-1", KEY_VARIANT: VARIANT_ESP32S3},
|
||||
)
|
||||
# JC3248W535 has swap_xy=cv.UNDEFINED -> transforms={mirror_x, mirror_y} only
|
||||
config = validated_config({"model": "JC3248W535"})
|
||||
get_instance(config)
|
||||
meta = get_display_metadata(str(config["id"]))
|
||||
config = CONFIG_SCHEMA({"model": "JC3248W535", "id": "jc3248w535"})
|
||||
meta = get_display_metadata(config["id"])
|
||||
assert meta is not None
|
||||
assert meta.has_hardware_rotation is False
|
||||
|
||||
@@ -145,7 +106,7 @@ def test_metadata_multiple_displays_independent(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
|
||||
)
|
||||
config_a = validated_config(
|
||||
CONFIG_SCHEMA(
|
||||
{
|
||||
"id": "disp_a",
|
||||
"model": "custom",
|
||||
@@ -154,7 +115,7 @@ def test_metadata_multiple_displays_independent(
|
||||
"init_sequence": [[0xA0, 0x01]],
|
||||
}
|
||||
)
|
||||
config_b = validated_config(
|
||||
CONFIG_SCHEMA(
|
||||
{
|
||||
"id": "disp_b",
|
||||
"model": "custom",
|
||||
@@ -163,13 +124,16 @@ def test_metadata_multiple_displays_independent(
|
||||
"init_sequence": [[0xA0, 0x01]],
|
||||
}
|
||||
)
|
||||
get_instance(config_a)
|
||||
get_instance(config_b)
|
||||
|
||||
all_meta = get_all_display_metadata()
|
||||
# final validation auto-enables show_test_card for both
|
||||
assert all_meta["disp_a"] == DisplayMetaData(320, 240, True, True)
|
||||
assert all_meta["disp_b"] == DisplayMetaData(128, 64, True, True)
|
||||
assert all_meta["disp_a"].width == 320
|
||||
assert all_meta["disp_a"].height == 240
|
||||
assert all_meta["disp_a"].has_hardware_rotation is True
|
||||
assert all_meta["disp_a"].byte_order == BYTE_ORDER_BIG
|
||||
assert all_meta["disp_b"].width == 128
|
||||
assert all_meta["disp_b"].height == 64
|
||||
assert all_meta["disp_b"].has_hardware_rotation is True
|
||||
assert all_meta["disp_b"].byte_order == BYTE_ORDER_BIG
|
||||
|
||||
|
||||
def test_metadata_via_code_generation_native(
|
||||
@@ -179,12 +143,13 @@ def test_metadata_via_code_generation_native(
|
||||
"""Full code generation for native.yaml should produce correct metadata."""
|
||||
generate_main(component_fixture_path("native.yaml"))
|
||||
all_meta = get_all_display_metadata()
|
||||
# native.yaml: model JC3636W518 -> 360x360, no writer, full hardware rotation
|
||||
# native.yaml: model JC3636W518 -> 360x360, full hardware rotation
|
||||
assert len(all_meta) == 1
|
||||
meta = next(iter(all_meta.values()))
|
||||
assert meta == DisplayMetaData(
|
||||
width=360, height=360, has_writer=True, has_hardware_rotation=True
|
||||
)
|
||||
assert meta.width == 360
|
||||
assert meta.height == 360
|
||||
assert meta.has_hardware_rotation is True
|
||||
assert meta.byte_order == BYTE_ORDER_BIG
|
||||
|
||||
|
||||
def test_metadata_via_code_generation_lvgl(
|
||||
@@ -194,9 +159,10 @@ def test_metadata_via_code_generation_lvgl(
|
||||
"""Full code generation for lvgl.yaml should produce correct metadata."""
|
||||
generate_main(component_fixture_path("lvgl.yaml"))
|
||||
all_meta = get_all_display_metadata()
|
||||
# lvgl.yaml: model ST7735 -> 128x160, no writer (lvgl draws directly), full hw rotation
|
||||
# lvgl.yaml: model ST7735 -> 128x160, full hw rotation
|
||||
assert len(all_meta) == 1
|
||||
meta = next(iter(all_meta.values()))
|
||||
assert meta == DisplayMetaData(
|
||||
width=128, height=160, has_writer=False, has_hardware_rotation=True
|
||||
)
|
||||
assert meta.width == 128
|
||||
assert meta.height == 160
|
||||
assert meta.has_hardware_rotation is True
|
||||
assert meta.byte_order == BYTE_ORDER_BIG
|
||||
|
||||
@@ -0,0 +1,899 @@
|
||||
"""Tests for the motion component."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from voluptuous import Invalid, MultipleInvalid
|
||||
|
||||
from esphome.components.motion import (
|
||||
CALIBRATE_ACTION_SCHEMA,
|
||||
CLEAR_ACTION_SCHEMA,
|
||||
CONF_AXIS_MAP,
|
||||
CONF_SAVE,
|
||||
CONF_TRANSFORM_MATRIX,
|
||||
_axis_map,
|
||||
_axis_map_to_matrix,
|
||||
_build_calibrate_action,
|
||||
_transform_matrix,
|
||||
_validate_matrix_options,
|
||||
clear_calibration_to_code,
|
||||
)
|
||||
from esphome.components.motion.sensor import (
|
||||
_ACCELERATIONS,
|
||||
_ANGULAR_RATES,
|
||||
_GYROSCOPES,
|
||||
CONF_PITCH,
|
||||
CONF_ROLL,
|
||||
CONFIG_SCHEMA,
|
||||
build_sensor_expr,
|
||||
)
|
||||
from esphome.const import CONF_ID, CONF_ON_ERROR, CONF_ON_SUCCESS
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
# --- Axis map validation ---
|
||||
|
||||
|
||||
class TestAxisMapValidation:
|
||||
"""Tests for the _axis_map validator."""
|
||||
|
||||
def test_identity_map(self):
|
||||
result = _axis_map({"x": "x", "y": "y", "z": "z"})
|
||||
assert result == {"x": "x", "y": "y", "z": "z"}
|
||||
|
||||
def test_axis_swap(self):
|
||||
result = _axis_map({"x": "y", "y": "z", "z": "x"})
|
||||
assert result == {"x": "y", "y": "z", "z": "x"}
|
||||
|
||||
def test_negation(self):
|
||||
result = _axis_map({"x": "-y", "y": "z", "z": "x"})
|
||||
assert result == {"x": "-y", "y": "z", "z": "x"}
|
||||
|
||||
def test_plus_prefix(self):
|
||||
result = _axis_map({"x": "+y", "y": "z", "z": "x"})
|
||||
assert result == {"x": "+y", "y": "z", "z": "x"}
|
||||
|
||||
def test_case_insensitive(self):
|
||||
result = _axis_map({"x": "X", "y": "Y", "z": "Z"})
|
||||
assert result == {"x": "X", "y": "Y", "z": "Z"}
|
||||
|
||||
def test_invalid_axis_value(self):
|
||||
with pytest.raises(MultipleInvalid):
|
||||
_axis_map({"x": "a", "y": "y", "z": "z"})
|
||||
|
||||
def test_duplicate_mapping(self):
|
||||
with pytest.raises(MultipleInvalid):
|
||||
_axis_map({"x": "x", "y": "x", "z": "z"})
|
||||
|
||||
def test_all_same_axis(self):
|
||||
with pytest.raises(MultipleInvalid):
|
||||
_axis_map({"x": "x", "y": "x", "z": "x"})
|
||||
|
||||
def test_empty_value(self):
|
||||
with pytest.raises(MultipleInvalid):
|
||||
_axis_map({"x": "", "y": "y", "z": "z"})
|
||||
|
||||
def test_invalid_and_duplicate(self):
|
||||
"""Both invalid value and duplicate should produce multiple errors."""
|
||||
with pytest.raises(MultipleInvalid) as exc_info:
|
||||
_axis_map({"x": "a", "y": "x", "z": "z"})
|
||||
# Should have at least the invalid regex error and the duplicate error
|
||||
assert len(exc_info.value.errors) >= 2
|
||||
|
||||
|
||||
# --- Transform matrix validation ---
|
||||
|
||||
|
||||
class TestTransformMatrix:
|
||||
"""Tests for the _transform_matrix validator."""
|
||||
|
||||
def test_flat_identity(self):
|
||||
result = _transform_matrix([1, 0, 0, 0, 1, 0, 0, 0, 1])
|
||||
assert result == [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
|
||||
|
||||
def test_flat_values_converted_to_float(self):
|
||||
result = _transform_matrix([1, 2, 3, 4, 5, 6, 7, 8, 9])
|
||||
assert all(isinstance(v, float) for v in result)
|
||||
|
||||
def test_nested_3x3(self):
|
||||
result = _transform_matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
|
||||
assert result == [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
|
||||
|
||||
def test_nested_3x3_values(self):
|
||||
result = _transform_matrix(
|
||||
[[0.5, 0.1, -0.2], [-0.1, 0.9, 0.3], [0.2, -0.3, 0.8]]
|
||||
)
|
||||
assert len(result) == 9
|
||||
assert result[0] == pytest.approx(0.5)
|
||||
assert result[3] == pytest.approx(-0.1)
|
||||
assert result[8] == pytest.approx(0.8)
|
||||
|
||||
def test_flat_wrong_length_short(self):
|
||||
with pytest.raises(Invalid, match="exactly 9"):
|
||||
_transform_matrix([1, 0, 0])
|
||||
|
||||
def test_flat_wrong_length_long(self):
|
||||
with pytest.raises(Invalid, match="exactly 9"):
|
||||
_transform_matrix([1] * 12)
|
||||
|
||||
def test_nested_wrong_row_count(self):
|
||||
with pytest.raises(Invalid, match="3 rows"):
|
||||
_transform_matrix([[1, 0, 0], [0, 1, 0]])
|
||||
|
||||
def test_nested_wrong_column_count(self):
|
||||
with pytest.raises(Invalid, match="3 numbers"):
|
||||
_transform_matrix([[1, 0], [0, 1, 0], [0, 0, 1]])
|
||||
|
||||
def test_empty_list(self):
|
||||
with pytest.raises(Invalid):
|
||||
_transform_matrix([])
|
||||
|
||||
def test_not_a_list(self):
|
||||
with pytest.raises(Invalid):
|
||||
_transform_matrix("identity")
|
||||
|
||||
|
||||
class TestValidateMatrixOptions:
|
||||
"""Tests for mutual exclusivity of axis_map and transform_matrix."""
|
||||
|
||||
def test_neither_passes(self):
|
||||
config = {"some_key": "value"}
|
||||
assert _validate_matrix_options(config) is config
|
||||
|
||||
def test_axis_map_only_passes(self):
|
||||
config = {CONF_AXIS_MAP: {"x": "x", "y": "y", "z": "z"}}
|
||||
assert _validate_matrix_options(config) is config
|
||||
|
||||
def test_transform_matrix_only_passes(self):
|
||||
config = {CONF_TRANSFORM_MATRIX: [1, 0, 0, 0, 1, 0, 0, 0, 1]}
|
||||
assert _validate_matrix_options(config) is config
|
||||
|
||||
def test_both_raises(self):
|
||||
config = {
|
||||
CONF_AXIS_MAP: {"x": "x", "y": "y", "z": "z"},
|
||||
CONF_TRANSFORM_MATRIX: [1, 0, 0, 0, 1, 0, 0, 0, 1],
|
||||
}
|
||||
with pytest.raises(Invalid, match="mutually exclusive"):
|
||||
_validate_matrix_options(config)
|
||||
|
||||
|
||||
# --- Axis map to matrix ---
|
||||
|
||||
|
||||
class TestAxisMapToMatrix:
|
||||
"""Tests for _axis_map_to_matrix conversion."""
|
||||
|
||||
def test_identity(self):
|
||||
assert _axis_map_to_matrix({"x": "x", "y": "y", "z": "z"}) == [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
]
|
||||
|
||||
def test_swap_xy(self):
|
||||
# x←y, y←x, z←z
|
||||
assert _axis_map_to_matrix({"x": "y", "y": "x", "z": "z"}) == [
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
]
|
||||
|
||||
def test_rotate_xyz(self):
|
||||
# x←y, y←z, z←x
|
||||
assert _axis_map_to_matrix({"x": "y", "y": "z", "z": "x"}) == [
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
]
|
||||
|
||||
def test_negate_x(self):
|
||||
assert _axis_map_to_matrix({"x": "-x", "y": "y", "z": "z"}) == [
|
||||
-1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
]
|
||||
|
||||
def test_negate_z(self):
|
||||
assert _axis_map_to_matrix({"x": "x", "y": "y", "z": "-z"}) == [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
-1,
|
||||
]
|
||||
|
||||
def test_swap_and_negate(self):
|
||||
# x←-y, y←z, z←x
|
||||
assert _axis_map_to_matrix({"x": "-y", "y": "z", "z": "x"}) == [
|
||||
0,
|
||||
-1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
]
|
||||
|
||||
def test_plus_prefix_ignored(self):
|
||||
assert _axis_map_to_matrix({"x": "+y", "y": "z", "z": "x"}) == [
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
]
|
||||
|
||||
|
||||
# --- Sensor expression generation ---
|
||||
|
||||
|
||||
def _expr_str(sensor_type: str) -> str:
|
||||
"""Build a sensor expression via the production function and return its string form."""
|
||||
return str(build_sensor_expr(sensor_type, MockObj("data")))
|
||||
|
||||
|
||||
class TestSensorExpressions:
|
||||
"""Tests that sensor code generation produces correct C++ expressions."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"sensor_type,expected_index",
|
||||
[
|
||||
("acceleration_x", 0),
|
||||
("acceleration_y", 1),
|
||||
("acceleration_z", 2),
|
||||
],
|
||||
)
|
||||
def test_acceleration_sensors(self, sensor_type, expected_index):
|
||||
assert _expr_str(sensor_type) == f"data.acceleration[{expected_index}]"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"sensor_type,expected_index",
|
||||
[
|
||||
("angular_rate_x", 0),
|
||||
("angular_rate_y", 1),
|
||||
("angular_rate_z", 2),
|
||||
],
|
||||
)
|
||||
def test_angular_rate_sensors(self, sensor_type, expected_index):
|
||||
assert _expr_str(sensor_type) == f"data.angular_rate[{expected_index}]"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"sensor_type,expected_index",
|
||||
[
|
||||
("gyroscope_x", 0),
|
||||
("gyroscope_y", 1),
|
||||
("gyroscope_z", 2),
|
||||
],
|
||||
)
|
||||
def test_gyroscope_maps_to_angular_rate(self, sensor_type, expected_index):
|
||||
"""Gyroscope sensor types should be remapped to angular_rate in the expression."""
|
||||
assert _expr_str(sensor_type) == f"data.angular_rate[{expected_index}]"
|
||||
|
||||
def test_roll_expression(self):
|
||||
expr = _expr_str("roll")
|
||||
assert "std::atan2" in expr
|
||||
assert "data.acceleration[1]" in expr
|
||||
assert "data.acceleration[2]" in expr
|
||||
assert "180.0f" in expr
|
||||
assert "std::numbers::pi_v<float>" in expr
|
||||
# Roll should NOT reference acceleration[0]
|
||||
assert "data.acceleration[0]" not in expr
|
||||
|
||||
def test_pitch_expression(self):
|
||||
expr = _expr_str("pitch")
|
||||
assert "std::atan2" in expr
|
||||
assert "std::sqrt" in expr
|
||||
# All three axes used
|
||||
assert "data.acceleration[0]" in expr
|
||||
assert "data.acceleration[1]" in expr
|
||||
assert "data.acceleration[2]" in expr
|
||||
assert "180.0f" in expr
|
||||
assert "std::numbers::pi_v<float>" in expr
|
||||
# Pitch negates the x component
|
||||
assert "(-data.acceleration[0])" in expr
|
||||
|
||||
|
||||
# --- Calibration math ---
|
||||
#
|
||||
# Pure-Python reimplementation of the C++ calibration algorithms so we can
|
||||
# verify the mathematical properties without needing to compile C++.
|
||||
|
||||
|
||||
def _mat_vec(m: list[float], v: list[float]) -> list[float]:
|
||||
"""Multiply a row-major 3x3 matrix by a 3-vector."""
|
||||
return [
|
||||
m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
|
||||
m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
|
||||
m[6] * v[0] + m[7] * v[1] + m[8] * v[2],
|
||||
]
|
||||
|
||||
|
||||
def _mat_mul(a: list[float], b: list[float]) -> list[float]:
|
||||
"""Multiply two row-major 3x3 matrices."""
|
||||
r = [0.0] * 9
|
||||
for i in range(3):
|
||||
for j in range(3):
|
||||
r[i * 3 + j] = sum(a[i * 3 + k] * b[k * 3 + j] for k in range(3))
|
||||
return r
|
||||
|
||||
|
||||
def _transpose(m: list[float]) -> list[float]:
|
||||
"""Transpose a row-major 3x3 matrix."""
|
||||
return [m[0], m[3], m[6], m[1], m[4], m[7], m[2], m[5], m[8]]
|
||||
|
||||
|
||||
def _det(m: list[float]) -> float:
|
||||
"""Determinant of a 3x3 matrix."""
|
||||
return (
|
||||
m[0] * (m[4] * m[8] - m[5] * m[7])
|
||||
- m[1] * (m[3] * m[8] - m[5] * m[6])
|
||||
+ m[2] * (m[3] * m[7] - m[4] * m[6])
|
||||
)
|
||||
|
||||
|
||||
def _calibrate_level(
|
||||
raw: list[float], matrix: list[float] | None = None
|
||||
) -> list[float]:
|
||||
"""Python port of MotionComponent::calibrate_level.
|
||||
|
||||
Composes the correction with *matrix* (defaults to identity).
|
||||
"""
|
||||
import math
|
||||
|
||||
if matrix is None:
|
||||
matrix = list(IDENTITY)
|
||||
|
||||
# Apply current matrix first
|
||||
mapped = _mat_vec(matrix, raw)
|
||||
|
||||
nx, ny, nz = mapped
|
||||
mag = math.sqrt(nx * nx + ny * ny + nz * nz)
|
||||
nx /= mag
|
||||
ny /= mag
|
||||
nz /= mag
|
||||
|
||||
if nz > 0.9999:
|
||||
return matrix[:] # already aligned, preserve existing matrix
|
||||
|
||||
if nz < -0.9999:
|
||||
r = [1, 0, 0, 0, -1, 0, 0, 0, -1]
|
||||
else:
|
||||
f = 1.0 / (1.0 + nz)
|
||||
r = [
|
||||
1.0 - nx * nx * f,
|
||||
-nx * ny * f,
|
||||
-nx,
|
||||
-nx * ny * f,
|
||||
1.0 - ny * ny * f,
|
||||
-ny,
|
||||
nx,
|
||||
ny,
|
||||
nz,
|
||||
]
|
||||
|
||||
return _mat_mul(r, matrix)
|
||||
|
||||
|
||||
def _calibrate_heading(matrix: list[float], raw: list[float]) -> list[float]:
|
||||
"""Python port of MotionComponent::calibrate_heading."""
|
||||
import math
|
||||
|
||||
mapped = _mat_vec(matrix, raw)
|
||||
mx, my = mapped[0], mapped[1]
|
||||
h = math.sqrt(mx * mx + my * my)
|
||||
sign_mx = 1.0 if mx >= 0 else -1.0
|
||||
cos_phi = sign_mx * mx / h # = |mx| / h
|
||||
sin_phi = sign_mx * my / h
|
||||
|
||||
old = matrix[:]
|
||||
new = old[:]
|
||||
new[0] = cos_phi * old[0] + sin_phi * old[3]
|
||||
new[1] = cos_phi * old[1] + sin_phi * old[4]
|
||||
new[2] = cos_phi * old[2] + sin_phi * old[5]
|
||||
new[3] = -sin_phi * old[0] + cos_phi * old[3]
|
||||
new[4] = -sin_phi * old[1] + cos_phi * old[4]
|
||||
new[5] = -sin_phi * old[2] + cos_phi * old[5]
|
||||
return new
|
||||
|
||||
|
||||
IDENTITY = [1, 0, 0, 0, 1, 0, 0, 0, 1]
|
||||
|
||||
|
||||
class TestCalibrateLevel:
|
||||
"""Verify the Rodrigues-based level calibration matrix."""
|
||||
|
||||
def _assert_maps_to_z(self, raw: list[float]) -> list[float]:
|
||||
"""Assert that the calibration matrix maps raw to [0, 0, 1]."""
|
||||
import math
|
||||
|
||||
m = _calibrate_level(raw)
|
||||
mag = math.sqrt(sum(v * v for v in raw))
|
||||
norm = [v / mag for v in raw]
|
||||
result = _mat_vec(m, norm)
|
||||
assert result[0] == pytest.approx(0, abs=1e-6)
|
||||
assert result[1] == pytest.approx(0, abs=1e-6)
|
||||
assert result[2] == pytest.approx(1, abs=1e-6)
|
||||
return m
|
||||
|
||||
def test_already_flat(self):
|
||||
m = _calibrate_level([0, 0, 1.0])
|
||||
assert m == IDENTITY
|
||||
|
||||
def test_preserves_existing_matrix_when_flat(self):
|
||||
"""If already flat after axis mapping, level cal should not change the matrix."""
|
||||
swap = [0, 1, 0, 1, 0, 0, 0, 0, 1] # swap X↔Y
|
||||
m = _calibrate_level([0, 0, 1.0], swap)
|
||||
assert m == swap
|
||||
|
||||
def test_composes_with_existing_matrix(self):
|
||||
"""Level calibration should correct tilt while preserving an existing axis swap."""
|
||||
import math
|
||||
|
||||
swap = [0, 1, 0, 1, 0, 0, 0, 0, 1] # swap X↔Y
|
||||
# Tilted raw: gravity has X component in raw frame
|
||||
raw = [0.3, 0.0, 0.954]
|
||||
m = _calibrate_level(raw, swap)
|
||||
# After calibration, current raw should map to [0, 0, ~1]
|
||||
mag = math.sqrt(sum(v * v for v in raw))
|
||||
norm = [v / mag for v in raw]
|
||||
result = _mat_vec(m, norm)
|
||||
assert result[0] == pytest.approx(0, abs=1e-5)
|
||||
assert result[1] == pytest.approx(0, abs=1e-5)
|
||||
assert result[2] == pytest.approx(1, abs=1e-5)
|
||||
# Result should differ from calibrating without the swap
|
||||
m_no_swap = _calibrate_level(raw)
|
||||
assert m != m_no_swap
|
||||
|
||||
def test_upside_down(self):
|
||||
m = _calibrate_level([0, 0, -1.0])
|
||||
# 180° about X
|
||||
assert m == [1, 0, 0, 0, -1, 0, 0, 0, -1]
|
||||
result = _mat_vec(m, [0, 0, -1])
|
||||
assert result[2] == pytest.approx(1, abs=1e-6)
|
||||
|
||||
def test_gravity_along_x(self):
|
||||
self._assert_maps_to_z([1.0, 0, 0])
|
||||
|
||||
def test_gravity_along_neg_x(self):
|
||||
self._assert_maps_to_z([-1.0, 0, 0])
|
||||
|
||||
def test_gravity_along_y(self):
|
||||
self._assert_maps_to_z([0, 1.0, 0])
|
||||
|
||||
def test_tilted_45_degrees(self):
|
||||
import math
|
||||
|
||||
self._assert_maps_to_z(
|
||||
[math.sin(math.radians(45)), 0, math.cos(math.radians(45))]
|
||||
)
|
||||
|
||||
def test_arbitrary_vector(self):
|
||||
self._assert_maps_to_z([0.3, -0.5, 0.81])
|
||||
|
||||
def test_unnormalized_input(self):
|
||||
"""Input does not need to be unit length."""
|
||||
self._assert_maps_to_z([0.6, -1.0, 1.62])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"raw",
|
||||
[
|
||||
[1.0, 0, 0],
|
||||
[0, 1.0, 0],
|
||||
[0.3, -0.5, 0.81],
|
||||
[-0.7, 0.4, 0.59],
|
||||
],
|
||||
)
|
||||
def test_result_is_proper_rotation(self, raw):
|
||||
"""The resulting matrix should be orthogonal with determinant +1."""
|
||||
m = _calibrate_level(raw)
|
||||
# R^T * R ≈ I
|
||||
product = _mat_mul(_transpose(m), m)
|
||||
for i in range(9):
|
||||
expected = 1.0 if i % 4 == 0 else 0.0
|
||||
assert product[i] == pytest.approx(expected, abs=1e-6)
|
||||
# det ≈ 1
|
||||
assert _det(m) == pytest.approx(1.0, abs=1e-6)
|
||||
|
||||
|
||||
class TestCalibrateHeading:
|
||||
"""Verify the Z-rotation heading correction."""
|
||||
|
||||
def test_y_axis_tilt_no_heading_error(self):
|
||||
"""Device tilted purely around Y — heading should already be correct."""
|
||||
import math
|
||||
|
||||
flat_raw = [0, 0, 1.0]
|
||||
level_m = _calibrate_level(flat_raw)
|
||||
# Tilt 30° around Y: gravity = [-sin30, 0, cos30]
|
||||
tilted_raw = [-math.sin(math.radians(30)), 0, math.cos(math.radians(30))]
|
||||
heading_m = _calibrate_heading(level_m, tilted_raw)
|
||||
# Matrix should barely change since there's no Y component
|
||||
for i in range(9):
|
||||
assert heading_m[i] == pytest.approx(level_m[i], abs=1e-6)
|
||||
|
||||
def test_corrects_heading_rotation(self):
|
||||
"""After level+heading calibration, mapped Y should be ~0 when tilted."""
|
||||
import math
|
||||
|
||||
# Simulate a sensor whose chip is rotated 30° around Z relative to enclosure
|
||||
angle = math.radians(30)
|
||||
# When the enclosure is flat, the raw reading is [0, 0, 1] regardless of Z rotation
|
||||
level_m = _calibrate_level([0, 0, 1.0])
|
||||
|
||||
# When tilted around the enclosure's Y axis, the raw reading in the
|
||||
# chip frame has both X and Y components due to the Z-rotation offset
|
||||
tilt = math.radians(20)
|
||||
# In enclosure frame: [-sin(tilt), 0, cos(tilt)]
|
||||
# Rotated by Z-angle into chip frame:
|
||||
ex = -math.sin(tilt) * math.cos(angle)
|
||||
ey = -math.sin(tilt) * math.sin(angle)
|
||||
ez = math.cos(tilt)
|
||||
tilted_raw = [ex, ey, ez]
|
||||
|
||||
heading_m = _calibrate_heading(level_m, tilted_raw)
|
||||
# After correction, mapped Y should be 0
|
||||
result = _mat_vec(heading_m, tilted_raw)
|
||||
assert result[1] == pytest.approx(0, abs=1e-6)
|
||||
# Z should still be correct
|
||||
assert result[2] == pytest.approx(math.cos(tilt), abs=1e-6)
|
||||
|
||||
def test_full_calibration_sequence(self):
|
||||
"""End-to-end: level then heading produces correct frame alignment."""
|
||||
import math
|
||||
|
||||
# Chip is mounted tilted 15° around Y and 25° around Z
|
||||
# Build the chip-to-enclosure rotation: Rz(25°) * Ry(15°)
|
||||
yz = math.radians(25)
|
||||
yy = math.radians(15)
|
||||
# Ry(yy)
|
||||
ry = [
|
||||
math.cos(yy),
|
||||
0,
|
||||
math.sin(yy),
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
-math.sin(yy),
|
||||
0,
|
||||
math.cos(yy),
|
||||
]
|
||||
# Rz(yz)
|
||||
rz = [
|
||||
math.cos(yz),
|
||||
-math.sin(yz),
|
||||
0,
|
||||
math.sin(yz),
|
||||
math.cos(yz),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
]
|
||||
chip_rot = _mat_mul(rz, ry) # chip orientation in enclosure frame
|
||||
# Inverse (transpose) maps enclosure vectors to chip readings
|
||||
chip_rot_inv = _transpose(chip_rot)
|
||||
|
||||
# Step 1: Device flat — gravity in enclosure frame is [0, 0, 1]
|
||||
flat_raw = _mat_vec(chip_rot_inv, [0, 0, 1])
|
||||
level_m = _calibrate_level(flat_raw)
|
||||
|
||||
# After level calibration, flat reading should map to [0, 0, 1]
|
||||
check_flat = _mat_vec(level_m, flat_raw)
|
||||
assert check_flat[0] == pytest.approx(0, abs=1e-5)
|
||||
assert check_flat[1] == pytest.approx(0, abs=1e-5)
|
||||
assert check_flat[2] == pytest.approx(1, abs=1e-5)
|
||||
|
||||
# Step 2: Tilt enclosure around Y by 20°
|
||||
tilt = math.radians(20)
|
||||
tilted_enclosure = [-math.sin(tilt), 0, math.cos(tilt)]
|
||||
tilted_raw = _mat_vec(chip_rot_inv, tilted_enclosure)
|
||||
heading_m = _calibrate_heading(level_m, tilted_raw)
|
||||
|
||||
# After heading calibration, the mapped reading should be
|
||||
# [-sin(tilt), 0, cos(tilt)] — all horizontal component in X
|
||||
result = _mat_vec(heading_m, tilted_raw)
|
||||
assert result[0] == pytest.approx(-math.sin(tilt), abs=1e-5)
|
||||
assert result[1] == pytest.approx(0, abs=1e-5)
|
||||
assert result[2] == pytest.approx(math.cos(tilt), abs=1e-5)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"raw",
|
||||
[
|
||||
[0.3, -0.5, 0.81],
|
||||
[-0.7, 0.4, 0.59],
|
||||
],
|
||||
)
|
||||
def test_heading_preserves_orthogonality(self, raw):
|
||||
"""Heading correction composed with level should remain a proper rotation."""
|
||||
|
||||
level_m = _calibrate_level(raw)
|
||||
# Create a tilted reading for heading calibration
|
||||
tilt_raw = [v + 0.3 for v in raw] # perturb to get XY component
|
||||
heading_m = _calibrate_heading(level_m, tilt_raw)
|
||||
product = _mat_mul(_transpose(heading_m), heading_m)
|
||||
for i in range(9):
|
||||
expected = 1.0 if i % 4 == 0 else 0.0
|
||||
assert product[i] == pytest.approx(expected, abs=1e-5)
|
||||
assert _det(heading_m) == pytest.approx(1.0, abs=1e-5)
|
||||
|
||||
|
||||
# --- Calibration action schema & codegen ---
|
||||
|
||||
|
||||
class TestCalibrateActionSchema:
|
||||
"""Tests for the CALIBRATE_ACTION_SCHEMA used by both calibration actions."""
|
||||
|
||||
def test_schema_accepts_on_success_key(self):
|
||||
"""on_success must be a recognised optional key."""
|
||||
schema_keys = {str(k) for k in CALIBRATE_ACTION_SCHEMA.schema}
|
||||
assert CONF_ON_SUCCESS in schema_keys
|
||||
|
||||
def test_schema_accepts_on_error_key(self):
|
||||
"""on_error must be a recognised optional key."""
|
||||
schema_keys = {str(k) for k in CALIBRATE_ACTION_SCHEMA.schema}
|
||||
assert CONF_ON_ERROR in schema_keys
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_codegen():
|
||||
"""Mock cg and automation functions used by _build_calibrate_action."""
|
||||
mock_var = MagicMock()
|
||||
mock_parent = MagicMock()
|
||||
|
||||
with (
|
||||
patch(
|
||||
"esphome.components.motion.cg.get_variable",
|
||||
new_callable=AsyncMock,
|
||||
return_value=mock_parent,
|
||||
) as mock_get_var,
|
||||
patch(
|
||||
"esphome.components.motion.cg.new_Pvariable",
|
||||
return_value=mock_var,
|
||||
) as mock_new_pvar,
|
||||
patch(
|
||||
"esphome.components.motion.automation.build_automation",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_build_auto,
|
||||
):
|
||||
yield {
|
||||
"get_variable": mock_get_var,
|
||||
"new_Pvariable": mock_new_pvar,
|
||||
"build_automation": mock_build_auto,
|
||||
"var": mock_var,
|
||||
"parent": mock_parent,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_build_calibrate_action_no_triggers(mock_codegen):
|
||||
"""Without on_success/on_error, build_automation should not be called."""
|
||||
config = {CONF_ID: MagicMock()}
|
||||
action_id = MagicMock()
|
||||
template_arg = MagicMock()
|
||||
|
||||
result = await _build_calibrate_action(config, action_id, template_arg, [])
|
||||
|
||||
assert result is mock_codegen["var"]
|
||||
mock_codegen["new_Pvariable"].assert_called_once_with(
|
||||
action_id, template_arg, mock_codegen["parent"]
|
||||
)
|
||||
mock_codegen["build_automation"].assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_build_calibrate_action_with_on_success(mock_codegen):
|
||||
"""on_success should wire build_automation to get_success_trigger()."""
|
||||
on_success_config = MagicMock()
|
||||
config = {CONF_ID: MagicMock(), CONF_ON_SUCCESS: on_success_config}
|
||||
|
||||
await _build_calibrate_action(config, MagicMock(), MagicMock(), [])
|
||||
|
||||
mock_codegen["build_automation"].assert_called_once_with(
|
||||
mock_codegen["var"].get_success_trigger(), [], on_success_config
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_build_calibrate_action_with_on_error(mock_codegen):
|
||||
"""on_error should wire build_automation to get_error_trigger()."""
|
||||
on_error_config = MagicMock()
|
||||
config = {CONF_ID: MagicMock(), CONF_ON_ERROR: on_error_config}
|
||||
|
||||
await _build_calibrate_action(config, MagicMock(), MagicMock(), [])
|
||||
|
||||
mock_codegen["build_automation"].assert_called_once_with(
|
||||
mock_codegen["var"].get_error_trigger(), [], on_error_config
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_build_calibrate_action_with_both_triggers(mock_codegen):
|
||||
"""Both on_success and on_error should each produce a build_automation call."""
|
||||
on_success_config = MagicMock()
|
||||
on_error_config = MagicMock()
|
||||
config = {
|
||||
CONF_ID: MagicMock(),
|
||||
CONF_ON_SUCCESS: on_success_config,
|
||||
CONF_ON_ERROR: on_error_config,
|
||||
}
|
||||
|
||||
await _build_calibrate_action(config, MagicMock(), MagicMock(), [])
|
||||
|
||||
assert mock_codegen["build_automation"].call_count == 2
|
||||
calls = mock_codegen["build_automation"].call_args_list
|
||||
# First call: on_success
|
||||
assert calls[0].args == (
|
||||
mock_codegen["var"].get_success_trigger(),
|
||||
[],
|
||||
on_success_config,
|
||||
)
|
||||
# Second call: on_error
|
||||
assert calls[1].args == (
|
||||
mock_codegen["var"].get_error_trigger(),
|
||||
[],
|
||||
on_error_config,
|
||||
)
|
||||
|
||||
|
||||
# --- Clear calibration action ---
|
||||
|
||||
|
||||
class TestClearActionSchema:
|
||||
"""Tests for CLEAR_ACTION_SCHEMA."""
|
||||
|
||||
def test_schema_has_save_key(self):
|
||||
schema_keys = {str(k) for k in CLEAR_ACTION_SCHEMA.schema}
|
||||
assert CONF_SAVE in schema_keys
|
||||
|
||||
def test_save_defaults_to_false(self):
|
||||
result = CLEAR_ACTION_SCHEMA({CONF_ID: "x"})
|
||||
assert result[CONF_SAVE] is False
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_clear_codegen():
|
||||
"""Mock cg functions used by clear_calibration_to_code."""
|
||||
mock_var = MagicMock()
|
||||
mock_parent = MagicMock()
|
||||
with (
|
||||
patch(
|
||||
"esphome.components.motion.cg.get_variable",
|
||||
new_callable=AsyncMock,
|
||||
return_value=mock_parent,
|
||||
),
|
||||
patch(
|
||||
"esphome.components.motion.cg.new_Pvariable",
|
||||
return_value=mock_var,
|
||||
) as mock_new_pvar,
|
||||
patch("esphome.components.motion.cg.add") as mock_add,
|
||||
):
|
||||
yield {"new_Pvariable": mock_new_pvar, "add": mock_add, "var": mock_var}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_clear_action_without_save(mock_clear_codegen):
|
||||
"""With save=False, set_save should not be emitted."""
|
||||
config = {CONF_ID: MagicMock(), CONF_SAVE: False}
|
||||
result = await clear_calibration_to_code(config, MagicMock(), MagicMock(), [])
|
||||
assert result is mock_clear_codegen["var"]
|
||||
mock_clear_codegen["add"].assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_clear_action_with_save(mock_clear_codegen):
|
||||
"""With save=True, set_save(True) should be emitted exactly once."""
|
||||
config = {CONF_ID: MagicMock(), CONF_SAVE: True}
|
||||
await clear_calibration_to_code(config, MagicMock(), MagicMock(), [])
|
||||
mock_clear_codegen["var"].set_save.assert_called_once_with(True)
|
||||
mock_clear_codegen["add"].assert_called_once()
|
||||
|
||||
|
||||
# --- Calibration persistence invalidation ---
|
||||
#
|
||||
# The C++ side stores a hash of the build-time base matrix alongside the saved
|
||||
# calibration so a changed axis_map invalidates stale NVS data without orphaning
|
||||
# storage (the pref key stays ID-stable). These tests pin the design properties
|
||||
# of that base-matrix fingerprint: deterministic for identical maps, distinct
|
||||
# for different ones.
|
||||
|
||||
|
||||
def _hash_matrix(matrix: list[float]) -> int:
|
||||
"""Python port of the C++ hash_matrix() (FNV-1a over the float bytes)."""
|
||||
import struct
|
||||
|
||||
data = struct.pack("<9f", *matrix)
|
||||
h = 2166136261
|
||||
for b in data:
|
||||
h ^= b
|
||||
h = (h * 16777619) & 0xFFFFFFFF
|
||||
return h
|
||||
|
||||
|
||||
class TestBaseMatrixHash:
|
||||
"""Properties of the base-matrix fingerprint used for NVS invalidation."""
|
||||
|
||||
def test_identical_axis_maps_hash_equal(self):
|
||||
a = _axis_map_to_matrix({"x": "x", "y": "y", "z": "z"})
|
||||
b = _axis_map_to_matrix({"x": "x", "y": "y", "z": "z"})
|
||||
assert _hash_matrix([float(v) for v in a]) == _hash_matrix(
|
||||
[float(v) for v in b]
|
||||
)
|
||||
|
||||
def test_different_axis_maps_hash_differ(self):
|
||||
identity = _axis_map_to_matrix({"x": "x", "y": "y", "z": "z"})
|
||||
swapped = _axis_map_to_matrix({"x": "y", "y": "x", "z": "z"})
|
||||
assert _hash_matrix([float(v) for v in identity]) != _hash_matrix(
|
||||
[float(v) for v in swapped]
|
||||
)
|
||||
|
||||
def test_sign_change_hashes_differ(self):
|
||||
pos = _axis_map_to_matrix({"x": "x", "y": "y", "z": "z"})
|
||||
neg = _axis_map_to_matrix({"x": "-x", "y": "y", "z": "z"})
|
||||
assert _hash_matrix([float(v) for v in pos]) != _hash_matrix(
|
||||
[float(v) for v in neg]
|
||||
)
|
||||
|
||||
|
||||
# --- Sensor config schema type validation ---
|
||||
|
||||
|
||||
class TestSensorConfigSchema:
|
||||
"""Tests for sensor CONFIG_SCHEMA type key validation."""
|
||||
|
||||
def test_invalid_type_rejected(self):
|
||||
with pytest.raises((Invalid, MultipleInvalid), match="Unknown value"):
|
||||
CONFIG_SCHEMA({"type": "invalid_type"})
|
||||
|
||||
def test_missing_type_rejected(self):
|
||||
with pytest.raises((Invalid, MultipleInvalid)):
|
||||
CONFIG_SCHEMA({})
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"sensor_type",
|
||||
_ACCELERATIONS + _GYROSCOPES + _ANGULAR_RATES + [CONF_PITCH, CONF_ROLL],
|
||||
)
|
||||
def test_valid_types_accepted(self, sensor_type):
|
||||
"""Valid sensor types should pass type validation (errors from missing
|
||||
required fields like motion_id are expected and acceptable)."""
|
||||
try:
|
||||
CONFIG_SCHEMA({"type": sensor_type})
|
||||
except (Invalid, MultipleInvalid) as e:
|
||||
# Should NOT be a type validation error
|
||||
assert "Unknown value" not in str(e), (
|
||||
f"Type '{sensor_type}' was rejected as unknown"
|
||||
)
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: P23
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: GPIO1
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: GPIO1
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: A0
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: GPIO16
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: GPIO1
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: GPIO1
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: VCC
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: A5
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: VCC
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: VCC
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
id: bin1
|
||||
id: alarm_control_panel_bin1
|
||||
pin: 1
|
||||
|
||||
alarm_control_panel:
|
||||
@@ -18,7 +18,7 @@ alarm_control_panel:
|
||||
pending_time: 15s
|
||||
trigger_time: 30s
|
||||
binary_sensors:
|
||||
- input: bin1
|
||||
- input: alarm_control_panel_bin1
|
||||
bypass_armed_home: true
|
||||
bypass_armed_night: true
|
||||
bypass_auto: true
|
||||
@@ -39,7 +39,7 @@ alarm_control_panel:
|
||||
pending_time: 15s
|
||||
trigger_time: 30s
|
||||
binary_sensors:
|
||||
- input: bin1
|
||||
- input: alarm_control_panel_bin1
|
||||
bypass_armed_home: true
|
||||
bypass_armed_night: true
|
||||
bypass_auto: true
|
||||
|
||||
@@ -4,7 +4,7 @@ packages:
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
id: main_lcd
|
||||
id: animation_main_lcd
|
||||
spi_id: spi_bus
|
||||
model: ili9342
|
||||
cs_pin: 12
|
||||
|
||||
@@ -4,7 +4,7 @@ packages:
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
id: main_lcd
|
||||
id: animation_main_lcd
|
||||
spi_id: spi_bus
|
||||
model: ili9342
|
||||
cs_pin: 5
|
||||
|
||||
@@ -4,7 +4,7 @@ packages:
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
id: main_lcd
|
||||
id: animation_main_lcd
|
||||
spi_id: spi_bus
|
||||
model: ili9342
|
||||
cs_pin: 20
|
||||
|
||||
@@ -296,7 +296,7 @@ api:
|
||||
event:
|
||||
- platform: template
|
||||
name: Test Event
|
||||
id: test_event
|
||||
id: api_test_event
|
||||
event_types:
|
||||
- single_click
|
||||
- double_click
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
audio_file:
|
||||
- id: test_audio
|
||||
- id: audio_file_test_audio
|
||||
file:
|
||||
type: local
|
||||
path: $component_dir/test.wav
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
audio_file:
|
||||
- id: test_audio
|
||||
- id: audio_file_test_audio
|
||||
file:
|
||||
type: local
|
||||
path: $component_dir/test.wav
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: ssd1306_i2c_display
|
||||
id: axs15231_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_i2c_display
|
||||
display: axs15231_ssd1306_i2c_display
|
||||
interrupt_pin: 20
|
||||
reset_pin: 18
|
||||
|
||||
@@ -4,7 +4,7 @@ packages:
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: ssd1306_display
|
||||
id: axs15231_ssd1306_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: 13
|
||||
pages:
|
||||
@@ -15,5 +15,5 @@ display:
|
||||
touchscreen:
|
||||
- platform: axs15231
|
||||
i2c_id: i2c_bus
|
||||
display: ssd1306_display
|
||||
display: axs15231_ssd1306_display
|
||||
interrupt_pin: 12
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
switch:
|
||||
- platform: template
|
||||
id: template_switch1
|
||||
id: bang_bang_template_switch1
|
||||
optimistic: true
|
||||
- platform: template
|
||||
id: template_switch2
|
||||
@@ -8,7 +8,7 @@ switch:
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
id: template_sensor1
|
||||
id: bang_bang_template_sensor1
|
||||
lambda: |-
|
||||
if (millis() > 10000) {
|
||||
return 42.0;
|
||||
@@ -20,16 +20,16 @@ sensor:
|
||||
climate:
|
||||
- platform: bang_bang
|
||||
name: Bang Bang Climate
|
||||
sensor: template_sensor1
|
||||
humidity_sensor: template_sensor1
|
||||
sensor: bang_bang_template_sensor1
|
||||
humidity_sensor: bang_bang_template_sensor1
|
||||
default_target_temperature_low: 18°C
|
||||
default_target_temperature_high: 24°C
|
||||
idle_action:
|
||||
- switch.turn_on: template_switch1
|
||||
- switch.turn_on: bang_bang_template_switch1
|
||||
cool_action:
|
||||
- switch.turn_on: template_switch2
|
||||
heat_action:
|
||||
- switch.turn_on: template_switch1
|
||||
- switch.turn_on: bang_bang_template_switch1
|
||||
away_config:
|
||||
default_target_temperature_low: 16°C
|
||||
default_target_temperature_high: 20°C
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
trigger_on_initial_state: true
|
||||
id: some_binary_sensor
|
||||
id: binary_sensor_some_binary_sensor
|
||||
name: "Random binary"
|
||||
lambda: return (random_uint32() & 1) == 0;
|
||||
filters:
|
||||
@@ -21,7 +21,7 @@ binary_sensor:
|
||||
time_off: 100ms
|
||||
time_on: 400ms
|
||||
- lambda: |-
|
||||
if (id(some_binary_sensor).state) {
|
||||
if (id(binary_sensor_some_binary_sensor).state) {
|
||||
return x;
|
||||
}
|
||||
return {};
|
||||
@@ -36,7 +36,7 @@ binary_sensor:
|
||||
- logger.log:
|
||||
format: "New state is %s"
|
||||
args: ['x.has_value() ? ONOFF(x) : "Unknown"']
|
||||
- binary_sensor.invalidate_state: some_binary_sensor
|
||||
- binary_sensor.invalidate_state: binary_sensor_some_binary_sensor
|
||||
|
||||
# Test autorepeat with default configuration (no timings)
|
||||
- platform: template
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
id: bin1
|
||||
id: binary_sensor_map_bin1
|
||||
lambda: |-
|
||||
if (millis() > 10000) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
- platform: template
|
||||
id: bin2
|
||||
id: binary_sensor_map_bin2
|
||||
lambda: |-
|
||||
if (millis() > 20000) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
- platform: template
|
||||
id: bin3
|
||||
id: binary_sensor_map_bin3
|
||||
lambda: |-
|
||||
if (millis() > 30000) {
|
||||
return true;
|
||||
@@ -26,33 +26,33 @@ sensor:
|
||||
name: Binary Sensor Map Group
|
||||
type: group
|
||||
channels:
|
||||
- binary_sensor: bin1
|
||||
- binary_sensor: binary_sensor_map_bin1
|
||||
value: 10.0
|
||||
- binary_sensor: bin2
|
||||
- binary_sensor: binary_sensor_map_bin2
|
||||
value: 15.0
|
||||
- binary_sensor: bin3
|
||||
- binary_sensor: binary_sensor_map_bin3
|
||||
value: 100.0
|
||||
- platform: binary_sensor_map
|
||||
name: Binary Sensor Map Sum
|
||||
type: sum
|
||||
channels:
|
||||
- binary_sensor: bin1
|
||||
- binary_sensor: binary_sensor_map_bin1
|
||||
value: 10.0
|
||||
- binary_sensor: bin2
|
||||
- binary_sensor: binary_sensor_map_bin2
|
||||
value: 15.0
|
||||
- binary_sensor: bin3
|
||||
- binary_sensor: binary_sensor_map_bin3
|
||||
value: 100.0
|
||||
- platform: binary_sensor_map
|
||||
name: Binary Sensor Map Bayesian
|
||||
type: bayesian
|
||||
prior: 0.4
|
||||
observations:
|
||||
- binary_sensor: bin1
|
||||
- binary_sensor: binary_sensor_map_bin1
|
||||
prob_given_true: 0.9
|
||||
prob_given_false: 0.4
|
||||
- binary_sensor: bin2
|
||||
- binary_sensor: binary_sensor_map_bin2
|
||||
prob_given_true: 0.7
|
||||
prob_given_false: 0.05
|
||||
- binary_sensor: bin3
|
||||
- binary_sensor: binary_sensor_map_bin3
|
||||
prob_given_true: 0.8
|
||||
prob_given_false: 0.2
|
||||
|
||||
@@ -56,7 +56,7 @@ sensor:
|
||||
number:
|
||||
- platform: template
|
||||
name: "Test Number"
|
||||
id: test_number
|
||||
id: ble_client_test_number
|
||||
optimistic: true
|
||||
min_value: 0
|
||||
max_value: 255
|
||||
@@ -72,5 +72,5 @@ button:
|
||||
service_uuid: "abcd1234-abcd-1234-abcd-abcd12345678"
|
||||
characteristic_uuid: "abcd1235-abcd-1234-abcd-abcd12345678"
|
||||
value: !lambda |-
|
||||
uint8_t val = (uint8_t)id(test_number).state;
|
||||
uint8_t val = (uint8_t)id(ble_client_test_number).state;
|
||||
return std::vector<uint8_t>{0xAA, val, 0xBB};
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
sensor:
|
||||
- platform: bmi270
|
||||
name: "BMI270 Temperature"
|
||||
|
||||
- platform: motion
|
||||
type: acceleration_x
|
||||
name: "Accel X"
|
||||
accuracy_decimals: 4
|
||||
filters:
|
||||
- sliding_window_moving_average:
|
||||
window_size: 4
|
||||
send_every: 1
|
||||
- platform: motion
|
||||
type: acceleration_y
|
||||
name: "Accel Y"
|
||||
accuracy_decimals: 4
|
||||
- platform: motion
|
||||
type: acceleration_z
|
||||
name: "Accel Z"
|
||||
accuracy_decimals: 4
|
||||
|
||||
# Gyroscope axes (unit: °/s)
|
||||
- platform: motion
|
||||
type: gyroscope_x
|
||||
name: "Gyro X"
|
||||
- platform: motion
|
||||
type: gyroscope_y
|
||||
name: "Gyro Y"
|
||||
- platform: motion
|
||||
type: gyroscope_z
|
||||
name: "Gyro Z"
|
||||
|
||||
- platform: motion
|
||||
type: angular_rate_x
|
||||
name: "Angular Rate X"
|
||||
- platform: motion
|
||||
type: angular_rate_y
|
||||
name: "Angular Rate Y"
|
||||
- platform: motion
|
||||
type: angular_rate_z
|
||||
name: "Angular Rate Z"
|
||||
|
||||
- platform: motion
|
||||
type: pitch
|
||||
name: "Pitch"
|
||||
- platform: motion
|
||||
type: roll
|
||||
name: "Roll"
|
||||
|
||||
motion:
|
||||
- platform: bmi270
|
||||
# Accelerometer full-scale range: 2G | 4G | 8G | 16G
|
||||
accelerometer_range: 4G
|
||||
|
||||
# Accelerometer output data rate: 12_5HZ | 25HZ | 50HZ | 100HZ |
|
||||
# 200HZ | 400HZ | 800HZ | 1600HZ
|
||||
accelerometer_odr: 100HZ
|
||||
|
||||
# Gyroscope full-scale range: 125DPS | 250DPS | 500DPS | 1000DPS | 2000DPS
|
||||
gyroscope_range: 2000DPS
|
||||
|
||||
# Gyroscope output data rate: 25HZ | 50HZ | 100HZ | 200HZ |
|
||||
# 400HZ | 800HZ | 1600HZ | 3200HZ
|
||||
gyroscope_odr: 200HZ
|
||||
axis_map:
|
||||
x: y
|
||||
y: x
|
||||
z: -z
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,6 +1,6 @@
|
||||
canbus:
|
||||
- platform: esp32_can
|
||||
id: esp32_internal_can
|
||||
id: canbus_esp32_internal_can
|
||||
rx_pin: 4
|
||||
tx_pin: 5
|
||||
can_id: 4
|
||||
@@ -40,7 +40,7 @@ canbus:
|
||||
number:
|
||||
- platform: template
|
||||
name: "Test Number"
|
||||
id: test_number
|
||||
id: canbus_test_number
|
||||
optimistic: true
|
||||
min_value: 0
|
||||
max_value: 255
|
||||
@@ -62,5 +62,5 @@ button:
|
||||
- canbus.send: !lambda return {0, 1, 2};
|
||||
# Test canbus.send with lambda that references a component (function pointer)
|
||||
- canbus.send: !lambda |-
|
||||
uint8_t val = (uint8_t)id(test_number).state;
|
||||
uint8_t val = (uint8_t)id(canbus_test_number).state;
|
||||
return std::vector<uint8_t>{0xAA, val, 0xBB};
|
||||
|
||||
@@ -6,13 +6,13 @@ cd74hc4067:
|
||||
|
||||
sensor:
|
||||
- platform: adc
|
||||
id: esp_adc_sensor
|
||||
id: cd74hc4067_esp_adc_sensor
|
||||
pin: ${pin}
|
||||
- platform: cd74hc4067
|
||||
id: cd74hc4067_adc_0
|
||||
number: 0
|
||||
sensor: esp_adc_sensor
|
||||
sensor: cd74hc4067_esp_adc_sensor
|
||||
- platform: cd74hc4067
|
||||
id: cd74hc4067_adc_1
|
||||
number: 1
|
||||
sensor: esp_adc_sensor
|
||||
sensor: cd74hc4067_esp_adc_sensor
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
sensor:
|
||||
- platform: template
|
||||
id: temp_sensor
|
||||
id: climate_ir_lg_temp_sensor
|
||||
lambda: return 22.0;
|
||||
update_interval: 60s
|
||||
- platform: template
|
||||
@@ -12,5 +12,5 @@ climate:
|
||||
- platform: climate_ir_lg
|
||||
name: LG Climate
|
||||
transmitter_id: xmitr
|
||||
sensor: temp_sensor
|
||||
sensor: climate_ir_lg_temp_sensor
|
||||
humidity_sensor: humidity_sensor
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
output:
|
||||
- platform: ${light_platform}
|
||||
id: light_output_1
|
||||
id: color_temperature_light_output_1
|
||||
pin: ${pin_o1}
|
||||
- platform: ${light_platform}
|
||||
id: light_output_2
|
||||
id: color_temperature_light_output_2
|
||||
pin: ${pin_o2}
|
||||
|
||||
light:
|
||||
- platform: color_temperature
|
||||
name: Lights
|
||||
color_temperature: light_output_1
|
||||
brightness: light_output_2
|
||||
color_temperature: color_temperature_light_output_1
|
||||
brightness: color_temperature_light_output_2
|
||||
cold_white_color_temperature: 153 mireds
|
||||
warm_white_color_temperature: 500 mireds
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
display:
|
||||
- platform: qspi_dbi
|
||||
model: RM690B0
|
||||
data_rate: 80MHz
|
||||
spi_mode: mode0
|
||||
dimensions:
|
||||
width: 450
|
||||
height: 600
|
||||
offset_width: 16
|
||||
color_order: rgb
|
||||
invert_colors: false
|
||||
brightness: 255
|
||||
cs_pin: 11
|
||||
reset_pin: 13
|
||||
enable_pin: 9
|
||||
|
||||
- platform: qspi_dbi
|
||||
model: CUSTOM
|
||||
id: main_lcd
|
||||
draw_from_origin: true
|
||||
dimensions:
|
||||
height: 240
|
||||
width: 536
|
||||
transform:
|
||||
mirror_x: true
|
||||
swap_xy: true
|
||||
color_order: rgb
|
||||
brightness: 255
|
||||
cs_pin: 6
|
||||
reset_pin: 17
|
||||
enable_pin: 38
|
||||
init_sequence:
|
||||
- [0x3A, 0x66]
|
||||
- [0x11]
|
||||
- delay 120ms
|
||||
- [0x29]
|
||||
- delay 20ms
|
||||
@@ -1,4 +0,0 @@
|
||||
packages:
|
||||
qspi: !include ../../test_build_components/common/qspi/esp32-s3-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,17 +1,17 @@
|
||||
output:
|
||||
- platform: ${pwm_platform}
|
||||
id: fan_output_1
|
||||
id: copy_fan_output_1
|
||||
pin: ${pin}
|
||||
|
||||
fan:
|
||||
- platform: speed
|
||||
id: fan_speed
|
||||
output: fan_output_1
|
||||
id: copy_fan_speed
|
||||
output: copy_fan_output_1
|
||||
preset_modes:
|
||||
- Eco
|
||||
- Turbo
|
||||
- platform: copy
|
||||
source_id: fan_speed
|
||||
source_id: copy_fan_speed
|
||||
name: Fan Speed Copy
|
||||
|
||||
select:
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
sensor:
|
||||
- platform: adc
|
||||
id: esp_adc_sensor
|
||||
id: ct_clamp_esp_adc_sensor
|
||||
pin: ${pin}
|
||||
- platform: ct_clamp
|
||||
sensor: esp_adc_sensor
|
||||
sensor: ct_clamp_esp_adc_sensor
|
||||
name: CT Clamp
|
||||
sample_duration: 500ms
|
||||
update_interval: 5s
|
||||
|
||||
@@ -31,7 +31,7 @@ sensor:
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
id: template_switch1
|
||||
id: current_based_template_switch1
|
||||
optimistic: true
|
||||
- platform: template
|
||||
id: template_switch2
|
||||
@@ -46,7 +46,7 @@ cover:
|
||||
open_obstacle_current_threshold: 0.8
|
||||
open_duration: 12s
|
||||
open_action:
|
||||
- switch.turn_on: template_switch1
|
||||
- switch.turn_on: current_based_template_switch1
|
||||
close_sensor: ade7953_current_b
|
||||
close_moving_current_threshold: 0.5
|
||||
close_obstacle_current_threshold: 0.8
|
||||
@@ -54,7 +54,7 @@ cover:
|
||||
close_action:
|
||||
- switch.turn_on: template_switch2
|
||||
stop_action:
|
||||
- switch.turn_off: template_switch1
|
||||
- switch.turn_off: current_based_template_switch1
|
||||
- switch.turn_off: template_switch2
|
||||
obstacle_rollback: 30%
|
||||
start_sensing_delay: 0.8s
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
light:
|
||||
- platform: cwww
|
||||
name: CWWW Light
|
||||
cold_white: light_output_1
|
||||
warm_white: light_output_2
|
||||
cold_white: cwww_light_output_1
|
||||
warm_white: cwww_light_output_2
|
||||
cold_white_color_temperature: 153 mireds
|
||||
warm_white_color_temperature: 500 mireds
|
||||
constant_brightness: true
|
||||
|
||||
@@ -5,11 +5,11 @@ substitutions:
|
||||
|
||||
output:
|
||||
- platform: ${light_platform}
|
||||
id: light_output_1
|
||||
id: cwww_light_output_1
|
||||
pin: ${pin_o1}
|
||||
channel: 0
|
||||
- platform: ${light_platform}
|
||||
id: light_output_2
|
||||
id: cwww_light_output_2
|
||||
pin: ${pin_o2}
|
||||
channel: 1
|
||||
phase_angle: 180°
|
||||
|
||||
@@ -5,10 +5,10 @@ substitutions:
|
||||
|
||||
output:
|
||||
- platform: ${light_platform}
|
||||
id: light_output_1
|
||||
id: cwww_light_output_1
|
||||
pin: ${pin_o1}
|
||||
- platform: ${light_platform}
|
||||
id: light_output_2
|
||||
id: cwww_light_output_2
|
||||
pin: ${pin_o2}
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -5,10 +5,10 @@ substitutions:
|
||||
|
||||
output:
|
||||
- platform: ${light_platform}
|
||||
id: light_output_1
|
||||
id: cwww_light_output_1
|
||||
pin: ${pin_o1}
|
||||
- platform: ${light_platform}
|
||||
id: light_output_2
|
||||
id: cwww_light_output_2
|
||||
pin: ${pin_o2}
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
dlms_meter:
|
||||
decryption_key: "36C66639E48A8CA4D6BC8B282A793BBB" # change this to your decryption key!
|
||||
|
||||
sensor:
|
||||
- platform: dlms_meter
|
||||
reactive_energy_plus:
|
||||
name: "Reactive energy taken from grid"
|
||||
reactive_energy_minus:
|
||||
name: "Reactive energy put into grid"
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,17 +0,0 @@
|
||||
dlms_meter:
|
||||
decryption_key: "36C66639E48A8CA4D6BC8B282A793BBB" # change this to your decryption key!
|
||||
provider: netznoe # (optional) key - only set if using evn
|
||||
|
||||
sensor:
|
||||
- platform: dlms_meter
|
||||
# EVN
|
||||
power_factor:
|
||||
name: "Power Factor"
|
||||
|
||||
text_sensor:
|
||||
- platform: dlms_meter
|
||||
# EVN
|
||||
meternumber:
|
||||
name: "meterNumber"
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,4 +1,16 @@
|
||||
dlms_meter:
|
||||
id: dlms_meter_hub
|
||||
receive_timeout: 50ms
|
||||
decryption_key: "36C66639E48A8CA4D6BC8B282A793BBB"
|
||||
auth_key: "11223344556677889900AABBCCDDEEFF"
|
||||
skip_crc: true
|
||||
provider: "netznoe"
|
||||
custom_patterns:
|
||||
- "custom_pattern_1"
|
||||
- "custom_pattern_2"
|
||||
|
||||
sensor:
|
||||
# Old Schema tests
|
||||
- platform: dlms_meter
|
||||
voltage_l1:
|
||||
name: "Voltage L1"
|
||||
@@ -20,8 +32,36 @@ sensor:
|
||||
name: "Active energy taken from grid"
|
||||
active_energy_minus:
|
||||
name: "Active energy put into grid"
|
||||
reactive_energy_plus:
|
||||
name: "Reactive energy taken from grid"
|
||||
reactive_energy_minus:
|
||||
name: "Reactive energy put into grid"
|
||||
power_factor:
|
||||
name: "Power factor"
|
||||
|
||||
# Dynamic Schema tests
|
||||
- platform: dlms_meter
|
||||
dlms_meter_id: dlms_meter_hub
|
||||
obis_code: "1-0:99.99.9"
|
||||
name: "Custom Dynamic Sensor"
|
||||
|
||||
text_sensor:
|
||||
# Old Schema tests
|
||||
- platform: dlms_meter
|
||||
timestamp:
|
||||
name: "timestamp"
|
||||
meternumber:
|
||||
name: "Meter Number"
|
||||
|
||||
# Dynamic Schema tests
|
||||
- platform: dlms_meter
|
||||
dlms_meter_id: dlms_meter_hub
|
||||
obis_code: "0-0:99.99.9"
|
||||
name: "Custom Dynamic Text Sensor"
|
||||
|
||||
binary_sensor:
|
||||
# Dynamic Schema tests (Binary sensors only use the dynamic schema)
|
||||
- platform: dlms_meter
|
||||
dlms_meter_id: dlms_meter_hub
|
||||
obis_code: "0-1:2.3.4"
|
||||
name: "Custom Binary Sensor"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
packages:
|
||||
uart_2400: !include ../../test_build_components/common/uart_2400/esp32-ard.yaml
|
||||
uart: !include ../../test_build_components/common/uart/esp32-ard.yaml
|
||||
|
||||
<<: !include common-generic.yaml
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
packages:
|
||||
uart_2400: !include ../../test_build_components/common/uart_2400/esp32-idf.yaml
|
||||
uart: !include ../../test_build_components/common/uart/esp32-idf.yaml
|
||||
|
||||
<<: !include common-netznoe.yaml
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
packages:
|
||||
uart_2400: !include ../../test_build_components/common/uart_2400/esp8266-ard.yaml
|
||||
uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml
|
||||
|
||||
<<: !include common-generic.yaml
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,6 +1,6 @@
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
id: bin1
|
||||
id: duty_time_bin1
|
||||
lambda: |-
|
||||
if (millis() > 10000) {
|
||||
return true;
|
||||
@@ -10,4 +10,4 @@ binary_sensor:
|
||||
sensor:
|
||||
- platform: duty_time
|
||||
name: Duty Time
|
||||
sensor: bin1
|
||||
sensor: duty_time_bin1
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
light:
|
||||
- platform: rp2040_pio_led_strip
|
||||
id: led_strip
|
||||
id: e131_led_strip
|
||||
pin: 2
|
||||
pio: 0
|
||||
num_leds: 256
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: ssd1306_i2c_display
|
||||
id: ektf2232_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_i2c_display
|
||||
display: ektf2232_ssd1306_i2c_display
|
||||
on_touch:
|
||||
- logger.log:
|
||||
format: Touch at (%d, %d)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
id: bin1
|
||||
id: endstop_bin1
|
||||
lambda: |-
|
||||
if (millis() > 10000) {
|
||||
return true;
|
||||
@@ -9,7 +9,7 @@ binary_sensor:
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
id: template_switch1
|
||||
id: endstop_template_switch1
|
||||
optimistic: true
|
||||
- platform: template
|
||||
id: template_switch2
|
||||
@@ -20,12 +20,12 @@ cover:
|
||||
id: endstop_cover
|
||||
name: Endstop Cover
|
||||
stop_action:
|
||||
- switch.turn_on: template_switch1
|
||||
open_endstop: bin1
|
||||
- switch.turn_on: endstop_template_switch1
|
||||
open_endstop: endstop_bin1
|
||||
open_action:
|
||||
- switch.turn_on: template_switch1
|
||||
- switch.turn_on: endstop_template_switch1
|
||||
open_duration: 5min
|
||||
close_endstop: bin1
|
||||
close_endstop: endstop_bin1
|
||||
close_action:
|
||||
- switch.turn_on: template_switch2
|
||||
close_duration: 4.5min
|
||||
|
||||
@@ -13,7 +13,7 @@ esphome:
|
||||
|
||||
canbus:
|
||||
- platform: esp32_can
|
||||
id: esp32_internal_can
|
||||
id: esp32_can_esp32_internal_can
|
||||
rx_pin: ${rx_pin}
|
||||
tx_pin: ${tx_pin}
|
||||
can_id: 4
|
||||
|
||||
@@ -3,20 +3,20 @@ esphome:
|
||||
then:
|
||||
- canbus.send:
|
||||
# Extended ID explicit
|
||||
canbus_id: esp32_internal_can
|
||||
canbus_id: esp32_can_esp32_internal_can
|
||||
use_extended_id: true
|
||||
can_id: 0x100
|
||||
data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
|
||||
- canbus.send:
|
||||
# Standard ID by default
|
||||
canbus_id: esp32_internal_can
|
||||
canbus_id: esp32_can_esp32_internal_can
|
||||
can_id: 0x100
|
||||
data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
|
||||
# Note: esp32_internal_can_2 uses LISTENONLY mode, so no send actions
|
||||
|
||||
canbus:
|
||||
- platform: esp32_can
|
||||
id: esp32_internal_can
|
||||
id: esp32_can_esp32_internal_can
|
||||
rx_pin: GPIO8
|
||||
tx_pin: GPIO7
|
||||
can_id: 4
|
||||
|
||||
@@ -62,7 +62,7 @@ packet_transport:
|
||||
encryption:
|
||||
key: "0123456789abcdef0123456789abcdef"
|
||||
sensors:
|
||||
- temp_sensor
|
||||
- espnow_temp_sensor
|
||||
providers:
|
||||
- name: test-provider
|
||||
encryption:
|
||||
@@ -70,9 +70,9 @@ packet_transport:
|
||||
|
||||
sensor:
|
||||
- platform: internal_temperature
|
||||
id: temp_sensor
|
||||
id: espnow_temp_sensor
|
||||
|
||||
- platform: packet_transport
|
||||
provider: test-provider
|
||||
remote_id: temp_sensor
|
||||
remote_id: espnow_temp_sensor
|
||||
id: remote_temp
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
ethernet:
|
||||
id: eth
|
||||
type: W5500
|
||||
clk_pin: 19
|
||||
mosi_pin: 21
|
||||
miso_pin: 23
|
||||
cs_pin: 18
|
||||
interrupt_pin: 36
|
||||
reset_pin: 22
|
||||
enable_on_boot: false
|
||||
manual_ip:
|
||||
static_ip: 192.168.178.56
|
||||
gateway: 192.168.178.1
|
||||
subnet: 255.255.255.0
|
||||
mac_address: "02:AA:BB:CC:DD:01"
|
||||
interface: spi2
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
priority: 200
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
not:
|
||||
ethernet.enabled:
|
||||
then:
|
||||
- ethernet.enable:
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: "Disable Ethernet"
|
||||
on_press:
|
||||
- ethernet.disable:
|
||||
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
name: "Ethernet Connected"
|
||||
lambda: |-
|
||||
return id(eth).is_connected();
|
||||
@@ -1,6 +1,6 @@
|
||||
light:
|
||||
- platform: fastled_clockless
|
||||
id: addr1
|
||||
id: fastled_clockless_addr1
|
||||
chipset: WS2811
|
||||
pin: 13
|
||||
num_leds: 100
|
||||
@@ -59,13 +59,13 @@ light:
|
||||
name: Custom Effect
|
||||
sequence:
|
||||
- light.addressable_set:
|
||||
id: addr1
|
||||
id: fastled_clockless_addr1
|
||||
red: 100%
|
||||
green: 100%
|
||||
blue: 0%
|
||||
- delay: 100ms
|
||||
- light.addressable_set:
|
||||
id: addr1
|
||||
id: fastled_clockless_addr1
|
||||
red: 0%
|
||||
green: 100%
|
||||
blue: 0%
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
light:
|
||||
- platform: fastled_spi
|
||||
id: addr1
|
||||
id: fastled_spi_addr1
|
||||
chipset: WS2801
|
||||
clock_pin: 22
|
||||
data_pin: 23
|
||||
@@ -59,13 +59,13 @@ light:
|
||||
name: Custom Effect
|
||||
sequence:
|
||||
- light.addressable_set:
|
||||
id: addr1
|
||||
id: fastled_spi_addr1
|
||||
red: 100%
|
||||
green: 100%
|
||||
blue: 0%
|
||||
- delay: 100ms
|
||||
- light.addressable_set:
|
||||
id: addr1
|
||||
id: fastled_spi_addr1
|
||||
red: 0%
|
||||
green: 100%
|
||||
blue: 0%
|
||||
|
||||
@@ -8,7 +8,7 @@ font:
|
||||
id: roboto32
|
||||
|
||||
- file: "gfonts://Roboto"
|
||||
id: roboto
|
||||
id: font_roboto
|
||||
size: 20
|
||||
glyphs: "0123456789."
|
||||
extras:
|
||||
@@ -50,11 +50,11 @@ font:
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: ssd1306_display
|
||||
id: font_ssd1306_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: ${display_reset_pin}
|
||||
lambda: |-
|
||||
it.print(0, 0, id(roboto), "Hello, World!");
|
||||
it.print(0, 0, id(font_roboto), "Hello, World!");
|
||||
it.print(0, 20, id(roboto_web), "Hello, World!");
|
||||
it.print(0, 40, id(monocraft), "Hello, World!");
|
||||
it.print(0, 60, id(monocraft2), "Hello, World!");
|
||||
|
||||
@@ -8,7 +8,7 @@ font:
|
||||
id: roboto32
|
||||
|
||||
- file: "gfonts://Roboto"
|
||||
id: roboto
|
||||
id: font_roboto
|
||||
size: 20
|
||||
glyphs: "0123456789."
|
||||
extras:
|
||||
@@ -44,12 +44,12 @@ font:
|
||||
|
||||
display:
|
||||
- platform: sdl
|
||||
id: sdl_display
|
||||
id: font_sdl_display
|
||||
dimensions:
|
||||
width: 800
|
||||
height: 600
|
||||
lambda: |-
|
||||
it.print(0, 0, id(roboto), "Hello, World!");
|
||||
it.print(0, 0, id(font_roboto), "Hello, World!");
|
||||
it.print(0, 20, id(roboto_web), "Hello, World!");
|
||||
it.print(0, 40, id(roboto_greek), "Hello κόσμε!");
|
||||
it.print(0, 60, id(monocraft), "Hello, World!");
|
||||
|
||||
@@ -12,7 +12,7 @@ graph:
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: ssd1306_display
|
||||
id: graph_ssd1306_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: ${reset_pin}
|
||||
pages:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
id: ssd1306_i2c_display
|
||||
i2c_id: i2c_bus
|
||||
id: graphical_display_menu_ssd1306_i2c_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: ${reset_pin}
|
||||
pages:
|
||||
@@ -10,12 +11,12 @@ display:
|
||||
|
||||
font:
|
||||
- file: "gfonts://Roboto"
|
||||
id: roboto
|
||||
id: graphical_display_menu_roboto
|
||||
size: 20
|
||||
|
||||
number:
|
||||
- platform: template
|
||||
id: test_number
|
||||
id: graphical_display_menu_test_number
|
||||
min_value: 0
|
||||
step: 1
|
||||
max_value: 10
|
||||
@@ -31,13 +32,13 @@ select:
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
id: test_switch
|
||||
id: graphical_display_menu_test_switch
|
||||
optimistic: true
|
||||
|
||||
graphical_display_menu:
|
||||
id: test_graphical_display_menu
|
||||
display: ssd1306_i2c_display
|
||||
font: roboto
|
||||
display: graphical_display_menu_ssd1306_i2c_display
|
||||
font: graphical_display_menu_roboto
|
||||
active: false
|
||||
mode: rotary
|
||||
on_enter:
|
||||
@@ -80,7 +81,7 @@ graphical_display_menu:
|
||||
lambda: 'ESP_LOGI("graphical_display_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());'
|
||||
- type: number
|
||||
text: "Number"
|
||||
number: test_number
|
||||
number: graphical_display_menu_test_number
|
||||
on_enter:
|
||||
then:
|
||||
lambda: 'ESP_LOGI("graphical_display_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());'
|
||||
@@ -97,7 +98,7 @@ graphical_display_menu:
|
||||
- display_menu.hide: test_graphical_display_menu
|
||||
- type: switch
|
||||
text: "Switch"
|
||||
switch: test_switch
|
||||
switch: graphical_display_menu_test_switch
|
||||
on_text: "Bright"
|
||||
off_text: "Dark"
|
||||
immediate_edit: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: ssd1306_i2c_display
|
||||
id: gt911_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_i2c_display
|
||||
display: gt911_ssd1306_i2c_display
|
||||
interrupt_pin: ${interrupt_pin}
|
||||
reset_pin: ${reset_pin}
|
||||
|
||||
|
||||
@@ -93,12 +93,12 @@ text_sensor:
|
||||
event:
|
||||
- platform: template
|
||||
name: Test Event
|
||||
id: test_event
|
||||
id: homeassistant_test_event
|
||||
event_types:
|
||||
- test_event_type
|
||||
on_event:
|
||||
- homeassistant.event:
|
||||
event: esphome.test_event
|
||||
event: esphome.homeassistant_test_event
|
||||
data:
|
||||
event_name: !lambda |-
|
||||
return event_type;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/host.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -3,7 +3,7 @@ i2s_audio:
|
||||
|
||||
speaker:
|
||||
- platform: i2s_audio
|
||||
id: speaker_id
|
||||
id: spdif_speaker_id
|
||||
dac_type: external
|
||||
i2s_dout_pin: ${spdif_data_pin}
|
||||
spdif_mode: true
|
||||
|
||||
@@ -3,7 +3,7 @@ packages:
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
id: main_lcd
|
||||
id: image_main_lcd
|
||||
spi_id: spi_bus
|
||||
model: ili9342
|
||||
cs_pin: 15
|
||||
|
||||
@@ -3,7 +3,7 @@ packages:
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
id: main_lcd
|
||||
id: image_main_lcd
|
||||
spi_id: spi_bus
|
||||
model: ili9342
|
||||
cs_pin: 5
|
||||
|
||||
@@ -3,7 +3,7 @@ packages:
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
id: main_lcd
|
||||
id: image_main_lcd
|
||||
spi_id: spi_bus
|
||||
model: ili9342
|
||||
cs_pin: 20
|
||||
|
||||
@@ -23,7 +23,7 @@ infrared:
|
||||
|
||||
# Infrared receiver
|
||||
- platform: ir_rf_proxy
|
||||
id: ir_rx
|
||||
id: infrared_ir_rx
|
||||
name: "IR Receiver"
|
||||
remote_receiver_id: ir_receiver
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ esphome:
|
||||
|
||||
sensor:
|
||||
- platform: adc
|
||||
id: my_sensor
|
||||
id: integration_my_sensor
|
||||
pin: ${pin}
|
||||
attenuation: 12db
|
||||
- platform: integration
|
||||
id: integration_sensor
|
||||
sensor: my_sensor
|
||||
sensor: integration_my_sensor
|
||||
name: Integration Sensor
|
||||
time_unit: s
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
sensor:
|
||||
- platform: adc
|
||||
id: my_sensor
|
||||
id: integration_my_sensor
|
||||
pin: VCC
|
||||
- platform: integration
|
||||
sensor: my_sensor
|
||||
sensor: integration_my_sensor
|
||||
name: Integration Sensor
|
||||
time_unit: s
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
sensor:
|
||||
- platform: adc
|
||||
id: my_sensor
|
||||
id: integration_my_sensor
|
||||
pin: VCC
|
||||
- platform: integration
|
||||
sensor: my_sensor
|
||||
sensor: integration_my_sensor
|
||||
name: Integration Sensor
|
||||
time_unit: s
|
||||
|
||||
@@ -6,7 +6,7 @@ remote_receiver:
|
||||
infrared:
|
||||
# Infrared receiver
|
||||
- platform: ir_rf_proxy
|
||||
id: ir_rx
|
||||
id: ir_rf_proxy_ir_rx
|
||||
name: "IR Receiver"
|
||||
receiver_frequency: 38kHz
|
||||
remote_receiver_id: ir_receiver
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
display:
|
||||
- platform: lcd_gpio
|
||||
id: my_lcd_gpio
|
||||
id: lcd_gpio_my_lcd_gpio
|
||||
dimensions: 18x4
|
||||
data_pins:
|
||||
- number: ${d0_pin}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
number:
|
||||
- platform: template
|
||||
id: test_number
|
||||
id: lcd_menu_test_number
|
||||
min_value: 0
|
||||
step: 1
|
||||
max_value: 10
|
||||
@@ -22,7 +22,7 @@ switch:
|
||||
|
||||
display:
|
||||
- platform: lcd_gpio
|
||||
id: my_lcd_gpio
|
||||
id: lcd_menu_my_lcd_gpio
|
||||
dimensions: 18x4
|
||||
data_pins:
|
||||
- number: ${d0_pin}
|
||||
@@ -36,7 +36,7 @@ display:
|
||||
|
||||
lcd_menu:
|
||||
id: test_lcd_menu
|
||||
display_id: my_lcd_gpio
|
||||
display_id: lcd_menu_my_lcd_gpio
|
||||
mark_back: 0x5e
|
||||
mark_selected: 0x3e
|
||||
mark_editing: 0x2a
|
||||
@@ -83,7 +83,7 @@ lcd_menu:
|
||||
lambda: 'ESP_LOGI("lcd_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());'
|
||||
- type: number
|
||||
text: Number
|
||||
number: test_number
|
||||
number: lcd_menu_test_number
|
||||
on_enter:
|
||||
then:
|
||||
lambda: 'ESP_LOGI("lcd_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());'
|
||||
|
||||
@@ -156,7 +156,7 @@ light:
|
||||
- platform: binary
|
||||
id: test_binary_light
|
||||
name: Binary Light
|
||||
output: test_binary
|
||||
output: light_test_binary
|
||||
effects:
|
||||
- strobe:
|
||||
on_state:
|
||||
@@ -182,6 +182,9 @@ light:
|
||||
state += 1;
|
||||
if (state == 4)
|
||||
state = 0;
|
||||
if (initial_run) {
|
||||
ESP_LOGD("custom_effect", "Effect %s started", it.get_name().c_str());
|
||||
}
|
||||
- pulse:
|
||||
transition_length: 10s
|
||||
update_interval: 20s
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
output:
|
||||
- platform: gpio
|
||||
id: test_binary
|
||||
id: light_test_binary
|
||||
pin: 12
|
||||
- platform: ledc
|
||||
id: test_ledc_1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
output:
|
||||
- platform: gpio
|
||||
id: test_binary
|
||||
id: light_test_binary
|
||||
pin: 4
|
||||
- platform: esp8266_pwm
|
||||
id: test_ledc_1
|
||||
|
||||
@@ -5,14 +5,14 @@ esphome:
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
id: test_binary
|
||||
id: light_test_binary
|
||||
pin: 0
|
||||
|
||||
light:
|
||||
- platform: binary
|
||||
id: test_binary_light
|
||||
name: Binary Light
|
||||
output: test_binary
|
||||
output: light_test_binary
|
||||
effects:
|
||||
- strobe:
|
||||
on_state:
|
||||
|
||||
@@ -5,14 +5,14 @@ esphome:
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
id: test_binary
|
||||
id: light_test_binary
|
||||
pin: 0
|
||||
|
||||
light:
|
||||
- platform: binary
|
||||
id: test_binary_light
|
||||
name: Binary Light
|
||||
output: test_binary
|
||||
output: light_test_binary
|
||||
effects:
|
||||
- strobe:
|
||||
on_state:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
output:
|
||||
- platform: gpio
|
||||
id: test_binary
|
||||
id: light_test_binary
|
||||
pin: 0
|
||||
- platform: rp2040_pwm
|
||||
id: test_ledc_1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: ssd1306_i2c_display
|
||||
id: lilygo_t5_47_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_i2c_display
|
||||
display: lilygo_t5_47_ssd1306_i2c_display
|
||||
on_touch:
|
||||
- logger.log:
|
||||
format: Touch at (%d, %d)
|
||||
|
||||
@@ -7,7 +7,7 @@ esphome:
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
id: test_binary
|
||||
id: lock_test_binary
|
||||
pin: 4
|
||||
|
||||
lock:
|
||||
@@ -32,4 +32,4 @@ lock:
|
||||
- platform: output
|
||||
name: Generic Output Lock
|
||||
id: test_lock2
|
||||
output: test_binary
|
||||
output: lock_test_binary
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
logger:
|
||||
hardware_uart: UART0
|
||||
baud_rate: 0
|
||||
@@ -0,0 +1 @@
|
||||
<<: !include common-uart0_no_logging.yaml
|
||||
@@ -0,0 +1,63 @@
|
||||
sensor:
|
||||
- platform: lsm6ds
|
||||
name: "lsm6ds Temperature"
|
||||
|
||||
- platform: motion
|
||||
type: acceleration_x
|
||||
name: "Accel X"
|
||||
accuracy_decimals: 4
|
||||
filters:
|
||||
- sliding_window_moving_average:
|
||||
window_size: 4
|
||||
send_every: 1
|
||||
- platform: motion
|
||||
type: acceleration_y
|
||||
name: "Accel Y"
|
||||
accuracy_decimals: 4
|
||||
- platform: motion
|
||||
type: acceleration_z
|
||||
name: "Accel Z"
|
||||
accuracy_decimals: 4
|
||||
|
||||
# Gyroscope axes (unit: °/s)
|
||||
- platform: motion
|
||||
type: gyroscope_x
|
||||
name: "Gyro X"
|
||||
- platform: motion
|
||||
type: gyroscope_y
|
||||
name: "Gyro Y"
|
||||
- platform: motion
|
||||
type: gyroscope_z
|
||||
name: "Gyro Z"
|
||||
|
||||
- platform: motion
|
||||
type: angular_rate_x
|
||||
name: "Angular Rate X"
|
||||
- platform: motion
|
||||
type: angular_rate_y
|
||||
name: "Angular Rate Y"
|
||||
- platform: motion
|
||||
type: angular_rate_z
|
||||
name: "Angular Rate Z"
|
||||
|
||||
- platform: motion
|
||||
type: pitch
|
||||
name: "Pitch"
|
||||
- platform: motion
|
||||
type: roll
|
||||
name: "Roll"
|
||||
|
||||
motion:
|
||||
- platform: lsm6ds
|
||||
# Accelerometer full-scale range: 2G | 4G | 8G | 16G
|
||||
accelerometer_range: 4G
|
||||
|
||||
accelerometer_odr: 104HZ
|
||||
|
||||
gyroscope_range: 2000DPS
|
||||
|
||||
gyroscope_odr: 208HZ
|
||||
axis_map:
|
||||
x: y
|
||||
y: x
|
||||
z: -z
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1313,6 +1313,7 @@ lvgl:
|
||||
width: 6
|
||||
start_value: 0
|
||||
end_value: 360
|
||||
rounded: true
|
||||
- id: page3
|
||||
layout: Horizontal
|
||||
pad_all: 6px
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
psram:
|
||||
mode: quad
|
||||
|
||||
i2s_audio:
|
||||
i2s_lrclk_pin: GPIO18
|
||||
i2s_bclk_pin: GPIO19
|
||||
|
||||
microphone:
|
||||
- platform: i2s_audio
|
||||
id: echo_microphone
|
||||
i2s_din_pin: GPIO17
|
||||
i2s_audio_id: i2s_audio_bus
|
||||
i2s_din_pin: ${mic_din_pin}
|
||||
adc_type: external
|
||||
pdm: true
|
||||
bits_per_sample: 16bit
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
substitutions:
|
||||
mic_din_pin: GPIO36
|
||||
|
||||
packages:
|
||||
i2s_audio: !include ../../test_build_components/common/i2s_audio/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
substitutions:
|
||||
mic_din_pin: GPIO18
|
||||
|
||||
packages:
|
||||
i2s_audio: !include ../../test_build_components/common/i2s_audio/esp32-s3-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -6,20 +6,16 @@ esphome:
|
||||
decibel_reduction: 10
|
||||
duration: 1s
|
||||
|
||||
i2s_audio:
|
||||
i2s_lrclk_pin: ${lrclk_pin}
|
||||
i2s_bclk_pin: ${bclk_pin}
|
||||
i2s_mclk_pin: ${mclk_pin}
|
||||
|
||||
speaker:
|
||||
- platform: i2s_audio
|
||||
id: speaker_id
|
||||
id: mixer_output_speaker_id
|
||||
i2s_audio_id: i2s_audio_bus
|
||||
dac_type: external
|
||||
i2s_dout_pin: ${dout_pin}
|
||||
bits_per_sample: 32bit
|
||||
channel: stereo
|
||||
- platform: mixer
|
||||
output_speaker: speaker_id
|
||||
output_speaker: mixer_output_speaker_id
|
||||
bits_per_sample: 32
|
||||
num_channels: 2
|
||||
source_speakers:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user