mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Add multicast support to udp component (#8051)
This commit is contained in:
		| @@ -49,6 +49,7 @@ struct IPAddress { | |||||||
|   } |   } | ||||||
|   IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); } |   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; } |   IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; } | ||||||
|  |   std::string str() const { return str_lower_case(inet_ntoa(ip_addr_)); } | ||||||
| #else | #else | ||||||
|   IPAddress() { ip_addr_set_zero(&ip_addr_); } |   IPAddress() { ip_addr_set_zero(&ip_addr_); } | ||||||
|   IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) { |   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_set() { return !ip_addr_isany(&ip_addr_); }  // NOLINT(readability-simplify-boolean-expr) | ||||||
|   bool is_ip4() { return IP_IS_V4(&ip_addr_); } |   bool is_ip4() { return IP_IS_V4(&ip_addr_); } | ||||||
|   bool is_ip6() { return IP_IS_V6(&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_)); } |   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_); } | ||||||
|   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_); } | ||||||
|   | |||||||
| @@ -27,6 +27,7 @@ UDPComponent = udp_ns.class_("UDPComponent", cg.PollingComponent) | |||||||
| CONF_BROADCAST = "broadcast" | CONF_BROADCAST = "broadcast" | ||||||
| CONF_BROADCAST_ID = "broadcast_id" | CONF_BROADCAST_ID = "broadcast_id" | ||||||
| CONF_ADDRESSES = "addresses" | CONF_ADDRESSES = "addresses" | ||||||
|  | CONF_LISTEN_ADDRESS = "listen_address" | ||||||
| CONF_PROVIDER = "provider" | CONF_PROVIDER = "provider" | ||||||
| CONF_PROVIDERS = "providers" | CONF_PROVIDERS = "providers" | ||||||
| CONF_REMOTE_ID = "remote_id" | CONF_REMOTE_ID = "remote_id" | ||||||
| @@ -84,6 +85,9 @@ CONFIG_SCHEMA = cv.All( | |||||||
|         { |         { | ||||||
|             cv.GenerateID(): cv.declare_id(UDPComponent), |             cv.GenerateID(): cv.declare_id(UDPComponent), | ||||||
|             cv.Optional(CONF_PORT, default=18511): cv.port, |             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.Optional(CONF_ADDRESSES, default=["255.255.255.255"]): cv.ensure_list( | ||||||
|                 cv.ipv4address, |                 cv.ipv4address, | ||||||
|             ), |             ), | ||||||
| @@ -154,5 +158,7 @@ async def to_code(config): | |||||||
|     for provider in config.get(CONF_PROVIDERS, ()): |     for provider in config.get(CONF_PROVIDERS, ()): | ||||||
|         name = provider[CONF_NAME] |         name = provider[CONF_NAME] | ||||||
|         cg.add(var.add_provider(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): |         if encryption := provider.get(CONF_ENCRYPTION): | ||||||
|             cg.add(var.set_provider_encryption(name, hash_encryption_key(encryption))) |             cg.add(var.set_provider_encryption(name, hash_encryption_key(encryption))) | ||||||
|   | |||||||
| @@ -249,6 +249,21 @@ void UDPComponent::setup() { | |||||||
|     server.sin_addr.s_addr = ESPHOME_INADDR_ANY; |     server.sin_addr.s_addr = ESPHOME_INADDR_ANY; | ||||||
|     server.sin_port = htons(this->port_); |     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)); |     err = this->listen_socket_->bind((struct sockaddr *) &server, sizeof(server)); | ||||||
|     if (err != 0) { |     if (err != 0) { | ||||||
|       ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); |       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_)); |   ESP_LOGCONFIG(TAG, "  Ping-pong: %s", YESNO(this->ping_pong_enable_)); | ||||||
|   for (const auto &address : this->addresses_) |   for (const auto &address : this->addresses_) | ||||||
|     ESP_LOGCONFIG(TAG, "  Address: %s", address.c_str()); |     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 | #ifdef USE_SENSOR | ||||||
|   for (auto sensor : this->sensors_) |   for (auto sensor : this->sensors_) | ||||||
|     ESP_LOGCONFIG(TAG, "  Sensor: %s", sensor.id); |     ESP_LOGCONFIG(TAG, "  Sensor: %s", sensor.id); | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/network/ip_address.h" | ||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
| #include "esphome/components/sensor/sensor.h" | #include "esphome/components/sensor/sensor.h" | ||||||
| #endif | #endif | ||||||
| @@ -69,6 +70,7 @@ class UDPComponent : public PollingComponent { | |||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|   void add_address(const char *addr) { this->addresses_.emplace_back(addr); } |   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; } |   void set_port(uint16_t port) { this->port_ = port; } | ||||||
|   float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } |   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_{}; |   std::map<std::string, std::map<std::string, binary_sensor::BinarySensor *>> remote_binary_sensors_{}; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  |   optional<network::IPAddress> listen_address_{}; | ||||||
|   std::map<std::string, Provider> providers_{}; |   std::map<std::string, Provider> providers_{}; | ||||||
|   std::vector<uint8_t> ping_header_{}; |   std::vector<uint8_t> ping_header_{}; | ||||||
|   std::vector<uint8_t> header_{}; |   std::vector<uint8_t> header_{}; | ||||||
|   | |||||||
| @@ -1168,6 +1168,15 @@ def ipv4address(value): | |||||||
|     return address |     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): | def ipaddress(value): | ||||||
|     try: |     try: | ||||||
|         address = ip_address(value) |         address = ip_address(value) | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ udp: | |||||||
|   encryption: "our key goes here" |   encryption: "our key goes here" | ||||||
|   rolling_code_enable: true |   rolling_code_enable: true | ||||||
|   ping_pong_enable: true |   ping_pong_enable: true | ||||||
|  |   listen_address: 239.0.60.53 | ||||||
|   binary_sensors: |   binary_sensors: | ||||||
|     - binary_sensor_id1 |     - binary_sensor_id1 | ||||||
|     - id: binary_sensor_id1 |     - id: binary_sensor_id1 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user