├── README.md └── wemos ├── CallbackFunction.h ├── Switch.cpp ├── Switch.h ├── UpnpBroadcastResponder.cpp ├── UpnpBroadcastResponder.h └── wemos.ino /README.md: -------------------------------------------------------------------------------- 1 | #Arduino Esp8266 Alexa Wemo switch emulator 2 | 3 | youtube tutorial https://www.youtube.com/watch?v=OC6QSXzkQU8 4 | 5 | This project is completly based on the [repo](https://github.com/kakopappa/arduino-esp8266-alexa-multiple-wemo-switch), all the hard work was done by kakopappa. 6 | 7 | This library enables your esp8266 to simulate a Belkin Wemo switch. It can be discovered as a device by your Amazon Echo/Dot on the Smart home section. It supports calling the emulated device a custom name e.g. "Alexa, turn off test lights", where test lights is the custom name 8 | 9 | The library supports emulating up to 14 switches using 1 ESP8266 chip. 10 | 11 | 12 | -------------------------------------------------------------------------------- /wemos/CallbackFunction.h: -------------------------------------------------------------------------------- 1 | #ifndef CALLBACKFUNCTION_H 2 | #define CALLBACKFUNCTION_H 3 | 4 | #include 5 | 6 | typedef bool (*CallbackFunction) (); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /wemos/Switch.cpp: -------------------------------------------------------------------------------- 1 | #include "Switch.h" 2 | #include "CallbackFunction.h" 3 | 4 | 5 | 6 | //<> 7 | Switch::Switch(){ 8 | Serial.println("default constructor called"); 9 | } 10 | //Switch::Switch(String alexaInvokeName,unsigned int port){ 11 | Switch::Switch(String alexaInvokeName, unsigned int port, CallbackFunction oncb, CallbackFunction offcb){ 12 | uint32_t chipId = ESP.getChipId(); 13 | char uuid[64]; 14 | sprintf_P(uuid, PSTR("38323636-4558-4dda-9188-cda0e6%02x%02x%02x"), 15 | (uint16_t) ((chipId >> 16) & 0xff), 16 | (uint16_t) ((chipId >> 8) & 0xff), 17 | (uint16_t) chipId & 0xff); 18 | 19 | serial = String(uuid); 20 | persistent_uuid = "Socket-1_0-" + serial+"-"+ String(port); 21 | 22 | device_name = alexaInvokeName; 23 | localPort = port; 24 | onCallback = oncb; 25 | offCallback = offcb; 26 | 27 | startWebServer(); 28 | } 29 | 30 | 31 | 32 | //<> 33 | Switch::~Switch(){/*nothing to destruct*/} 34 | 35 | void Switch::serverLoop(){ 36 | if (server != NULL) { 37 | server->handleClient(); 38 | delay(1); 39 | } 40 | } 41 | 42 | void Switch::startWebServer(){ 43 | server = new ESP8266WebServer(localPort); 44 | 45 | server->on("/", [&]() { 46 | handleRoot(); 47 | }); 48 | 49 | 50 | server->on("/setup.xml", [&]() { 51 | handleSetupXml(); 52 | }); 53 | 54 | server->on("/index.html", [&]() { 55 | String response = "

It works!

"; 56 | server->send(200, "text/html", response.c_str()); 57 | }); 58 | server->on("/upnp/control/basicevent1", [&]() { 59 | handleUpnpControl(); 60 | }); 61 | 62 | server->on("/eventservice.xml", [&]() { 63 | handleEventservice(); 64 | }); 65 | 66 | //server->onNotFound(handleNotFound); 67 | server->begin(); 68 | 69 | Serial.println("WebServer started on port: "); 70 | Serial.println(localPort); 71 | } 72 | 73 | void Switch::handleEventservice(){ 74 | Serial.println(" ########## Responding to eventservice.xml ... ########\n"); 75 | 76 | String eventservice_xml = "" 77 | "" 78 | "" 79 | "SetBinaryState" 80 | "" 81 | "" 82 | "" 83 | "BinaryState" 84 | "BinaryState" 85 | "in" 86 | "" 87 | "" 88 | "" 89 | "" 90 | "BinaryState" 91 | "Boolean" 92 | "0" 93 | "" 94 | "" 95 | "level" 96 | "string" 97 | "0" 98 | "" 99 | "" 100 | "" 101 | "\r\n" 102 | "\r\n"; 103 | 104 | server->send(200, "text/plain", eventservice_xml.c_str()); 105 | } 106 | 107 | void Switch::handleUpnpControl(){ 108 | Serial.println("########## Responding to /upnp/control/basicevent1 ... ##########"); 109 | 110 | for (int x=0; x <= server->args(); x++) { 111 | Serial.println(server->arg(x)); 112 | } 113 | 114 | String request = server->arg(0); 115 | Serial.print("request:"); 116 | Serial.println(request); 117 | 118 | if(request.indexOf("SetBinaryState") >= 0) { 119 | if(request.indexOf("1") >= 0) { 120 | Serial.println("Got Turn on request"); 121 | switchStatus = onCallback(); 122 | sendRelayState(); 123 | } 124 | 125 | if(request.indexOf("0") >= 0) { 126 | Serial.println("Got Turn off request"); 127 | switchStatus = offCallback(); 128 | sendRelayState(); 129 | } 130 | } 131 | 132 | if(request.indexOf("GetBinaryState") >= 0) { 133 | Serial.println("Got binary state request"); 134 | sendRelayState(); 135 | } 136 | 137 | server->send(200, "text/plain", ""); 138 | } 139 | 140 | void Switch::handleRoot(){ 141 | server->send(200, "text/plain", "You should tell Alexa to discover devices"); 142 | } 143 | 144 | void Switch::handleSetupXml(){ 145 | Serial.println(" ########## Responding to setup.xml ... ########\n"); 146 | 147 | IPAddress localIP = WiFi.localIP(); 148 | char s[16]; 149 | sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]); 150 | Serial.println("localiP:"); 151 | Serial.println(WiFi.localIP()); 152 | String setup_xml = "" 153 | "" 154 | "" 155 | "urn:Belkin:device:controllee:1" 156 | ""+ device_name +"" 157 | "Belkin International Inc." 158 | "Emulated Socket" 159 | "3.1415" 160 | "uuid:"+ persistent_uuid +"" 161 | "221517K0101769" 162 | "0" 163 | "" 164 | "" 165 | "urn:Belkin:service:basicevent:1" 166 | "urn:Belkin:serviceId:basicevent1" 167 | "/upnp/control/basicevent1" 168 | "/upnp/event/basicevent1" 169 | "/eventservice.xml" 170 | "" 171 | "" 172 | "" 173 | "\r\n" 174 | "\r\n"; 175 | 176 | server->send(200, "text/xml", setup_xml.c_str()); 177 | 178 | Serial.print("Sending :"); 179 | Serial.println(setup_xml); 180 | } 181 | 182 | String Switch::getAlexaInvokeName() { 183 | return device_name; 184 | } 185 | void Switch::sendRelayState() { 186 | String body = 187 | "\r\n" 188 | "\r\n" 189 | ""; 190 | 191 | body += (switchStatus ? "1" : "0"); 192 | 193 | body += "\r\n" 194 | "\r\n" 195 | " \r\n"; 196 | 197 | server->send(200, "text/xml", body.c_str()); 198 | 199 | Serial.print("Sending :"); 200 | Serial.println(body); 201 | } 202 | void Switch::respondToSearch(IPAddress& senderIP, unsigned int senderPort) { 203 | Serial.println(""); 204 | Serial.print("Sending response to "); 205 | Serial.println(senderIP); 206 | Serial.print("Port : "); 207 | Serial.println(senderPort); 208 | 209 | IPAddress localIP = WiFi.localIP(); 210 | char s[16]; 211 | sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]); 212 | 213 | String response = 214 | "HTTP/1.1 200 OK\r\n" 215 | "CACHE-CONTROL: max-age=86400\r\n" 216 | "DATE: Sat, 26 Nov 2016 04:56:29 GMT\r\n" 217 | "EXT:\r\n" 218 | "LOCATION: http://" + String(s) + ":" + String(localPort) + "/setup.xml\r\n" 219 | "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" 220 | "01-NLS: b9200ebb-736d-4b93-bf03-835149d13983\r\n" 221 | "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n" 222 | "ST: urn:Belkin:device:**\r\n" 223 | "USN: uuid:" + persistent_uuid + "::urn:Belkin:device:**\r\n" 224 | "X-User-Agent: redsonic\r\n\r\n"; 225 | 226 | UDP.beginPacket(senderIP, senderPort); 227 | UDP.write(response.c_str()); 228 | UDP.endPacket(); 229 | 230 | Serial.println("Response sent !"); 231 | } 232 | -------------------------------------------------------------------------------- /wemos/Switch.h: -------------------------------------------------------------------------------- 1 | #ifndef SWITCH_H 2 | #define SWITCH_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "CallbackFunction.h" 9 | 10 | 11 | class Switch { 12 | private: 13 | ESP8266WebServer *server = NULL; 14 | WiFiUDP UDP; 15 | String serial; 16 | String persistent_uuid; 17 | String device_name; 18 | unsigned int localPort; 19 | CallbackFunction onCallback; 20 | CallbackFunction offCallback; 21 | bool switchStatus; 22 | 23 | void startWebServer(); 24 | void handleEventservice(); 25 | void handleUpnpControl(); 26 | void handleRoot(); 27 | void handleSetupXml(); 28 | public: 29 | Switch(); 30 | Switch(String alexaInvokeName, unsigned int port, CallbackFunction onCallback, CallbackFunction offCallback); 31 | ~Switch(); 32 | String getAlexaInvokeName(); 33 | void serverLoop(); 34 | void respondToSearch(IPAddress& senderIP, unsigned int senderPort); 35 | void sendRelayState(); 36 | }; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /wemos/UpnpBroadcastResponder.cpp: -------------------------------------------------------------------------------- 1 | #include "UpnpBroadcastResponder.h" 2 | #include "Switch.h" 3 | #include 4 | 5 | // Multicast declarations 6 | IPAddress ipMulti(239, 255, 255, 250); 7 | const unsigned int portMulti = 1900; 8 | char packetBuffer[512]; 9 | 10 | #define MAX_SWITCHES 14 11 | Switch switches[MAX_SWITCHES] = {}; 12 | int numOfSwitchs = 0; 13 | 14 | //#define numOfSwitchs (sizeof(switches)/sizeof(Switch)) //array size 15 | 16 | //<> 17 | UpnpBroadcastResponder::UpnpBroadcastResponder(){ 18 | 19 | } 20 | 21 | //<> 22 | UpnpBroadcastResponder::~UpnpBroadcastResponder(){/*nothing to destruct*/} 23 | 24 | bool UpnpBroadcastResponder::beginUdpMulticast(){ 25 | boolean state = false; 26 | 27 | Serial.println("Begin multicast .."); 28 | 29 | if(UDP.beginMulticast(WiFi.localIP(), ipMulti, portMulti)) { 30 | Serial.print("Udp multicast server started at "); 31 | Serial.print(ipMulti); 32 | Serial.print(":"); 33 | Serial.println(portMulti); 34 | 35 | state = true; 36 | } 37 | else{ 38 | Serial.println("Connection failed"); 39 | } 40 | 41 | return state; 42 | } 43 | 44 | //Switch *ptrArray; 45 | 46 | void UpnpBroadcastResponder::addDevice(Switch& device) { 47 | Serial.print("Adding switch : "); 48 | Serial.print(device.getAlexaInvokeName()); 49 | Serial.print(" index : "); 50 | Serial.println(numOfSwitchs); 51 | 52 | switches[numOfSwitchs] = device; 53 | numOfSwitchs++; 54 | } 55 | 56 | void UpnpBroadcastResponder::serverLoop(){ 57 | int packetSize = UDP.parsePacket(); 58 | if (packetSize <= 0) 59 | return; 60 | 61 | IPAddress senderIP = UDP.remoteIP(); 62 | unsigned int senderPort = UDP.remotePort(); 63 | 64 | // read the packet into the buffer 65 | UDP.read(packetBuffer, packetSize); 66 | 67 | // check if this is a M-SEARCH for WeMo device 68 | String request = String((char *)packetBuffer); 69 | 70 | if(request.indexOf('M-SEARCH') > 0) { 71 | //if(request.indexOf("urn:Belkin:device:**") > 0) { 72 | if((request.indexOf("urn:Belkin:device:**") > 0) || (request.indexOf("ssdp:all") > 0) || (request.indexOf("upnp:rootdevice") > 0)) { 73 | Serial.println("Got UDP Belkin Request.."); 74 | 75 | // int arrSize = sizeof(switchs) / sizeof(Switch); 76 | 77 | for(int n = 0; n < numOfSwitchs; n++) { 78 | Switch &sw = switches[n]; 79 | 80 | if (&sw != NULL) { 81 | sw.respondToSearch(senderIP, senderPort); 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /wemos/UpnpBroadcastResponder.h: -------------------------------------------------------------------------------- 1 | #ifndef UPNPBROADCASTRESPONDER_H 2 | #define UPNPBROADCASTRESPONDER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "Switch.h" 8 | 9 | class UpnpBroadcastResponder { 10 | private: 11 | WiFiUDP UDP; 12 | public: 13 | UpnpBroadcastResponder(); 14 | ~UpnpBroadcastResponder(); 15 | bool beginUdpMulticast(); 16 | void serverLoop(); 17 | void addDevice(Switch& device); 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /wemos/wemos.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "switch.h" 6 | #include "UpnpBroadcastResponder.h" 7 | #include "CallbackFunction.h" 8 | 9 | // prototypes 10 | boolean connectWifi(); 11 | 12 | //on/off callbacks 13 | bool officeLightsOn(); 14 | bool officeLightsOff(); 15 | bool kitchenLightsOn(); 16 | bool kitchenLightsOff(); 17 | 18 | // Change this before you flash 19 | //####################################### 20 | const char* ssid = "your-ssid-name"; //enter your access point/wifi router name 21 | const char* password = "password"; //enter router password 22 | // change gpio pins as you need it. 23 | //I am using ESP8266 EPS-12E GPIO16 and GPIO14 24 | const int relayPin1 = 16; 25 | const int relayPin2 = 14; 26 | 27 | //####################################### 28 | boolean wifiConnected = false; 29 | 30 | UpnpBroadcastResponder upnpBroadcastResponder; 31 | 32 | Switch *office = NULL; 33 | Switch *kitchen = NULL; 34 | bool isOfficeLightsOn = false; 35 | bool isKitchenLightstsOn = false; 36 | 37 | void setup() 38 | { 39 | Serial.begin(115200); 40 | pinMode(relayPin1, OUTPUT); 41 | pinMode(relayPin2, OUTPUT); 42 | // Initialise wifi connection 43 | wifiConnected = connectWifi(); 44 | 45 | if(wifiConnected){ 46 | upnpBroadcastResponder.beginUdpMulticast(); 47 | 48 | // Define your switches here. Max 14 49 | // Format: Alexa invocation name, local port no, on callback, off callback 50 | office = new Switch("office lights", 80, officeLightsOn, officeLightsOff); 51 | kitchen = new Switch("kitchen lights", 81, kitchenLightsOn, kitchenLightsOff); 52 | 53 | Serial.println("Adding switches upnp broadcast responder"); 54 | upnpBroadcastResponder.addDevice(*office); 55 | upnpBroadcastResponder.addDevice(*kitchen); 56 | } 57 | } 58 | 59 | void loop() 60 | { 61 | if(wifiConnected){ 62 | upnpBroadcastResponder.serverLoop(); 63 | 64 | kitchen->serverLoop(); 65 | office->serverLoop(); 66 | } 67 | } 68 | 69 | bool officeLightsOn() { 70 | Serial.print("Switch 1 turn on ..."); 71 | digitalWrite(relayPin1, HIGH); 72 | return true; 73 | } 74 | 75 | bool officeLightsOff() { 76 | Serial.print("Switch 1 turn off ..."); 77 | digitalWrite(relayPin1, LOW); 78 | return false; 79 | } 80 | 81 | bool kitchenLightsOn() { 82 | Serial.print("Switch 2 turn on ..."); 83 | digitalWrite(relayPin2, HIGH); 84 | return true; 85 | } 86 | 87 | bool kitchenLightsOff() { 88 | Serial.print("Switch 2 turn off ..."); 89 | digitalWrite(relayPin2, LOW); 90 | return false; 91 | } 92 | 93 | // connect to wifi – returns true if successful or false if not 94 | boolean connectWifi(){ 95 | boolean state = true; 96 | int i = 0; 97 | 98 | WiFi.mode(WIFI_STA); 99 | WiFi.begin(ssid, password); 100 | Serial.println(""); 101 | Serial.println("Connecting to WiFi"); 102 | 103 | // Wait for connection 104 | Serial.print("Connecting ..."); 105 | while (WiFi.status() != WL_CONNECTED) { 106 | delay(500); 107 | Serial.print("."); 108 | if (i > 10){ 109 | state = false; 110 | break; 111 | } 112 | i++; 113 | } 114 | 115 | if (state){ 116 | Serial.println(""); 117 | Serial.print("Connected to "); 118 | Serial.println(ssid); 119 | Serial.print("IP address: "); 120 | Serial.println(WiFi.localIP()); 121 | } 122 | else { 123 | Serial.println(""); 124 | Serial.println("Connection failed."); 125 | } 126 | 127 | return state; 128 | } 129 | --------------------------------------------------------------------------------