mirror of https://github.com/arendst/Tasmota.git
Merge pull request #8563 from s-hadinger/udplistener
reduce footprint of multicast udp listener
This commit is contained in:
commit
fdc5ffa36b
|
@ -0,0 +1,7 @@
|
|||
name=UdpListener
|
||||
version=1.0
|
||||
author=Ivan Grokhotkov, Stephan Hadinger
|
||||
maintainer=Stephan <stephan.hadinger@gmail.com>
|
||||
sentence=UdpListener optimized for static and limite memory allocation, to reduce memory footprint of receiving SSDP request, as a replacement for WifiUdp.
|
||||
paragraph=This class only handles receiving UDP Multicast packets. For sending packets, use WifiUdp.
|
||||
architectures=esp8266
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
UdpListener.h - webserver for Tasmota
|
||||
|
||||
Copyright (C) 2020 Theo Arends & Stephan Hadinger
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.@
|
||||
*/
|
||||
|
||||
// adapted from:
|
||||
/*
|
||||
UdpContext.h - UDP connection handling on top of lwIP
|
||||
|
||||
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is a stripped down version of Udp handler to avoid overflowing
|
||||
* memory when lots of multicast SSDP packets arrive.
|
||||
* The pbuf is freed immediately upon arrival of the packet.
|
||||
*
|
||||
* Packet data are kept in a statically area in RAM and keeps
|
||||
* only the <n> first bytes (200 by default) of each packet.
|
||||
* The number of packets treated is limited (3 by default), any
|
||||
* new packet arriving is dropped.
|
||||
*
|
||||
* This class does only receiving multicast packets for LWIP2
|
||||
*/
|
||||
|
||||
#ifndef UDPMULTICASTLISTENER_H
|
||||
#define UDPMULTICASTLISTENER_H
|
||||
|
||||
#ifdef ESP8266
|
||||
// #include <Arduino.h>
|
||||
|
||||
extern "C" {
|
||||
#include <lwip/udp.h>
|
||||
#include <lwip/igmp.h>
|
||||
}
|
||||
|
||||
template <size_t PACKET_SIZE>
|
||||
struct UdpPacket {
|
||||
IPAddress srcaddr;
|
||||
IPAddress dstaddr;
|
||||
int16_t srcport;
|
||||
netif* input_netif;
|
||||
size_t len;
|
||||
uint8_t buf[PACKET_SIZE];
|
||||
};
|
||||
|
||||
template <size_t PACKET_SIZE>
|
||||
class UdpListener
|
||||
{
|
||||
public:
|
||||
|
||||
typedef std::function<void(void)> rxhandler_t;
|
||||
|
||||
UdpListener(size_t packet_number)
|
||||
: _pcb(0)
|
||||
, _packet_number(packet_number)
|
||||
, _buffers(nullptr)
|
||||
, _udp_packets(0)
|
||||
, _udp_ready(false)
|
||||
, _udp_index(0)
|
||||
{
|
||||
_packet_number = packet_number;
|
||||
_buffers = new UdpPacket<PACKET_SIZE>[_packet_number];
|
||||
_pcb = udp_new();
|
||||
}
|
||||
|
||||
~UdpListener()
|
||||
{
|
||||
udp_remove(_pcb);
|
||||
_pcb = 0;
|
||||
delete[] _buffers;
|
||||
_buffers = nullptr;
|
||||
}
|
||||
|
||||
void reset(void)
|
||||
{
|
||||
_udp_packets = 0;
|
||||
_udp_index = 0;
|
||||
}
|
||||
|
||||
bool listen(const IPAddress& addr, uint16_t port)
|
||||
{
|
||||
if (!_buffers) { return false; }
|
||||
udp_recv(_pcb, &_s_recv, (void *) this);
|
||||
err_t err = udp_bind(_pcb, addr, port);
|
||||
return err == ERR_OK;
|
||||
}
|
||||
|
||||
void disconnect()
|
||||
{
|
||||
udp_disconnect(_pcb);
|
||||
}
|
||||
|
||||
bool next()
|
||||
{
|
||||
if (!_buffers) { return false; }
|
||||
if (_udp_packets > 0) {
|
||||
if (!_udp_ready) {
|
||||
// we just consume the first packet
|
||||
_udp_ready = true;
|
||||
} else {
|
||||
_udp_packets--;
|
||||
_udp_index = (_udp_index + 1) % _packet_number; // advance to next buffer index in ring
|
||||
if (_udp_packets == 0) {
|
||||
_udp_ready = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_udp_ready = false;
|
||||
}
|
||||
return _udp_ready;
|
||||
}
|
||||
|
||||
UdpPacket<PACKET_SIZE> * read(void)
|
||||
{
|
||||
if (!_buffers) { return nullptr; }
|
||||
if (_udp_ready) { // we have a packet ready to consume
|
||||
return &_buffers[_udp_index];
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void _recv(udp_pcb *upcb, pbuf *pb,
|
||||
const ip_addr_t *srcaddr, u16_t srcport)
|
||||
{
|
||||
if (!_buffers) { pbuf_free(pb); return; }
|
||||
// Serial.printf(">>> _recv: _udp_packets = %d, _udp_index = %d, tot_len = %d\n", _udp_packets, _udp_index, pb->tot_len);
|
||||
if (_udp_packets >= _packet_number) {
|
||||
// we don't have slots anymore, drop packet
|
||||
pbuf_free(pb);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t next_slot = (_udp_index + _udp_packets) % _packet_number;
|
||||
|
||||
size_t packet_len = pb->tot_len;
|
||||
if (packet_len > PACKET_SIZE) { packet_len = PACKET_SIZE; }
|
||||
|
||||
uint8_t * dst = &_buffers[next_slot].buf[0];
|
||||
void* buf = pbuf_get_contiguous(pb, dst, PACKET_SIZE, packet_len, 0);
|
||||
if (buf) {
|
||||
|
||||
if (buf != dst)
|
||||
memcpy(dst, buf, packet_len);
|
||||
_buffers[next_slot].len = packet_len;
|
||||
|
||||
_buffers[next_slot].srcaddr = srcaddr;
|
||||
_buffers[next_slot].dstaddr = ip_current_dest_addr();
|
||||
_buffers[next_slot].srcport = srcport;
|
||||
_buffers[next_slot].input_netif = ip_current_input_netif();
|
||||
_udp_packets++; // we have one packet ready
|
||||
}
|
||||
pbuf_free(pb); // free memory immediately
|
||||
}
|
||||
|
||||
static void _s_recv(void *arg,
|
||||
udp_pcb *upcb, pbuf *p,
|
||||
CONST ip_addr_t *srcaddr, u16_t srcport)
|
||||
{
|
||||
reinterpret_cast<UdpListener*>(arg)->_recv(upcb, p, srcaddr, srcport);
|
||||
}
|
||||
|
||||
private:
|
||||
udp_pcb* _pcb;
|
||||
uint8_t _packet_number;
|
||||
|
||||
UdpPacket<PACKET_SIZE> * _buffers;
|
||||
|
||||
// how many packets are ready.
|
||||
int8_t _udp_packets; // number of udp packets ready to consume
|
||||
bool _udp_ready; // is a packet currenlty consumed after a call to next()
|
||||
// ring buffer ranges from 0..(_packet_number-1)
|
||||
int8_t _udp_index; // current index in the ring buffer
|
||||
};
|
||||
|
||||
#endif // ESP8266
|
||||
#endif //UDPMULTICASTLISTENER_H
|
|
@ -19,7 +19,9 @@
|
|||
|
||||
#ifdef USE_EMULATION
|
||||
|
||||
#define UDP_BUFFER_SIZE 200 // Max UDP buffer size needed for M-SEARCH message
|
||||
#ifndef UDP_BUFFER_SIZE
|
||||
#define UDP_BUFFER_SIZE 120 // Max UDP buffer size needed for M-SEARCH message
|
||||
#endif
|
||||
#define UDP_MSEARCH_SEND_DELAY 1500 // Delay in ms before M-Search response is send
|
||||
|
||||
#include <Ticker.h>
|
||||
|
@ -31,6 +33,15 @@ uint16_t udp_remote_port; // M-Search remote port
|
|||
bool udp_connected = false;
|
||||
bool udp_response_mutex = false; // M-Search response mutex to control re-entry
|
||||
|
||||
#ifdef ESP8266
|
||||
#ifndef UDP_MAX_PACKETS
|
||||
#define UDP_MAX_PACKETS 3 // we support x more packets than the current one
|
||||
#endif
|
||||
|
||||
#include "UdpListener.h"
|
||||
UdpListener<UDP_BUFFER_SIZE> UdpCtx(UDP_MAX_PACKETS);
|
||||
#endif
|
||||
|
||||
/*********************************************************************************************\
|
||||
* UPNP/SSDP search targets
|
||||
\*********************************************************************************************/
|
||||
|
@ -48,10 +59,16 @@ const char SSDP_ALL[] PROGMEM = "ssdp:all";
|
|||
bool UdpDisconnect(void)
|
||||
{
|
||||
if (udp_connected) {
|
||||
// flush any outgoing packet
|
||||
PortUdp.flush();
|
||||
#ifdef ESP8266
|
||||
UdpCtx.disconnect();
|
||||
#endif
|
||||
#ifdef USE_DEVICE_GROUPS
|
||||
// stop
|
||||
PortUdp.stop();
|
||||
#else // USE_DEVICE_GROUPS
|
||||
// stop all
|
||||
WiFiUDP::stopAll();
|
||||
#endif // !USE_DEVICE_GROUPS
|
||||
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_MULTICAST_DISABLED));
|
||||
|
@ -64,13 +81,25 @@ bool UdpConnect(void)
|
|||
{
|
||||
if (!udp_connected && !restart_flag) {
|
||||
// Simple Service Discovery Protocol (SSDP)
|
||||
#ifdef ESP8266
|
||||
UdpCtx.reset();
|
||||
if (igmp_joingroup(WiFi.localIP(), IPAddress(239,255,255,250)) == ERR_OK) { // addr 239.255.255.250
|
||||
ip_addr_t addr = IPADDR4_INIT(INADDR_ANY);
|
||||
if (UdpCtx.listen(&addr, 1900)) { // port 1900
|
||||
// OK
|
||||
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_REJOINED));
|
||||
udp_response_mutex = false;
|
||||
udp_connected = true;
|
||||
}
|
||||
#else // ESP32
|
||||
if (PortUdp.beginMulticast(WiFi.localIP(), IPAddress(239,255,255,250), 1900)) {
|
||||
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_REJOINED));
|
||||
udp_response_mutex = false;
|
||||
udp_connected = true;
|
||||
} else {
|
||||
#endif
|
||||
}
|
||||
if (!udp_connected) { // if connection failed
|
||||
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_JOIN_FAILED));
|
||||
udp_connected = false;
|
||||
}
|
||||
}
|
||||
return udp_connected;
|
||||
|
@ -79,14 +108,25 @@ bool UdpConnect(void)
|
|||
void PollUdp(void)
|
||||
{
|
||||
if (udp_connected) {
|
||||
#ifdef ESP8266
|
||||
while (UdpCtx.next()) {
|
||||
UdpPacket<UDP_BUFFER_SIZE> *packet;
|
||||
packet = UdpCtx.read();
|
||||
if (packet->len >= UDP_BUFFER_SIZE) {
|
||||
packet->len--; // leave space for NULL terminator
|
||||
}
|
||||
packet->buf[packet->len] = 0; // add NULL at the end of the packer
|
||||
char * packet_buffer = (char*) &packet->buf;
|
||||
int32_t len = packet->len;
|
||||
#else // ESP32
|
||||
while (PortUdp.parsePacket()) {
|
||||
char packet_buffer[UDP_BUFFER_SIZE]; // buffer to hold incoming UDP/SSDP packet
|
||||
|
||||
int len = PortUdp.read(packet_buffer, UDP_BUFFER_SIZE -1);
|
||||
int32_t len = PortUdp.read(packet_buffer, UDP_BUFFER_SIZE -1);
|
||||
packet_buffer[len] = 0;
|
||||
|
||||
#endif
|
||||
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet (%d)"), len);
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), packet_buffer);
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), packet_buffer);
|
||||
|
||||
// Simple Service Discovery Protocol (SSDP)
|
||||
if (Settings.flag2.emulation) {
|
||||
|
@ -97,11 +137,16 @@ void PollUdp(void)
|
|||
#endif
|
||||
udp_response_mutex = true;
|
||||
|
||||
#ifdef ESP8266
|
||||
udp_remote_ip = packet->srcaddr;
|
||||
udp_remote_port = packet->srcport;
|
||||
#else
|
||||
udp_remote_ip = PortUdp.remoteIP();
|
||||
udp_remote_port = PortUdp.remotePort();
|
||||
#endif
|
||||
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: M-SEARCH Packet from %s:%d\n%s"),
|
||||
// udp_remote_ip.toString().c_str(), udp_remote_port, packet_buffer);
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: M-SEARCH Packet from %s:%d\n%s"),
|
||||
// udp_remote_ip.toString().c_str(), udp_remote_port, packet_buffer);
|
||||
|
||||
uint32_t response_delay = UDP_MSEARCH_SEND_DELAY + ((millis() &0x7) * 100); // 1500 - 2200 msec
|
||||
|
||||
|
|
Loading…
Reference in New Issue