 1d4d885276
			
		
	
	1d4d885276
	
	
	
		
			
			Added E1.31 support Added more realtime options Attempted to fix welcome page not showing on fresh install
		
			
				
	
	
		
			293 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			293 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | ||
|  * 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 alexaInit()
 | ||
| {
 | ||
|   if (alexaEnabled && WiFi.status() == WL_CONNECTED)
 | ||
|   {
 | ||
|     prepareIds();
 | ||
|     
 | ||
|     udpConnected = connectUDP();
 | ||
|     
 | ||
|     if (udpConnected) alexaInitPages();
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| void handleAlexa()
 | ||
| {
 | ||
|   if (alexaEnabled && WiFi.status() == WL_CONNECTED)
 | ||
|   {
 | ||
|     if(udpConnected){    
 | ||
|     // if there’s data available, read a packet
 | ||
|     int packetSize = UDP.parsePacket();
 | ||
|       if(packetSize>0) {
 | ||
|         IPAddress remote = UDP.remoteIP();
 | ||
|         int len = UDP.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((alexaNotify)?"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((alexaNotify)?"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 = (alexaNotify)?"win&IN&A=":"win&NN&IN&A=";
 | ||
|   if (briL < 255)
 | ||
|   {
 | ||
|     ct = ct + (briL+1);
 | ||
|   } else
 | ||
|   {
 | ||
|     ct = ct + (255);
 | ||
|   }
 | ||
|   handleSet(ct);
 | ||
| }
 | ||
| 
 | ||
| void prepareIds() {
 | ||
|   escapedMac = WiFi.macAddress();
 | ||
|   escapedMac.replace(":", "");
 | ||
|   escapedMac.toLowerCase();
 | ||
| }
 | ||
| 
 | ||
| void respondToSearch() {
 | ||
|     DEBUG_PRINTLN("");
 | ||
|     DEBUG_PRINT("Send resp to ");
 | ||
|     DEBUG_PRINTLN(UDP.remoteIP());
 | ||
|     DEBUG_PRINT("Port : ");
 | ||
|     DEBUG_PRINTLN(UDP.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");
 | ||
| 
 | ||
|     UDP.beginPacket(UDP.remoteIP(), UDP.remotePort());
 | ||
|     #ifdef ARDUINO_ARCH_ESP32
 | ||
|     UDP.write((byte*)obuf, olen);
 | ||
|     #else
 | ||
|     UDP.write(obuf);
 | ||
|     #endif
 | ||
|     UDP.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("<?xml version=\"1.0\" ?>"
 | ||
|           "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
 | ||
|           "<specVersion><major>1</major><minor>0</minor></specVersion>"
 | ||
|           "<URLBase>http://");
 | ||
|       oappend(s);
 | ||
|       oappend(":80/</URLBase>"
 | ||
|           "<device>"
 | ||
|             "<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>"
 | ||
|             "<friendlyName>Philips hue (");
 | ||
|       oappend(s);
 | ||
|       oappend(")</friendlyName>"
 | ||
|             "<manufacturer>Royal Philips Electronics</manufacturer>"
 | ||
|             "<manufacturerURL>http://www.philips.com</manufacturerURL>"
 | ||
|             "<modelDescription>Philips hue Personal Wireless Lighting</modelDescription>"
 | ||
|             "<modelName>Philips hue bridge 2012</modelName>"
 | ||
|             "<modelNumber>929000226503</modelNumber>"
 | ||
|             "<modelURL>http://www.meethue.com</modelURL>"
 | ||
|             "<serialNumber>");
 | ||
|       oappend((char*)escapedMac.c_str());
 | ||
|       oappend("</serialNumber>"
 | ||
|             "<UDN>uuid:2f402f80-da50-11e1-9b23-");
 | ||
|       oappend((char*)escapedMac.c_str());
 | ||
|       oappend("</UDN>"
 | ||
|             "<presentationURL>index.html</presentationURL>"
 | ||
|             "<iconList>"
 | ||
|             "  <icon>"
 | ||
|             "    <mimetype>image/png</mimetype>"
 | ||
|             "    <height>48</height>"
 | ||
|             "    <width>48</width>"
 | ||
|             "    <depth>24</depth>"
 | ||
|             "    <url>hue_logo_0.png</url>"
 | ||
|             "  </icon>"
 | ||
|             "  <icon>"
 | ||
|             "    <mimetype>image/png</mimetype>"
 | ||
|             "    <height>120</height>"
 | ||
|             "    <width>120</width>"
 | ||
|             "    <depth>24</depth>"
 | ||
|             "    <url>hue_logo_3.png</url>"
 | ||
|             "  </icon>"
 | ||
|             "</iconList>"
 | ||
|           "</device>"
 | ||
|           "</root>");
 | ||
|             
 | ||
|         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(UDP.beginMulticast(ipMulti, portMulti))
 | ||
|   #else
 | ||
|   if(UDP.beginMulticast(WiFi.localIP(), ipMulti, portMulti))
 | ||
|   #endif
 | ||
|   {
 | ||
|     DEBUG_PRINTLN("Con success");
 | ||
|     state = true;
 | ||
|   }
 | ||
|   else{
 | ||
|     DEBUG_PRINTLN("Con failed");
 | ||
|   }
 | ||
|   
 | ||
|   return state;
 | ||
| }
 |