Merge remote-tracking branch 'origin/dev' into HEAD

This commit is contained in:
J. Nick Koston
2026-10-09 18:28:16 -10:00
1171 changed files with 7016 additions and 3286 deletions
@@ -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
+130 -6
View File
@@ -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
+19 -18
View File
@@ -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
+3 -3
View File
@@ -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
@@ -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
@@ -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
@@ -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
View File
@@ -1,6 +1,7 @@
sensor:
- platform: ags10
id: ags10_1
i2c_id: i2c_bus
tvoc:
name: AGS10 TVOC
update_interval: 60s
+18 -10
View File
@@ -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")
+1
View File
@@ -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
+2 -1
View File
@@ -1,4 +1,5 @@
<<: !include common.yaml
packages:
debug: !include common.yaml
sensor:
- platform: debug
+2 -1
View File
@@ -1,4 +1,5 @@
<<: !include common.yaml
packages:
debug: !include common.yaml
esp32:
cpu_frequency: 240MHz
+2 -3
View File
@@ -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"
+2 -1
View File
@@ -1,4 +1,5 @@
<<: !include common.yaml
packages:
debug: !include common.yaml
sensor:
- platform: debug
+2 -1
View File
@@ -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
+2 -1
View File
@@ -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
+20 -36
View File
@@ -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"
+4
View File
@@ -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
+16 -1
View File
@@ -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
+23 -1
View File
@@ -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
+2 -1
View File
@@ -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