mirror of
https://github.com/esphome/esphome.git
synced 2026-08-17 02:47:52 +08:00
[rp2] Size the lwIP segment pool and heap for concurrent senders (#18257)
This commit is contained in:
@@ -149,7 +149,7 @@ class APIFrameHelper {
|
||||
// holding data too long waiting for Nagle's timer causes buffer exhaustion
|
||||
// and dropped messages.
|
||||
//
|
||||
// ESP32 (TCP_SND_BUF=4×MSS+) / RP2040 (8×MSS) / LibreTiny (4×MSS): 4 logs per cycle
|
||||
// ESP32 (TCP_SND_BUF=4×MSS+) / RP2040 (4×MSS) / LibreTiny (4×MSS): 4 logs per cycle
|
||||
// ESP8266 (2×MSS): 3 logs per cycle (tightest buffers)
|
||||
//
|
||||
// Flow (ESP32/RP2040/LT): Log 1 (Nagle on) -> Log 2 -> Log 3 -> Log 4 (NODELAY, flush)
|
||||
@@ -312,7 +312,7 @@ class APIFrameHelper {
|
||||
// Values 1..LOG_NAGLE_COUNT count log messages in the current Nagle batch.
|
||||
// After LOG_NAGLE_COUNT logs, we flush by re-enabling NODELAY and resetting to 0.
|
||||
// ESP8266 has the tightest TCP send buffer (2×MSS) and needs conservative batching.
|
||||
// ESP32 (4×MSS+), RP2040 (8×MSS), and LibreTiny (4×MSS) can coalesce more.
|
||||
// ESP32 (4×MSS+), RP2040 (4×MSS), and LibreTiny (4×MSS) can coalesce more.
|
||||
#ifdef USE_ESP8266
|
||||
static constexpr uint8_t LOG_NAGLE_COUNT = 2;
|
||||
#else
|
||||
|
||||
@@ -388,6 +388,74 @@ async def to_code(config):
|
||||
_configure_lwip()
|
||||
|
||||
|
||||
# --- lwIP sizing. See _configure_lwip() for the platform comparison table. ---
|
||||
|
||||
# TCP_SND_BUF: 4×MSS=5,840 matches ESP32. Down from arduino-pico's 8×MSS.
|
||||
# ESPAsyncWebServer allocates malloc(tcp_sndbuf()) per response chunk.
|
||||
LWIP_TCP_SND_BUF = "(4*TCP_MSS)"
|
||||
|
||||
# TCP_WND: receive window. 4×MSS matches ESP32. Down from arduino-pico's 8×MSS.
|
||||
LWIP_TCP_WND = "(4*TCP_MSS)"
|
||||
|
||||
# TCP_SND_QUEUELEN: max pbufs queued per PCB for the send buffer
|
||||
# ESP-IDF formula: (4 * TCP_SND_BUF + (TCP_MSS - 1)) / TCP_MSS
|
||||
# With 4×MSS: (4*5840 + 1459) / 1460 = 17 — match ESP32
|
||||
LWIP_TCP_SND_QUEUELEN = 17
|
||||
|
||||
# MEMP_NUM_TCP_SEG: pool shared by every PCB, so it must not be the per-PCB
|
||||
# queue length — lwIP's sanity check only demands >=, the floor for a single
|
||||
# connection. 2× lets two PCBs fill up before the rest see ERR_MEM. Measured
|
||||
# at 20 bytes per entry, so under 700 bytes total.
|
||||
LWIP_MEMP_NUM_TCP_SEG = 2 * LWIP_TCP_SND_QUEUELEN
|
||||
|
||||
# PBUF_POOL_SIZE: RP2040 has 264KB RAM, more generous than LibreTiny.
|
||||
# 16 matches ESP32 (vs arduino-pico's 24). Receive side only; the send path
|
||||
# copies into PBUF_RAM out of MEM_SIZE.
|
||||
LWIP_PBUF_POOL_SIZE = 16
|
||||
|
||||
# MEM_SIZE: lwIP heap backing PBUF_RAM, where tcp_write() copies outgoing
|
||||
# data. TCP_OVERSIZE defaults to TCP_MSS, so each queued segment takes a full
|
||||
# MSS block whatever was written (pbuf 16 + PBUF_TRANSPORT 54 + MSS 1460 +
|
||||
# block header ≈ 1.5KB); a PCB at a full TCP_SND_BUF holds four, ~6KB.
|
||||
#
|
||||
# Two of those is ~12KB of arduino-pico's 16KB heap and already fails: mem.c
|
||||
# is first-fit, so a *contiguous* 1.5KB block must be free, and at 75%
|
||||
# occupancy interleaved with ARP/DHCP/DNS/mDNS the largest run collapses well
|
||||
# before the total does — hence the intermittent failures. With rp2's
|
||||
# max_connections of 4, a third sender has nothing left.
|
||||
#
|
||||
# 32KB is arduino-pico's own next tier (__LWIP_MEMMULT=2 boards).
|
||||
# Must stay under 64000 or lwIP widens mem_size_t to u32_t.
|
||||
LWIP_MEM_SIZE = 32768
|
||||
|
||||
|
||||
def build_lwip_defines(
|
||||
tcp_sockets: int, udp_sockets: int, listening_tcp: int
|
||||
) -> dict[str, str]:
|
||||
"""Render the lwIP override values for the Jinja2 template.
|
||||
|
||||
The template uses #include_next to chain to the framework's original
|
||||
lwipopts.h, then #undef/#define only these. Split out from
|
||||
_configure_lwip() so the values that actually reach the generated header
|
||||
can be checked without standing up CORE.
|
||||
|
||||
Both malloc flags stay 0 (framework defaults); see _configure_lwip(). The
|
||||
static pools are the only IRQ-safe allocator on this platform, so the fix
|
||||
is to size them correctly rather than to make them dynamic.
|
||||
"""
|
||||
return {
|
||||
"TCP_SND_BUF": LWIP_TCP_SND_BUF,
|
||||
"TCP_WND": LWIP_TCP_WND,
|
||||
"TCP_SND_QUEUELEN": str(LWIP_TCP_SND_QUEUELEN),
|
||||
"MEM_SIZE": str(LWIP_MEM_SIZE),
|
||||
"MEMP_NUM_TCP_SEG": str(LWIP_MEMP_NUM_TCP_SEG),
|
||||
"PBUF_POOL_SIZE": str(LWIP_PBUF_POOL_SIZE),
|
||||
"MEMP_NUM_TCP_PCB": str(tcp_sockets),
|
||||
"MEMP_NUM_TCP_PCB_LISTEN": str(listening_tcp),
|
||||
"MEMP_NUM_UDP_PCB": str(udp_sockets),
|
||||
}
|
||||
|
||||
|
||||
def _configure_lwip() -> None:
|
||||
"""Configure lwIP options for RP2040 by generating a custom lwipopts.h.
|
||||
|
||||
@@ -407,25 +475,36 @@ def _configure_lwip() -> None:
|
||||
────────────────────────────────────────────────────────────────
|
||||
TCP_SND_BUF 2×MSS 4×MSS 8×MSS 4×MSS
|
||||
TCP_WND 4×MSS 4×MSS 8×MSS 4×MSS
|
||||
TCP_SND_QUEUELEN ~8 17 32 17
|
||||
MEM_LIBC_MALLOC 1 1 0 0*
|
||||
MEMP_MEM_MALLOC 1 1 0 0**
|
||||
MEM_SIZE N/A*** N/A*** 16KB 16KB
|
||||
MEM_SIZE N/A*** N/A*** 16KB 32KB
|
||||
PBUF_POOL_SIZE 10 16 24 16
|
||||
MEMP_NUM_TCP_SEG 10 16 32 17
|
||||
MEMP_NUM_TCP_SEG 10 16 32 34****
|
||||
MEMP_NUM_TCP_PCB 5 16 5 dynamic
|
||||
MEMP_NUM_TCP_PCB_LISTEN 4 16 8**** dynamic
|
||||
MEMP_NUM_TCP_PCB_LISTEN 4 16 8***** dynamic
|
||||
MEMP_NUM_UDP_PCB 4 16 7 dynamic
|
||||
TCP_SND_QUEUELEN ~8 17 32 17
|
||||
|
||||
* MEM_LIBC_MALLOC must stay 0: arduino-pico uses
|
||||
PICO_CYW43_ARCH_THREADSAFE_BACKGROUND which runs lwIP callbacks from
|
||||
a low-priority pendsv IRQ. The pico-sdk explicitly blocks
|
||||
MEM_LIBC_MALLOC=1 because libc malloc uses mutexes (unsafe in IRQ).
|
||||
** MEMP_MEM_MALLOC must stay 0: the dedicated lwIP heap (MEM_SIZE=16KB)
|
||||
is too small to hold all pools dynamically. The PBUF_POOL alone needs
|
||||
~24KB (16 × 1524 bytes). Increasing MEM_SIZE would negate BSS savings.
|
||||
*** ESP8266/ESP32 use MEM_LIBC_MALLOC=1 (system heap, no dedicated pool).
|
||||
**** opt.h default; arduino-pico doesn't override MEMP_NUM_TCP_PCB_LISTEN.
|
||||
** MEMP_MEM_MALLOC must stay 0 for IRQ safety, not size. memp_malloc()
|
||||
pops the pool free list inside SYS_ARCH_PROTECT, but lwIP's heap takes
|
||||
its protection from LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT (default 0),
|
||||
so under NO_SYS=1 mem_malloc()/mem_free() are unprotected — and memp.c
|
||||
calls mem_malloc() outside the guard anyway. RX pbufs would then be
|
||||
allocated from the pendsv IRQ on the same unguarded free list the main
|
||||
loop uses for tcp_write(). Tried on hardware: faults within seconds on
|
||||
CYW43. Ethernet survives only because it polls from the main loop.
|
||||
*** ESP8266/ESP32 ship MEMP_MEM_MALLOC=1, so their pool entries come from
|
||||
the heap on demand and MEMP_NUM_*/PBUF_POOL_SIZE are labels, not caps
|
||||
(MEM_LIBC_MALLOC=1 points that heap at the system heap). Both flags are
|
||||
0 here, so ours are hard limits; don't copy their numbers.
|
||||
**** MEMP_NUM_TCP_SEG is *global* while TCP_SND_QUEUELEN is *per-PCB*, so
|
||||
sizing it to the per-PCB value lets one busy connection drain it for
|
||||
every other. 2× covers two PCBs; MEM_SIZE is the real limit past that.
|
||||
***** opt.h default; arduino-pico doesn't override MEMP_NUM_TCP_PCB_LISTEN.
|
||||
"dynamic" = auto-calculated from component socket registrations via
|
||||
socket.get_socket_counts() with minimums of 8 TCP / 6 UDP / 2 TCP_LISTEN.
|
||||
"""
|
||||
@@ -444,48 +523,7 @@ def _configure_lwip() -> None:
|
||||
# UDP PCBs (2) are absorbed by the generous minimum of 6.
|
||||
listening_tcp = max(MIN_TCP_LISTEN_SOCKETS, sc.tcp_listen)
|
||||
|
||||
# TCP_SND_BUF: 4×MSS=5,840 matches ESP32. Down from arduino-pico's 8×MSS.
|
||||
# ESPAsyncWebServer allocates malloc(tcp_sndbuf()) per response chunk.
|
||||
tcp_snd_buf = "(4*TCP_MSS)"
|
||||
|
||||
# TCP_WND: receive window. 4×MSS matches ESP32. Down from arduino-pico's 8×MSS.
|
||||
tcp_wnd = "(4*TCP_MSS)"
|
||||
|
||||
# TCP_SND_QUEUELEN: max pbufs queued for send buffer
|
||||
# ESP-IDF formula: (4 * TCP_SND_BUF + (TCP_MSS - 1)) / TCP_MSS
|
||||
# With 4×MSS: (4*5840 + 1459) / 1460 = 17 — match ESP32
|
||||
tcp_snd_queuelen = 17
|
||||
# MEMP_NUM_TCP_SEG: segment pool, must be >= TCP_SND_QUEUELEN (lwIP sanity check)
|
||||
memp_num_tcp_seg = tcp_snd_queuelen
|
||||
|
||||
# PBUF_POOL_SIZE: RP2040 has 264KB RAM, more generous than LibreTiny.
|
||||
# 16 matches ESP32 (vs arduino-pico's 24). With MEMP_MEM_MALLOC=1,
|
||||
# this is a max count (allocated on demand from heap).
|
||||
pbuf_pool_size = 16
|
||||
|
||||
# Build the lwIP override defines for the Jinja2 template.
|
||||
# The template uses #include_next to chain to the framework's original
|
||||
# lwipopts.h, then #undef/#define only the values we need to change.
|
||||
#
|
||||
# Note: MEMP_MEM_MALLOC stays 0 (framework default). While the memp
|
||||
# allocations use the dedicated lwIP heap (IRQ-safe), the 16KB MEM_SIZE
|
||||
# is too small to hold all pools dynamically under stress. The PBUF_POOL
|
||||
# alone needs ~24KB (16 × 1524 bytes). Increasing MEM_SIZE would negate
|
||||
# the BSS savings.
|
||||
#
|
||||
# MEM_LIBC_MALLOC stays 0 (framework default): arduino-pico uses
|
||||
# PICO_CYW43_ARCH_THREADSAFE_BACKGROUND which runs lwIP callbacks from
|
||||
# a low-priority pendsv IRQ where libc malloc (mutex-based) is unsafe.
|
||||
lwip_defines: dict[str, str] = {
|
||||
"TCP_SND_BUF": tcp_snd_buf,
|
||||
"TCP_WND": tcp_wnd,
|
||||
"TCP_SND_QUEUELEN": str(tcp_snd_queuelen),
|
||||
"MEMP_NUM_TCP_SEG": str(memp_num_tcp_seg),
|
||||
"PBUF_POOL_SIZE": str(pbuf_pool_size),
|
||||
"MEMP_NUM_TCP_PCB": str(tcp_sockets),
|
||||
"MEMP_NUM_TCP_PCB_LISTEN": str(listening_tcp),
|
||||
"MEMP_NUM_UDP_PCB": str(udp_sockets),
|
||||
}
|
||||
lwip_defines = build_lwip_defines(tcp_sockets, udp_sockets, listening_tcp)
|
||||
|
||||
# Store for copy_files() to generate the header
|
||||
CORE.data[KEY_RP2][KEY_LWIP_OPTS] = lwip_defines
|
||||
@@ -500,7 +538,8 @@ def _configure_lwip() -> None:
|
||||
udp_min = " (min)" if udp_sockets > sc.udp else ""
|
||||
listen_min = " (min)" if listening_tcp > sc.tcp_listen else ""
|
||||
_LOGGER.info(
|
||||
"Configuring lwIP: TCP=%d%s [%s], UDP=%d%s [%s], TCP_LISTEN=%d%s [%s]",
|
||||
"Configuring lwIP: %d byte heap; TCP=%d%s [%s], UDP=%d%s [%s], TCP_LISTEN=%d%s [%s]",
|
||||
LWIP_MEM_SIZE,
|
||||
tcp_sockets,
|
||||
tcp_min,
|
||||
sc.tcp_details,
|
||||
@@ -521,7 +560,7 @@ def _generate_lwipopts_h() -> None:
|
||||
in the build directory, and a pre-build script injects this directory
|
||||
into the compiler include path before the framework's own include dir.
|
||||
"""
|
||||
from jinja2 import Environment
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
lwip_defines = CORE.data[KEY_RP2].get(KEY_LWIP_OPTS)
|
||||
if not lwip_defines:
|
||||
@@ -534,7 +573,10 @@ def _generate_lwipopts_h() -> None:
|
||||
template_text = (Path(__file__).parent / "lwipopts.h.jinja").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
jinja_env = Environment(keep_trailing_newline=True)
|
||||
# StrictUndefined: a placeholder with no value would otherwise render
|
||||
# empty, emitting a bare #define that compiles and silently means
|
||||
# something else in lwIP's config.
|
||||
jinja_env = Environment(keep_trailing_newline=True, undefined=StrictUndefined)
|
||||
template = jinja_env.from_string(template_text)
|
||||
content = template.render(**lwip_defines)
|
||||
|
||||
|
||||
@@ -20,13 +20,24 @@
|
||||
#undef TCP_WND
|
||||
#define TCP_WND {{ TCP_WND }}
|
||||
|
||||
// Queued segment limits: derived from 4xMSS buffer size, matching ESP32
|
||||
// Per-PCB send queue: derived from 4xMSS buffer size, matching ESP32
|
||||
#undef TCP_SND_QUEUELEN
|
||||
#define TCP_SND_QUEUELEN {{ TCP_SND_QUEUELEN }}
|
||||
|
||||
// Segment pool: global across every PCB, so it is sized above the per-PCB
|
||||
// queue length rather than equal to it. lwIP's sanity check only requires
|
||||
// >= TCP_SND_QUEUELEN, which is the floor for a single connection.
|
||||
#undef MEMP_NUM_TCP_SEG
|
||||
#define MEMP_NUM_TCP_SEG {{ MEMP_NUM_TCP_SEG }}
|
||||
|
||||
// lwIP heap backing PBUF_RAM, which is what tcp_write() copies into.
|
||||
// Raised from arduino-pico's 16KB: TCP_OVERSIZE is TCP_MSS, so a single PCB
|
||||
// at a full TCP_SND_BUF pins about 6KB. Two of those left the 16KB heap at
|
||||
// 75%, and mem.c is first-fit, so the largest contiguous run ran out well
|
||||
// before the total did.
|
||||
#undef MEM_SIZE
|
||||
#define MEM_SIZE {{ MEM_SIZE }}
|
||||
|
||||
// Packet buffer pool: 16 matches ESP32 (down from 24)
|
||||
#undef PBUF_POOL_SIZE
|
||||
#define PBUF_POOL_SIZE {{ PBUF_POOL_SIZE }}
|
||||
|
||||
@@ -13,25 +13,24 @@ itself (Python imports, YAML key rename, deprecation warning) is covered
|
||||
by the framework tests under ``tests/unit_tests/``.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
from esphome.components import rp2
|
||||
|
||||
|
||||
def test_board_id_has_wifi_for_known_wifi_board() -> None:
|
||||
"""``rpipicow`` is the canonical Pico W → True."""
|
||||
from esphome.components import rp2
|
||||
|
||||
assert rp2.board_id_has_wifi("rpipicow") is True
|
||||
|
||||
|
||||
def test_board_id_has_wifi_for_known_non_wifi_board() -> None:
|
||||
"""Plain ``rpipico`` has no CYW43 → False."""
|
||||
from esphome.components import rp2
|
||||
|
||||
assert rp2.board_id_has_wifi("rpipico") is False
|
||||
|
||||
|
||||
def test_board_id_has_wifi_for_rp2350_w_variant() -> None:
|
||||
"""``rpipico2w`` is the RP2350 Pico 2 W → True."""
|
||||
from esphome.components import rp2
|
||||
|
||||
assert rp2.board_id_has_wifi("rpipico2w") is True
|
||||
|
||||
|
||||
@@ -43,8 +42,6 @@ def test_board_id_has_wifi_for_unknown_board_returns_true() -> None:
|
||||
block and any genuinely-unsupported config trips the existing
|
||||
"no CYW43" guard at compile time.
|
||||
"""
|
||||
from esphome.components import rp2
|
||||
|
||||
assert rp2.board_id_has_wifi("not-a-real-board-id") is True
|
||||
|
||||
|
||||
@@ -55,8 +52,6 @@ def test_rp2_declares_rp2040_as_alias() -> None:
|
||||
opts in via ``ALIASES``; without this declaration the rename
|
||||
framework wouldn't route legacy configs.
|
||||
"""
|
||||
from esphome.components import rp2
|
||||
|
||||
assert "rp2040" in rp2.ALIASES
|
||||
assert rp2.ALIAS_REMOVAL_VERSION == "2027.7.0"
|
||||
|
||||
@@ -93,3 +88,95 @@ def test_rp2040_submodule_imports_resolve_to_rp2_submodules() -> None:
|
||||
|
||||
assert rp2040_boards is rp2_boards
|
||||
assert rp2040_generate is rp2_generate
|
||||
|
||||
|
||||
def test_lwip_segment_pool_exceeds_per_pcb_queue() -> None:
|
||||
"""The segment pool is global while the send queue is per-PCB.
|
||||
|
||||
lwIP's sanity check only requires ``MEMP_NUM_TCP_SEG >= TCP_SND_QUEUELEN``,
|
||||
which is the floor for a *single* connection: at equality one busy PCB can
|
||||
drain the pool for every other PCB. Dropping back to that floor would
|
||||
rebuild the starvation this sizing exists to prevent, and nothing in the
|
||||
build would complain.
|
||||
"""
|
||||
assert rp2.LWIP_MEMP_NUM_TCP_SEG >= 2 * rp2.LWIP_TCP_SND_QUEUELEN
|
||||
|
||||
|
||||
def test_lwip_mem_size_keeps_mem_size_t_narrow() -> None:
|
||||
"""``lwip/mem.h`` widens ``mem_size_t`` to ``u32_t`` on
|
||||
``MEM_SIZE > 64000L``, growing the header on every heap block. Raising the
|
||||
heap past that bound is a real option, but it should be a deliberate one
|
||||
rather than a side effect of tuning.
|
||||
"""
|
||||
assert rp2.LWIP_MEM_SIZE <= 64000
|
||||
|
||||
|
||||
def test_lwip_mem_size_holds_the_concurrent_senders_it_claims() -> None:
|
||||
"""Pin the floor as well as the ceiling.
|
||||
|
||||
The ceiling above is satisfied by arduino-pico's own 16 KB, which is the
|
||||
value this change exists to move off, so on its own it would let a revert
|
||||
through. Derive the floor from the sizing comment on the constant: with
|
||||
TCP_OVERSIZE at TCP_MSS every queued segment takes a full MSS-sized block
|
||||
(pbuf header + PBUF_TRANSPORT offset + 1460 + heap block header, ~1.5 KB),
|
||||
a PCB at a full 4xMSS TCP_SND_BUF holds four of them, and api's
|
||||
max_connections on rp2 is 4. Room for three concurrent senders is the
|
||||
minimum that makes the change worth making; 16 KB does not reach it.
|
||||
"""
|
||||
segments_per_full_send_buf = 4
|
||||
bytes_per_mss_block = 1536
|
||||
concurrent_senders = 3
|
||||
|
||||
assert (
|
||||
concurrent_senders * segments_per_full_send_buf * bytes_per_mss_block
|
||||
<= rp2.LWIP_MEM_SIZE
|
||||
)
|
||||
|
||||
|
||||
def test_lwip_defines_carry_the_sizing_into_the_header() -> None:
|
||||
"""The constants above only matter if they reach the generated header.
|
||||
|
||||
``build_lwip_defines()`` is what feeds lwipopts.h.jinja, so assert on it
|
||||
rather than on the constants alone: dropping a key here would silently
|
||||
fall back to arduino-pico's own value while every other assertion in this
|
||||
file stayed green.
|
||||
"""
|
||||
defines = rp2.build_lwip_defines(tcp_sockets=8, udp_sockets=6, listening_tcp=2)
|
||||
|
||||
assert defines["MEM_SIZE"] == str(rp2.LWIP_MEM_SIZE)
|
||||
assert defines["MEMP_NUM_TCP_SEG"] == str(rp2.LWIP_MEMP_NUM_TCP_SEG)
|
||||
assert defines["TCP_SND_QUEUELEN"] == str(rp2.LWIP_TCP_SND_QUEUELEN)
|
||||
# Socket-derived counts pass through untouched.
|
||||
assert defines["MEMP_NUM_TCP_PCB"] == "8"
|
||||
assert defines["MEMP_NUM_UDP_PCB"] == "6"
|
||||
assert defines["MEMP_NUM_TCP_PCB_LISTEN"] == "2"
|
||||
|
||||
|
||||
def test_lwipopts_template_renders_every_sizing_value() -> None:
|
||||
"""Render the template the way _generate_lwipopts_h() does and check the
|
||||
header that actually ships.
|
||||
|
||||
Covers both directions. A ``#define`` block deleted from the template
|
||||
leaves the value at arduino-pico's own, which for MEM_SIZE is the 16 KB
|
||||
heap this change exists to move off, and the loop below catches that. A
|
||||
placeholder with no dict key would otherwise render empty and emit a bare
|
||||
``#define FOO``; StrictUndefined turns that into an error instead.
|
||||
Matching on text also survives a filter or conditional appearing in the
|
||||
template later, which a placeholder regex would not.
|
||||
"""
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
defines = rp2.build_lwip_defines(tcp_sockets=8, udp_sockets=6, listening_tcp=2)
|
||||
template_text = (Path(rp2.__file__).parent / "lwipopts.h.jinja").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
rendered = (
|
||||
Environment(keep_trailing_newline=True, undefined=StrictUndefined)
|
||||
.from_string(template_text)
|
||||
.render(**defines)
|
||||
)
|
||||
|
||||
for name, value in defines.items():
|
||||
assert re.search(
|
||||
rf"^#define {re.escape(name)} +{re.escape(value)}$", rendered, re.MULTILINE
|
||||
), f"{name} did not reach the generated header as {value!r}"
|
||||
|
||||
Reference in New Issue
Block a user