2019-01-09 22:52:42 +01:00
|
|
|
|
#ifndef Espalexa_h
|
|
|
|
|
#define Espalexa_h
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Alexa Voice On/Off/Brightness/Color 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
|
|
|
|
|
*/
|
|
|
|
|
/*
|
|
|
|
|
* @title Espalexa library
|
2019-06-20 14:40:12 +02:00
|
|
|
|
* @version 2.4.3
|
2019-01-09 22:52:42 +01:00
|
|
|
|
* @author Christian Schwinne
|
|
|
|
|
* @license MIT
|
|
|
|
|
* @contributors d-999
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "Arduino.h"
|
|
|
|
|
|
|
|
|
|
//you can use these defines for library config in your sketch. Just use them before #include <Espalexa.h>
|
|
|
|
|
//#define ESPALEXA_ASYNC
|
|
|
|
|
|
2019-03-01 17:10:42 +01:00
|
|
|
|
//in case this is unwanted in your application (will disable the /espalexa value page)
|
|
|
|
|
//#define ESPALEXA_NO_SUBPAGE
|
|
|
|
|
|
2019-01-09 22:52:42 +01:00
|
|
|
|
#ifndef ESPALEXA_MAXDEVICES
|
|
|
|
|
#define ESPALEXA_MAXDEVICES 10 //this limit only has memory reasons, set it higher should you need to
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
//#define ESPALEXA_DEBUG
|
|
|
|
|
|
|
|
|
|
#ifdef ESPALEXA_ASYNC
|
|
|
|
|
#ifdef ARDUINO_ARCH_ESP32
|
|
|
|
|
#include <AsyncTCP.h>
|
|
|
|
|
#else
|
|
|
|
|
#include <ESPAsyncTCP.h>
|
|
|
|
|
#endif
|
|
|
|
|
#include <ESPAsyncWebServer.h>
|
|
|
|
|
#else
|
|
|
|
|
#ifdef ARDUINO_ARCH_ESP32
|
|
|
|
|
#include <WiFi.h>
|
2019-02-17 17:11:10 +01:00
|
|
|
|
#include <WebServer.h> //if you get an error here please update to ESP32 arduino core 1.0.0
|
2019-01-09 22:52:42 +01:00
|
|
|
|
#else
|
|
|
|
|
#include <ESP8266WebServer.h>
|
|
|
|
|
#include <ESP8266WiFi.h>
|
|
|
|
|
#endif
|
|
|
|
|
#endif
|
|
|
|
|
#include <WiFiUdp.h>
|
|
|
|
|
|
|
|
|
|
#ifdef ESPALEXA_DEBUG
|
2019-06-20 14:40:12 +02:00
|
|
|
|
#pragma message "Espalexa 2.4.3 debug mode"
|
2019-01-09 22:52:42 +01:00
|
|
|
|
#define EA_DEBUG(x) Serial.print (x)
|
|
|
|
|
#define EA_DEBUGLN(x) Serial.println (x)
|
|
|
|
|
#else
|
|
|
|
|
#define EA_DEBUG(x)
|
|
|
|
|
#define EA_DEBUGLN(x)
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include "EspalexaDevice.h"
|
|
|
|
|
|
2019-03-01 17:10:42 +01:00
|
|
|
|
|
2019-01-09 22:52:42 +01:00
|
|
|
|
class Espalexa {
|
|
|
|
|
private:
|
|
|
|
|
//private member vars
|
|
|
|
|
#ifdef ESPALEXA_ASYNC
|
|
|
|
|
AsyncWebServer* serverAsync;
|
|
|
|
|
AsyncWebServerRequest* server; //this saves many #defines
|
|
|
|
|
String body = "";
|
|
|
|
|
#elif defined ARDUINO_ARCH_ESP32
|
|
|
|
|
WebServer* server;
|
|
|
|
|
#else
|
|
|
|
|
ESP8266WebServer* server;
|
|
|
|
|
#endif
|
|
|
|
|
uint8_t currentDeviceCount = 0;
|
2019-03-01 17:10:42 +01:00
|
|
|
|
bool discoverable = true;
|
2019-01-09 22:52:42 +01:00
|
|
|
|
|
|
|
|
|
EspalexaDevice* devices[ESPALEXA_MAXDEVICES] = {};
|
|
|
|
|
//Keep in mind that Device IDs go from 1 to DEVICES, cpp arrays from 0 to DEVICES-1!!
|
|
|
|
|
|
|
|
|
|
WiFiUDP espalexaUdp;
|
|
|
|
|
IPAddress ipMulti;
|
|
|
|
|
bool udpConnected = false;
|
|
|
|
|
char packetBuffer[255]; //buffer to hold incoming udp packet
|
|
|
|
|
String escapedMac=""; //lowercase mac address
|
|
|
|
|
|
|
|
|
|
//private member functions
|
2019-03-01 17:10:42 +01:00
|
|
|
|
String boolString(bool st)
|
|
|
|
|
{
|
|
|
|
|
return(st)?"true":"false";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String modeString(EspalexaColorMode m)
|
|
|
|
|
{
|
|
|
|
|
if (m == EspalexaColorMode::xy) return "xy";
|
|
|
|
|
if (m == EspalexaColorMode::hs) return "hs";
|
|
|
|
|
return "ct";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String typeString(EspalexaDeviceType t)
|
|
|
|
|
{
|
|
|
|
|
switch (t)
|
|
|
|
|
{
|
|
|
|
|
case EspalexaDeviceType::dimmable: return "Dimmable light";
|
|
|
|
|
case EspalexaDeviceType::whitespectrum: return "Color temperature light";
|
|
|
|
|
case EspalexaDeviceType::color: return "Color light";
|
|
|
|
|
case EspalexaDeviceType::extendedcolor: return "Extended color light";
|
|
|
|
|
}
|
|
|
|
|
return "Light";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String modelidString(EspalexaDeviceType t)
|
|
|
|
|
{
|
|
|
|
|
switch (t)
|
|
|
|
|
{
|
|
|
|
|
case EspalexaDeviceType::dimmable: return "LWB010";
|
|
|
|
|
case EspalexaDeviceType::whitespectrum: return "LWT010";
|
|
|
|
|
case EspalexaDeviceType::color: return "LST001";
|
|
|
|
|
case EspalexaDeviceType::extendedcolor: return "LCT015";
|
|
|
|
|
}
|
2019-04-14 19:31:25 +02:00
|
|
|
|
return "Plug";
|
2019-03-01 17:10:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-20 14:40:12 +02:00
|
|
|
|
//Workaround functions courtesy of Sonoff-Tasmota
|
|
|
|
|
uint32_t encodeLightId(uint8_t idx)
|
|
|
|
|
{
|
|
|
|
|
uint8_t mac[6];
|
|
|
|
|
WiFi.macAddress(mac);
|
|
|
|
|
uint32_t id = (mac[3] << 20) | (mac[4] << 12) | (mac[5] << 4) | (idx & 0xF);
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t decodeLightId(uint32_t id) {
|
|
|
|
|
return id & 0xF;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-17 17:11:10 +01:00
|
|
|
|
//device JSON string: color+temperature device emulates LCT015, dimmable device LWB010, (TODO: on/off Plug 01, color temperature device LWT010, color device LST001)
|
2019-01-09 22:52:42 +01:00
|
|
|
|
String deviceJsonString(uint8_t deviceId)
|
|
|
|
|
{
|
2019-06-20 14:40:12 +02:00
|
|
|
|
deviceId--;
|
|
|
|
|
if (deviceId >= currentDeviceCount) return "{}"; //error
|
|
|
|
|
EspalexaDevice* dev = devices[deviceId];
|
2019-03-01 17:10:42 +01:00
|
|
|
|
|
|
|
|
|
String json = "{\"state\":{\"on\":";
|
|
|
|
|
json += boolString(dev->getValue());
|
|
|
|
|
if (dev->getType() != EspalexaDeviceType::onoff) //bri support
|
2019-01-09 22:52:42 +01:00
|
|
|
|
{
|
2019-03-01 17:10:42 +01:00
|
|
|
|
json += ",\"bri\":" + String(dev->getLastValue()-1);
|
|
|
|
|
if (static_cast<uint8_t>(dev->getType()) > 2) //color support
|
|
|
|
|
{
|
|
|
|
|
json += ",\"hue\":" + String(dev->getHue()) + ",\"sat\":" + String(dev->getSat());
|
|
|
|
|
json += ",\"effect\":\"none\",\"xy\":[" + String(dev->getX()) + "," + String(dev->getY()) + "]";
|
|
|
|
|
}
|
|
|
|
|
if (static_cast<uint8_t>(dev->getType()) > 1 && dev->getType() != EspalexaDeviceType::color) //white spectrum support
|
|
|
|
|
{
|
|
|
|
|
json += ",\"ct\":" + String(dev->getCt());
|
|
|
|
|
}
|
2019-01-09 22:52:42 +01:00
|
|
|
|
}
|
2019-03-01 17:10:42 +01:00
|
|
|
|
json += ",\"alert\":\"none";
|
|
|
|
|
if (static_cast<uint8_t>(dev->getType()) > 1) json += "\",\"colormode\":\"" + modeString(dev->getColorMode());
|
|
|
|
|
json += "\",\"mode\":\"homeautomation\",\"reachable\":true},";
|
|
|
|
|
json += "\"type\":\"" + typeString(dev->getType());
|
|
|
|
|
json += "\",\"name\":\"" + dev->getName();
|
|
|
|
|
json += "\",\"modelid\":\"" + modelidString(dev->getType());
|
2019-04-14 19:31:25 +02:00
|
|
|
|
json += "\",\"manufacturername\":\"Philips\",\"productname\":\"E" + String(static_cast<uint8_t>(dev->getType()));
|
2019-06-20 14:40:12 +02:00
|
|
|
|
json += "\",\"uniqueid\":\"" + String(encodeLightId(deviceId+1));
|
|
|
|
|
json += "\",\"swversion\":\"espalexa-2.4.3\"}";
|
2019-03-01 17:10:42 +01:00
|
|
|
|
|
2019-01-09 22:52:42 +01:00
|
|
|
|
return json;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Espalexa status page /espalexa
|
2019-03-01 17:10:42 +01:00
|
|
|
|
#ifndef ESPALEXA_NO_SUBPAGE
|
2019-01-09 22:52:42 +01:00
|
|
|
|
void servePage()
|
|
|
|
|
{
|
|
|
|
|
EA_DEBUGLN("HTTP Req espalexa ...\n");
|
|
|
|
|
String res = "Hello from Espalexa!\r\n\r\n";
|
|
|
|
|
for (int i=0; i<currentDeviceCount; i++)
|
|
|
|
|
{
|
2019-03-01 17:10:42 +01:00
|
|
|
|
EspalexaDevice* dev = devices[i];
|
|
|
|
|
res += "Value of device " + String(i+1) + " (" + dev->getName() + "): " + String(dev->getValue()) + " (" + typeString(dev->getType());
|
|
|
|
|
if (static_cast<uint8_t>(dev->getType()) > 1) //color support
|
|
|
|
|
{
|
|
|
|
|
res += ", colormode=" + modeString(dev->getColorMode()) + ", r=" + String(dev->getR()) + ", g=" + String(dev->getG()) + ", b=" + String(dev->getB());
|
|
|
|
|
res +=", ct=" + String(dev->getCt()) + ", hue=" + String(dev->getHue()) + ", sat=" + String(dev->getSat()) + ", x=" + String(dev->getX()) + ", y=" + String(dev->getY());
|
|
|
|
|
}
|
|
|
|
|
res += ")\r\n";
|
2019-01-09 22:52:42 +01:00
|
|
|
|
}
|
|
|
|
|
res += "\r\nFree Heap: " + (String)ESP.getFreeHeap();
|
|
|
|
|
res += "\r\nUptime: " + (String)millis();
|
2019-06-20 14:40:12 +02:00
|
|
|
|
res += "\r\n\r\nEspalexa library v2.4.3 by Christian Schwinne 2019";
|
2019-01-09 22:52:42 +01:00
|
|
|
|
server->send(200, "text/plain", res);
|
|
|
|
|
}
|
2019-03-01 17:10:42 +01:00
|
|
|
|
#endif
|
2019-01-09 22:52:42 +01:00
|
|
|
|
|
|
|
|
|
//not found URI (only if internal webserver is used)
|
|
|
|
|
void serveNotFound()
|
|
|
|
|
{
|
|
|
|
|
EA_DEBUGLN("Not-Found HTTP call:");
|
|
|
|
|
#ifndef ESPALEXA_ASYNC
|
|
|
|
|
EA_DEBUGLN("URI: " + server->uri());
|
|
|
|
|
EA_DEBUGLN("Body: " + server->arg(0));
|
|
|
|
|
if(!handleAlexaApiCall(server->uri(), server->arg(0)))
|
|
|
|
|
#else
|
|
|
|
|
EA_DEBUGLN("URI: " + server->url());
|
|
|
|
|
EA_DEBUGLN("Body: " + body);
|
|
|
|
|
if(!handleAlexaApiCall(server))
|
|
|
|
|
#endif
|
|
|
|
|
server->send(404, "text/plain", "Not Found (espalexa-internal)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//send description.xml device property page
|
|
|
|
|
void serveDescription()
|
|
|
|
|
{
|
|
|
|
|
EA_DEBUGLN("# 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]);
|
|
|
|
|
|
|
|
|
|
String setup_xml = "<?xml version=\"1.0\" ?>"
|
|
|
|
|
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
|
|
|
|
|
"<specVersion><major>1</major><minor>0</minor></specVersion>"
|
|
|
|
|
"<URLBase>http://"+ String(s) +":80/</URLBase>"
|
|
|
|
|
"<device>"
|
|
|
|
|
"<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>"
|
2019-02-17 17:11:10 +01:00
|
|
|
|
"<friendlyName>Espalexa ("+ String(s) +")</friendlyName>"
|
2019-01-09 22:52:42 +01:00
|
|
|
|
"<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>"+ escapedMac +"</serialNumber>"
|
|
|
|
|
"<UDN>uuid:2f402f80-da50-11e1-9b23-"+ escapedMac +"</UDN>"
|
|
|
|
|
"<presentationURL>index.html</presentationURL>"
|
|
|
|
|
"</device>"
|
|
|
|
|
"</root>";
|
|
|
|
|
|
|
|
|
|
server->send(200, "text/xml", setup_xml.c_str());
|
|
|
|
|
|
|
|
|
|
EA_DEBUG("Sending :");
|
|
|
|
|
EA_DEBUGLN(setup_xml);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//init the server
|
|
|
|
|
void startHttpServer()
|
|
|
|
|
{
|
|
|
|
|
#ifdef ESPALEXA_ASYNC
|
|
|
|
|
if (serverAsync == nullptr) {
|
|
|
|
|
serverAsync = new AsyncWebServer(80);
|
|
|
|
|
serverAsync->onNotFound([=](AsyncWebServerRequest *request){server = request; serveNotFound();});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
serverAsync->onRequestBody([=](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){
|
|
|
|
|
char b[len +1];
|
|
|
|
|
b[len] = 0;
|
|
|
|
|
memcpy(b, data, len);
|
|
|
|
|
body = b; //save the body so we can use it for the API call
|
|
|
|
|
EA_DEBUG("Received body: ");
|
|
|
|
|
EA_DEBUGLN(body);
|
|
|
|
|
});
|
2019-03-01 17:10:42 +01:00
|
|
|
|
#ifndef ESPALEXA_NO_SUBPAGE
|
2019-01-09 22:52:42 +01:00
|
|
|
|
serverAsync->on("/espalexa", HTTP_GET, [=](AsyncWebServerRequest *request){server = request; servePage();});
|
2019-03-01 17:10:42 +01:00
|
|
|
|
#endif
|
2019-01-09 22:52:42 +01:00
|
|
|
|
serverAsync->on("/description.xml", HTTP_GET, [=](AsyncWebServerRequest *request){server = request; serveDescription();});
|
|
|
|
|
serverAsync->begin();
|
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
if (server == nullptr) {
|
|
|
|
|
#ifdef ARDUINO_ARCH_ESP32
|
|
|
|
|
server = new WebServer(80);
|
|
|
|
|
#else
|
|
|
|
|
server = new ESP8266WebServer(80);
|
|
|
|
|
#endif
|
|
|
|
|
server->onNotFound([=](){serveNotFound();});
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-01 17:10:42 +01:00
|
|
|
|
#ifndef ESPALEXA_NO_SUBPAGE
|
2019-01-09 22:52:42 +01:00
|
|
|
|
server->on("/espalexa", HTTP_GET, [=](){servePage();});
|
2019-03-01 17:10:42 +01:00
|
|
|
|
#endif
|
2019-01-09 22:52:42 +01:00
|
|
|
|
server->on("/description.xml", HTTP_GET, [=](){serveDescription();});
|
|
|
|
|
server->begin();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//respond to UDP SSDP M-SEARCH
|
|
|
|
|
void respondToSearch()
|
|
|
|
|
{
|
|
|
|
|
IPAddress localIP = WiFi.localIP();
|
|
|
|
|
char s[16];
|
|
|
|
|
sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
|
|
|
|
|
|
|
|
|
|
String response =
|
|
|
|
|
"HTTP/1.1 200 OK\r\n"
|
|
|
|
|
"EXT:\r\n"
|
|
|
|
|
"CACHE-CONTROL: max-age=100\r\n" // SSDP_INTERVAL
|
|
|
|
|
"LOCATION: http://"+ String(s) +":80/description.xml\r\n"
|
|
|
|
|
"SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.17.0\r\n" // _modelName, _modelNumber
|
|
|
|
|
"hue-bridgeid: "+ escapedMac +"\r\n"
|
|
|
|
|
"ST: urn:schemas-upnp-org:device:basic:1\r\n" // _deviceType
|
|
|
|
|
"USN: uuid:2f402f80-da50-11e1-9b23-"+ escapedMac +"::upnp:rootdevice\r\n" // _uuid::_deviceType
|
|
|
|
|
"\r\n";
|
|
|
|
|
|
|
|
|
|
espalexaUdp.beginPacket(espalexaUdp.remoteIP(), espalexaUdp.remotePort());
|
|
|
|
|
#ifdef ARDUINO_ARCH_ESP32
|
|
|
|
|
espalexaUdp.write((uint8_t*)response.c_str(), response.length());
|
|
|
|
|
#else
|
|
|
|
|
espalexaUdp.write(response.c_str());
|
|
|
|
|
#endif
|
|
|
|
|
espalexaUdp.endPacket();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
Espalexa(){}
|
|
|
|
|
|
|
|
|
|
//initialize interfaces
|
|
|
|
|
#ifdef ESPALEXA_ASYNC
|
|
|
|
|
bool begin(AsyncWebServer* externalServer = nullptr)
|
|
|
|
|
#elif defined ARDUINO_ARCH_ESP32
|
|
|
|
|
bool begin(WebServer* externalServer = nullptr)
|
|
|
|
|
#else
|
|
|
|
|
bool begin(ESP8266WebServer* externalServer = nullptr)
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
EA_DEBUGLN("Espalexa Begin...");
|
|
|
|
|
EA_DEBUG("MAXDEVICES ");
|
|
|
|
|
EA_DEBUGLN(ESPALEXA_MAXDEVICES);
|
|
|
|
|
escapedMac = WiFi.macAddress();
|
|
|
|
|
escapedMac.replace(":", "");
|
|
|
|
|
escapedMac.toLowerCase();
|
|
|
|
|
|
|
|
|
|
#ifdef ESPALEXA_ASYNC
|
|
|
|
|
serverAsync = externalServer;
|
|
|
|
|
#else
|
|
|
|
|
server = externalServer;
|
|
|
|
|
#endif
|
|
|
|
|
#ifdef ARDUINO_ARCH_ESP32
|
|
|
|
|
udpConnected = espalexaUdp.beginMulticast(IPAddress(239, 255, 255, 250), 1900);
|
|
|
|
|
#else
|
|
|
|
|
udpConnected = espalexaUdp.beginMulticast(WiFi.localIP(), IPAddress(239, 255, 255, 250), 1900);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if (udpConnected){
|
|
|
|
|
|
|
|
|
|
startHttpServer();
|
|
|
|
|
EA_DEBUGLN("Done");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
EA_DEBUGLN("Failed");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//service loop
|
|
|
|
|
void loop() {
|
|
|
|
|
#ifndef ESPALEXA_ASYNC
|
|
|
|
|
if (server == nullptr) return; //only if begin() was not called
|
|
|
|
|
server->handleClient();
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if (!udpConnected) return;
|
|
|
|
|
int packetSize = espalexaUdp.parsePacket();
|
|
|
|
|
if (!packetSize) return; //no new udp packet
|
|
|
|
|
|
|
|
|
|
EA_DEBUGLN("Got UDP!");
|
|
|
|
|
int len = espalexaUdp.read(packetBuffer, 254);
|
|
|
|
|
if (len > 0) {
|
|
|
|
|
packetBuffer[len] = 0;
|
|
|
|
|
}
|
2019-02-11 23:49:04 +01:00
|
|
|
|
espalexaUdp.flush();
|
2019-03-01 17:10:42 +01:00
|
|
|
|
if (!discoverable) return; //do not reply to M-SEARCH if not discoverable
|
|
|
|
|
|
2019-01-09 22:52:42 +01:00
|
|
|
|
String request = packetBuffer;
|
|
|
|
|
if(request.indexOf("M-SEARCH") >= 0) {
|
2019-03-01 17:10:42 +01:00
|
|
|
|
EA_DEBUGLN(request);
|
2019-01-09 22:52:42 +01:00
|
|
|
|
if(request.indexOf("upnp:rootdevice") > 0 || request.indexOf("asic:1") > 0) {
|
|
|
|
|
EA_DEBUGLN("Responding search req...");
|
|
|
|
|
respondToSearch();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool addDevice(EspalexaDevice* d)
|
|
|
|
|
{
|
|
|
|
|
EA_DEBUG("Adding device ");
|
|
|
|
|
EA_DEBUGLN((currentDeviceCount+1));
|
|
|
|
|
if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return false;
|
2019-03-01 17:10:42 +01:00
|
|
|
|
if (d == nullptr) return false;
|
|
|
|
|
d->setId(currentDeviceCount);
|
2019-01-09 22:52:42 +01:00
|
|
|
|
devices[currentDeviceCount] = d;
|
|
|
|
|
currentDeviceCount++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2019-03-01 17:10:42 +01:00
|
|
|
|
|
2019-04-14 19:31:25 +02:00
|
|
|
|
//brightness-only callback
|
2019-03-01 17:10:42 +01:00
|
|
|
|
bool addDevice(String deviceName, BrightnessCallbackFunction callback, uint8_t initialValue = 0)
|
2019-01-09 22:52:42 +01:00
|
|
|
|
{
|
|
|
|
|
EA_DEBUG("Constructing device ");
|
|
|
|
|
EA_DEBUGLN((currentDeviceCount+1));
|
|
|
|
|
if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return false;
|
|
|
|
|
EspalexaDevice* d = new EspalexaDevice(deviceName, callback, initialValue);
|
|
|
|
|
return addDevice(d);
|
|
|
|
|
}
|
2019-04-14 19:31:25 +02:00
|
|
|
|
|
|
|
|
|
//brightness-only callback
|
|
|
|
|
bool addDevice(String deviceName, ColorCallbackFunction callback, uint8_t initialValue = 0)
|
|
|
|
|
{
|
|
|
|
|
EA_DEBUG("Constructing device ");
|
|
|
|
|
EA_DEBUGLN((currentDeviceCount+1));
|
|
|
|
|
if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return false;
|
|
|
|
|
EspalexaDevice* d = new EspalexaDevice(deviceName, callback, initialValue);
|
|
|
|
|
return addDevice(d);
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-09 22:52:42 +01:00
|
|
|
|
|
2019-03-01 17:10:42 +01:00
|
|
|
|
bool addDevice(String deviceName, DeviceCallbackFunction callback, EspalexaDeviceType t = EspalexaDeviceType::dimmable, uint8_t initialValue = 0)
|
2019-01-09 22:52:42 +01:00
|
|
|
|
{
|
|
|
|
|
EA_DEBUG("Constructing device ");
|
|
|
|
|
EA_DEBUGLN((currentDeviceCount+1));
|
|
|
|
|
if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return false;
|
2019-03-01 17:10:42 +01:00
|
|
|
|
EspalexaDevice* d = new EspalexaDevice(deviceName, callback, t, initialValue);
|
2019-01-09 22:52:42 +01:00
|
|
|
|
return addDevice(d);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//basic implementation of Philips hue api functions needed for basic Alexa control
|
|
|
|
|
#ifdef ESPALEXA_ASYNC
|
|
|
|
|
bool handleAlexaApiCall(AsyncWebServerRequest* request)
|
|
|
|
|
{
|
|
|
|
|
server = request; //copy request reference
|
|
|
|
|
String req = request->url(); //body from global variable
|
|
|
|
|
EA_DEBUGLN(request->contentType());
|
|
|
|
|
if (request->hasParam("body", true)) // This is necessary, otherwise ESP crashes if there is no body
|
|
|
|
|
{
|
|
|
|
|
EA_DEBUG("BodyMethod2");
|
|
|
|
|
body = request->getParam("body", true)->value();
|
|
|
|
|
}
|
|
|
|
|
EA_DEBUG("FinalBody: ");
|
|
|
|
|
EA_DEBUGLN(body);
|
|
|
|
|
#else
|
|
|
|
|
bool handleAlexaApiCall(String req, String body)
|
|
|
|
|
{
|
|
|
|
|
#endif
|
|
|
|
|
EA_DEBUGLN("AlexaApiCall");
|
|
|
|
|
if (req.indexOf("api") <0) return false; //return if not an API call
|
|
|
|
|
EA_DEBUGLN("ok");
|
|
|
|
|
|
2019-11-10 00:54:35 +01:00
|
|
|
|
if (body.indexOf("devicetype") > 0) //client wants a hue api username, we don't care and give static
|
2019-01-09 22:52:42 +01:00
|
|
|
|
{
|
|
|
|
|
EA_DEBUGLN("devType");
|
|
|
|
|
body = "";
|
2019-03-01 17:10:42 +01:00
|
|
|
|
server->send(200, "application/json", "[{\"success\":{\"username\":\"2WLEDHardQrI3WHYTHoMcXHgEspsM8ZZRpSKtBQr\"}}]");
|
2019-01-09 22:52:42 +01:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (req.indexOf("state") > 0) //client wants to control light
|
|
|
|
|
{
|
2019-11-10 00:54:35 +01:00
|
|
|
|
server->send(200, "application/json", "[{\"success\":{\"/lights/1/state/\": true}}]");
|
2019-03-01 17:10:42 +01:00
|
|
|
|
|
2019-06-20 14:40:12 +02:00
|
|
|
|
uint32_t devId = req.substring(req.indexOf("lights")+7).toInt();
|
2019-03-01 17:10:42 +01:00
|
|
|
|
EA_DEBUG("ls"); EA_DEBUGLN(devId);
|
2019-06-20 14:40:12 +02:00
|
|
|
|
devId = decodeLightId(devId);
|
|
|
|
|
EA_DEBUGLN(devId);
|
|
|
|
|
devId--; //zero-based for devices array
|
|
|
|
|
if (devId >= currentDeviceCount) return true; //return if invalid ID
|
|
|
|
|
|
|
|
|
|
devices[devId]->setPropertyChanged(EspalexaDeviceProperty::none);
|
2019-03-01 17:10:42 +01:00
|
|
|
|
|
|
|
|
|
if (body.indexOf("false")>0) //OFF command
|
|
|
|
|
{
|
2019-06-20 14:40:12 +02:00
|
|
|
|
devices[devId]->setValue(0);
|
|
|
|
|
devices[devId]->setPropertyChanged(EspalexaDeviceProperty::off);
|
|
|
|
|
devices[devId]->doCallback();
|
2019-03-01 17:10:42 +01:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (body.indexOf("true") >0) //ON command
|
|
|
|
|
{
|
2019-06-20 14:40:12 +02:00
|
|
|
|
devices[devId]->setValue(devices[devId]->getLastValue());
|
|
|
|
|
devices[devId]->setPropertyChanged(EspalexaDeviceProperty::on);
|
2019-03-01 17:10:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (body.indexOf("bri") >0) //BRIGHTNESS command
|
|
|
|
|
{
|
|
|
|
|
uint8_t briL = body.substring(body.indexOf("bri") +5).toInt();
|
|
|
|
|
if (briL == 255)
|
|
|
|
|
{
|
2019-06-20 14:40:12 +02:00
|
|
|
|
devices[devId]->setValue(255);
|
2019-03-01 17:10:42 +01:00
|
|
|
|
} else {
|
2019-06-20 14:40:12 +02:00
|
|
|
|
devices[devId]->setValue(briL+1);
|
2019-03-01 17:10:42 +01:00
|
|
|
|
}
|
2019-06-20 14:40:12 +02:00
|
|
|
|
devices[devId]->setPropertyChanged(EspalexaDeviceProperty::bri);
|
2019-03-01 17:10:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (body.indexOf("xy") >0) //COLOR command (XY mode)
|
|
|
|
|
{
|
2019-06-20 14:40:12 +02:00
|
|
|
|
devices[devId]->setColorXY(body.substring(body.indexOf("[") +1).toFloat(), body.substring(body.indexOf(",0") +1).toFloat());
|
|
|
|
|
devices[devId]->setPropertyChanged(EspalexaDeviceProperty::xy);
|
2019-03-01 17:10:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (body.indexOf("hue") >0) //COLOR command (HS mode)
|
|
|
|
|
{
|
2019-06-20 14:40:12 +02:00
|
|
|
|
devices[devId]->setColor(body.substring(body.indexOf("hue") +5).toInt(), body.substring(body.indexOf("sat") +5).toInt());
|
|
|
|
|
devices[devId]->setPropertyChanged(EspalexaDeviceProperty::hs);
|
2019-03-01 17:10:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (body.indexOf("ct") >0) //COLOR TEMP command (white spectrum)
|
|
|
|
|
{
|
2019-06-20 14:40:12 +02:00
|
|
|
|
devices[devId]->setColor(body.substring(body.indexOf("ct") +4).toInt());
|
|
|
|
|
devices[devId]->setPropertyChanged(EspalexaDeviceProperty::ct);
|
2019-03-01 17:10:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-20 14:40:12 +02:00
|
|
|
|
devices[devId]->doCallback();
|
2019-01-09 22:52:42 +01:00
|
|
|
|
|
2019-03-01 17:10:42 +01:00
|
|
|
|
#ifdef ESPALEXA_DEBUG
|
2019-06-20 14:40:12 +02:00
|
|
|
|
if (devices[devId]->getLastChangedProperty() == EspalexaDeviceProperty::none)
|
2019-03-01 17:10:42 +01:00
|
|
|
|
EA_DEBUGLN("STATE REQ WITHOUT BODY (likely Content-Type issue #6)");
|
|
|
|
|
#endif
|
2019-01-09 22:52:42 +01:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int pos = req.indexOf("lights");
|
|
|
|
|
if (pos > 0) //client wants light info
|
|
|
|
|
{
|
2019-03-01 17:10:42 +01:00
|
|
|
|
int devId = req.substring(pos+7).toInt();
|
|
|
|
|
EA_DEBUG("l"); EA_DEBUGLN(devId);
|
2019-01-09 22:52:42 +01:00
|
|
|
|
|
2019-03-01 17:10:42 +01:00
|
|
|
|
if (devId == 0) //client wants all lights
|
2019-01-09 22:52:42 +01:00
|
|
|
|
{
|
|
|
|
|
EA_DEBUGLN("lAll");
|
|
|
|
|
String jsonTemp = "{";
|
|
|
|
|
for (int i = 0; i<currentDeviceCount; i++)
|
|
|
|
|
{
|
2019-06-20 14:40:12 +02:00
|
|
|
|
jsonTemp += "\"" + String(encodeLightId(i+1)) + "\":";
|
2019-01-09 22:52:42 +01:00
|
|
|
|
jsonTemp += deviceJsonString(i+1);
|
|
|
|
|
if (i < currentDeviceCount-1) jsonTemp += ",";
|
|
|
|
|
}
|
|
|
|
|
jsonTemp += "}";
|
|
|
|
|
server->send(200, "application/json", jsonTemp);
|
2019-03-01 17:10:42 +01:00
|
|
|
|
} else //client wants one light (devId)
|
2019-01-09 22:52:42 +01:00
|
|
|
|
{
|
2019-06-20 14:40:12 +02:00
|
|
|
|
devId = decodeLightId(devId);
|
|
|
|
|
EA_DEBUGLN(devId);
|
|
|
|
|
if (devId > currentDeviceCount)
|
|
|
|
|
{
|
|
|
|
|
server->send(200, "application/json", "{}");
|
|
|
|
|
} else {
|
|
|
|
|
server->send(200, "application/json", deviceJsonString(devId));
|
|
|
|
|
}
|
2019-01-09 22:52:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-10 00:54:35 +01:00
|
|
|
|
//we don't care about other api commands at this time and send empty JSON
|
2019-01-09 22:52:42 +01:00
|
|
|
|
server->send(200, "application/json", "{}");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-01 17:10:42 +01:00
|
|
|
|
//set whether Alexa can discover any devices
|
|
|
|
|
void setDiscoverable(bool d)
|
2019-01-09 22:52:42 +01:00
|
|
|
|
{
|
2019-03-01 17:10:42 +01:00
|
|
|
|
discoverable = d;
|
2019-01-09 22:52:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-01 17:10:42 +01:00
|
|
|
|
//get EspalexaDevice at specific index
|
|
|
|
|
EspalexaDevice* getDevice(uint8_t index)
|
2019-01-09 22:52:42 +01:00
|
|
|
|
{
|
2019-03-01 17:10:42 +01:00
|
|
|
|
if (index >= currentDeviceCount) return nullptr;
|
|
|
|
|
return devices[index];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//is an unique device ID
|
|
|
|
|
String getEscapedMac()
|
|
|
|
|
{
|
|
|
|
|
return escapedMac;
|
2019-01-09 22:52:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-14 19:31:25 +02:00
|
|
|
|
//convert brightness (0-255) to percentage
|
|
|
|
|
uint8_t toPercent(uint8_t bri)
|
|
|
|
|
{
|
|
|
|
|
uint16_t perc = bri * 100;
|
|
|
|
|
return perc / 255;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-09 22:52:42 +01:00
|
|
|
|
~Espalexa(){delete devices;} //note: Espalexa is NOT meant to be destructed
|
|
|
|
|
};
|
|
|
|
|
|
2019-11-10 00:54:35 +01:00
|
|
|
|
#endif
|