Add multicast support to udp component (#8051)

This commit is contained in:
Jimmy Hedman 2025-01-29 11:00:18 +01:00 committed by GitHub
parent a23ce416ea
commit 9957840dfc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 39 additions and 0 deletions

View File

@ -49,6 +49,7 @@ struct IPAddress {
}
IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); }
IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; }
std::string str() const { return str_lower_case(inet_ntoa(ip_addr_)); }
#else
IPAddress() { ip_addr_set_zero(&ip_addr_); }
IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) {
@ -119,6 +120,7 @@ struct IPAddress {
bool is_set() { return !ip_addr_isany(&ip_addr_); } // NOLINT(readability-simplify-boolean-expr)
bool is_ip4() { return IP_IS_V4(&ip_addr_); }
bool is_ip6() { return IP_IS_V6(&ip_addr_); }
bool is_multicast() { return ip_addr_ismulticast(&ip_addr_); }
std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); }
bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); }

View File

@ -27,6 +27,7 @@ UDPComponent = udp_ns.class_("UDPComponent", cg.PollingComponent)
CONF_BROADCAST = "broadcast"
CONF_BROADCAST_ID = "broadcast_id"
CONF_ADDRESSES = "addresses"
CONF_LISTEN_ADDRESS = "listen_address"
CONF_PROVIDER = "provider"
CONF_PROVIDERS = "providers"
CONF_REMOTE_ID = "remote_id"
@ -84,6 +85,9 @@ CONFIG_SCHEMA = cv.All(
{
cv.GenerateID(): cv.declare_id(UDPComponent),
cv.Optional(CONF_PORT, default=18511): cv.port,
cv.Optional(
CONF_LISTEN_ADDRESS, default="255.255.255.255"
): cv.ipv4address_multi_broadcast,
cv.Optional(CONF_ADDRESSES, default=["255.255.255.255"]): cv.ensure_list(
cv.ipv4address,
),
@ -154,5 +158,7 @@ async def to_code(config):
for provider in config.get(CONF_PROVIDERS, ()):
name = provider[CONF_NAME]
cg.add(var.add_provider(name))
if (listen_address := str(config[CONF_LISTEN_ADDRESS])) != "255.255.255.255":
cg.add(var.set_listen_address(listen_address))
if encryption := provider.get(CONF_ENCRYPTION):
cg.add(var.set_provider_encryption(name, hash_encryption_key(encryption)))

View File

@ -249,6 +249,21 @@ void UDPComponent::setup() {
server.sin_addr.s_addr = ESPHOME_INADDR_ANY;
server.sin_port = htons(this->port_);
if (this->listen_address_.has_value()) {
struct ip_mreq imreq = {};
imreq.imr_interface.s_addr = ESPHOME_INADDR_ANY;
inet_aton(this->listen_address_.value().str().c_str(), &imreq.imr_multiaddr);
server.sin_addr.s_addr = imreq.imr_multiaddr.s_addr;
ESP_LOGV(TAG, "Join multicast %s", this->listen_address_.value().str().c_str());
err = this->listen_socket_->setsockopt(IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(imreq));
if (err < 0) {
ESP_LOGE(TAG, "Failed to set IP_ADD_MEMBERSHIP. Error %d", errno);
this->mark_failed();
this->status_set_error("Failed to set IP_ADD_MEMBERSHIP");
return;
}
}
err = this->listen_socket_->bind((struct sockaddr *) &server, sizeof(server));
if (err != 0) {
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
@ -565,6 +580,9 @@ void UDPComponent::dump_config() {
ESP_LOGCONFIG(TAG, " Ping-pong: %s", YESNO(this->ping_pong_enable_));
for (const auto &address : this->addresses_)
ESP_LOGCONFIG(TAG, " Address: %s", address.c_str());
if (this->listen_address_.has_value()) {
ESP_LOGCONFIG(TAG, " Listen address: %s", this->listen_address_.value().str().c_str());
}
#ifdef USE_SENSOR
for (auto sensor : this->sensors_)
ESP_LOGCONFIG(TAG, " Sensor: %s", sensor.id);

View File

@ -1,6 +1,7 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/network/ip_address.h"
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
@ -69,6 +70,7 @@ class UDPComponent : public PollingComponent {
}
#endif
void add_address(const char *addr) { this->addresses_.emplace_back(addr); }
void set_listen_address(const char *listen_addr) { this->listen_address_ = network::IPAddress(listen_addr); }
void set_port(uint16_t port) { this->port_ = port; }
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
@ -143,6 +145,7 @@ class UDPComponent : public PollingComponent {
std::map<std::string, std::map<std::string, binary_sensor::BinarySensor *>> remote_binary_sensors_{};
#endif
optional<network::IPAddress> listen_address_{};
std::map<std::string, Provider> providers_{};
std::vector<uint8_t> ping_header_{};
std::vector<uint8_t> header_{};

View File

@ -1168,6 +1168,15 @@ def ipv4address(value):
return address
def ipv4address_multi_broadcast(value):
address = ipv4address(value)
if not (address.is_multicast or (address == IPv4Address("255.255.255.255"))):
raise Invalid(
f"{value} is not a multicasst address nor local broadcast address"
)
return address
def ipaddress(value):
try:
address = ip_address(value)

View File

@ -7,6 +7,7 @@ udp:
encryption: "our key goes here"
rolling_code_enable: true
ping_pong_enable: true
listen_address: 239.0.60.53
binary_sensors:
- binary_sensor_id1
- id: binary_sensor_id1