[udp] Keep addresses in a shared flash table (#20085)

This commit is contained in:
J. Nick Koston
2026-10-05 13:45:18 +13:00
committed by GitHub
parent e4be4c7fcb
commit 60e511282a
7 changed files with 73 additions and 8 deletions
+9 -1
View File
@@ -124,7 +124,15 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_broadcast_port(conf_port[CONF_BROADCAST_PORT]))
if (listen_address := str(config[CONF_LISTEN_ADDRESS])) != "255.255.255.255":
cg.add(var.set_listen_address(listen_address))
cg.add(var.set_addresses([str(addr) for addr in config[CONF_ADDRESSES]]))
# Shared flash table ended by nullptr, so the component stores only a pointer.
if addresses := [str(addr) for addr in config[CONF_ADDRESSES]]:
cg.add(
var.set_addresses(
cg.shared_progmem_array(
"udp_addresses", cg.const_char_ptr, [*addresses, cg.nullptr]
)
)
)
for conf in config.get(CONF_ON_RECEIVE, []):
trigger_id = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
trigger = await automation.build_automation(trigger_id, trigger_argtype, conf)
+6 -3
View File
@@ -11,7 +11,8 @@ static const char *const TAG = "udp";
void UDPComponent::setup() {
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
for (const auto &address : this->addresses_) {
for (const char *const *it = this->addresses_; it != nullptr && *it != nullptr; it++) {
const char *address = *it;
struct sockaddr saddr {};
if (socket::set_sockaddr(&saddr, sizeof(saddr), address, this->broadcast_port_) == 0) {
ESP_LOGW(TAG, "Invalid address %s", address);
@@ -97,7 +98,8 @@ void UDPComponent::setup() {
#endif
#ifdef USE_SOCKET_IMPL_LWIP_TCP
// 8266 and RP2040 `Duino
for (const auto &address : this->addresses_) {
for (const char *const *it = this->addresses_; it != nullptr && *it != nullptr; it++) {
const char *address = *it;
auto ipaddr = IPAddress();
if (!ipaddr.fromString(address)) {
ESP_LOGW(TAG, "Invalid address %s", address);
@@ -138,7 +140,8 @@ void UDPComponent::dump_config() {
" Listen Port: %u\n"
" Broadcast Port: %u",
this->listen_port_, this->broadcast_port_);
for (const char *address : this->addresses_) {
for (const char *const *it = this->addresses_; it != nullptr && *it != nullptr; it++) {
const char *address = *it;
ESP_LOGCONFIG(TAG, " Address: %s", address);
}
if (this->listen_address_.has_value()) {
+3 -4
View File
@@ -20,9 +20,8 @@ namespace esphome::udp {
static const size_t MAX_PACKET_SIZE = 508;
class UDPComponent final : public Component {
public:
void set_addresses(std::initializer_list<const char *> addresses) { this->addresses_ = addresses; }
/// Prevent accidental use of std::string which would dangle
void set_addresses(std::initializer_list<std::string> addresses) = delete;
/// nullptr-terminated table of addresses; must outlive the component.
void set_addresses(const char *const *addresses) { this->addresses_ = addresses; }
void set_listen_address(const char *listen_addr) { this->listen_address_ = network::IPAddress(listen_addr); }
void set_listen_port(uint16_t port) { this->listen_port_ = port; }
void set_broadcast_port(uint16_t port) { this->broadcast_port_ = port; }
@@ -52,7 +51,7 @@ class UDPComponent final : public Component {
std::vector<IPAddress> ipaddrs_{};
WiFiUDP udp_client_{};
#endif
FixedVector<const char *> addresses_{};
const char *const *addresses_{nullptr};
optional<network::IPAddress> listen_address_{};
};
@@ -0,0 +1,14 @@
esphome:
name: test
esp8266:
board: d1_mini
wifi:
ssid: test
password: password1
udp:
addresses:
- 10.0.0.1
- 10.0.0.2
@@ -0,0 +1,12 @@
esphome:
name: test
esp8266:
board: d1_mini
wifi:
ssid: test
password: password1
udp:
addresses: []
@@ -0,0 +1,29 @@
"""Tests for the udp component codegen."""
from collections.abc import Callable
from pathlib import Path
def test_addresses_are_a_progmem_table(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
"""Addresses live in a nullptr-terminated flash table instead of a heap vector."""
main_cpp = generate_main(component_config_path("udp_addresses.yaml"))
assert (
"static constexpr const char * udp_addresses[] PROGMEM = "
'{"10.0.0.1", "10.0.0.2", nullptr};' in main_cpp
)
assert "->set_addresses(udp_addresses);" in main_cpp
def test_empty_addresses_emit_no_table(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
"""An empty address list leaves the pointer null instead of emitting a table."""
main_cpp = generate_main(component_config_path("udp_no_addresses.yaml"))
assert "udp_addresses" not in main_cpp
assert "set_addresses(" not in main_cpp