2019-05-20 14:09:42 +01:00
|
|
|
/*
|
2019-10-27 10:13:24 +00:00
|
|
|
support_udp.ino - Udp support for Tasmota
|
2019-05-20 14:09:42 +01:00
|
|
|
|
2021-01-01 12:44:04 +00:00
|
|
|
Copyright (C) 2021 Heiko Krupp and Theo Arends
|
2019-05-20 14:09:42 +01:00
|
|
|
|
|
|
|
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/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef USE_EMULATION
|
|
|
|
|
2020-05-27 18:42:43 +01:00
|
|
|
#ifndef UDP_BUFFER_SIZE
|
|
|
|
#define UDP_BUFFER_SIZE 120 // Max UDP buffer size needed for M-SEARCH message
|
|
|
|
#endif
|
2019-05-20 14:09:42 +01:00
|
|
|
#define UDP_MSEARCH_SEND_DELAY 1500 // Delay in ms before M-Search response is send
|
|
|
|
|
|
|
|
#include <Ticker.h>
|
|
|
|
Ticker TickerMSearch;
|
|
|
|
|
|
|
|
IPAddress udp_remote_ip; // M-Search remote IP address
|
|
|
|
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
|
|
|
|
|
2020-05-27 22:52:25 +01:00
|
|
|
#ifdef ESP8266
|
|
|
|
#ifndef UDP_MAX_PACKETS
|
|
|
|
#define UDP_MAX_PACKETS 3 // we support x more packets than the current one
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "UdpListener.h"
|
2020-05-27 18:42:43 +01:00
|
|
|
UdpListener<UDP_BUFFER_SIZE> UdpCtx(UDP_MAX_PACKETS);
|
2020-05-27 22:52:25 +01:00
|
|
|
#endif
|
2020-05-27 18:42:43 +01:00
|
|
|
|
2019-05-20 14:09:42 +01:00
|
|
|
/*********************************************************************************************\
|
|
|
|
* UPNP/SSDP search targets
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
|
|
|
const char URN_BELKIN_DEVICE[] PROGMEM = "urn:belkin:device:**";
|
2019-07-13 15:31:53 +01:00
|
|
|
const char URN_BELKIN_DEVICE_CAP[] PROGMEM = "urn:Belkin:device:**";
|
2019-05-20 14:09:42 +01:00
|
|
|
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) {
|
2020-05-27 18:42:43 +01:00
|
|
|
// flush any outgoing packet
|
2019-05-20 14:09:42 +01:00
|
|
|
PortUdp.flush();
|
2020-05-27 22:52:25 +01:00
|
|
|
#ifdef ESP8266
|
2020-05-27 19:48:40 +01:00
|
|
|
UdpCtx.disconnect();
|
2020-05-27 22:52:25 +01:00
|
|
|
#endif
|
2020-04-24 15:12:43 +01:00
|
|
|
#ifdef USE_DEVICE_GROUPS
|
2020-05-27 18:42:43 +01:00
|
|
|
// stop
|
2020-04-24 15:12:43 +01:00
|
|
|
PortUdp.stop();
|
|
|
|
#else // USE_DEVICE_GROUPS
|
2020-05-27 18:42:43 +01:00
|
|
|
// stop all
|
2019-05-20 14:09:42 +01:00
|
|
|
WiFiUDP::stopAll();
|
2020-04-24 15:12:43 +01:00
|
|
|
#endif // !USE_DEVICE_GROUPS
|
2019-05-20 14:09:42 +01:00
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_MULTICAST_DISABLED));
|
|
|
|
udp_connected = false;
|
|
|
|
}
|
|
|
|
return udp_connected;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool UdpConnect(void)
|
|
|
|
{
|
2020-10-29 11:21:24 +00:00
|
|
|
if (!udp_connected && !TasmotaGlobal.restart_flag) {
|
2019-05-20 14:09:42 +01:00
|
|
|
// Simple Service Discovery Protocol (SSDP)
|
2020-05-27 22:52:25 +01:00
|
|
|
#ifdef ESP8266
|
2020-05-27 18:42:43 +01:00
|
|
|
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;
|
|
|
|
}
|
2020-11-28 15:39:15 +00:00
|
|
|
#endif // ESP8266
|
|
|
|
#ifdef ESP32
|
2020-05-27 22:52:25 +01:00
|
|
|
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;
|
2020-11-28 15:39:15 +00:00
|
|
|
#endif // ESP32
|
2020-05-27 18:42:43 +01:00
|
|
|
}
|
|
|
|
if (!udp_connected) { // if connection failed
|
2019-05-20 14:09:42 +01:00
|
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_JOIN_FAILED));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return udp_connected;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PollUdp(void)
|
|
|
|
{
|
|
|
|
if (udp_connected) {
|
2020-05-27 22:52:25 +01:00
|
|
|
#ifdef ESP8266
|
2020-05-27 18:42:43 +01:00
|
|
|
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;
|
2020-05-27 22:52:25 +01:00
|
|
|
int32_t len = packet->len;
|
2020-11-28 15:39:15 +00:00
|
|
|
#endif // ESP8266
|
|
|
|
#ifdef ESP32
|
2020-05-27 22:52:25 +01:00
|
|
|
while (PortUdp.parsePacket()) {
|
|
|
|
char packet_buffer[UDP_BUFFER_SIZE]; // buffer to hold incoming UDP/SSDP packet
|
2019-05-20 14:09:42 +01:00
|
|
|
|
2020-05-27 22:52:25 +01:00
|
|
|
int32_t len = PortUdp.read(packet_buffer, UDP_BUFFER_SIZE -1);
|
|
|
|
packet_buffer[len] = 0;
|
2020-11-28 15:39:15 +00:00
|
|
|
#endif // ESP32
|
2020-11-06 16:09:13 +00:00
|
|
|
AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet (%d)"), len);
|
|
|
|
// AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), packet_buffer);
|
2019-05-20 14:09:42 +01:00
|
|
|
|
|
|
|
// Simple Service Discovery Protocol (SSDP)
|
2020-02-21 15:09:21 +00:00
|
|
|
if (Settings.flag2.emulation) {
|
2020-03-14 13:17:30 +00:00
|
|
|
#if defined(USE_SCRIPT_HUE) || defined(USE_ZIGBEE)
|
2020-02-21 15:09:21 +00:00
|
|
|
if (!udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) {
|
2019-10-02 12:38:05 +01:00
|
|
|
#else
|
2020-10-30 11:29:48 +00:00
|
|
|
if (TasmotaGlobal.devices_present && !udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) {
|
2019-10-02 12:38:05 +01:00
|
|
|
#endif
|
2020-02-21 15:09:21 +00:00
|
|
|
udp_response_mutex = true;
|
2019-05-20 14:09:42 +01:00
|
|
|
|
2020-05-27 22:52:25 +01:00
|
|
|
#ifdef ESP8266
|
2020-05-27 18:42:43 +01:00
|
|
|
udp_remote_ip = packet->srcaddr;
|
|
|
|
udp_remote_port = packet->srcport;
|
2020-05-27 22:52:25 +01:00
|
|
|
#else
|
|
|
|
udp_remote_ip = PortUdp.remoteIP();
|
|
|
|
udp_remote_port = PortUdp.remotePort();
|
|
|
|
#endif
|
2019-05-20 14:09:42 +01:00
|
|
|
|
2020-11-06 16:09:13 +00:00
|
|
|
// AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: M-SEARCH Packet from %s:%d\n%s"),
|
2020-05-27 18:42:43 +01:00
|
|
|
// udp_remote_ip.toString().c_str(), udp_remote_port, packet_buffer);
|
2019-05-20 14:09:42 +01:00
|
|
|
|
2020-02-21 15:09:21 +00:00
|
|
|
uint32_t response_delay = UDP_MSEARCH_SEND_DELAY + ((millis() &0x7) * 100); // 1500 - 2200 msec
|
2019-05-20 14:09:42 +01:00
|
|
|
|
2020-02-21 15:09:21 +00:00
|
|
|
LowerCase(packet_buffer, packet_buffer);
|
|
|
|
RemoveSpace(packet_buffer);
|
2019-05-20 14:09:42 +01:00
|
|
|
|
|
|
|
#ifdef USE_EMULATION_WEMO
|
2020-02-21 15:09:21 +00:00
|
|
|
if (EMUL_WEMO == Settings.flag2.emulation) {
|
|
|
|
if (strstr_P(packet_buffer, URN_BELKIN_DEVICE) != nullptr) { // type1 echo dot 2g, echo 1g's
|
2020-12-26 14:12:46 +00:00
|
|
|
TickerMSearch.once_ms(response_delay, WemoRespondToMSearch, 1);
|
2020-02-21 15:09:21 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
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)) {
|
2020-12-26 14:12:46 +00:00
|
|
|
TickerMSearch.once_ms(response_delay, WemoRespondToMSearch, 2);
|
2020-02-21 15:09:21 +00:00
|
|
|
return;
|
|
|
|
}
|
2019-05-20 14:09:42 +01:00
|
|
|
}
|
|
|
|
#endif // USE_EMULATION_WEMO
|
|
|
|
|
|
|
|
#ifdef USE_EMULATION_HUE
|
2020-02-21 15:09:21 +00:00
|
|
|
if (EMUL_HUE == Settings.flag2.emulation) {
|
|
|
|
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)) {
|
2020-12-26 14:12:46 +00:00
|
|
|
TickerMSearch.once_ms(response_delay, HueRespondToMSearch);
|
2020-02-21 15:09:21 +00:00
|
|
|
return;
|
|
|
|
}
|
2019-05-20 14:09:42 +01:00
|
|
|
}
|
|
|
|
#endif // USE_EMULATION_HUE
|
|
|
|
|
2020-02-21 15:09:21 +00:00
|
|
|
udp_response_mutex = false;
|
|
|
|
continue;
|
|
|
|
}
|
2019-05-20 14:09:42 +01:00
|
|
|
}
|
|
|
|
}
|
2019-11-03 19:30:34 +00:00
|
|
|
optimistic_yield(100);
|
2019-05-20 14:09:42 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // USE_EMULATION
|