mirror of
https://github.com/esphome/esphome.git
synced 2026-10-21 13:34:12 +00:00
[udp] Add UDP packet interface link
Each packet is one UDP datagram, using the settings of the udp component. The udp component now reports whether a packet was sent, and an ENOMEM refusal from lwIP is logged at verbose level as the packet can be sent again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
982eda4bab
commit
65da10e4d8
@@ -680,6 +680,7 @@ esphome/components/uart/packet_transport/* @clydebarrow
|
||||
esphome/components/uart_mux/* @kbx81
|
||||
esphome/components/uart_tcp/* @Bascht74
|
||||
esphome/components/udp/* @clydebarrow
|
||||
esphome/components/udp/packet_interface/* @clydebarrow
|
||||
esphome/components/ufire_ec/* @pvizeli
|
||||
esphome/components/ufire_ise/* @pvizeli
|
||||
esphome/components/ufm01/* @ljungqvist
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import packet_interface
|
||||
from esphome.components.packet_interface import (
|
||||
new_packet_interface,
|
||||
packet_interface_schema,
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_UDP_ID, UDP_SCHEMA, udp_ns
|
||||
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
|
||||
DEPENDENCIES = ["udp"]
|
||||
|
||||
# Size of the receive buffer in udp_component.h
|
||||
MAX_PACKET_SIZE = 508
|
||||
|
||||
UdpPacketInterface = udp_ns.class_(
|
||||
"UdpPacketInterface",
|
||||
packet_interface.PacketInterface,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = packet_interface_schema(
|
||||
UdpPacketInterface, MAX_PACKET_SIZE, MAX_PACKET_SIZE
|
||||
).extend(UDP_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
udp_var = await cg.get_variable(config[CONF_UDP_ID])
|
||||
cg.add(udp_var.set_should_listen())
|
||||
cg.add(udp_var.set_should_broadcast())
|
||||
await new_packet_interface(config, udp_var)
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "udp_packet_interface.h"
|
||||
#ifdef USE_NETWORK
|
||||
#include <array>
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::udp {
|
||||
|
||||
using packet_interface::PacketBuffer;
|
||||
using packet_interface::PacketMetaData;
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "udp.packet_interface");
|
||||
|
||||
void UdpPacketInterface::setup() {
|
||||
this->parent_->add_listener([this](std::span<const uint8_t> buf) {
|
||||
this->on_receive_from_interface_(PacketBuffer(buf.data(), buf.size()), {});
|
||||
});
|
||||
}
|
||||
|
||||
bool UdpPacketInterface::send_to_interface(const PacketBuffer &data, const PacketMetaData &meta_data) {
|
||||
if (!network::is_connected())
|
||||
return false;
|
||||
if (data.is_contiguous()) {
|
||||
auto span = data.as_span();
|
||||
return this->parent_->send_packet(span.data(), span.size());
|
||||
}
|
||||
if (data.size() > MAX_PACKET_SIZE) {
|
||||
ESP_LOGW(TAG, "Packet of %zu bytes is larger than %zu", data.size(), MAX_PACKET_SIZE);
|
||||
return false;
|
||||
}
|
||||
std::array<uint8_t, MAX_PACKET_SIZE> buf;
|
||||
size_t len = data.copy_to(buf.data(), buf.size());
|
||||
return this->parent_->send_packet(buf.data(), len);
|
||||
}
|
||||
|
||||
} // namespace esphome::udp
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
// udp_component.h declares nothing without USE_NETWORK
|
||||
#ifdef USE_NETWORK
|
||||
#include "esphome/components/packet_interface/packet_interface.h"
|
||||
#include "../udp_component.h"
|
||||
|
||||
namespace esphome::udp {
|
||||
|
||||
class UdpPacketInterface : public packet_interface::PacketInterface {
|
||||
public:
|
||||
explicit UdpPacketInterface(UDPComponent *udp_component) : parent_(udp_component) {}
|
||||
|
||||
void setup() override;
|
||||
bool send_to_interface(const packet_interface::PacketBuffer &data,
|
||||
const packet_interface::PacketMetaData &meta_data) override;
|
||||
|
||||
protected:
|
||||
UDPComponent *parent_;
|
||||
};
|
||||
} // namespace esphome::udp
|
||||
#endif
|
||||
@@ -1,12 +1,9 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "udp_transport.h"
|
||||
|
||||
namespace esphome::udp {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "udp_transport");
|
||||
|
||||
bool UDPTransport::should_send() { return network::is_connected(); }
|
||||
void UDPTransport::setup() {
|
||||
PacketTransport::setup();
|
||||
|
||||
@@ -205,18 +205,20 @@ void UDPComponent::set_poll_warning_(PollStep step, const LogString *what) {
|
||||
}
|
||||
|
||||
void UDPComponent::loop() {
|
||||
if (this->should_listen_) {
|
||||
std::array<uint8_t, MAX_PACKET_SIZE> buf;
|
||||
for (;;) {
|
||||
if (!this->listen_socket_->ready())
|
||||
break;
|
||||
auto len = this->listen_socket_->read(buf.data(), buf.size());
|
||||
if (len <= 0)
|
||||
break;
|
||||
size_t packet_len = static_cast<size_t>(len);
|
||||
ESP_LOGV(TAG, "Received packet of length %zu", packet_len);
|
||||
this->packet_listeners_.call(std::span<const uint8_t>(buf.data(), packet_len));
|
||||
}
|
||||
if (!this->should_listen_) {
|
||||
this->disable_loop();
|
||||
return;
|
||||
}
|
||||
std::array<uint8_t, MAX_PACKET_SIZE> buf;
|
||||
for (;;) {
|
||||
if (!this->listen_socket_->ready())
|
||||
break;
|
||||
auto len = this->listen_socket_->read(buf.data(), buf.size());
|
||||
if (len <= 0)
|
||||
break;
|
||||
size_t packet_len = static_cast<size_t>(len);
|
||||
ESP_LOGV(TAG, "Received packet of length %zu", packet_len);
|
||||
this->packet_listeners_.call(std::span<const uint8_t>(buf.data(), packet_len));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,9 +241,10 @@ void UDPComponent::dump_config() {
|
||||
YESNO(this->should_broadcast_), YESNO(this->should_listen_));
|
||||
}
|
||||
|
||||
void UDPComponent::send_packet(const uint8_t *data, size_t size) {
|
||||
bool UDPComponent::send_packet(const uint8_t *data, size_t size) {
|
||||
if (this->is_failed())
|
||||
return; // a send socket may be missing
|
||||
return false; // a send socket may be missing
|
||||
bool sent = true;
|
||||
for (const auto &entry : this->sockaddrs_) {
|
||||
#ifdef USE_UDP_IPV6_SEND
|
||||
const bool v6 = entry.sa.sa_family == AF_INET6;
|
||||
@@ -256,11 +259,23 @@ void UDPComponent::send_packet(const uint8_t *data, size_t size) {
|
||||
socket::UDPSendSocket *sock = this->broadcast_socket_.get();
|
||||
socklen_t sockaddr_size = sizeof(sockaddr_in);
|
||||
#endif
|
||||
if (sock == nullptr) {
|
||||
ESP_LOGW(TAG, "Broadcast socket not initialized");
|
||||
return false;
|
||||
}
|
||||
auto result = sock->sendto(data, size, 0, &entry.sa, sockaddr_size);
|
||||
if (result < 0) {
|
||||
ESP_LOGW(TAG, "sendto() error %d", errno);
|
||||
// lwIP refuses a packet with ENOMEM while the destination's ARP lookup is pending and its queue is full;
|
||||
// the packet can be sent again later
|
||||
if (errno == ENOMEM) {
|
||||
ESP_LOGV(TAG, "sendto() out of memory");
|
||||
} else {
|
||||
ESP_LOGW(TAG, "sendto() error %d", errno);
|
||||
}
|
||||
sent = false;
|
||||
}
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
} // namespace esphome::udp
|
||||
|
||||
|
||||
@@ -35,8 +35,9 @@ class UDPComponent final : public Component {
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
void send_packet(const uint8_t *data, size_t size);
|
||||
void send_packet(const std::vector<uint8_t> &buf) { this->send_packet(buf.data(), buf.size()); }
|
||||
/// Returns false if the packet could not be sent to every destination.
|
||||
bool send_packet(const uint8_t *data, size_t size);
|
||||
bool send_packet(const std::vector<uint8_t> &buf) { return this->send_packet(buf.data(), buf.size()); }
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; };
|
||||
|
||||
protected:
|
||||
@@ -53,7 +54,7 @@ class UDPComponent final : public Component {
|
||||
bool network_poll_();
|
||||
void set_poll_warning_(PollStep step, const LogString *what);
|
||||
|
||||
CallbackManager<void(std::span<const uint8_t>)> packet_listeners_{};
|
||||
LazyCallbackManager<void(std::span<const uint8_t>)> packet_listeners_{};
|
||||
std::unique_ptr<socket::UDPSendSocket> broadcast_socket_ = nullptr;
|
||||
std::unique_ptr<socket::UDPSocket> listen_socket_ = nullptr;
|
||||
#ifdef USE_UDP_IPV6_SEND
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
udp:
|
||||
- id: packet_interface_udp
|
||||
|
||||
packet_interface:
|
||||
- platform: uart
|
||||
id: packet_interface_uart
|
||||
max_packet_size: 256
|
||||
- platform: udp
|
||||
id: packet_interface_network
|
||||
udp_id: packet_interface_udp
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
@@ -13,3 +23,7 @@ packet_transport:
|
||||
packet_interface: packet_interface_uart
|
||||
sensors:
|
||||
- packet_interface_sensor
|
||||
- platform: packet_interface
|
||||
packet_interface: packet_interface_network
|
||||
sensors:
|
||||
- packet_interface_sensor
|
||||
|
||||
@@ -3,12 +3,14 @@ wifi:
|
||||
password: password1
|
||||
|
||||
udp:
|
||||
listen_address: 239.0.60.53
|
||||
addresses: ["239.0.60.53"]
|
||||
- id: transport_udp_component
|
||||
listen_address: 239.0.60.53
|
||||
addresses: ["239.0.60.53"]
|
||||
|
||||
packet_transport:
|
||||
- platform: udp
|
||||
id: transport_udp
|
||||
udp_id: transport_udp_component
|
||||
update_interval: 5s
|
||||
encryption: "our key goes here"
|
||||
rolling_code_enable: true
|
||||
|
||||
@@ -6,12 +6,14 @@ from esphome import config_validation as cv
|
||||
from esphome.components import packet_interface
|
||||
from esphome.components.packet_interface import CONF_MAX_PACKET_SIZE
|
||||
from esphome.components.uart import packet_interface as uart_packet_interface
|
||||
from esphome.components.udp import packet_interface as udp_packet_interface
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("schema", "default", "limit"),
|
||||
[
|
||||
(uart_packet_interface.CONFIG_SCHEMA, 508, 4096),
|
||||
(udp_packet_interface.CONFIG_SCHEMA, 508, 508),
|
||||
],
|
||||
)
|
||||
def test_platform_max_packet_size(
|
||||
|
||||
Reference in New Issue
Block a user