[espnow] Keep constant send payloads in shared flash tables (#20271)

This commit is contained in:
J. Nick Koston
2026-10-07 05:40:12 -10:00
committed by GitHub
parent 72413494b0
commit cf96c53279
8 changed files with 235 additions and 8 deletions
@@ -0,0 +1,22 @@
---
esphome:
name: test
on_boot:
then:
- espnow.send:
address: 11:22:33:44:55:66
data: [0x01, 0x02, 0x03]
- espnow.broadcast:
data: [0x01, 0x02, 0x03]
- espnow.broadcast: "OK"
- espnow.broadcast:
data: !lambda return {0x09};
esp32:
board: esp32dev
wifi:
ssid: test
password: password1
espnow:
@@ -0,0 +1,23 @@
"""Tests for ESP-NOW constant send payloads in shared flash tables."""
from collections.abc import Callable
from pathlib import Path
import re
def test_constant_payloads_share_tables(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
"""Equal payloads share one table; lambdas stay templates."""
main_cpp = generate_main(component_config_path("payload_tables.yaml"))
tables = dict(
re.findall(
r"static constexpr uint8_t (\w+)\[\] PROGMEM = (\{[^}]*\});", main_cpp
)
)
assert sorted(tables.values()) == sorted(["{0x01, 0x02, 0x03}", "{0x4F, 0x4B}"])
shared = next(k for k, v in tables.items() if v == "{0x01, 0x02, 0x03}")
assert main_cpp.count(f"set_data_static({shared}, 3);") == 2
assert "set_data_template(" in main_cpp
@@ -0,0 +1,43 @@
#include <gtest/gtest.h>
#include <vector>
#include "esphome/core/automation.h"
namespace esphome::testing {
static const uint8_t PAYLOAD[] = {1, 2, 3};
static std::vector<uint8_t> repeat(int count) { return std::vector<uint8_t>(count, 7); }
TEST(TemplatableBytesTest, VisitStaticTable) {
TemplatableBytes<> bytes;
bytes.set_static(PAYLOAD, sizeof(PAYLOAD));
std::vector<uint8_t> seen;
bytes.visit([&](const uint8_t *data, size_t len) { seen.assign(data, data + len); });
EXPECT_EQ(seen, (std::vector<uint8_t>{1, 2, 3}));
}
TEST(TemplatableBytesTest, VisitWithLargerStackBuffer) {
TemplatableBytes<> bytes;
bytes.set_static(PAYLOAD, sizeof(PAYLOAD));
size_t seen = 0;
bytes.visit<256>([&](const uint8_t *, size_t len) { seen = len; });
EXPECT_EQ(seen, sizeof(PAYLOAD));
}
TEST(TemplatableBytesTest, VisitEmptyStaticTable) {
TemplatableBytes<> bytes;
bytes.set_static(nullptr, 0);
size_t seen = 1;
bytes.visit([&](const uint8_t *, size_t len) { seen = len; });
EXPECT_EQ(seen, 0u);
}
TEST(TemplatableBytesTest, VisitLambdaWithArgument) {
TemplatableBytes<int> bytes;
bytes.set_template(repeat);
std::vector<uint8_t> seen;
bytes.visit([&](const uint8_t *data, size_t len) { seen.assign(data, data + len); }, 4);
EXPECT_EQ(seen, (std::vector<uint8_t>(4, 7)));
}
} // namespace esphome::testing
+47
View File
@@ -20,6 +20,7 @@ from esphome.automation import (
has_non_synchronous_actions,
literal_with_length,
maybe_simple_id,
progmem_bytes,
register_apply_action,
register_apply_condition,
register_bare_action,
@@ -28,6 +29,7 @@ from esphome.automation import (
register_parented_condition,
register_simple_action,
register_simple_condition,
templatable_bytes,
)
import esphome.codegen as cg
import esphome.config_validation as cv
@@ -989,3 +991,48 @@ async def test_apply_condition_string_lambda_paths(
text = _apply_definition(mock_cg)
assert expected in text
assert ("-> std::string {" in text) is called
def test_progmem_bytes_shares_equal_payloads() -> None:
CORE.config = {}
a = progmem_bytes("payload", [1, 2])
b = progmem_bytes("payload", b"\x01\x02")
assert a is b
assert str(progmem_bytes("payload", [])) == "nullptr"
@pytest.mark.asyncio
async def test_templatable_bytes_static_payload() -> None:
CORE.config = {}
var = MockObj("act", "->")
await templatable_bytes(
[0xA1, 0x02], [], var.set_code_template, var.set_code_static, "payload"
)
assert "act->set_code_static(payload, 2);" in CORE.cpp_main_section
@pytest.mark.asyncio
async def test_templatable_bytes_lambda_payload() -> None:
CORE.config = {}
var = MockObj("act", "->")
await templatable_bytes(
Lambda("return {0x01, 0x02};"),
[],
var.set_code_template,
var.set_code_static,
"payload",
)
text = CORE.cpp_main_section
assert "act->set_code_template(" in text
assert "-> std::vector<uint8_t>" in text
assert "set_code_static" not in text
@pytest.mark.asyncio
async def test_templatable_bytes_rejects_oversized_payload() -> None:
CORE.config = {}
var = MockObj("act", "->")
with pytest.raises(EsphomeError, match="maximum is 65535"):
await templatable_bytes(
[0] * 0x10000, [], var.set_code_template, var.set_code_static, "payload"
)