├── ProgramESP8266.jpg └── sonoff_wemo_homeassistant.ino /ProgramESP8266.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aneisch/Sonoff-Wemo-Home-Assistant/HEAD/ProgramESP8266.jpg -------------------------------------------------------------------------------- /sonoff_wemo_homeassistant.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | //Majority of code is from https://github.com/torinnguyen/ESP8266Wemo 6 | 7 | const char* ssid = "YOUR_SSID"; // your network SSID (name) 8 | const char* pass = "YOUR_PASSWORD"; // your network password 9 | 10 | String friendlyName = "emulated_wemo_1"; // Alexa and/or Home Assistant will use this name to identify your device 11 | const char* serialNumber = "221517K0101768"; // anything will do 12 | const char* uuid = "904bfa3c-1de2-11v2-8728-fd8eebaf492d"; // anything will do 13 | 14 | // Multicast declarations for discovery 15 | IPAddress ipMulti(239, 255, 255, 250); 16 | const unsigned int portMulti = 1900; 17 | 18 | // TCP port to listen on 19 | const unsigned int webserverPort = 49153; 20 | 21 | const int LED_PIN = 13; 22 | const int RELAY_PIN = 12; 23 | const int SWITCH_PIN = 0; 24 | //initial switch (button) state 25 | int switchState = 0; 26 | 27 | 28 | //----------------------------------------------------------------------- 29 | //----------------------------------------------------------------------- 30 | 31 | //int status = WL_IDLE_STATUS; 32 | 33 | WiFiUDP Udp; 34 | byte packetBuffer[512]; //buffer to hold incoming and outgoing packets 35 | 36 | //Start TCP server 37 | ESP8266WebServer server(webserverPort); 38 | 39 | 40 | //----------------------------------------------------------------------- 41 | // UDP Multicast Server 42 | //----------------------------------------------------------------------- 43 | 44 | char* getDateString() 45 | { 46 | //Doesn't matter which date & time, will work 47 | //Optional: replace with NTP Client implementation 48 | return "Wed, 29 Jun 2016 00:13:46 GMT"; 49 | } 50 | 51 | void responseToSearchUdp(IPAddress& senderIP, unsigned int senderPort) 52 | { 53 | Serial.println("responseToSearchUdp"); 54 | 55 | //This is absolutely neccessary as Udp.write cannot handle IPAddress or numbers correctly like Serial.print 56 | IPAddress myIP = WiFi.localIP(); 57 | char ipChar[20]; 58 | snprintf(ipChar, 20, "%d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]); 59 | char portChar[7]; 60 | snprintf(portChar, 7, ":%d", webserverPort); 61 | 62 | Udp.beginPacket(senderIP, senderPort); 63 | Udp.write("HTTP/1.1 200 OK\r\n"); 64 | Udp.write("CACHE-CONTROL: max-age=86400\r\n"); 65 | Udp.write("DATE: "); 66 | Udp.write(getDateString()); 67 | Udp.write("\r\n"); 68 | Udp.write("EXT:\r\n"); 69 | Udp.write("LOCATION: "); 70 | Udp.write("http://"); 71 | Udp.write(ipChar); 72 | Udp.write(portChar); 73 | Udp.write("/setup.xml\r\n"); 74 | Udp.write("OPT: \"http://schemas.upnp.org/upnp/1/0/\"); ns=01\r\n"); 75 | Udp.write("01-NLS: "); 76 | Udp.write(uuid); 77 | Udp.write("\r\n"); 78 | Udp.write("SERVER: Unspecified, UPnP/1.0, Unspecified\r\n"); 79 | Udp.write("X-User-Agent: redsonic\r\n"); 80 | Udp.write("ST: upnp:rootdevice\r\n"); 81 | Udp.write("USN: uuid:Socket-1_0-"); 82 | Udp.write(serialNumber); 83 | Udp.write("upnp:rootdevice\r\n"); 84 | Udp.write("\r\n"); 85 | Udp.endPacket(); 86 | } 87 | 88 | void UdpMulticastServerLoop() 89 | { 90 | int numBytes = Udp.parsePacket(); 91 | if (numBytes <= 0) 92 | return; 93 | 94 | IPAddress senderIP = Udp.remoteIP(); 95 | unsigned int senderPort = Udp.remotePort(); 96 | 97 | // read the packet into the buffer 98 | Udp.read(packetBuffer, numBytes); 99 | 100 | // print out the received packet 101 | //Serial.write(packetBuffer, numBytes); 102 | 103 | // check if this is a M-SEARCH for WeMo device 104 | String request = String((char *)packetBuffer); 105 | int mSearchIndex = request.indexOf("M-SEARCH"); 106 | if (mSearchIndex >= 0) 107 | //return; 108 | responseToSearchUdp(senderIP, senderPort); 109 | } 110 | 111 | 112 | //----------------------------------------------------------------------- 113 | // HTTP Server 114 | //----------------------------------------------------------------------- 115 | 116 | void handleRoot() 117 | { 118 | Serial.println("handleRoot"); 119 | 120 | server.send(200, "text/plain", "Tell Alexa to discover devices"); 121 | } 122 | 123 | void handleEventXml() 124 | { 125 | Serial.println("HandleEventXML"); 126 | 127 | String eventservice_xml = "" 128 | "" 129 | "" 130 | "SetBinaryState" 131 | "" 132 | "" 133 | "" 134 | "BinaryState" 135 | "BinaryState" 136 | "in" 137 | "" 138 | "" 139 | "" 140 | "" 141 | "GetBinaryState" 142 | "" 143 | "" 144 | "" 145 | "BinaryState" 146 | "BinaryState" 147 | "out" 148 | "" 149 | "" 150 | "" 151 | "" 152 | "" 153 | "" 154 | "BinaryState" 155 | "Boolean" 156 | "0" 157 | "" 158 | "" 159 | "level" 160 | "string" 161 | "0" 162 | "" 163 | "" 164 | "\r\n" 165 | "\r\n"; 166 | 167 | String header = "HTTP/1.1 200 OK\r\n"; 168 | header += "Content-Type: text/xml\r\n"; 169 | header += "Content-Length: "; 170 | header += eventservice_xml.length(); 171 | header += "\r\n"; 172 | header += "Date: "; 173 | header += getDateString(); 174 | header += "\r\n"; 175 | header += "LAST-MODIFIED: Sat, 01 Jan 2000 00:00:00 GMT\r\n"; 176 | header += "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n"; 177 | header += "X-User-Agent: redsonic\r\n"; 178 | header += "connection: close\r\n"; 179 | header += "\r\n"; 180 | header += eventservice_xml; 181 | 182 | Serial.println(header); 183 | 184 | server.sendContent(header); 185 | } 186 | 187 | void handleSetupXml() 188 | { 189 | Serial.println("handleSetupXml"); 190 | 191 | String body = 192 | "\r\n" 193 | "\r\n" 194 | "\r\n" 195 | "1\r\n" 196 | "0\r\n" 197 | "\r\n" 198 | "\r\n" 199 | "urn:Belkin:device:controllee:1\r\n" 200 | "" + friendlyName + "\r\n" 201 | "Belkin International Inc.\r\n" 202 | "http://www.belkin.com\r\n" 203 | "Belkin Plugin Socket 1.0\r\n" 204 | "Socket\r\n" 205 | "1.0\r\n" 206 | "uuid:Socket-1_0-" + uuid + "\r\n" 207 | "http://www.belkin.com/plugin/\r\n" 208 | "" + serialNumber + "\r\n" 209 | "\r\n" 210 | "\r\n" 211 | "urn:Belkin:service:basicevent:1\r\n" 212 | "urn:Belkin:serviceId:basicevent1\r\n" 213 | "/upnp/control/basicevent1\r\n" 214 | "/upnp/event/basicevent1\r\n" 215 | "/eventservice.xml\r\n" 216 | "\r\n" 217 | "\r\n" 218 | "\r\n" 219 | "\r\n" 220 | "\r\n"; 221 | 222 | String header = "HTTP/1.1 200 OK\r\n"; 223 | header += "Content-Type: text/xml\r\n"; 224 | header += "Content-Length: "; 225 | header += body.length(); 226 | header += "\r\n"; 227 | header += "Date: "; 228 | header += getDateString(); 229 | header += "\r\n"; 230 | header += "LAST-MODIFIED: Sat, 01 Jan 2000 00:00:00 GMT\r\n"; 231 | header += "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n"; 232 | header += "X-User-Agent: redsonic\r\n"; 233 | header += "connection: close\r\n"; 234 | header += "\r\n"; 235 | header += body; 236 | 237 | Serial.println(header); 238 | 239 | server.sendContent(header); 240 | } 241 | 242 | void handleUpnpControl() 243 | { 244 | Serial.println("handleUpnpControl"); 245 | 246 | //Extract raw body 247 | //Because there is a '=' before "1.0", it will give the following: 248 | //"1.0" encoding="utf-8"?>1 249 | String body = server.arg(0); 250 | 251 | //Check valid request 252 | boolean isOn = body.indexOf("1") >= 0; 253 | boolean isOff = body.indexOf("0") >= 0; 254 | boolean isQuestion = body.indexOf("GetBinaryState") >= 0; 255 | Serial.println("body:"); 256 | Serial.println(body); 257 | boolean isValid = isOn || isOff || isQuestion; 258 | if (!isValid) { 259 | Serial.println("Bad request from Amazon Echo"); 260 | //Serial.println(body); 261 | server.send(400, "text/plain", "Bad request from Amazon Echo"); 262 | return; 263 | } 264 | 265 | //On/Off Logic 266 | if (isOn) { 267 | digitalWrite(LED_PIN, 0); 268 | digitalWrite(RELAY_PIN, 1); 269 | Serial.println("Alexa is asking to turn ON a device"); 270 | String body = 271 | "\r\n" 272 | "\r\n" 273 | "1\r\n" 274 | "\r\n" 275 | " "; 276 | String header = "HTTP/1.1 200 OK\r\n"; 277 | header += "Content-Length: "; 278 | header += body.length(); 279 | header += "\r\n"; 280 | header += "Content-Type: text/xml\r\n"; 281 | header += "Date: "; 282 | header += getDateString(); 283 | header += "\r\n"; 284 | header += "EXT:\r\n"; 285 | header += "SERVER: Linux/2.6.21, UPnP/1.0, Portable SDK for UPnP devices/1.6.18\r\n"; 286 | header += "X-User-Agent: redsonic\r\n"; 287 | header += "\r\n"; 288 | header += body; 289 | server.sendContent(header); 290 | } 291 | else if (isOff) { 292 | digitalWrite(LED_PIN, 1); 293 | digitalWrite(RELAY_PIN, 0); 294 | Serial.println("Alexa is asking to turn OFF a device"); 295 | String body = 296 | "\r\n" 297 | "\r\n" 298 | "0\r\n" 299 | "\r\n" 300 | " "; 301 | String header = "HTTP/1.1 200 OK\r\n"; 302 | header += "Content-Length: "; 303 | header += body.length(); 304 | header += "\r\n"; 305 | header += "Content-Type: text/xml\r\n"; 306 | header += "Date: "; 307 | header += getDateString(); 308 | header += "\r\n"; 309 | header += "EXT:\r\n"; 310 | header += "SERVER: Linux/2.6.21, UPnP/1.0, Portable SDK for UPnP devices/1.6.18\r\n"; 311 | header += "X-User-Agent: redsonic\r\n"; 312 | header += "\r\n"; 313 | header += body; 314 | server.sendContent(header); 315 | } 316 | 317 | else if (isQuestion) { 318 | String body = 319 | "\r\n" 320 | "\r\n" 321 | "" + String(digitalRead(12)) + "\r\n" 322 | "\r\n" 323 | " "; 324 | String header = "HTTP/1.1 200 OK\r\n"; 325 | header += "Content-Length: "; 326 | header += body.length(); 327 | header += "\r\n"; 328 | header += "Content-Type: text/xml\r\n"; 329 | header += "Date: "; 330 | header += getDateString(); 331 | header += "\r\n"; 332 | header += "EXT:\r\n"; 333 | header += "SERVER: Linux/2.6.21, UPnP/1.0, Portable SDK for UPnP devices/1.6.18\r\n"; 334 | header += "X-User-Agent: redsonic\r\n"; 335 | header += "\r\n"; 336 | header += body; 337 | server.sendContent(header); 338 | } 339 | } 340 | 341 | void handleNotFound() 342 | { 343 | Serial.println("handleNotFound()"); 344 | 345 | String message = "File Not Found\n\n"; 346 | message += "URI: "; 347 | message += server.uri(); 348 | message += "\nMethod: "; 349 | message += (server.method() == HTTP_GET) ? "GET" : "POST"; 350 | message += "\nArguments: "; 351 | message += server.args(); 352 | message += "\n"; 353 | for (uint8_t i=0; i 50) 391 | break; 392 | } 393 | 394 | // print your WiFi info 395 | IPAddress ip = WiFi.localIP(); 396 | Serial.println(); 397 | Serial.print("Connected to "); 398 | Serial.print(WiFi.SSID()); 399 | Serial.print(" with IP: "); 400 | Serial.println(ip); 401 | 402 | //UDP Server 403 | Udp.beginMulticast(WiFi.localIP(), ipMulti, portMulti); 404 | Serial.print("Udp multicast server started at "); 405 | Serial.print(ipMulti); 406 | Serial.print(":"); 407 | Serial.println(portMulti); 408 | 409 | //Web Server 410 | server.on("/", handleRoot); 411 | server.on("/setup.xml", handleSetupXml); 412 | server.on("/eventservice.xml", handleEventXml); 413 | server.on("/upnp/control/basicevent1", handleUpnpControl); 414 | server.onNotFound(handleNotFound); 415 | server.begin(); 416 | Serial.print("HTTP server started on port "); 417 | Serial.println(webserverPort); 418 | } 419 | 420 | void loop(){ 421 | 422 | 423 | if (digitalRead(SWITCH_PIN)){ 424 | delay(250); 425 | if (!digitalRead(SWITCH_PIN)){ 426 | switchState = !switchState; 427 | Serial.print("Switch Pressed - Relay now "); 428 | 429 | // Show and change Relay State 430 | if (digitalRead(13)){ 431 | Serial.println("ON"); 432 | digitalWrite(LED_PIN, 0); 433 | digitalWrite(RELAY_PIN, 1); } 434 | else 435 | { 436 | Serial.println("OFF"); 437 | digitalWrite(LED_PIN, 1); 438 | digitalWrite(RELAY_PIN, 0); 439 | } 440 | delay(500); 441 | } 442 | } 443 | 444 | 445 | UdpMulticastServerLoop(); //UDP multicast receiver 446 | 447 | server.handleClient(); //Webserver 448 | } 449 | 450 | --------------------------------------------------------------------------------