/* * Alexa Voice On/Off/Brightness Control. Emulates a Philips Hue bridge to Alexa. * * This was put together from these two excellent projects: * https://github.com/kakopappa/arduino-esp8266-alexa-wemo-switch * https://github.com/probonopd/ESP8266HueEmulator */ void prepareIds() { escapedMac = WiFi.macAddress(); escapedMac.replace(":", ""); escapedMac.toLowerCase(); } #ifndef WLED_DISABLE_ALEXA void alexaInit() { if (alexaEnabled && WiFi.status() == WL_CONNECTED) { alexaUdpConnected = connectUDP(); if (alexaUdpConnected) alexaInitPages(); } } void handleAlexa() { if (!alexaEnabled || WiFi.status() != WL_CONNECTED || !alexaUdpConnected) return; // if there's data available, read a packet int packetSize = alexaUDP.parsePacket(); if(packetSize < 1) return; IPAddress remote = alexaUDP.remoteIP(); int len = alexaUDP.read(obuf, 254); if (len > 0) obuf[len] = 0; if(strstr(obuf,"M-SEARCH") > 0) { if(strstr(obuf,"upnp:rootdevice") > 0 || strstr(obuf,"device:basic:1") > 0) { DEBUG_PRINTLN("Responding search req..."); respondToSearch(); } } } void alexaOn() { if (macroAlexaOn == 0) { handleSet((notifyAlexa)?"win&T=1&IN":"win&T=1&NN&IN"); } else { applyMacro(macroAlexaOn); } server.send(200, "application/json", "[{\"success\":{\"/lights/1/state/on\":true}}]"); } void alexaOff() { if (macroAlexaOff == 0) { handleSet((notifyAlexa)?"win&T=0&IN":"win&T=0&NN&IN"); } else { applyMacro(macroAlexaOff); } server.send(200, "application/json", "[{\"success\":{\"/lights/1/state/on\":false}}]"); } void alexaDim(byte briL) { olen = 0; oappend("[{\"success\":{\"/lights/1/state/bri\":"); oappendi(briL); oappend("}}]"); server.send(200, "application/json", obuf); String ct = (notifyAlexa)?"win&IN&A=":"win&NN&IN&A="; if (briL < 255) { ct = ct + (briL+1); } else { ct = ct + (255); } handleSet(ct); } void respondToSearch() { DEBUG_PRINTLN(""); DEBUG_PRINT("Send resp to "); DEBUG_PRINTLN(alexaUDP.remoteIP()); DEBUG_PRINT("Port : "); DEBUG_PRINTLN(alexaUDP.remotePort()); IPAddress localIP = WiFi.localIP(); char s[16]; sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]); olen = 0; oappend( "HTTP/1.1 200 OK\r\n" "EXT:\r\n" "CACHE-CONTROL: max-age=100\r\n" // SSDP_INTERVAL "LOCATION: http://"); oappend(s); oappend(":80/description.xml\r\n" "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.17.0\r\n" // _modelName, _modelNumber "hue-bridgeid: "); oappend((char*)escapedMac.c_str()); oappend("\r\n" "ST: urn:schemas-upnp-org:device:basic:1\r\n" // _deviceType "USN: uuid:2f402f80-da50-11e1-9b23-"); oappend((char*)escapedMac.c_str()); oappend("::upnp:rootdevice\r\n" // _uuid::_deviceType "\r\n"); alexaUDP.beginPacket(alexaUDP.remoteIP(), alexaUDP.remotePort()); #ifdef ARDUINO_ARCH_ESP32 alexaUDP.write((byte*)obuf, olen); #else alexaUDP.write(obuf); #endif alexaUDP.endPacket(); DEBUG_PRINTLN("Response sent!"); } void alexaInitPages() { server.on("/description.xml", HTTP_GET, [](){ DEBUG_PRINTLN(" # Responding to description.xml ... #\n"); IPAddress localIP = WiFi.localIP(); char s[16]; sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]); olen = 0; oappend("" "" "10" "http://"); oappend(s); oappend(":80/" "" "urn:schemas-upnp-org:device:Basic:1" "Philips hue ("); oappend(s); oappend(")" "Royal Philips Electronics" "http://www.philips.com" "Philips hue Personal Wireless Lighting" "Philips hue bridge 2012" "929000226503" "http://www.meethue.com" ""); oappend((char*)escapedMac.c_str()); oappend("" "uuid:2f402f80-da50-11e1-9b23-"); oappend((char*)escapedMac.c_str()); oappend("" "index.html" "" " " " image/png" " 48" " 48" " 24" " hue_logo_0.png" " " " " " image/png" " 120" " 120" " 24" " hue_logo_3.png" " " "" "" ""); server.send(200, "text/xml", obuf); DEBUG_PRINTLN("Sending setup_xml"); }); // openHAB support server.on("/on.html", HTTP_GET, [](){ DEBUG_PRINTLN("on req"); server.send(200, "text/plain", "turned on"); alexaOn(); }); server.on("/off.html", HTTP_GET, [](){ DEBUG_PRINTLN("off req"); server.send(200, "text/plain", "turned off"); alexaOff(); }); server.on("/status.html", HTTP_GET, [](){ DEBUG_PRINTLN("Got status request"); char statrespone[] = "0"; if (bri > 0) { statrespone[0] = '1'; } server.send(200, "text/plain", statrespone); }); } String boolString(bool st) { return (st)?"true":"false"; } String briForHue(int realBri) { realBri--; if (realBri < 0) realBri = 0; return String(realBri); } bool handleAlexaApiCall(String req, String body) //basic implementation of Philips hue api functions needed for basic Alexa control { DEBUG_PRINTLN("AlexaApiCall"); if (req.indexOf("api") <0) return false; DEBUG_PRINTLN("ok"); if (body.indexOf("devicetype") > 0) //client wants a hue api username, we dont care and give static { DEBUG_PRINTLN("devType"); server.send(200, "application/json", "[{\"success\":{\"username\": \"2WLEDHardQrI3WHYTHoMcXHgEspsM8ZZRpSKtBQr\"}}]"); return true; } if (req.indexOf("state") > 0) //client wants to control light { DEBUG_PRINTLN("ls"); if (body.indexOf("bri")>0) {alexaDim(body.substring(body.indexOf("bri") +5).toInt()); return true;} if (body.indexOf("false")>0) {alexaOff(); return true;} alexaOn(); return true; } if (req.indexOf("lights/1") > 0) //client wants light info { DEBUG_PRINTLN("l1"); server.send(200, "application/json", "{\"manufacturername\":\"OpenSource\",\"modelid\":\"LST001\",\"name\":\""+ String(alexaInvocationName) +"\",\"state\":{\"on\":"+ boolString(bri) +",\"hue\":0,\"bri\":"+ briForHue(bri) +",\"sat\":0,\"xy\":[0.00000,0.00000],\"ct\":500,\"alert\":\"none\",\"effect\":\"none\",\"colormode\":\"hs\",\"reachable\":true},\"swversion\":\"0.1\",\"type\":\"Extended color light\",\"uniqueid\":\"2\"}"); return true; } if (req.indexOf("lights") > 0) //client wants all lights { DEBUG_PRINTLN("lAll"); server.send(200, "application/json", "{\"1\":{\"type\":\"Extended color light\",\"manufacturername\":\"OpenSource\",\"swversion\":\"0.1\",\"name\":\""+ String(alexaInvocationName) +"\",\"uniqueid\":\""+ WiFi.macAddress() +"-2\",\"modelid\":\"LST001\",\"state\":{\"on\":"+ boolString(bri) +",\"bri\":"+ briForHue(bri) +",\"xy\":[0.00000,0.00000],\"colormode\":\"hs\",\"effect\":\"none\",\"ct\":500,\"hue\":0,\"sat\":0,\"alert\":\"none\",\"reachable\":true}}}"); return true; } //we dont care about other api commands at this time and send empty JSON server.send(200, "application/json", "{}"); return true; } bool connectUDP(){ bool state = false; DEBUG_PRINTLN(""); DEBUG_PRINTLN("Con UDP"); #ifdef ARDUINO_ARCH_ESP32 if(alexaUDP.beginMulticast(ipMulti, portMulti)) #else if(alexaUDP.beginMulticast(WiFi.localIP(), ipMulti, portMulti)) #endif { DEBUG_PRINTLN("Con success"); state = true; } else{ DEBUG_PRINTLN("Con failed"); } return state; } #else void alexaInit(){} void handleAlexa(){} void alexaInitPages(){} bool handleAlexaApiCall(String req, String body){return false;} #endif