/*
support_udp.ino - Udp support for Tasmota
Copyright (C) 2021 Heiko Krupp and Theo Arends
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 .
*/
#ifdef USE_EMULATION
#ifndef UDP_BUFFER_SIZE
#define UDP_BUFFER_SIZE 120 // Max UDP buffer size needed for M-SEARCH message
#endif
#define UDP_MSEARCH_DEBOUNCE 300 // Don't send new response if same request within 300 ms
uint32_t udp_last_received = 0; // timestamp of last udp received packet
// if non-zero we keep silend and don't send response
// there is a very low probability that after 53 days the timestamp is
// genuingly zero. This is not an issue and would result in an extra response sent.
IPAddress udp_remote_ip; // M-Search remote IP address
uint16_t udp_remote_port; // M-Search remote port
bool udp_connected = false;
#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 UdpCtx(UDP_MAX_PACKETS);
#endif
/*********************************************************************************************\
* UPNP/SSDP search targets
\*********************************************************************************************/
const char URN_BELKIN_DEVICE[] PROGMEM = "urn:belkin:device:**";
const char URN_BELKIN_DEVICE_CAP[] PROGMEM = "urn:Belkin:device:**";
const char UPNP_ROOTDEVICE[] PROGMEM = "upnp:rootdevice";
const char SSDPSEARCH_ALL[] PROGMEM = "ssdpsearch:all";
const char SSDP_ALL[] PROGMEM = "ssdp:all";
/*********************************************************************************************\
* UDP support routines
\*********************************************************************************************/
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(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_MULTICAST_DISABLED));
udp_connected = false;
}
return udp_connected;
}
bool UdpConnect(void)
{
if (!udp_connected && !TasmotaGlobal.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(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_REJOINED));
udp_connected = true;
}
#endif // ESP8266
#ifdef ESP32
if (PortUdp.beginMulticast(WiFi.localIP(), IPAddress(239,255,255,250), 1900)) {
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_REJOINED));
udp_connected = true;
#endif // ESP32
}
if (!udp_connected) { // if connection failed
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_JOIN_FAILED));
}
}
return udp_connected;
}
void PollUdp(void)
{
if (udp_connected) {
if (TimeReached(udp_last_received + UDP_MSEARCH_DEBOUNCE)) {
udp_last_received = 0; // re-init timer
}
#ifdef ESP8266
while (UdpCtx.next()) {
UdpPacket *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 packet
char * packet_buffer = (char*) &packet->buf;
int32_t len = packet->len;
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet (%d)"), len);
#endif // ESP8266
#ifdef ESP32
while (uint32_t pack_len = PortUdp.parsePacket()) {
char packet_buffer[UDP_BUFFER_SIZE]; // buffer to hold incoming UDP/SSDP packet
int32_t len = PortUdp.read(packet_buffer, UDP_BUFFER_SIZE -1);
packet_buffer[len] = 0;
PortUdp.flush();
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet (%d/%d)"), len, pack_len);
#endif // ESP32
// AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), packet_buffer);
// Simple Service Discovery Protocol (SSDP)
if (Settings.flag2.emulation) {
#if defined(USE_SCRIPT_HUE) || defined(USE_ZIGBEE)
if (TasmotaGlobal.devices_present && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) {
#else
if (TasmotaGlobal.devices_present && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) {
#endif
if (0 == udp_last_received) {
udp_last_received = millis();
#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_P(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);
LowerCase(packet_buffer, packet_buffer);
RemoveSpace(packet_buffer);
bool udp_proccessed = false; // make sure we process the packet only once
#ifdef USE_EMULATION_WEMO
if (!udp_proccessed && (EMUL_WEMO == Settings.flag2.emulation)) {
if (strstr_P(packet_buffer, URN_BELKIN_DEVICE) != nullptr) { // type1 echo dot 2g, echo 1g's
WemoRespondToMSearch(1);
udp_proccessed = true;
}
else if ((strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) || // type2 Echo 2g (echo & echo plus)
(strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) ||
(strstr_P(packet_buffer, SSDP_ALL) != nullptr)) {
WemoRespondToMSearch(2);
udp_proccessed = true;
}
}
#endif // USE_EMULATION_WEMO
#ifdef USE_EMULATION_HUE
if (!udp_proccessed && (EMUL_HUE == Settings.flag2.emulation)) {
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: HUE"));
if ((strstr_P(packet_buffer, PSTR(":device:basic:1")) != nullptr) ||
(strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) ||
(strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) ||
(strstr_P(packet_buffer, SSDP_ALL) != nullptr)) {
HueRespondToMSearch();
udp_proccessed = true;
}
}
#endif // USE_EMULATION_HUE
}
}
}
}
optimistic_yield(100);
}
}
#endif // USE_EMULATION