mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 10:12:52 +00:00
Merge remote-tracking branch 'origin/dev' into HEAD
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: nodemcu-32s
|
||||
|
||||
logger:
|
||||
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
id: single_and
|
||||
on_press:
|
||||
- if:
|
||||
condition:
|
||||
- lambda: return true;
|
||||
then:
|
||||
- logger.log: single and
|
||||
- platform: template
|
||||
id: single_or
|
||||
on_press:
|
||||
- if:
|
||||
any:
|
||||
- lambda: return true;
|
||||
then:
|
||||
- logger.log: single or
|
||||
- platform: template
|
||||
id: double_and
|
||||
on_press:
|
||||
- if:
|
||||
condition:
|
||||
- lambda: return true;
|
||||
- lambda: return false;
|
||||
then:
|
||||
- logger.log: double and
|
||||
- platform: template
|
||||
id: single_xor
|
||||
on_press:
|
||||
- if:
|
||||
condition:
|
||||
xor:
|
||||
- lambda: return true;
|
||||
then:
|
||||
- logger.log: single xor
|
||||
@@ -0,0 +1,25 @@
|
||||
"""Tests for and/or condition group codegen."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
|
||||
def test_single_condition_groups_are_unwrapped(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""A group of one condition is passed to the action directly."""
|
||||
main_cpp = generate_main(component_config_path("condition_groups.yaml"))
|
||||
|
||||
assert "IfAction<false>(lambdacondition_id);" in main_cpp
|
||||
assert "IfAction<false>(lambdacondition_id_2);" in main_cpp
|
||||
group = re.search(
|
||||
r"new\((\w+)\) AndCondition<2>\(\{lambdacondition_id_3, lambdacondition_id_4\}\);",
|
||||
main_cpp,
|
||||
)
|
||||
assert group is not None
|
||||
assert f"IfAction<false>({group.group(1)});" in main_cpp
|
||||
# xor of one condition equals that condition, so it is unwrapped too.
|
||||
assert "IfAction<false>(lambdacondition_id_5);" in main_cpp
|
||||
assert "XorCondition<" not in main_cpp
|
||||
@@ -0,0 +1,17 @@
|
||||
"""Tests for the dlms_meter provider option removed in 2026.11.0."""
|
||||
|
||||
import pytest
|
||||
from voluptuous import Invalid, MultipleInvalid
|
||||
|
||||
from esphome.components.dlms_meter import CONFIG_SCHEMA
|
||||
|
||||
|
||||
def test_removed_provider_shows_the_netznoe_patterns() -> None:
|
||||
with pytest.raises(Invalid) as exc_info:
|
||||
CONFIG_SCHEMA({"provider": "netznoe"})
|
||||
errors = (
|
||||
exc_info.value.errors
|
||||
if isinstance(exc_info.value, MultipleInvalid)
|
||||
else [exc_info.value]
|
||||
)
|
||||
assert any('pattern: "L, TSTR"' in str(error) for error in errors)
|
||||
@@ -0,0 +1,33 @@
|
||||
"""Tests for the dlms_meter predefined keys removed in 2026.11.0."""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
import pytest
|
||||
from voluptuous import Invalid, MultipleInvalid
|
||||
|
||||
from esphome.components.dlms_meter.sensor import CONFIG_SCHEMA as SENSOR_SCHEMA
|
||||
from esphome.components.dlms_meter.text_sensor import (
|
||||
CONFIG_SCHEMA as TEXT_SENSOR_SCHEMA,
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
|
||||
def _errors(schema: Callable[[ConfigType], ConfigType], config: ConfigType) -> str:
|
||||
with pytest.raises(Invalid) as exc_info:
|
||||
schema(config)
|
||||
errors = (
|
||||
exc_info.value.errors
|
||||
if isinstance(exc_info.value, MultipleInvalid)
|
||||
else [exc_info.value]
|
||||
)
|
||||
return "\n".join(str(error) for error in errors)
|
||||
|
||||
|
||||
def test_removed_sensor_key_names_its_replacement() -> None:
|
||||
errors = _errors(SENSOR_SCHEMA, {"voltage_l1": {"name": "Voltage"}})
|
||||
assert 'obis_code: "1.0.32.7.0.255" and unit_of_measurement: V' in errors
|
||||
|
||||
|
||||
def test_removed_text_sensor_key_names_its_replacement() -> None:
|
||||
errors = _errors(TEXT_SENSOR_SCHEMA, {"timestamp": {"name": "Timestamp"}})
|
||||
assert 'obis_code: "0.0.1.0.0.255"' in errors
|
||||
@@ -10,6 +10,7 @@ from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components.const import CONF_HOLD_STATE
|
||||
from esphome.components.esp32 import (
|
||||
_ESP_TLS_LINKING_COMPONENTS,
|
||||
DEFAULT_EXCLUDED_IDF_COMPONENTS,
|
||||
@@ -38,13 +39,30 @@ from esphome.components.esp32.const import (
|
||||
KEY_NETWORK_SDKCONFIG,
|
||||
KEY_SDKCONFIG_OPTIONS,
|
||||
KEY_VARIANT,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32C61,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32H4,
|
||||
VARIANT_ESP32H21,
|
||||
VARIANT_ESP32P4,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
VARIANT_ESP32S31,
|
||||
)
|
||||
from esphome.components.esp32.gpio import validate_gpio_pin
|
||||
from esphome.components.esp32.gpio import validate_gpio_pin, validate_supports
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ESPHOME,
|
||||
CONF_IGNORE_PIN_VALIDATION_ERROR,
|
||||
CONF_INPUT,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_OPEN_DRAIN,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLDOWN,
|
||||
CONF_PULLUP,
|
||||
PlatformFramework,
|
||||
Toolchain,
|
||||
)
|
||||
@@ -57,7 +75,7 @@ def test_esp32_config(
|
||||
) -> None:
|
||||
set_core_config(PlatformFramework.ESP32_IDF)
|
||||
|
||||
from esphome.components.esp32 import CONFIG_SCHEMA, VARIANT_ESP32, VARIANT_FRIENDLY
|
||||
from esphome.components.esp32 import CONFIG_SCHEMA, VARIANT_FRIENDLY
|
||||
|
||||
# Example ESP32 configuration
|
||||
config = {
|
||||
@@ -1722,9 +1740,8 @@ def test_esp32_s31_gpio_validation(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""S31: GPIO26-28/30-32 are reserved for the SPI flash interface, GPIO29
|
||||
and GPIO41 do not exist, GPIO33 is a normal pin, and GPIO36 is a
|
||||
and GPIO41 do not exist, GPIO35 is a normal pin, and GPIO36 is a
|
||||
strapping pin."""
|
||||
from esphome.components.esp32.const import VARIANT_ESP32S31
|
||||
from esphome.components.esp32.gpio import validate_supports
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_OPEN_DRAIN, CONF_OUTPUT
|
||||
|
||||
@@ -1735,8 +1752,8 @@ def test_esp32_s31_gpio_validation(
|
||||
input_mode = {CONF_INPUT: True, CONF_OUTPUT: False, CONF_OPEN_DRAIN: False}
|
||||
|
||||
# Not reserved; a normal GPIO
|
||||
pin = {CONF_NUMBER: 33, CONF_IGNORE_PIN_VALIDATION_ERROR: False}
|
||||
assert validate_gpio_pin(pin)[CONF_NUMBER] == 33
|
||||
pin = {CONF_NUMBER: 35, CONF_IGNORE_PIN_VALIDATION_ERROR: False}
|
||||
assert validate_gpio_pin(pin)[CONF_NUMBER] == 35
|
||||
|
||||
# Reserved for the SPI flash interface, but can be bypassed with
|
||||
# ignore_pin_validation_error
|
||||
@@ -1764,6 +1781,113 @@ def test_esp32_s31_gpio_validation(
|
||||
assert "GPIO36 is a strapping pin" in caplog.text
|
||||
|
||||
|
||||
_INPUT_ONLY_SETTINGS = (
|
||||
(CONF_OUTPUT, "does not support output pin mode"),
|
||||
(CONF_PULLUP, "does not support pullups"),
|
||||
(CONF_PULLDOWN, "does not support pulldowns"),
|
||||
(CONF_HOLD_STATE, "is input-only and cannot be held"),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("variant", "number", "setting", "error"),
|
||||
[
|
||||
pytest.param(
|
||||
variant, number, setting, error, id=f"{name}-gpio{number}-{setting}"
|
||||
)
|
||||
for variant, name, numbers in (
|
||||
(VARIANT_ESP32, "esp32", range(34, 40)),
|
||||
(VARIANT_ESP32S2, "s2", (46,)),
|
||||
)
|
||||
for number in numbers
|
||||
for setting, error in _INPUT_ONLY_SETTINGS
|
||||
]
|
||||
+ [
|
||||
pytest.param(
|
||||
VARIANT_ESP32,
|
||||
20,
|
||||
CONF_HOLD_STATE,
|
||||
"GPIO20 has no hold function",
|
||||
id="esp32-gpio20-hold_state",
|
||||
)
|
||||
],
|
||||
)
|
||||
def test_input_only_gpio_rejects_unsupported_modes(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
variant: str,
|
||||
number: int,
|
||||
setting: str,
|
||||
error: str,
|
||||
) -> None:
|
||||
set_core_config(PlatformFramework.ESP32_IDF, platform_data={KEY_VARIANT: variant})
|
||||
mode = {
|
||||
CONF_INPUT: True,
|
||||
CONF_OUTPUT: False,
|
||||
CONF_OPEN_DRAIN: False,
|
||||
CONF_PULLUP: False,
|
||||
CONF_PULLDOWN: False,
|
||||
}
|
||||
pin = {CONF_NUMBER: number, CONF_MODE: mode}
|
||||
if setting == CONF_HOLD_STATE:
|
||||
pin[setting] = True
|
||||
else:
|
||||
mode[setting] = True
|
||||
|
||||
with pytest.raises(cv.Invalid, match=error):
|
||||
validate_supports(pin)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("variant", "number"),
|
||||
[
|
||||
pytest.param(VARIANT_ESP32C3, 18, id="c3-18"),
|
||||
pytest.param(VARIANT_ESP32C3, 19, id="c3-19"),
|
||||
pytest.param(VARIANT_ESP32C5, 13, id="c5-13"),
|
||||
pytest.param(VARIANT_ESP32C5, 14, id="c5-14"),
|
||||
pytest.param(VARIANT_ESP32C6, 12, id="c6-12"),
|
||||
pytest.param(VARIANT_ESP32C6, 13, id="c6-13"),
|
||||
pytest.param(VARIANT_ESP32C61, 12, id="c61-12"),
|
||||
pytest.param(VARIANT_ESP32C61, 13, id="c61-13"),
|
||||
pytest.param(VARIANT_ESP32H2, 26, id="h2-26"),
|
||||
pytest.param(VARIANT_ESP32H2, 27, id="h2-27"),
|
||||
pytest.param(VARIANT_ESP32H4, 13, id="h4-13"),
|
||||
pytest.param(VARIANT_ESP32H4, 14, id="h4-14"),
|
||||
pytest.param(VARIANT_ESP32H21, 17, id="h21-17"),
|
||||
pytest.param(VARIANT_ESP32H21, 18, id="h21-18"),
|
||||
pytest.param(VARIANT_ESP32P4, 24, id="p4-24"),
|
||||
pytest.param(VARIANT_ESP32P4, 25, id="p4-25"),
|
||||
pytest.param(VARIANT_ESP32S3, 19, id="s3-19"),
|
||||
pytest.param(VARIANT_ESP32S3, 20, id="s3-20"),
|
||||
pytest.param(VARIANT_ESP32S31, 33, id="s31-33"),
|
||||
pytest.param(VARIANT_ESP32S31, 34, id="s31-34"),
|
||||
],
|
||||
)
|
||||
def test_usb_jtag_gpio_hold_state_warns(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
variant: str,
|
||||
number: int,
|
||||
) -> None:
|
||||
set_core_config(PlatformFramework.ESP32_IDF, platform_data={KEY_VARIANT: variant})
|
||||
pin = {
|
||||
CONF_NUMBER: number,
|
||||
CONF_MODE: {
|
||||
CONF_INPUT: True,
|
||||
CONF_OUTPUT: False,
|
||||
CONF_OPEN_DRAIN: False,
|
||||
},
|
||||
CONF_HOLD_STATE: True,
|
||||
}
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
validate_supports(pin)
|
||||
|
||||
assert (
|
||||
f"GPIO{number} cannot hold at low level during wakeup from deep sleep."
|
||||
in caplog.text
|
||||
)
|
||||
|
||||
|
||||
_TLS_SERVER_OPTIONS = (
|
||||
"CONFIG_MBEDTLS_TLS_CLIENT_ONLY",
|
||||
"CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp8266:
|
||||
board: d1_mini
|
||||
@@ -0,0 +1,17 @@
|
||||
"""Tests for ESP8266 build flags."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
def test_esp8266_keeps_switches_out_of_ram(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
generate_main(component_config_path("minimal.yaml"))
|
||||
|
||||
assert "-fno-tree-switch-conversion" in CORE.build_flags
|
||||
@@ -8,6 +8,7 @@ from esphome.components.esp32 import (
|
||||
KEY_BOARD,
|
||||
KEY_IDF_VERSION,
|
||||
KEY_VARIANT,
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
from esphome.components.ethernet import CONF_CLOCK_SPEED, CONFIG_SCHEMA, _final_validate
|
||||
@@ -82,3 +83,40 @@ def test_ch390_rejects_clock_speed_above_the_datasheet_maximum(
|
||||
CORE.name = "ch390-test"
|
||||
with pytest.raises(Invalid, match="value must be at most 72000000"):
|
||||
CONFIG_SCHEMA({**_CH390_CONFIG, CONF_CLOCK_SPEED: "80MHz"})
|
||||
|
||||
|
||||
_LAN8720_CONFIG = {"type": "LAN8720", "mdc_pin": 23, "mdio_pin": 18}
|
||||
|
||||
|
||||
def _set_esp32_core(set_core_config: SetCoreConfigCallable) -> None:
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={
|
||||
KEY_BOARD: "esp32dev",
|
||||
KEY_VARIANT: VARIANT_ESP32,
|
||||
KEY_IDF_VERSION: cv.Version(5, 3, 2),
|
||||
},
|
||||
)
|
||||
CORE.name = "rmii-test"
|
||||
|
||||
|
||||
def test_rmii_rejects_removed_clk_mode(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
"""The removed clk_mode option points at the clk: replacement."""
|
||||
_set_esp32_core(set_core_config)
|
||||
with pytest.raises(Invalid, match="GPIO17_OUT -> mode: CLK_OUT, pin: GPIO17"):
|
||||
CONFIG_SCHEMA(
|
||||
{
|
||||
**_LAN8720_CONFIG,
|
||||
"clk_mode": "GPIO17_OUT",
|
||||
"clk": {"mode": "CLK_OUT", "pin": 17},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_rmii_requires_clk(set_core_config: SetCoreConfigCallable) -> None:
|
||||
"""An RMII PHY has no default clock, so clk must be given."""
|
||||
_set_esp32_core(set_core_config)
|
||||
with pytest.raises(Invalid, match=r"required key not provided @ data\['clk'\]"):
|
||||
CONFIG_SCHEMA(_LAN8720_CONFIG)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Tests for GPIO output final validation."""
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.const import CONF_HOLD_STATE
|
||||
from esphome.components.gpio.output import FINAL_VALIDATE_SCHEMA
|
||||
from esphome.config import Config
|
||||
from esphome.const import CONF_ID, CONF_PIN, CONF_POWER_SUPPLY, PlatformFramework
|
||||
from esphome.core import ID
|
||||
from tests.component_tests.types import SetCoreConfigCallable
|
||||
|
||||
|
||||
@pytest.mark.parametrize("power_supply_holds", [False, True])
|
||||
def test_output_hold_state_requires_power_supply_hold_state(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
power_supply_holds: bool,
|
||||
) -> None:
|
||||
supply_id = ID("supply", is_declaration=True, type="power_supply")
|
||||
full_config = Config()
|
||||
full_config["power_supply"] = [
|
||||
{CONF_ID: supply_id, CONF_PIN: {CONF_HOLD_STATE: power_supply_holds}}
|
||||
]
|
||||
full_config.declare_ids.append((supply_id, ["power_supply", 0, CONF_ID]))
|
||||
set_core_config(PlatformFramework.ESP32_IDF, full_config=full_config)
|
||||
|
||||
config = {
|
||||
CONF_PIN: {CONF_HOLD_STATE: True},
|
||||
CONF_POWER_SUPPLY: ID("supply", is_declaration=False, type="power_supply"),
|
||||
}
|
||||
if power_supply_holds:
|
||||
FINAL_VALIDATE_SCHEMA(config)
|
||||
else:
|
||||
with pytest.raises(
|
||||
cv.Invalid,
|
||||
match="The power supply pin must also set hold_state when an output it powers sets it",
|
||||
):
|
||||
FINAL_VALIDATE_SCHEMA(config)
|
||||
@@ -42,18 +42,20 @@ def test_platform_without_ble_server_rejected(
|
||||
assert "only available on" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_automations_emit_renamed_triggers(
|
||||
def test_automations_register_state_callbacks(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
main_cpp = generate_main(component_config_path("automations.yaml"))
|
||||
for trigger in (
|
||||
"ImprovBLEProvisionedTrigger",
|
||||
"ImprovBLEProvisioningTrigger",
|
||||
"ImprovBLEStartTrigger",
|
||||
"ImprovBLEStateTrigger",
|
||||
"ImprovBLEStoppedTrigger",
|
||||
assert "Trigger" not in main_cpp
|
||||
for state in (
|
||||
"improv::STATE_PROVISIONED",
|
||||
"improv::STATE_PROVISIONING",
|
||||
"improv::STATE_AUTHORIZED",
|
||||
"improv::STATE_STOPPED",
|
||||
):
|
||||
assert f"improv_ble::{trigger}" in main_cpp
|
||||
assert f"state == {state}" in main_cpp
|
||||
assert main_cpp.count("add_on_state_callback(") == 5
|
||||
assert "->is_failed()" in main_cpp
|
||||
assert "set_authorizer" in main_cpp
|
||||
assert "set_status_indicator" in main_cpp
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import light
|
||||
from esphome.components.light import types as light_types
|
||||
from esphome.core import ID, EsphomeError
|
||||
|
||||
|
||||
def test_default_flash_length_and_empty_effects_are_not_emitted(
|
||||
@@ -16,4 +24,20 @@ def test_default_flash_length_and_empty_effects_are_not_emitted(
|
||||
assert "bare_light->set_flash_transition_length(" not in main_cpp
|
||||
assert "bare_light->add_effects(" not in main_cpp
|
||||
assert "fancy_light->set_flash_transition_length(500);" in main_cpp
|
||||
assert "fancy_light->add_effects({" in main_cpp
|
||||
call = re.search(r"fancy_light->add_effects\((\w+), (\d+)\);", main_cpp)
|
||||
assert call is not None
|
||||
# The effect pointers form a flash table; they are address constants, not constexpr
|
||||
assert re.search(
|
||||
rf"static light::LightEffect \* const {call.group(1)}\[\] PROGMEM = \{{[^}}]+\}};",
|
||||
main_cpp,
|
||||
)
|
||||
|
||||
|
||||
def test_effect_assigned_in_setup_is_rejected() -> None:
|
||||
"""A pointer assigned in setup() would make the flash table need dynamic init."""
|
||||
dynamic_effect = cg.Pvariable(
|
||||
ID("dynamic_effect", is_declaration=True, type=light_types.LightEffect),
|
||||
cg.RawExpression("make_effect()"),
|
||||
)
|
||||
with pytest.raises(EsphomeError, match="dynamic_effect"):
|
||||
light._effects_table([dynamic_effect])
|
||||
|
||||
@@ -10,9 +10,11 @@ import pytest
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.esp32 import KEY_BOARD, VARIANT_ESP32P4
|
||||
|
||||
# Importing xl9535 registers its pin schema with pins.PIN_SCHEMA_REGISTRY so that
|
||||
# models (e.g. SEEED-RETERMINAL-D1001) that reference xl9535-backed pins in their
|
||||
# defaults can be validated by the mipi_dsi CONFIG_SCHEMA in this test.
|
||||
# Importing the I/O expanders registers their pin schemas with
|
||||
# pins.PIN_SCHEMA_REGISTRY so that models whose defaults reference expander-backed
|
||||
# pins (e.g. SEEED-RETERMINAL-D1001 uses xl9535, the M5Stack Tab5 models use
|
||||
# pi4ioe5v6408) can be validated by the mipi_dsi CONFIG_SCHEMA in this test.
|
||||
import esphome.components.pi4ioe5v6408 # noqa: F401
|
||||
import esphome.components.xl9535 # noqa: F401
|
||||
from esphome.const import (
|
||||
CONF_DIMENSIONS,
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
remote_transmitter:
|
||||
pin: GPIO4
|
||||
carrier_duty_percent: 50%
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: Midea A
|
||||
on_press:
|
||||
- remote_transmitter.transmit_midea:
|
||||
code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF]
|
||||
- platform: template
|
||||
name: Midea B
|
||||
on_press:
|
||||
- remote_transmitter.transmit_midea:
|
||||
code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF]
|
||||
- platform: template
|
||||
name: AEHA
|
||||
on_press:
|
||||
- remote_transmitter.transmit_aeha:
|
||||
address: 0x8008
|
||||
data: [0x00, 0x02, 0xFD, 0xFF, 0x00, 0x33]
|
||||
- platform: template
|
||||
name: Haier lambda
|
||||
on_press:
|
||||
- remote_transmitter.transmit_haier:
|
||||
code: !lambda |-
|
||||
return {0xA6, 0xDA, 0x00, 0x00};
|
||||
- platform: template
|
||||
name: Haier static
|
||||
on_press:
|
||||
- remote_transmitter.transmit_haier:
|
||||
code: [0xA6, 0xDA, 0x00, 0x00, 0x40, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x05]
|
||||
- platform: template
|
||||
name: Mirage static
|
||||
on_press:
|
||||
- remote_transmitter.transmit_mirage:
|
||||
code: [0x56, 0x6C, 0x00, 0x00, 0x20, 0x1A, 0x00, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x00, 0x42]
|
||||
@@ -0,0 +1,24 @@
|
||||
"""Constant IR byte codes live in shared PROGMEM tables; lambdas stay lambdas."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
|
||||
def test_ir_codes_use_shared_progmem_tables(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
main_cpp = generate_main(component_config_path("ir_codes.yaml"))
|
||||
|
||||
tables = re.findall(
|
||||
r"static constexpr uint8_t (remote_base_code\w*)\[\] PROGMEM", main_cpp
|
||||
)
|
||||
assert len(tables) == 4 # Midea (shared by two actions), AEHA, Haier, Mirage
|
||||
midea = tables[0]
|
||||
assert main_cpp.count(f"->set_code_static({midea}, 5);") == 2
|
||||
assert re.search(r"->set_data_static\(remote_base_code\w*, 6\);", main_cpp)
|
||||
assert re.search(r"->set_code_static\(remote_base_code\w*, 13\);", main_cpp)
|
||||
assert re.search(r"->set_code_static\(remote_base_code\w*, 14\);", main_cpp)
|
||||
assert "->set_code_template([](" in main_cpp
|
||||
assert "std::vector<uint8_t>{" not in main_cpp
|
||||
@@ -20,7 +20,7 @@ def test_proxy_claims_its_slots_through_the_shared_accounting(
|
||||
# A default (3-slot) proxy build records one claim per slot, attributed
|
||||
# to the consumer, and passes final validation.
|
||||
generate_main(component_config_path("rp2_proxy_default.yaml"))
|
||||
used = CORE.data[rp2040_ble.KEY_RP2040_BLE][rp2040_ble.KEY_USED_CONNECTION_SLOTS]
|
||||
used = CORE.data[rp2040_ble.DOMAIN][rp2040_ble.KEY_USED_CONNECTION_SLOTS]
|
||||
assert used == ["bluetooth_proxy"] * 3
|
||||
|
||||
|
||||
|
||||
@@ -1,24 +1,42 @@
|
||||
"""Validation tests for the hub's unpaired access policy."""
|
||||
"""Validation tests for the unpaired access policy.
|
||||
|
||||
The hub's `unpaired_access` key and an `unpaired_access` switch both set the
|
||||
policy, so the switch platform rejects a config that has both.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.sendspin import (
|
||||
CONF_UNPAIRED_ACCESS,
|
||||
CONFIG_SCHEMA as HUB_CONFIG_SCHEMA,
|
||||
DOMAIN,
|
||||
FINAL_VALIDATE_SCHEMA as HUB_FINAL_VALIDATE_SCHEMA,
|
||||
_get_data,
|
||||
request_pairing_code_display_support,
|
||||
)
|
||||
from esphome.components.sendspin.switch import (
|
||||
CONFIG_SCHEMA as SWITCH_CONFIG_SCHEMA,
|
||||
FINAL_VALIDATE_SCHEMA as SWITCH_FINAL_VALIDATE_SCHEMA,
|
||||
)
|
||||
from esphome.const import PlatformFramework
|
||||
from esphome.types import ConfigType
|
||||
from tests.component_tests.types import SetCoreConfigCallable
|
||||
|
||||
HUB_ID = "sendspin_hub_id"
|
||||
NO_PAIRING_WARNING = "nothing lets a server pair"
|
||||
NO_PAIRING_WARNING = "no new server can pair with this device"
|
||||
|
||||
|
||||
def _switch_config(switch_type: str) -> ConfigType:
|
||||
return SWITCH_CONFIG_SCHEMA(
|
||||
{"name": "Sendspin Switch", "type": switch_type, "sendspin_id": HUB_ID}
|
||||
)
|
||||
|
||||
|
||||
def test_hub_key_absent_by_default(set_core_config: SetCoreConfigCallable) -> None:
|
||||
"""Codegen leaves the hub's built-in default of on in place when the key is not set."""
|
||||
"""With no default in the schema, the switch can tell whether the key was set."""
|
||||
set_core_config(PlatformFramework.ESP32_IDF)
|
||||
|
||||
assert CONF_UNPAIRED_ACCESS not in HUB_CONFIG_SCHEMA({"id": HUB_ID})
|
||||
@@ -36,7 +54,7 @@ def test_hub_key_accepted(set_core_config: SetCoreConfigCallable, value: bool) -
|
||||
def test_off_without_pairing_method_warns(
|
||||
set_core_config: SetCoreConfigCallable, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""With unpaired access off and no way to pair, no server could ever play."""
|
||||
"""With unpaired access off and no way to pair, no new server could pair."""
|
||||
set_core_config(PlatformFramework.ESP32_IDF)
|
||||
config = HUB_CONFIG_SCHEMA({"id": HUB_ID, CONF_UNPAIRED_ACCESS: False})
|
||||
|
||||
@@ -76,3 +94,92 @@ def test_on_does_not_warn(
|
||||
HUB_FINAL_VALIDATE_SCHEMA(config)
|
||||
|
||||
assert NO_PAIRING_WARNING not in caplog.text
|
||||
|
||||
|
||||
def test_switch_with_hub_key_rejected(set_core_config: SetCoreConfigCallable) -> None:
|
||||
"""Both would set the policy, and the hub's value would be silently ignored."""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
full_config={DOMAIN: {"id": HUB_ID, CONF_UNPAIRED_ACCESS: False}},
|
||||
)
|
||||
config = _switch_config(CONF_UNPAIRED_ACCESS)
|
||||
|
||||
with pytest.raises(cv.Invalid, match="set the switch's restore_mode instead"):
|
||||
SWITCH_FINAL_VALIDATE_SCHEMA(config)
|
||||
|
||||
|
||||
def test_switch_without_hub_key_accepted(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
set_core_config(PlatformFramework.ESP32_IDF, full_config={DOMAIN: {"id": HUB_ID}})
|
||||
config = _switch_config(CONF_UNPAIRED_ACCESS)
|
||||
|
||||
assert SWITCH_FINAL_VALIDATE_SCHEMA(config) is config
|
||||
|
||||
|
||||
def test_switch_without_pairing_method_warns(
|
||||
set_core_config: SetCoreConfigCallable, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Turning the switch off would leave no way for a new server to pair."""
|
||||
set_core_config(PlatformFramework.ESP32_IDF, full_config={DOMAIN: {"id": HUB_ID}})
|
||||
config = _switch_config(CONF_UNPAIRED_ACCESS)
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
SWITCH_FINAL_VALIDATE_SCHEMA(config)
|
||||
|
||||
assert NO_PAIRING_WARNING in caplog.text
|
||||
|
||||
|
||||
def test_switch_with_pairing_method_does_not_warn(
|
||||
set_core_config: SetCoreConfigCallable, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
full_config={DOMAIN: {"id": HUB_ID, "static_pairing_code": "01234567"}},
|
||||
)
|
||||
config = _switch_config(CONF_UNPAIRED_ACCESS)
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
SWITCH_FINAL_VALIDATE_SCHEMA(config)
|
||||
|
||||
assert NO_PAIRING_WARNING not in caplog.text
|
||||
|
||||
|
||||
def test_enabled_switch_ignores_hub_key(set_core_config: SetCoreConfigCallable) -> None:
|
||||
"""Only the unpaired access switch competes with the hub key."""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
full_config={DOMAIN: {"id": HUB_ID, CONF_UNPAIRED_ACCESS: False}},
|
||||
)
|
||||
config = _switch_config("enabled")
|
||||
|
||||
assert SWITCH_FINAL_VALIDATE_SCHEMA(config) is config
|
||||
|
||||
|
||||
@pytest.mark.parametrize("switch_type", ["enabled", CONF_UNPAIRED_ACCESS])
|
||||
def test_duplicate_switch_type_rejected(
|
||||
set_core_config: SetCoreConfigCallable, switch_type: str
|
||||
) -> None:
|
||||
"""Two switches of one type would fight over the same hub setting."""
|
||||
entry = {"platform": DOMAIN, "type": switch_type}
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
full_config={DOMAIN: {"id": HUB_ID}, "switch": [entry, dict(entry)]},
|
||||
)
|
||||
config = _switch_config(switch_type)
|
||||
|
||||
with pytest.raises(cv.Invalid, match="Only one sendspin"):
|
||||
SWITCH_FINAL_VALIDATE_SCHEMA(config)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("switch_type", ["enabled", CONF_UNPAIRED_ACCESS])
|
||||
def test_switch_requests_its_type(
|
||||
set_core_config: SetCoreConfigCallable, switch_type: str
|
||||
) -> None:
|
||||
"""The hub skips the codegen value for a setting a switch drives, so the client
|
||||
waits for that switch to restore before its first start."""
|
||||
set_core_config(PlatformFramework.ESP32_IDF)
|
||||
|
||||
_switch_config(switch_type)
|
||||
|
||||
assert _get_data().switch_types == {switch_type}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
"""timeout (value last) followed by throttle_with_priority with the same period becomes one filter."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
|
||||
def test_timeout_throttle_pair_is_merged(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
) -> None:
|
||||
main_cpp = generate_main("tests/component_tests/sensor/timeout_throttle.yaml")
|
||||
|
||||
# Only the `merged` sensor's adjacent pair becomes one object
|
||||
assert (
|
||||
len(re.findall(r"new\(\w+\) sensor::TimeoutThrottleFilter\(1000\);", main_cpp))
|
||||
== 1
|
||||
)
|
||||
merged = re.search(r"merged->set_filters\(\{(\w+)\}\);", main_cpp)
|
||||
assert merged is not None
|
||||
assert f"new({merged.group(1)}) sensor::TimeoutThrottleFilter(1000);" in main_cpp
|
||||
# Every other shape keeps its two filters
|
||||
for sensor_id in ("other_periods", "other_order", "value_list", "configured_value"):
|
||||
call = re.search(rf"{sensor_id}->set_filters\(\{{([^}}]*)\}}\);", main_cpp)
|
||||
assert call is not None and len(call.group(1).split(",")) == 2, sensor_id
|
||||
@@ -0,0 +1,52 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
id: merged
|
||||
filters:
|
||||
- timeout:
|
||||
timeout: 1s
|
||||
value: last
|
||||
- throttle_with_priority: 1s
|
||||
- platform: template
|
||||
id: other_periods
|
||||
filters:
|
||||
- timeout:
|
||||
timeout: 1s
|
||||
value: last
|
||||
- throttle_with_priority: 2s
|
||||
- platform: template
|
||||
id: other_order
|
||||
filters:
|
||||
- throttle_with_priority: 1s
|
||||
- timeout:
|
||||
timeout: 1s
|
||||
value: last
|
||||
- platform: template
|
||||
id: value_list
|
||||
filters:
|
||||
- timeout:
|
||||
timeout: 1s
|
||||
value: last
|
||||
- throttle_with_priority:
|
||||
timeout: 1s
|
||||
value: [0.0]
|
||||
- platform: template
|
||||
id: configured_value
|
||||
filters:
|
||||
- timeout:
|
||||
timeout: 1s
|
||||
value: 0.0
|
||||
- throttle_with_priority: 1s
|
||||
- platform: template
|
||||
id: inside_or
|
||||
filters:
|
||||
- or:
|
||||
- timeout:
|
||||
timeout: 1s
|
||||
value: last
|
||||
- throttle_with_priority: 1s
|
||||
@@ -8,7 +8,8 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.sntp.time import CONF_SNTP, _sntp_final_validate
|
||||
from esphome.components.sntp import DOMAIN as SNTP_DOMAIN
|
||||
from esphome.components.sntp.time import _sntp_final_validate
|
||||
from esphome.const import CONF_ID, CONF_PLATFORM, CONF_SERVERS, CONF_TIME
|
||||
from esphome.core import ID
|
||||
import esphome.final_validate as fv
|
||||
@@ -20,7 +21,7 @@ import esphome.final_validate as fv
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_PLATFORM: SNTP_DOMAIN,
|
||||
CONF_ID: ID("sntp_time", is_manual=False),
|
||||
CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"],
|
||||
}
|
||||
@@ -33,12 +34,12 @@ import esphome.final_validate as fv
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_PLATFORM: SNTP_DOMAIN,
|
||||
CONF_ID: ID("sntp_time_1", is_manual=False),
|
||||
CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"],
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_PLATFORM: SNTP_DOMAIN,
|
||||
CONF_ID: ID("sntp_time_2", is_manual=False),
|
||||
CONF_SERVERS: ["192.168.1.2"],
|
||||
},
|
||||
@@ -51,12 +52,12 @@ import esphome.final_validate as fv
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_PLATFORM: SNTP_DOMAIN,
|
||||
CONF_ID: ID("sntp_time_1", is_manual=False),
|
||||
CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"],
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_PLATFORM: SNTP_DOMAIN,
|
||||
CONF_ID: ID("sntp_time_2", is_manual=False),
|
||||
CONF_SERVERS: ["pool.ntp.org", "192.168.1.2"],
|
||||
},
|
||||
@@ -69,17 +70,17 @@ import esphome.final_validate as fv
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_PLATFORM: SNTP_DOMAIN,
|
||||
CONF_ID: ID("sntp_time_1", is_manual=False),
|
||||
CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"],
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_PLATFORM: SNTP_DOMAIN,
|
||||
CONF_ID: ID("sntp_time_2", is_manual=False),
|
||||
CONF_SERVERS: ["192.168.1.2", "pool2.ntp.org"],
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_PLATFORM: SNTP_DOMAIN,
|
||||
CONF_ID: ID("sntp_time_3", is_manual=False),
|
||||
CONF_SERVERS: ["pool3.ntp.org"],
|
||||
},
|
||||
@@ -95,7 +96,7 @@ import esphome.final_validate as fv
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_PLATFORM: SNTP_DOMAIN,
|
||||
CONF_ID: ID("sntp_time_1", is_manual=False),
|
||||
CONF_SERVERS: [
|
||||
"192.168.1.1",
|
||||
@@ -105,7 +106,7 @@ import esphome.final_validate as fv
|
||||
],
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_PLATFORM: SNTP_DOMAIN,
|
||||
CONF_ID: ID("sntp_time_2", is_manual=False),
|
||||
CONF_SERVERS: ["pool.ntp.org", "192.168.1.2"],
|
||||
},
|
||||
@@ -143,7 +144,7 @@ def test_sntp_instance_merging(
|
||||
sntp_instances = [
|
||||
tc
|
||||
for tc in updated_conf[CONF_TIME]
|
||||
if tc.get(CONF_PLATFORM) == CONF_SNTP
|
||||
if tc.get(CONF_PLATFORM) == SNTP_DOMAIN
|
||||
]
|
||||
assert len(sntp_instances) == expected_count
|
||||
|
||||
@@ -169,12 +170,12 @@ def test_sntp_inconsistent_manual_ids() -> None:
|
||||
# Create configs with manual IDs that are inconsistent
|
||||
time_configs = [
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_PLATFORM: SNTP_DOMAIN,
|
||||
CONF_ID: ID("sntp_time_1", is_manual=True),
|
||||
CONF_SERVERS: ["192.168.1.1"],
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_PLATFORM: SNTP_DOMAIN,
|
||||
CONF_ID: ID("sntp_time_2", is_manual=True),
|
||||
CONF_SERVERS: ["192.168.1.2"],
|
||||
},
|
||||
@@ -197,7 +198,7 @@ def test_sntp_with_other_time_platforms(caplog: pytest.LogCaptureFixture) -> Non
|
||||
"""Test that SNTP merging doesn't affect other time platforms."""
|
||||
time_configs = [
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_PLATFORM: SNTP_DOMAIN,
|
||||
CONF_ID: ID("sntp_time_1", is_manual=False),
|
||||
CONF_SERVERS: ["192.168.1.1"],
|
||||
},
|
||||
@@ -206,7 +207,7 @@ def test_sntp_with_other_time_platforms(caplog: pytest.LogCaptureFixture) -> Non
|
||||
CONF_ID: ID("homeassistant_time", is_manual=False),
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: CONF_SNTP,
|
||||
CONF_PLATFORM: SNTP_DOMAIN,
|
||||
CONF_ID: ID("sntp_time_2", is_manual=False),
|
||||
CONF_SERVERS: ["192.168.1.2"],
|
||||
},
|
||||
@@ -226,11 +227,11 @@ def test_sntp_with_other_time_platforms(caplog: pytest.LogCaptureFixture) -> Non
|
||||
|
||||
# Find the platforms
|
||||
platforms = {tc[CONF_PLATFORM] for tc in updated_conf[CONF_TIME]}
|
||||
assert platforms == {CONF_SNTP, "homeassistant"}
|
||||
assert platforms == {SNTP_DOMAIN, "homeassistant"}
|
||||
|
||||
# Verify SNTP was merged
|
||||
sntp_instances = [
|
||||
tc for tc in updated_conf[CONF_TIME] if tc[CONF_PLATFORM] == CONF_SNTP
|
||||
tc for tc in updated_conf[CONF_TIME] if tc[CONF_PLATFORM] == SNTP_DOMAIN
|
||||
]
|
||||
assert len(sntp_instances) == 1
|
||||
assert sntp_instances[0][CONF_SERVERS] == ["192.168.1.1", "192.168.1.2"]
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp8266:
|
||||
board: d1_mini
|
||||
|
||||
alarm_control_panel:
|
||||
- platform: template
|
||||
id: panel_a
|
||||
name: Panel A
|
||||
codes:
|
||||
- "1234"
|
||||
- "5678"
|
||||
- platform: template
|
||||
id: panel_b
|
||||
name: Panel B
|
||||
codes:
|
||||
- "1234"
|
||||
- "5678"
|
||||
- platform: template
|
||||
id: panel_none
|
||||
name: Panel None
|
||||
codes: []
|
||||
@@ -0,0 +1,20 @@
|
||||
"""Tests for the template alarm control panel codes codegen."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_codes_share_one_progmem_table(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""Identical code lists share one nullptr-terminated flash table; empty lists set none."""
|
||||
main_cpp = generate_main(component_config_path("alarm_codes.yaml"))
|
||||
|
||||
assert (
|
||||
'static constexpr const char * alarm_codes[] PROGMEM = {"1234", "5678", nullptr};'
|
||||
in main_cpp
|
||||
)
|
||||
assert "panel_a->set_codes(alarm_codes);" in main_cpp
|
||||
assert "panel_b->set_codes(alarm_codes);" in main_cpp
|
||||
assert "panel_none->set_codes(" not in main_cpp
|
||||
@@ -1,5 +1,5 @@
|
||||
"""automation.cpp (CronTrigger and SyncTrigger) is only compiled when an
|
||||
on_time or on_time_sync automation exists, so the define must follow them."""
|
||||
"""automation.cpp (CronTrigger) is only compiled when an on_time automation
|
||||
exists, so the define must follow it; on_time_sync needs no class."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
@@ -14,7 +14,7 @@ from esphome.core import CORE
|
||||
[
|
||||
("no_triggers.yaml", False),
|
||||
("on_time.yaml", True),
|
||||
("on_time_sync.yaml", True),
|
||||
("on_time_sync.yaml", False),
|
||||
],
|
||||
)
|
||||
def test_triggers_define_follows_automations(
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
wifi:
|
||||
ssid: test
|
||||
password: password1
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
|
||||
wireguard:
|
||||
time_id: sntp_time
|
||||
address: 172.16.34.100
|
||||
netmask: 255.255.255.0
|
||||
private_key: wPBMxtNYH3mChicrbpsRpZIasIdPq3yZuthn23FbGG8=
|
||||
peer_public_key: Hs2JfikvYU03/Kv3YoAs1hrUIPPTEkpsZKSPUljE9yc=
|
||||
peer_endpoint: wg.server.example
|
||||
peer_allowed_ips:
|
||||
- 172.16.34.0/24
|
||||
- 192.168.0.0/16
|
||||
@@ -0,0 +1,21 @@
|
||||
"""Tests for the wireguard allowed IPs codegen."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_allowed_ips_are_a_progmem_table(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""Allowed IPs live in a flash table ended by an empty entry instead of a heap vector."""
|
||||
main_cpp = generate_main(component_config_path("allowed_ips.yaml"))
|
||||
|
||||
table = main_cpp.split(
|
||||
"wireguard::AllowedIP wireguard_allowed_ips[] PROGMEM = ", 1
|
||||
)[1]
|
||||
table = table.split(";", 1)[0]
|
||||
assert '.ip = "172.16.34.0"' in table
|
||||
assert '.netmask = "255.255.0.0"' in table
|
||||
assert ".ip = nullptr" in table
|
||||
assert "->set_allowed_ips(wireguard_allowed_ips);" in main_cpp
|
||||
Reference in New Issue
Block a user