From d599f21758afc47155f8bb6ca7fd74d2630bca7f Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Mon, 20 May 2019 15:09:42 +0200 Subject: [PATCH] Add defines USE_EMULATION_WEMO and USE_EMULATION_HUE * Remove define USE_EMULATION from my_user_config.h (#5826) * Add defines USE_EMULATION_WEMO and USE_EMULATION_HUE to my_user_config.h to control emulation features at compile time (#5826) --- sonoff/_changelog.ino | 2 + sonoff/my_user_config.h | 3 +- sonoff/sonoff.ino | 7 + sonoff/sonoff_post.h | 7 + sonoff/support_features.ino | 8 +- sonoff/support_udp.ino | 136 +++++++ sonoff/support_wifi.ino | 8 +- sonoff/xdrv_01_webserver.ino | 34 +- sonoff/xdrv_02_mqtt.ino | 2 +- sonoff/{xplg_wemohue.ino => xdrv_20_hue.ino} | 368 +------------------ sonoff/xdrv_21_wemo.ino | 271 ++++++++++++++ tools/decode-status.py | 10 +- 12 files changed, 486 insertions(+), 370 deletions(-) create mode 100644 sonoff/support_udp.ino rename sonoff/{xplg_wemohue.ino => xdrv_20_hue.ino} (64%) create mode 100644 sonoff/xdrv_21_wemo.ino diff --git a/sonoff/_changelog.ino b/sonoff/_changelog.ino index 815fc3374..4e4ef94e6 100644 --- a/sonoff/_changelog.ino +++ b/sonoff/_changelog.ino @@ -3,6 +3,8 @@ * Add initial support for Scripts as replacement for Rules. Default disabled but can be enabled in my_user_config.h (#5689) * Add rule System#Save executed just before a planned restart * Add HX711 weight restore after controlled restart or after power restore just before executing command Sensor34 7 (#5367, #5786) + * Remove define USE_EMULATION from my_user_config.h (#5826) + * Add defines USE_EMULATION_WEMO and USE_EMULATION_HUE to my_user_config.h to control emulation features at compile time (#5826) * * 6.5.0.10 20190513 * Enable ADC0 by default in my_user_config.h (#5671) diff --git a/sonoff/my_user_config.h b/sonoff/my_user_config.h index 164dbca0c..2453d2ce5 100644 --- a/sonoff/my_user_config.h +++ b/sonoff/my_user_config.h @@ -275,7 +275,8 @@ #define USE_WEBSERVER // Enable web server and Wifi Manager (+66k code, +8k mem) #define WEB_PORT 80 // Web server Port for User and Admin mode #define WEB_USERNAME "admin" // Web server Admin mode user name - #define USE_EMULATION // Enable Belkin WeMo and Hue Bridge emulation for Alexa (+18k code, +2k mem) + #define USE_EMULATION_HUE // Enable Hue Bridge emulation for Alexa (+14k code, +2k mem common) + #define USE_EMULATION_WEMO // Enable Belkin WeMo emulation for Alexa (+6k code, +2k mem common) // -- mDNS ---------------------------------------- #define USE_DISCOVERY // Enable mDNS for the following services (+8k code, +0.3k mem) diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index f58fc8947..e04619fdf 100755 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -2652,6 +2652,13 @@ void setup(void) sleep = Settings.sleep; #ifndef USE_EMULATION Settings.flag2.emulation = 0; +#else +#ifndef USE_EMULATION_WEMO + if (EMUL_WEMO == Settings.flag2.emulation) { Settings.flag2.emulation = 0; } +#endif +#ifndef USE_EMULATION_HUE + if (EMUL_HUE == Settings.flag2.emulation) { Settings.flag2.emulation = 0; } +#endif #endif // USE_EMULATION if (Settings.param[P_BOOT_LOOP_OFFSET]) { diff --git a/sonoff/sonoff_post.h b/sonoff/sonoff_post.h index 10a466981..6089adfa5 100644 --- a/sonoff/sonoff_post.h +++ b/sonoff/sonoff_post.h @@ -46,6 +46,13 @@ void KNX_CB_Action(message_t const &msg, void *arg); * Default global defines \*********************************************************************************************/ +#ifdef USE_EMULATION_HUE +#define USE_EMULATION +#endif +#ifdef USE_EMULATION_WEMO +#define USE_EMULATION +#endif + #ifndef MODULE #define MODULE SONOFF_BASIC // [Module] Select default model #endif diff --git a/sonoff/support_features.ino b/sonoff/support_features.ino index fef9766ea..9ae3df3b2 100644 --- a/sonoff/support_features.ino +++ b/sonoff/support_features.ino @@ -49,8 +49,8 @@ void GetFeatures(void) #ifdef WEBSERVER_ADVERTISE feature_drv1 |= 0x00000100; // xdrv_02_webserver.ino #endif -#ifdef USE_EMULATION - feature_drv1 |= 0x00000200; // xplg_wemohue.ino +#ifdef USE_EMULATION_HUE + feature_drv1 |= 0x00000200; // xdrv_20_hue.ino #endif #if (MQTT_LIBRARY_TYPE == MQTT_PUBSUBCLIENT) feature_drv1 |= 0x00000400; // xdrv_01_mqtt.ino @@ -183,8 +183,10 @@ void GetFeatures(void) #ifdef USE_SCRIPT feature_drv2 |= 0x00080000; // xdrv_10_scripter.ino #endif +#ifdef USE_EMULATION_WEMO + feature_drv2 |= 0x00100000; // xdrv_21_wemo.ino +#endif -// feature_drv2 |= 0x00100000; // feature_drv2 |= 0x00200000; // feature_drv2 |= 0x00400000; diff --git a/sonoff/support_udp.ino b/sonoff/support_udp.ino new file mode 100644 index 000000000..eccc4681d --- /dev/null +++ b/sonoff/support_udp.ino @@ -0,0 +1,136 @@ +/* + support_udp.ino - Udp support for Sonoff-Tasmota + + Copyright (C) 2019 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 + +#define UDP_BUFFER_SIZE 200 // Max UDP buffer size needed for M-SEARCH message +#define UDP_MSEARCH_SEND_DELAY 1500 // Delay in ms before M-Search response is send + +#include +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 + +/*********************************************************************************************\ + * UPNP/SSDP search targets +\*********************************************************************************************/ + +const char URN_BELKIN_DEVICE[] 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) { + PortUdp.flush(); + WiFiUDP::stopAll(); + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_MULTICAST_DISABLED)); + udp_connected = false; + } + return udp_connected; +} + +bool UdpConnect(void) +{ + if (!udp_connected) { + // Simple Service Discovery Protocol (SSDP) + 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 { + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_JOIN_FAILED)); + udp_connected = false; + } + } + return udp_connected; +} + +void PollUdp(void) +{ + if (udp_connected) { + if (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); + packet_buffer[len] = 0; + + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet (%d)"), len); +// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), packet_buffer); + + // Simple Service Discovery Protocol (SSDP) + if (devices_present && !udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) { + udp_response_mutex = true; + + udp_remote_ip = PortUdp.remoteIP(); + udp_remote_port = PortUdp.remotePort(); + +// 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 + + LowerCase(packet_buffer, packet_buffer); + RemoveSpace(packet_buffer); + +#ifdef USE_EMULATION_WEMO + if (EMUL_WEMO == Settings.flag2.emulation) { + if (strstr_P(packet_buffer, URN_BELKIN_DEVICE) != nullptr) { // type1 echo dot 2g, echo 1g's + TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 1); + 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)) { + TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 2); + return; + } + } +#endif // USE_EMULATION_WEMO + +#ifdef USE_EMULATION_HUE + 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)) { + TickerMSearch.attach_ms(response_delay, HueRespondToMSearch); + return; + } + } +#endif // USE_EMULATION_HUE + + udp_response_mutex = false; + } + + } + delay(1); + } +} + +#endif // USE_EMULATION diff --git a/sonoff/support_wifi.ino b/sonoff/support_wifi.ino index 44efdac0f..8c406fad5 100644 --- a/sonoff/support_wifi.ino +++ b/sonoff/support_wifi.ino @@ -131,7 +131,7 @@ void WifiConfig(uint8_t type) { if (!wifi_config_type) { if ((WIFI_RETRY == type) || (WIFI_WAIT == type)) { return; } -#if defined(USE_WEBSERVER) && defined(USE_EMULATION) +#ifdef USE_EMULATION UdpDisconnect(); #endif // USE_EMULATION WiFi.disconnect(); // Solve possible Wifi hangs @@ -159,7 +159,7 @@ void WifiConfig(uint8_t type) #ifdef USE_SMARTCONFIG else if (WIFI_SMARTCONFIG == wifi_config_type) { AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_1_SMARTCONFIG " " D_ACTIVE_FOR_3_MINUTES)); - WiFi.mode(WIFI_STA); // Disable AP mode + WiFi.mode(WIFI_STA); // Disable AP mode WiFi.beginSmartConfig(); } #endif // USE_SMARTCONFIG @@ -211,7 +211,7 @@ void WifiBegin(uint8_t flag, uint8_t channel) { const char kWifiPhyMode[] = " BGN"; -#if defined(USE_WEBSERVER) && defined(USE_EMULATION) +#ifdef USE_EMULATION UdpDisconnect(); #endif // USE_EMULATION @@ -565,7 +565,7 @@ void WifiCheck(uint8_t param) } else { WifiSetState(0); -#if defined(USE_WEBSERVER) && defined(USE_EMULATION) +#ifdef USE_EMULATION UdpDisconnect(); #endif // USE_EMULATION mdns_begun = 0; diff --git a/sonoff/xdrv_01_webserver.ino b/sonoff/xdrv_01_webserver.ino index dfbce53ba..fc5ffa467 100644 --- a/sonoff/xdrv_01_webserver.ino +++ b/sonoff/xdrv_01_webserver.ino @@ -493,9 +493,6 @@ void StartWebserver(int type, IPAddress ipweb) WebServer->on("/rs", HandleRestoreConfiguration); WebServer->on("/rt", HandleResetConfiguration); WebServer->on("/in", HandleInformation); -#ifdef USE_EMULATION - HueWemoAddHandlers(); -#endif // USE_EMULATION XdrvCall(FUNC_WEB_ADD_HANDLER); XsnsCall(FUNC_WEB_ADD_HANDLER); #endif // Not FIRMWARE_MINIMAL @@ -1529,11 +1526,19 @@ void HandleOtherConfiguration(void) #ifdef USE_EMULATION WSContentSend_P(PSTR("

 " D_EMULATION " 

")); // Keep close to Friendlynames so do not use
for (uint8_t i = 0; i < EMUL_MAX; i++) { - WSContentSend_P(PSTR("%s %s
"), // Different id only used for labels - i, i, - (i == Settings.flag2.emulation) ? " checked" : "", - GetTextIndexed(stemp, sizeof(stemp), i, kEmulationOptions), - (i == EMUL_NONE) ? "" : (i == EMUL_WEMO) ? D_SINGLE_DEVICE : D_MULTI_DEVICE); +#ifndef USE_EMULATION_WEMO + if (i == EMUL_WEMO) { i++; } +#endif +#ifndef USE_EMULATION_HUE + if (i == EMUL_HUE) { i++; } +#endif + if (i < EMUL_MAX) { + WSContentSend_P(PSTR("%s %s
"), // Different id only used for labels + i, i, + (i == Settings.flag2.emulation) ? " checked" : "", + GetTextIndexed(stemp, sizeof(stemp), i, kEmulationOptions), + (i == EMUL_NONE) ? "" : (i == EMUL_WEMO) ? D_SINGLE_DEVICE : D_MULTI_DEVICE); + } } WSContentSend_P(PSTR("

")); #endif // USE_EMULATION @@ -2186,11 +2191,13 @@ void HandleNotFound(void) if (CaptivePortal()) { return; } // If captive portal redirect instead of displaying the error page. #ifdef USE_EMULATION +#ifdef USE_EMULATION_HUE String path = WebServer->uri(); if ((EMUL_HUE == Settings.flag2.emulation) && (path.startsWith("/api"))) { HandleHueApi(&path); } else -#endif // USE_EMULATION +#endif // USE_EMULATION_HUE +#endif // USE_EMULATION { WSContentBegin(404, CT_PLAIN); WSContentSend_P(PSTR(D_FILE_NOT_FOUND "\n\nURI: %s\nMethod: %s\nArguments: %d\n"), WebServer->uri().c_str(), (WebServer->method() == HTTP_GET) ? "GET" : "POST", WebServer->args()); @@ -2427,7 +2434,16 @@ bool WebCommand(void) } #ifdef USE_EMULATION else if (CMND_EMULATION == command_code) { +#if defined(USE_EMULATION_WEMO) && defined(USE_EMULATION_HUE) if ((XdrvMailbox.payload >= EMUL_NONE) && (XdrvMailbox.payload < EMUL_MAX)) { +#else +#ifndef USE_EMULATION_WEMO + if ((EMUL_NONE == XdrvMailbox.payload) || (EMUL_HUE == XdrvMailbox.payload)) { +#endif +#ifndef USE_EMULATION_HUE + if ((EMUL_NONE == XdrvMailbox.payload) || (EMUL_WEMO == XdrvMailbox.payload)) { +#endif +#endif Settings.flag2.emulation = XdrvMailbox.payload; restart_flag = 2; } diff --git a/sonoff/xdrv_02_mqtt.ino b/sonoff/xdrv_02_mqtt.ino index cc510b20c..4d60e157b 100644 --- a/sonoff/xdrv_02_mqtt.ino +++ b/sonoff/xdrv_02_mqtt.ino @@ -423,7 +423,7 @@ void MqttReconnect(void) return; } -#if defined(USE_WEBSERVER) && defined(USE_EMULATION) +#ifdef USE_EMULATION UdpDisconnect(); #endif // USE_EMULATION diff --git a/sonoff/xplg_wemohue.ino b/sonoff/xdrv_20_hue.ino similarity index 64% rename from sonoff/xplg_wemohue.ino rename to sonoff/xdrv_20_hue.ino index b4ca72124..d54becd66 100644 --- a/sonoff/xplg_wemohue.ino +++ b/sonoff/xdrv_20_hue.ino @@ -1,5 +1,5 @@ /* - xplg_wemohue.ino - wemo and hue support for Sonoff-Tasmota + xdrv_20_hue.ino - Philips Hue support for Sonoff-Tasmota Copyright (C) 2019 Heiko Krupp and Theo Arends @@ -17,93 +17,10 @@ along with this program. If not, see . */ -#if defined(USE_WEBSERVER) && defined(USE_EMULATION) -/*********************************************************************************************\ - * Belkin WeMo and Philips Hue bridge emulation -\*********************************************************************************************/ - -#define UDP_BUFFER_SIZE 200 // Max UDP buffer size needed for M-SEARCH message -#define UDP_MSEARCH_SEND_DELAY 1500 // Delay in ms before M-Search response is send - -#include -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 - -/*********************************************************************************************\ - * UPNP search targets -\*********************************************************************************************/ - -const char URN_BELKIN_DEVICE[] 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"; - -/*********************************************************************************************\ - * WeMo UPNP support routines -\*********************************************************************************************/ - -const char WEMO_MSEARCH[] PROGMEM = - "HTTP/1.1 200 OK\r\n" - "CACHE-CONTROL: max-age=86400\r\n" - "DATE: Fri, 15 Apr 2016 04:56:29 GMT\r\n" - "EXT:\r\n" - "LOCATION: http://%s:80/setup.xml\r\n" - "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" - "01-NLS: b9200ebb-736d-4b93-bf03-835149d13983\r\n" - "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n" - "ST: %s\r\n" // type1 = urn:Belkin:device:**, type2 = upnp:rootdevice - "USN: uuid:%s::%s\r\n" // type1 = urn:Belkin:device:**, type2 = upnp:rootdevice - "X-User-Agent: redsonic\r\n" - "\r\n"; - -String WemoSerialnumber(void) -{ - char serial[16]; - - snprintf_P(serial, sizeof(serial), PSTR("201612K%08X"), ESP.getChipId()); - return String(serial); -} - -String WemoUuid(void) -{ - char uuid[27]; - - snprintf_P(uuid, sizeof(uuid), PSTR("Socket-1_0-%s"), WemoSerialnumber().c_str()); - return String(uuid); -} - -void WemoRespondToMSearch(int echo_type) -{ - char message[TOPSZ]; - - TickerMSearch.detach(); - if (PortUdp.beginPacket(udp_remote_ip, udp_remote_port)) { - char type[24]; - if (1 == echo_type) { // type1 echo 1g & dot 2g - strcpy_P(type, URN_BELKIN_DEVICE); - } else { // type2 echo 2g (echo, plus, show) - strcpy_P(type, UPNP_ROOTDEVICE); - } - char response[400]; - snprintf_P(response, sizeof(response), WEMO_MSEARCH, WiFi.localIP().toString().c_str(), type, WemoUuid().c_str(), type); - PortUdp.write(response); - PortUdp.endPacket(); - snprintf_P(message, sizeof(message), PSTR(D_RESPONSE_SENT)); - } else { - snprintf_P(message, sizeof(message), PSTR(D_FAILED_TO_SEND_RESPONSE)); - } - AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_WEMO " " D_JSON_TYPE " %d, %s " D_TO " %s:%d"), - echo_type, message, udp_remote_ip.toString().c_str(), udp_remote_port); - - udp_response_mutex = false; -} - +#if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) /*********************************************************************************************\ + * Philips Hue bridge emulation + * * Hue Bridge UPNP support routines * Need to send 3 response packets with varying ST and USN * @@ -111,6 +28,8 @@ void WemoRespondToMSearch(int echo_type) * Philips Lighting is 00:17:88:00:00:00 \*********************************************************************************************/ +#define XDRV_20 20 + const char HUE_RESPONSE[] PROGMEM = "HTTP/1.1 200 OK\r\n" "HOST: 239.255.255.250:1900\r\n" @@ -187,256 +106,6 @@ void HueRespondToMSearch(void) udp_response_mutex = false; } -/*********************************************************************************************\ - * Belkin WeMo and Philips Hue bridge UDP multicast support -\*********************************************************************************************/ - -bool UdpDisconnect(void) -{ - if (udp_connected) { - PortUdp.flush(); - WiFiUDP::stopAll(); - AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_MULTICAST_DISABLED)); - udp_connected = false; - } - return udp_connected; -} - -bool UdpConnect(void) -{ - if (!udp_connected) { - // Simple Service Discovery Protocol (SSDP) - 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 { - AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_JOIN_FAILED)); - udp_connected = false; - } - } - return udp_connected; -} - -void PollUdp(void) -{ - if (udp_connected) { - if (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); - packet_buffer[len] = 0; - - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet (%d)"), len); -// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), packet_buffer); - - if (devices_present && !udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) { - udp_response_mutex = true; - - udp_remote_ip = PortUdp.remoteIP(); - udp_remote_port = PortUdp.remotePort(); - -// 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 - - LowerCase(packet_buffer, packet_buffer); - RemoveSpace(packet_buffer); - if (EMUL_WEMO == Settings.flag2.emulation) { - if (strstr_P(packet_buffer, URN_BELKIN_DEVICE) != nullptr) { // type1 echo dot 2g, echo 1g's - TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 1); - 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)) { - TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 2); - return; - } - } else { - 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)) { - TickerMSearch.attach_ms(response_delay, HueRespondToMSearch); - return; - } - } - udp_response_mutex = false; - } - } - delay(1); - } -} - -/*********************************************************************************************\ - * Wemo web server additions -\*********************************************************************************************/ - -const char WEMO_EVENTSERVICE_XML[] PROGMEM = - "" - "" - "" - "SetBinaryState" - "" - "" - "" - "BinaryState" - "BinaryState" - "in" - "" - "" - "" - "" - "GetBinaryState" - "" - "" - "" - "BinaryState" - "BinaryState" - "out" - "" - "" - "" - "" - "" - "" - "BinaryState" - "bool" - "0" - "" - "" - "level" - "string" - "0" - "" - "" - "\r\n\r\n"; - -const char WEMO_METASERVICE_XML[] PROGMEM = - "" - "" - "1" - "0" - "" - "" - "" - "GetMetaInfo" - "" - "" - "GetMetaInfo" - "MetaInfo" - "in" - "" - "" - "" - "" - "" - "MetaInfo" - "string" - "0" - "" - "" - "\r\n\r\n"; - -const char WEMO_RESPONSE_STATE_SOAP[] PROGMEM = - "" - "" - "" - "%d" - "" - "" - "\r\n"; - -const char WEMO_SETUP_XML[] PROGMEM = - "" - "" - "" - "urn:Belkin:device:controllee:1" - "{x1" - "Belkin International Inc." - "Socket" - "3.1415" - "uuid:{x2" - "{x3" - "0" - "" - "" - "urn:Belkin:service:basicevent:1" - "urn:Belkin:serviceId:basicevent1" - "/upnp/control/basicevent1" - "/upnp/event/basicevent1" - "/eventservice.xml" - "" - "" - "urn:Belkin:service:metainfo:1" - "urn:Belkin:serviceId:metainfo1" - "/upnp/control/metainfo1" - "/upnp/event/metainfo1" - "/metainfoservice.xml" - "" - "" - "" - "\r\n"; - -/********************************************************************************************/ - -void HandleUpnpEvent(void) -{ - AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_BASIC_EVENT)); - - char event[500]; - strlcpy(event, WebServer->arg(0).c_str(), sizeof(event)); - -// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), event); - - //differentiate get and set state - char state = 'G'; - if (strstr_P(event, PSTR("SetBinaryState")) != nullptr) { - state = 'S'; - uint8_t power = POWER_TOGGLE; - if (strstr_P(event, PSTR("State>10on("/upnp/control/basicevent1", HTTP_POST, HandleUpnpEvent); - WebServer->on("/eventservice.xml", HandleUpnpService); - WebServer->on("/metainfoservice.xml", HandleUpnpMetaService); - WebServer->on("/setup.xml", HandleUpnpSetupWemo); - } - if (EMUL_HUE == Settings.flag2.emulation) { - WebServer->on("/description.xml", HandleUpnpSetupHue); + bool result = false; + + if (devices_present && (EMUL_HUE == Settings.flag2.emulation)) { + switch (function) { + case FUNC_WEB_ADD_HANDLER: + WebServer->on("/description.xml", HandleUpnpSetupHue); + break; } } + return result; } -#endif // USE_WEBSERVER && USE_EMULATION +#endif // USE_WEBSERVER && USE_EMULATION && USE_EMULATION_HUE diff --git a/sonoff/xdrv_21_wemo.ino b/sonoff/xdrv_21_wemo.ino new file mode 100644 index 000000000..6d5a41715 --- /dev/null +++ b/sonoff/xdrv_21_wemo.ino @@ -0,0 +1,271 @@ +/* + xdrv_21_wemo.ino - wemo support for Sonoff-Tasmota + + Copyright (C) 2019 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 . +*/ + +#if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined (USE_EMULATION_WEMO) +/*********************************************************************************************\ + * Belkin WeMo emulation +\*********************************************************************************************/ + +#define XDRV_21 21 + +const char WEMO_MSEARCH[] PROGMEM = + "HTTP/1.1 200 OK\r\n" + "CACHE-CONTROL: max-age=86400\r\n" + "DATE: Fri, 15 Apr 2016 04:56:29 GMT\r\n" + "EXT:\r\n" + "LOCATION: http://%s:80/setup.xml\r\n" + "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" + "01-NLS: b9200ebb-736d-4b93-bf03-835149d13983\r\n" + "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n" + "ST: %s\r\n" // type1 = urn:Belkin:device:**, type2 = upnp:rootdevice + "USN: uuid:%s::%s\r\n" // type1 = urn:Belkin:device:**, type2 = upnp:rootdevice + "X-User-Agent: redsonic\r\n" + "\r\n"; + +String WemoSerialnumber(void) +{ + char serial[16]; + + snprintf_P(serial, sizeof(serial), PSTR("201612K%08X"), ESP.getChipId()); + return String(serial); +} + +String WemoUuid(void) +{ + char uuid[27]; + + snprintf_P(uuid, sizeof(uuid), PSTR("Socket-1_0-%s"), WemoSerialnumber().c_str()); + return String(uuid); +} + +void WemoRespondToMSearch(int echo_type) +{ + char message[TOPSZ]; + + TickerMSearch.detach(); + if (PortUdp.beginPacket(udp_remote_ip, udp_remote_port)) { + char type[24]; + if (1 == echo_type) { // type1 echo 1g & dot 2g + strcpy_P(type, URN_BELKIN_DEVICE); + } else { // type2 echo 2g (echo, plus, show) + strcpy_P(type, UPNP_ROOTDEVICE); + } + char response[400]; + snprintf_P(response, sizeof(response), WEMO_MSEARCH, WiFi.localIP().toString().c_str(), type, WemoUuid().c_str(), type); + PortUdp.write(response); + PortUdp.endPacket(); + snprintf_P(message, sizeof(message), PSTR(D_RESPONSE_SENT)); + } else { + snprintf_P(message, sizeof(message), PSTR(D_FAILED_TO_SEND_RESPONSE)); + } + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_WEMO " " D_JSON_TYPE " %d, %s " D_TO " %s:%d"), + echo_type, message, udp_remote_ip.toString().c_str(), udp_remote_port); + + udp_response_mutex = false; +} + +/*********************************************************************************************\ + * Wemo web server additions +\*********************************************************************************************/ + +const char WEMO_EVENTSERVICE_XML[] PROGMEM = + "" + "" + "" + "SetBinaryState" + "" + "" + "" + "BinaryState" + "BinaryState" + "in" + "" + "" + "" + "" + "GetBinaryState" + "" + "" + "" + "BinaryState" + "BinaryState" + "out" + "" + "" + "" + "" + "" + "" + "BinaryState" + "bool" + "0" + "" + "" + "level" + "string" + "0" + "" + "" + "\r\n\r\n"; + +const char WEMO_METASERVICE_XML[] PROGMEM = + "" + "" + "1" + "0" + "" + "" + "" + "GetMetaInfo" + "" + "" + "GetMetaInfo" + "MetaInfo" + "in" + "" + "" + "" + "" + "" + "MetaInfo" + "string" + "0" + "" + "" + "\r\n\r\n"; + +const char WEMO_RESPONSE_STATE_SOAP[] PROGMEM = + "" + "" + "" + "%d" + "" + "" + "\r\n"; + +const char WEMO_SETUP_XML[] PROGMEM = + "" + "" + "" + "urn:Belkin:device:controllee:1" + "{x1" + "Belkin International Inc." + "Socket" + "3.1415" + "uuid:{x2" + "{x3" + "0" + "" + "" + "urn:Belkin:service:basicevent:1" + "urn:Belkin:serviceId:basicevent1" + "/upnp/control/basicevent1" + "/upnp/event/basicevent1" + "/eventservice.xml" + "" + "" + "urn:Belkin:service:metainfo:1" + "urn:Belkin:serviceId:metainfo1" + "/upnp/control/metainfo1" + "/upnp/event/metainfo1" + "/metainfoservice.xml" + "" + "" + "" + "\r\n"; + +/********************************************************************************************/ + +void HandleUpnpEvent(void) +{ + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_BASIC_EVENT)); + + char event[500]; + strlcpy(event, WebServer->arg(0).c_str(), sizeof(event)); + +// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), event); + + //differentiate get and set state + char state = 'G'; + if (strstr_P(event, PSTR("SetBinaryState")) != nullptr) { + state = 'S'; + uint8_t power = POWER_TOGGLE; + if (strstr_P(event, PSTR("State>10on("/upnp/control/basicevent1", HTTP_POST, HandleUpnpEvent); + WebServer->on("/eventservice.xml", HandleUpnpService); + WebServer->on("/metainfoservice.xml", HandleUpnpMetaService); + WebServer->on("/setup.xml", HandleUpnpSetupWemo); + break; + } + } + return result; +} + +#endif // USE_WEBSERVER && USE_EMULATION && USE_EMULATION_WEMO diff --git a/tools/decode-status.py b/tools/decode-status.py index 5b8f60923..43ba0c6fb 100755 --- a/tools/decode-status.py +++ b/tools/decode-status.py @@ -106,7 +106,9 @@ a_setoption = [[ "Enable normal sleep instead of dynamic sleep", "Force local operation when button/switch topic is set", "Do not use retain flag on HOLD messages", - "","","", + "Do not scan relay power state at restart", + "Use _ instead of - as sensor index separator", + "", "","","","", "","","","", "","","","", @@ -116,7 +118,7 @@ a_setoption = [[ a_features = [[ "","","USE_I2C","USE_SPI", "USE_DISCOVERY","USE_ARDUINO_OTA","USE_MQTT_TLS","USE_WEBSERVER", - "WEBSERVER_ADVERTISE","USE_EMULATION","MQTT_PUBSUBCLIENT","MQTT_TASMOTAMQTT", + "WEBSERVER_ADVERTISE","USE_EMULATION_HUE","MQTT_PUBSUBCLIENT","MQTT_TASMOTAMQTT", "MQTT_ESPMQTTARDUINO","MQTT_HOST_DISCOVERY","USE_ARILUX_RF","USE_WS2812", "USE_WS2812_DMA","USE_IR_REMOTE","USE_IR_HVAC","USE_IR_RECEIVE", "USE_DOMOTICZ","USE_DISPLAY","USE_HOME_ASSISTANT","USE_SERIAL_BRIDGE", @@ -127,8 +129,8 @@ a_features = [[ "FIRMWARE_KNX_NO_EMULATION","USE_DISPLAY_MODES1TO5","USE_DISPLAY_GRAPH","USE_DISPLAY_LCD", "USE_DISPLAY_SSD1306","USE_DISPLAY_MATRIX","USE_DISPLAY_ILI9341","USE_DISPLAY_EPAPER", "USE_DISPLAY_SH1106","USE_MP3_PLAYER","USE_PCA9685","USE_TUYA_DIMMER", - "USE_RC_SWITCH","USE_ARMTRONIX_DIMMERS","","", - "","","","NO_EXTRA_4K_HEAP", + "USE_RC_SWITCH","USE_ARMTRONIX_DIMMERS","USE_SM16716","USE_SCRIPT", + "USE_EMULATION_WEMO","","","NO_EXTRA_4K_HEAP", "VTABLES_IN_IRAM","VTABLES_IN_DRAM","VTABLES_IN_FLASH","PIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH", "PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY","PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH","DEBUG_THEO","USE_DEBUG_DRIVER" ],[