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:
@@ -3,6 +3,8 @@
|
||||
#include "esphome/components/api/api_pb2.h"
|
||||
#include "esphome/components/api/api_buffer.h"
|
||||
#include "esphome/components/light/color_mode.h"
|
||||
#include "esphome/components/light/light_effect.h"
|
||||
#include "esphome/components/light/light_effect_names.h"
|
||||
|
||||
namespace esphome::api::benchmarks {
|
||||
|
||||
@@ -150,7 +152,17 @@ BENCHMARK(CalcAndEncode_ListEntitiesBinarySensorResponse);
|
||||
// --- ListEntitiesLightResponse ---
|
||||
|
||||
static light::ColorModeMask light_color_modes;
|
||||
static FixedVector<const char *> light_effects;
|
||||
|
||||
class BenchEffect : public light::LightEffect {
|
||||
public:
|
||||
explicit BenchEffect(const char *name) : LightEffect(name) {}
|
||||
void apply() override {}
|
||||
};
|
||||
static BenchEffect rainbow_effect("Rainbow");
|
||||
static BenchEffect strobe_effect("Strobe");
|
||||
static light::LightEffect *const LIGHT_EFFECT_TABLE[] = {&rainbow_effect, &strobe_effect};
|
||||
static const ConstVector<light::LightEffect *> light_effect_list(LIGHT_EFFECT_TABLE, 2);
|
||||
static const light::LightEffectNames light_effects(light_effect_list);
|
||||
|
||||
static ListEntitiesLightResponse make_light_response() {
|
||||
// Initialize static data on first call
|
||||
@@ -158,10 +170,6 @@ static ListEntitiesLightResponse make_light_response() {
|
||||
if (!initialized) {
|
||||
light_color_modes.insert(light::ColorMode::RGB_WHITE);
|
||||
light_color_modes.insert(light::ColorMode::COLOR_TEMPERATURE);
|
||||
light_effects.init(3);
|
||||
light_effects.push_back("None");
|
||||
light_effects.push_back("Rainbow");
|
||||
light_effects.push_back("Strobe");
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,9 @@ ethernet:
|
||||
type: LAN8720
|
||||
mdc_pin: GPIO23
|
||||
mdio_pin: GPIO18
|
||||
clk_mode: GPIO17_OUT
|
||||
clk:
|
||||
mode: CLK_OUT
|
||||
pin: GPIO17
|
||||
phy_addr: 0
|
||||
power_pin: GPIO12
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -1,23 +0,0 @@
|
||||
sensor:
|
||||
- platform: adc
|
||||
pin: VDDHDIV5
|
||||
name: "VDDH Voltage"
|
||||
update_interval: 5sec
|
||||
filters:
|
||||
- multiply: 5
|
||||
- platform: adc
|
||||
pin: VDD
|
||||
name: "VDD Voltage"
|
||||
update_interval: 5sec
|
||||
- platform: adc
|
||||
pin: AIN0
|
||||
name: "AIN0 Voltage"
|
||||
update_interval: 5sec
|
||||
- platform: adc
|
||||
pin: P0.03
|
||||
name: "AIN1 Voltage"
|
||||
update_interval: 5sec
|
||||
- platform: adc
|
||||
name: "AIN2 Voltage"
|
||||
update_interval: 5sec
|
||||
pin: 4
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
# Config only: every LibreTiny chip compiles the same ADC code, covered by the
|
||||
# ln882x-ard build; this checks the BK72xx analog pin names.
|
||||
sensor:
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
# Config only: the P4 compiles the same ADC code as the esp32-s3-idf build
|
||||
# (curve fitting calibration), only the pin table differs.
|
||||
sensor:
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
+3
@@ -1,3 +1,6 @@
|
||||
# Config only: the S2 shares the C2's line fitting path (no default_vref) and
|
||||
# the original ESP32's ADC init (no clk_src); both are compiled by those
|
||||
# builds, only the S2 pin table differs.
|
||||
sensor:
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
@@ -1,6 +1,7 @@
|
||||
sensor:
|
||||
- platform: ags10
|
||||
id: ags10_1
|
||||
i2c_id: i2c_bus
|
||||
tvoc:
|
||||
name: AGS10 TVOC
|
||||
update_interval: 60s
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
from pathlib import Path
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_ESPHOME, CONF_INCLUDES
|
||||
from esphome.core import CORE
|
||||
from tests.testing_helpers import ComponentManifestOverride
|
||||
|
||||
TESTS_DIR = Path(__file__).parent
|
||||
|
||||
|
||||
def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
# USE_API compiles every api source, so emit what they need. No socket
|
||||
@@ -10,16 +16,18 @@ def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
cg.add_define("USE_API_PLAINTEXT")
|
||||
# Linked wizard inputs only exist next to homeassistant entities, which need this
|
||||
cg.add_define("USE_API_HOMEASSISTANT_STATES")
|
||||
# test_wizard.cpp supplies the tables that codegen emits for a real build
|
||||
for define in (
|
||||
"USE_API_WIZARD",
|
||||
"USE_API_WIZARD_INPUTS",
|
||||
"USE_API_WIZARD_LINKED_INPUTS",
|
||||
"USE_API_WIZARD_STANDALONE_INPUTS",
|
||||
):
|
||||
cg.add_define(define)
|
||||
cg.add_define("API_WIZARD_DATA_SIZE", 200)
|
||||
cg.add_define("API_WIZARD_INPUT_COUNT", 2)
|
||||
# test_wizard.cpp supplies the tables that codegen emits for a real build;
|
||||
# it is only compiled when api's own tests are, not when api is a dependency
|
||||
if any(Path(p) == TESTS_DIR for p in CORE.config[CONF_ESPHOME][CONF_INCLUDES]):
|
||||
for define in (
|
||||
"USE_API_WIZARD",
|
||||
"USE_API_WIZARD_INPUTS",
|
||||
"USE_API_WIZARD_LINKED_INPUTS",
|
||||
"USE_API_WIZARD_STANDALONE_INPUTS",
|
||||
):
|
||||
cg.add_define(define)
|
||||
cg.add_define("API_WIZARD_DATA_SIZE", 200)
|
||||
cg.add_define("API_WIZARD_INPUT_COUNT", 2)
|
||||
cg.add_define("API_MAX_SEND_QUEUE", 8)
|
||||
cg.add_define("MAX_API_CONNECTIONS", 1)
|
||||
cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS")
|
||||
|
||||
@@ -61,6 +61,7 @@ sensor:
|
||||
motion:
|
||||
- platform: bmi270
|
||||
id: bmi270_motion
|
||||
i2c_id: i2c_bus
|
||||
# Accelerometer full-scale range: 2G | 4G | 8G | 16G
|
||||
accelerometer_range: 4G
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
datetime:
|
||||
- platform: template
|
||||
name: Datetime Trigger Test
|
||||
id: datetime_trigger_test
|
||||
type: datetime
|
||||
optimistic: true
|
||||
on_value:
|
||||
- logger.log:
|
||||
format: "New value %04d-%02d-%02d"
|
||||
args: [x.year, x.month, x.day_of_month]
|
||||
@@ -0,0 +1,3 @@
|
||||
packages:
|
||||
datetime: !include common.yaml
|
||||
triggers: !include common-triggers.yaml
|
||||
@@ -1,4 +1,5 @@
|
||||
<<: !include common.yaml
|
||||
packages:
|
||||
debug: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- platform: debug
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<<: !include common.yaml
|
||||
packages:
|
||||
debug: !include common.yaml
|
||||
|
||||
esp32:
|
||||
cpu_frequency: 240MHz
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<<: !include common.yaml
|
||||
packages:
|
||||
debug: !include common.yaml
|
||||
|
||||
esp32:
|
||||
cpu_frequency: 240MHz
|
||||
|
||||
sensor:
|
||||
- platform: debug
|
||||
free:
|
||||
name: "Heap Free"
|
||||
psram:
|
||||
name: "Free PSRAM"
|
||||
fragmentation:
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- platform: debug
|
||||
fragmentation:
|
||||
name: "Heap Fragmentation"
|
||||
min_free:
|
||||
name: "Heap Min Free"
|
||||
@@ -1,4 +1,5 @@
|
||||
<<: !include common.yaml
|
||||
packages:
|
||||
debug: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- platform: debug
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
<<: !include common.yaml
|
||||
packages:
|
||||
debug: !include common.yaml
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- platform: debug
|
||||
min_free:
|
||||
name: "Heap Min Free"
|
||||
@@ -1,4 +1,5 @@
|
||||
<<: !include common.yaml
|
||||
packages:
|
||||
debug: !include common.yaml
|
||||
|
||||
nrf52:
|
||||
reg0:
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
<<: !include common.yaml
|
||||
packages:
|
||||
debug: !include common.yaml
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<<: !include common.yaml
|
||||
@@ -1 +1,2 @@
|
||||
<<: !include common.yaml
|
||||
packages:
|
||||
debug: !include common.yaml
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- platform: debug
|
||||
min_free:
|
||||
name: "Heap Min Free"
|
||||
@@ -0,0 +1,4 @@
|
||||
# No pin anywhere in the config: the pin class methods deep_sleep calls must still link
|
||||
deep_sleep:
|
||||
run_duration: 10s
|
||||
sleep_duration: 50s
|
||||
@@ -1,5 +1,27 @@
|
||||
substitutions:
|
||||
wakeup_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
||||
<<: !include common-esp32-all.yaml
|
||||
packages:
|
||||
deep_sleep: !include common.yaml
|
||||
deep_sleep_esp32: !include common-esp32-all.yaml
|
||||
|
||||
power_supply:
|
||||
- id: power_supply_ds_esp32
|
||||
pin:
|
||||
number: GPIO5
|
||||
hold_state: true
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO22
|
||||
hold_state: true
|
||||
id: gpio_output_ds_esp32
|
||||
power_supply: power_supply_ds_esp32
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO23
|
||||
hold_state: true
|
||||
id: gpio_switch_ds_esp32
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
model: SSD1306_128X64
|
||||
reset_pin: ${reset_pin}
|
||||
id: display_page_change_display
|
||||
pages:
|
||||
- id: display_page_change_page1
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
- id: display_page_change_page2
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, 10, 10);
|
||||
on_page_change:
|
||||
- then:
|
||||
lambda: 'ESP_LOGD("display", "any change, %p %p", from, to);'
|
||||
- from: display_page_change_page1
|
||||
then:
|
||||
lambda: 'ESP_LOGD("display", "left page 1, %p %p", from, to);'
|
||||
- to: display_page_change_page1
|
||||
then:
|
||||
lambda: 'ESP_LOGD("display", "entered page 1, %p %p", from, to);'
|
||||
- from: display_page_change_page1
|
||||
to: display_page_change_page2
|
||||
then:
|
||||
lambda: 'ESP_LOGD("display", "1 -> 2, %p %p", from, to);'
|
||||
@@ -0,0 +1,6 @@
|
||||
substitutions:
|
||||
reset_pin: GPIO15
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
display: !include common-page-change.yaml
|
||||
@@ -4,63 +4,47 @@ dlms_meter:
|
||||
decryption_key: "36C66639E48A8CA4D6BC8B282A793BBB"
|
||||
auth_key: "11223344556677889900AABBCCDDEEFF"
|
||||
skip_crc: true
|
||||
provider: "netznoe"
|
||||
custom_patterns:
|
||||
- "custom_pattern_1"
|
||||
- "custom_pattern_2"
|
||||
- pattern: "L, TSTR"
|
||||
name: "MeterID"
|
||||
default_obis: "0.0.96.1.0.255"
|
||||
- pattern: "F, TDTM"
|
||||
name: "DateTime"
|
||||
default_obis: "0.0.1.0.0.255"
|
||||
|
||||
sensor:
|
||||
# Old Schema tests
|
||||
- platform: dlms_meter
|
||||
voltage_l1:
|
||||
name: "Voltage L1"
|
||||
voltage_l2:
|
||||
name: "Voltage L2"
|
||||
voltage_l3:
|
||||
name: "Voltage L3"
|
||||
current_l1:
|
||||
name: "Current L1"
|
||||
current_l2:
|
||||
name: "Current L2"
|
||||
current_l3:
|
||||
name: "Current L3"
|
||||
active_power_plus:
|
||||
name: "Active power taken from grid"
|
||||
active_power_minus:
|
||||
name: "Active power put into grid"
|
||||
active_energy_plus:
|
||||
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"
|
||||
obis_code: "1.0.32.7.0.255"
|
||||
name: "Voltage L1"
|
||||
unit_of_measurement: V
|
||||
accuracy_decimals: 1
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
- platform: dlms_meter
|
||||
obis_code: "1.0.1.8.0.255"
|
||||
name: "Active energy taken from grid"
|
||||
unit_of_measurement: Wh
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
|
||||
# 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"
|
||||
obis_code: "0.0.1.0.0.255"
|
||||
name: "timestamp"
|
||||
|
||||
# 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"
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
# - DS2484: adjustable 1-Wire timing + RWPU pullup resistor selection
|
||||
ds248x:
|
||||
- id: ds2482_100
|
||||
i2c_id: i2c_bus
|
||||
address: 0x18
|
||||
type: ds2482-100
|
||||
active_pullup: true
|
||||
- id: ds2482_101
|
||||
i2c_id: i2c_bus
|
||||
address: 0x19
|
||||
type: ds2482-101
|
||||
active_pullup: true
|
||||
@@ -18,10 +20,12 @@ ds248x:
|
||||
bus_sleep: true
|
||||
hub_sleep: true
|
||||
- id: ds2482_800
|
||||
i2c_id: i2c_bus
|
||||
address: 0x1a
|
||||
type: ds2482-800
|
||||
active_pullup: true
|
||||
- id: ds2484_hub
|
||||
i2c_id: i2c_bus
|
||||
address: 0x1b
|
||||
type: ds2484
|
||||
active_pullup: true
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
ethernet:
|
||||
type: W5500
|
||||
spi_id: spi_bus
|
||||
cs_pin: 5
|
||||
interrupt_pin: 36
|
||||
reset_pin: 22
|
||||
clock_speed: 10Mhz
|
||||
manual_ip:
|
||||
static_ip: 192.168.178.56
|
||||
gateway: 192.168.178.1
|
||||
subnet: 255.255.255.0
|
||||
domain: .local
|
||||
mac_address: "02:AA:BB:CC:DD:01"
|
||||
on_connect:
|
||||
- logger.log: "Ethernet connected!"
|
||||
on_disconnect:
|
||||
- logger.log: "Ethernet disconnected!"
|
||||
@@ -1 +1,6 @@
|
||||
<<: !include common-ip101.yaml
|
||||
packages:
|
||||
ethernet: !include common-ip101.yaml
|
||||
|
||||
# IPv6 on the internal EMAC: compiles the IPv6 event handler and multicast filter
|
||||
network:
|
||||
enable_ipv6: true
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
network:
|
||||
enable_ipv6: true
|
||||
|
||||
ethernet:
|
||||
type: IP101
|
||||
mdc_pin: 23
|
||||
mdio_pin: 18
|
||||
clk:
|
||||
pin: 0
|
||||
mode: CLK_EXT_IN
|
||||
phy_addr: 1
|
||||
power_pin: 5
|
||||
@@ -1,39 +0,0 @@
|
||||
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,3 +0,0 @@
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-idf.yaml
|
||||
ethernet: !include common-w5500-spi-id.yaml
|
||||
@@ -1,15 +0,0 @@
|
||||
# W5500 next to wifi with PSRAM: compiles the input path that moves received frames to PSRAM
|
||||
packages:
|
||||
ethernet: !include common-w5500.yaml
|
||||
|
||||
psram:
|
||||
mode: quad
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
network:
|
||||
priority:
|
||||
- ethernet
|
||||
- wifi
|
||||
@@ -1,20 +1,47 @@
|
||||
# W5500 on a shared SPI bus, started from an automation instead of on boot.
|
||||
# Covers the spi_id attach path and the enable/disable lifecycle in one build.
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-idf.yaml
|
||||
|
||||
ethernet:
|
||||
id: eth
|
||||
type: W5500
|
||||
clk_pin: 19
|
||||
mosi_pin: 21
|
||||
miso_pin: 23
|
||||
cs_pin: 18
|
||||
spi_id: spi_bus
|
||||
cs_pin: 5
|
||||
interrupt_pin: 36
|
||||
reset_pin: 22
|
||||
clock_speed: 10Mhz
|
||||
enable_on_boot: false
|
||||
manual_ip:
|
||||
static_ip: 192.168.178.56
|
||||
gateway: 192.168.178.1
|
||||
subnet: 255.255.255.0
|
||||
domain: .local
|
||||
mac_address: "02:AA:BB:CC:DD:01"
|
||||
interface: spi2
|
||||
on_connect:
|
||||
- logger.log: "Ethernet connected!"
|
||||
on_disconnect:
|
||||
- logger.log: "Ethernet disconnected!"
|
||||
|
||||
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 +1,15 @@
|
||||
<<: !include common-w5500.yaml
|
||||
# W5500 next to wifi with PSRAM: compiles the input path that moves received frames to PSRAM
|
||||
packages:
|
||||
ethernet: !include common-w5500.yaml
|
||||
|
||||
psram:
|
||||
mode: quad
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
network:
|
||||
priority:
|
||||
- ethernet
|
||||
- wifi
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Config only: a user supplied SPI interface on dedicated pins exercises the
|
||||
# interface option in validation.
|
||||
ethernet:
|
||||
type: W5500
|
||||
interface: spi3
|
||||
clk_pin: 19
|
||||
mosi_pin: 21
|
||||
miso_pin: 17
|
||||
cs_pin: 18
|
||||
interrupt_pin: 36
|
||||
reset_pin: 12
|
||||
clock_speed: 10Mhz
|
||||
@@ -6,4 +6,19 @@ substitutions:
|
||||
switch_pin_3: GPIO6
|
||||
switch_pin_4: GPIO7
|
||||
|
||||
<<: !include common.yaml
|
||||
packages:
|
||||
gpio: !include common.yaml
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO8
|
||||
hold_state: true
|
||||
id: gpio_binary_sensor_esp32
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO9
|
||||
hold_state: true
|
||||
id: gpio_output_esp32
|
||||
|
||||
@@ -6,4 +6,26 @@ substitutions:
|
||||
switch_pin_3: GPIO16
|
||||
switch_pin_4: GPIO17
|
||||
|
||||
<<: !include common.yaml
|
||||
packages:
|
||||
gpio: !include common.yaml
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO18
|
||||
hold_state: true
|
||||
id: gpio_binary_sensor_esp32
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO19
|
||||
hold_state: true
|
||||
id: gpio_output_esp32
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO21
|
||||
hold_state: true
|
||||
id: gpio_switch_esp32
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: graphical_display_menu_triggers_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: ${reset_pin}
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
|
||||
font:
|
||||
- file: "gfonts://Roboto"
|
||||
id: graphical_display_menu_triggers_font
|
||||
size: 20
|
||||
|
||||
graphical_display_menu:
|
||||
id: graphical_display_menu_triggers_menu
|
||||
display: graphical_display_menu_triggers_display
|
||||
font: graphical_display_menu_triggers_font
|
||||
on_redraw:
|
||||
then:
|
||||
lambda: 'ESP_LOGI("graphical_display_menu", "redraw, active: %d", it->is_active());'
|
||||
items:
|
||||
- type: label
|
||||
text: "Label"
|
||||
@@ -0,0 +1,6 @@
|
||||
substitutions:
|
||||
reset_pin: GPIO15
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
graphical_display_menu: !include common-triggers.yaml
|
||||
@@ -71,6 +71,8 @@ esphome:
|
||||
# encode_json_ receives const copies of non-const reference args.
|
||||
- http_request.post:
|
||||
url: https://esphome.io
|
||||
request_headers:
|
||||
X-Body-Length: !lambda 'return body.empty() ? "0" : "1";'
|
||||
json:
|
||||
status: "ok"
|
||||
# Same with json: lambda variant, exercises json_func_ path
|
||||
@@ -102,3 +104,23 @@ script:
|
||||
then:
|
||||
- http_request.get:
|
||||
url: "http://google.com"
|
||||
|
||||
- id: send_with_headers
|
||||
parameters:
|
||||
token: string
|
||||
then:
|
||||
- http_request.get:
|
||||
url: https://esphome.io
|
||||
request_headers:
|
||||
Accept: application/json
|
||||
X-Device: esphome
|
||||
- http_request.get:
|
||||
url: https://esphome.io/other
|
||||
request_headers:
|
||||
Accept: application/json
|
||||
X-Device: esphome
|
||||
- http_request.get:
|
||||
url: https://esphome.io
|
||||
request_headers:
|
||||
Accept: application/json
|
||||
X-Token-Set: !lambda 'return token.empty() ? "no" : "yes";'
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin: 0
|
||||
id: improv_trigger_authorizer
|
||||
|
||||
improv_ble:
|
||||
authorizer: improv_trigger_authorizer
|
||||
on_start:
|
||||
- logger.log: Improv started
|
||||
on_provisioning:
|
||||
- logger.log: Provisioning
|
||||
on_provisioned:
|
||||
- logger.log: Provisioned
|
||||
on_stop:
|
||||
- logger.log: Improv stopped
|
||||
on_state:
|
||||
- lambda: ESP_LOGD("improv", "State %d error %d", (int) state, (int) error);
|
||||
@@ -1,8 +0,0 @@
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
logger:
|
||||
hardware_uart: USB_SERIAL_JTAG
|
||||
|
||||
improv_serial:
|
||||
@@ -1,3 +0,0 @@
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml
|
||||
improv_serial: !include common-uart-bus.yaml
|
||||
@@ -1 +0,0 @@
|
||||
<<: !include common-uart0.yaml
|
||||
@@ -1 +0,0 @@
|
||||
<<: !include common-uart0.yaml
|
||||
@@ -1 +0,0 @@
|
||||
<<: !include common-uart0.yaml
|
||||
@@ -1 +0,0 @@
|
||||
<<: !include common-uart0.yaml
|
||||
@@ -1 +0,0 @@
|
||||
<<: !include common-uart0.yaml
|
||||
@@ -1 +0,0 @@
|
||||
<<: !include common-uart0.yaml
|
||||
@@ -1 +1,2 @@
|
||||
<<: !include common-usb_cdc.yaml
|
||||
packages:
|
||||
improv_serial: !include common-usb_cdc.yaml
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<<: !include common-usb_cdc.yaml
|
||||
@@ -1 +0,0 @@
|
||||
<<: !include common-usb_serial_jtag.yaml
|
||||
@@ -1 +0,0 @@
|
||||
<<: !include common-usb_serial_jtag.yaml
|
||||
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
improv_serial: !include common.yaml
|
||||
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
improv_serial: !include common.yaml
|
||||
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
improv_serial: !include common.yaml
|
||||
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
improv_serial: !include common.yaml
|
||||
@@ -1,9 +0,0 @@
|
||||
substitutions:
|
||||
cs_pin: GPIO5
|
||||
gdo0_pin: GPIO4
|
||||
gdo2_pin: GPIO16
|
||||
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml
|
||||
cc1101: !include common-cc1101.yaml
|
||||
@@ -1,7 +0,0 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
rx: !include common-rx.yaml
|
||||
@@ -1,7 +0,0 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
rx: !include common-rx.yaml
|
||||
@@ -1,7 +0,0 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
tx: !include common-tx.yaml
|
||||
@@ -1,7 +0,0 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
tx: !include common-tx.yaml
|
||||
@@ -1,7 +0,0 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
tx: !include common-tx.yaml
|
||||
@@ -32,6 +32,7 @@ esphome:
|
||||
- lambda: |-
|
||||
const auto &effects = id(test_monochromatic_light).get_effects();
|
||||
auto &same = id(test_monochromatic_light).get_effects();
|
||||
const auto copy = id(test_monochromatic_light).get_effects(); // the view is copyable
|
||||
uint32_t total = effects.size();
|
||||
for (auto *effect : effects) {
|
||||
ESP_LOGD("test", "Effect %s", effect->get_name().c_str());
|
||||
@@ -39,7 +40,7 @@ esphome:
|
||||
if (total > 0) {
|
||||
ESP_LOGD("test", "First %s", effects.at(0)->get_name().c_str());
|
||||
// Raw pointer iterators on purpose: external code binds std::find's result to `const auto *`
|
||||
auto *it = std::find(same.begin(), same.end(), effects[0]);
|
||||
auto *it = std::find(same.begin(), same.end(), copy[0]);
|
||||
ESP_LOGD("test", "Index %d", (int) (it - same.begin()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "esphome/components/light/light_effect.h"
|
||||
#include "esphome/components/light/light_output.h"
|
||||
#include "esphome/components/light/light_state.h"
|
||||
|
||||
@@ -18,36 +17,8 @@ class BrightnessOutput : public LightOutput {
|
||||
void write_state(LightState *state) override {}
|
||||
};
|
||||
|
||||
class NoopEffect : public LightEffect {
|
||||
public:
|
||||
using LightEffect::LightEffect;
|
||||
void apply() override {}
|
||||
};
|
||||
|
||||
// start_effect_() is where the uint32_t index is narrowed to the stored uint16_t.
|
||||
class TestableLightState : public LightState {
|
||||
public:
|
||||
using LightState::LightState;
|
||||
using LightState::start_effect_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// add_effects() is public, so an external component can exceed the codegen cap on effect count;
|
||||
// an index the uint16_t can't hold must be ignored rather than wrap onto another effect.
|
||||
TEST(LightStateEffect, IndexAboveUint16IsIgnoredAndKeepsTheActiveEffect) {
|
||||
BrightnessOutput output;
|
||||
TestableLightState state(&output);
|
||||
NoopEffect effect("Noop");
|
||||
state.add_effects({&effect});
|
||||
|
||||
state.start_effect_(1);
|
||||
ASSERT_EQ(state.get_current_effect_index(), 1u);
|
||||
|
||||
state.start_effect_(0x10000u); // unchecked narrowing wraps this to 0, which stops the effect
|
||||
EXPECT_EQ(state.get_current_effect_index(), 1u);
|
||||
}
|
||||
|
||||
// get_gamma_correct() reads the gamma codegen stores after the lookup table, rounded to two decimals.
|
||||
TEST(LightStateGamma, ReadsTheGammaStoredWithTheTable) {
|
||||
static constexpr GammaTable TABLE{{}, 280};
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<<: !include common-default_uart.yaml
|
||||
|
||||
logger:
|
||||
id: logger_id
|
||||
task_log_buffer_size: 1024B # Set a custom buffer size
|
||||
@@ -1,5 +0,0 @@
|
||||
<<: !include common-default_uart.yaml
|
||||
|
||||
logger:
|
||||
id: logger_id
|
||||
task_log_buffer_size: 0
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user