diff --git a/examples/EspalexaColor/EspalexaColor.ino b/examples/EspalexaColor/EspalexaColor.ino new file mode 100644 index 0000000..3cfbf68 --- /dev/null +++ b/examples/EspalexaColor/EspalexaColor.ino @@ -0,0 +1,96 @@ +/* + * This is a basic example on how to use Espalexa with RGB color devices. + */ +#ifdef ARDUINO_ARCH_ESP32 +#include +#else +#include +#endif + +#include + +// prototypes +boolean connectWifi(); + +//callback function prototype +void colorLightChanged(uint8_t brightness, uint32_t rgb); + +// Change this!! +const char* ssid = "..."; +const char* password = "wifipassword"; + +boolean wifiConnected = false; + +Espalexa espalexa; + +void setup() +{ + Serial.begin(115200); + // Initialise wifi connection + wifiConnected = connectWifi(); + + if(wifiConnected){ + espalexa.addDevice("Light", colorLightChanged); + + espalexa.begin(); + + } else + { + while (1) { + Serial.println("Cannot connect to WiFi. Please check data and reset the ESP."); + delay(2500); + } + } +} + +void loop() +{ + espalexa.loop(); + delay(1); +} + +//the color device callback function has two parameters +void colorLightChanged(uint8_t brightness, uint32_t rgb) { + //do what you need to do here, for example control RGB LED strip + Serial.print("Brightness: "); + Serial.print(brightness); + Serial.print(", Red: "); + Serial.print((rgb >> 16) & 0xFF); //get red component + Serial.print(", Green: "); + Serial.print((rgb >> 8) & 0xFF); //get green + Serial.print(", Blue: "); + Serial.println(rgb & 0xFF); //get blue +} + +// connect to wifi – returns true if successful or false if not +boolean connectWifi(){ + boolean state = true; + int i = 0; + + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + Serial.println(""); + Serial.println("Connecting to WiFi"); + + // Wait for connection + Serial.print("Connecting..."); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + if (i > 40){ + state = false; break; + } + i++; + } + Serial.println(""); + if (state){ + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + } + else { + Serial.println("Connection failed."); + } + return state; +} diff --git a/library.properties b/library.properties index 990f8bb..dcce767 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Espalexa -version=2.1.2 +version=2.2.0 author=Christian Schwinne maintainer=Christian Schwinne sentence=Library to control an ESP module with the Alexa voice assistant diff --git a/readme.md b/readme.md index 7f2f21a..1f40b8b 100644 --- a/readme.md +++ b/readme.md @@ -4,12 +4,16 @@ Now compatible with both ESP8266 and ESP32! #### What does this do similar projects like Fauxmo don't already? -It allows you to set a ranged value (e.g. Brightness) additionally to standard on/off control. -You can say "Alexa, turn the light to 75%". +It allows you to set a ranged value (e.g. Brightness, Temperature) additionally to standard on/off control. +For example, you can say "Alexa, turn the light to 75% / 21 degrees". +Alexa finally added color support to the local API! You can see how to add color devices in the EspalexaColor example. +Then, you can say "Alexa, turn the light to Blue". Color temperature (white shades) is also supported, but still a WIP. + If you just need On/Off (eg. for a relay) I'd recommend [arduino-esp8266-alexa-wemo-switch](https://github.com/kakopappa/arduino-esp8266-alexa-wemo-switch) instead. Additionally, it's possible to add up to a total of 20 devices. Each device has a brightness range from 0 to 255, where 0 is off and 255 is fully on. +You can get a percentage from that value using `espalexa.toPercent(brightness)` #### How do I install the library? diff --git a/src/Espalexa.cpp b/src/Espalexa.cpp index d8ba913..d3a9f68 100644 --- a/src/Espalexa.cpp +++ b/src/Espalexa.cpp @@ -7,19 +7,10 @@ */ /* * @title Espalexa library - * @version 2.1.2 + * @version 2.2.0 * @author Christian Schwinne * @license MIT */ -//#define DEBUG_ESPALEXA - -#ifdef DEBUG_ESPALEXA - #define DEBUG(x) Serial.print (x) - #define DEBUGLN(x) Serial.println (x) -#else - #define DEBUG(x) - #define DEBUGLN(x) -#endif #ifdef ARDUINO_ARCH_ESP32 #include "dependencies/webserver/WebServer.h" //https://github.com/bbx10/WebServer_tng @@ -31,11 +22,9 @@ #include #include "Espalexa.h" -#define MAXDEVICES 20 //this limit only has memory reasons, set it higher should you need to - uint8_t currentDeviceCount = 0; -EspalexaDevice* devices[MAXDEVICES] = {}; +EspalexaDevice* devices[ESPALEXA_MAXDEVICES] = {}; //Keep in mind that Device IDs go from 1 to DEVICES, cpp arrays from 0 to DEVICES-1!! WiFiUDP UDP; @@ -47,7 +36,7 @@ String escapedMac=""; //lowercase mac address Espalexa* instance; Espalexa::Espalexa() { //constructor - instance = this; + instance = this; } Espalexa::~Espalexa(){/*nothing to destruct*/} @@ -58,72 +47,98 @@ bool Espalexa::begin(WebServer* externalServer) bool Espalexa::begin(ESP8266WebServer* externalServer) #endif { - DEBUGLN("Espalexa Begin..."); - escapedMac = WiFi.macAddress(); - escapedMac.replace(":", ""); - escapedMac.toLowerCase(); - - server = externalServer; - udpConnected = connectUDP(); + DEBUGLN("Espalexa Begin..."); + DEBUG("MAXDEVICES "); + DEBUGLN(ESPALEXA_MAXDEVICES); + escapedMac = WiFi.macAddress(); + escapedMac.replace(":", ""); + escapedMac.toLowerCase(); + + server = externalServer; + udpConnected = connectUDP(); + + if (udpConnected){ - if (udpConnected){ - - startHttpServer(); - DEBUGLN("Done"); - return true; - } - DEBUGLN("Failed"); - return false; + startHttpServer(); + DEBUGLN("Done"); + return true; + } + DEBUGLN("Failed"); + return false; } void Espalexa::loop() { - server->handleClient(); - - if(udpConnected){ - int packetSize = UDP.parsePacket(); - - if(packetSize) { - DEBUGLN("Got UDP!"); - int len = UDP.read(packetBuffer, 254); - if (len > 0) { - packetBuffer[len] = 0; - } + server->handleClient(); + + if(udpConnected){ + int packetSize = UDP.parsePacket(); + + if(packetSize) { + DEBUGLN("Got UDP!"); + int len = UDP.read(packetBuffer, 254); + if (len > 0) { + packetBuffer[len] = 0; + } - String request = packetBuffer; - DEBUGLN(request); - if(request.indexOf("M-SEARCH") >= 0) { - if(request.indexOf("upnp:rootdevice") > 0 || request.indexOf("device:basic:1") > 0) { - DEBUGLN("Responding search req..."); - respondToSearch(); - } + String request = packetBuffer; + DEBUGLN(request); + if(request.indexOf("M-SEARCH") >= 0) { + if(request.indexOf("upnp:rootdevice") > 0 || request.indexOf("device:basic:1") > 0) { + DEBUGLN("Responding search req..."); + respondToSearch(); } } } + } } bool Espalexa::addDevice(EspalexaDevice* d) { - DEBUG("Adding device "); - DEBUGLN((currentDeviceCount+1)); - if (currentDeviceCount >= MAXDEVICES) return false; - devices[currentDeviceCount] = d; - currentDeviceCount++; - return true; + DEBUG("Adding device "); + DEBUGLN((currentDeviceCount+1)); + if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return false; + devices[currentDeviceCount] = d; + currentDeviceCount++; + return true; } bool Espalexa::addDevice(String deviceName, CallbackBriFunction callback, uint8_t initialValue) { - DEBUG("Constructing device "); - DEBUGLN((currentDeviceCount+1)); - if (currentDeviceCount >= MAXDEVICES) return false; - EspalexaDevice* d = new EspalexaDevice(deviceName, callback, initialValue); - return addDevice(d); + DEBUG("Constructing device "); + DEBUGLN((currentDeviceCount+1)); + if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return false; + EspalexaDevice* d = new EspalexaDevice(deviceName, callback, initialValue); + return addDevice(d); +} + +bool Espalexa::addDevice(String deviceName, CallbackColFunction callback, uint8_t initialValue) +{ + DEBUG("Constructing device "); + DEBUGLN((currentDeviceCount+1)); + if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return false; + EspalexaDevice* d = new EspalexaDevice(deviceName, callback, initialValue); + return addDevice(d); } String Espalexa::deviceJsonString(uint8_t deviceId) { if (deviceId < 1 || deviceId > currentDeviceCount) return "{}"; //error - return "{\"type\":\"Extended color light\",\"manufacturername\":\"OpenSource\",\"swversion\":\"0.1\",\"name\":\""+ devices[deviceId-1]->getName() +"\",\"uniqueid\":\""+ WiFi.macAddress() +"-"+ (deviceId+1) +"\",\"modelid\":\"LST001\",\"state\":{\"on\":"+ boolString(devices[deviceId-1]->getValue()) +",\"bri\":"+ (String)(devices[deviceId-1]->getLastValue()-1) +",\"xy\":[0.00000,0.00000],\"colormode\":\"hs\",\"effect\":\"none\",\"ct\":500,\"hue\":0,\"sat\":0,\"alert\":\"none\",\"reachable\":true}}"; + EspalexaDevice* dev = devices[deviceId-1]; + String json = "{\"type\":\""; + json += dev->isColorDevice() ? "Extended color light" : "Dimmable light"; + json += "\",\"manufacturername\":\"OpenSource\",\"swversion\":\"0.1\",\"name\":\""; + json += dev->getName(); + json += "\",\"uniqueid\":\""+ WiFi.macAddress() +"-"+ (deviceId+1) ; + json += "\",\"modelid\":\"LST001\",\"state\":{\"on\":"; + json += boolString(dev->getValue()) +",\"bri\":"+ (String)(dev->getLastValue()-1) ; + if (dev->isColorDevice()) + { + json += ",\"xy\":[0.00000,0.00000],\"colormode\":\""; + json += (dev->isColorTemperatureMode()) ? "ct":"hs"; + json += "\",\"effect\":\"none\",\"ct\":" + (String)(dev->getCt()) + ",\"hue\":" + (String)(dev->getHue()) + ",\"sat\":" + (String)(dev->getSat()); + } + json +=",\"alert\":\"none\",\"reachable\":true}}"; + return json; } bool Espalexa::handleAlexaApiCall(String req, String body) //basic implementation of Philips hue api functions needed for basic Alexa control @@ -141,10 +156,14 @@ bool Espalexa::handleAlexaApiCall(String req, String body) //basic implementatio if (req.indexOf("state") > 0) //client wants to control light { + server->send(200, "application/json", "[{\"success\":true}]"); //short valid response + int tempDeviceId = req.substring(req.indexOf("lights")+7).toInt(); DEBUG("ls"); DEBUGLN(tempDeviceId); - if (body.indexOf("bri")>0) {alexaDim(tempDeviceId, body.substring(body.indexOf("bri") +5).toInt()); return true;} if (body.indexOf("false")>0) {alexaOff(tempDeviceId); return true;} + if (body.indexOf("bri")>0 ) {alexaDim(tempDeviceId, body.substring(body.indexOf("bri") +5).toInt()); return true;} + if (body.indexOf("hue")>0 ) {alexaCol(tempDeviceId, body.substring(body.indexOf("hue") +5).toInt(), body.substring(body.indexOf("sat") +5).toInt()); return true;} + if (body.indexOf("ct") >0 ) {alexaCt (tempDeviceId, body.substring(body.indexOf("ct") +4).toInt()); return true;} alexaOn(tempDeviceId); return true; @@ -183,149 +202,150 @@ bool Espalexa::handleAlexaApiCall(String req, String body) //basic implementatio void servePage() { - DEBUGLN("HTTP Req espalexa ...\n"); - String res = "Hello from Espalexa!\r\n\r\n"; - for (int i=0; igetName() + "): " + String(devices[i]->getValue()) + "\r\n"; - } - res += "\r\nFree Heap: " + (String)ESP.getFreeHeap(); - res += "\r\nUptime: " + (String)millis(); - res += "\r\n\r\nEspalexa library V2.1.2 by Christian Schwinne 2018"; - instance->server->send(200, "text/plain", res); + DEBUGLN("HTTP Req espalexa ...\n"); + String res = "Hello from Espalexa!\r\n\r\n"; + for (int i=0; igetName() + "): " + String(devices[i]->getValue()) + "\r\n"; + } + res += "\r\nFree Heap: " + (String)ESP.getFreeHeap(); + res += "\r\nUptime: " + (String)millis(); + res += "\r\n\r\nEspalexa library V2.2.0 by Christian Schwinne 2019"; + instance->server->send(200, "text/plain", res); } void serveNotFound() { - DEBUGLN("Not-Found HTTP call:"); - DEBUGLN("URI: " + instance->server->uri()); - DEBUGLN("Body: " + instance->server->arg(0)); - if(!instance->handleAlexaApiCall(instance->server->uri(),instance->server->arg(0))) - instance->server->send(404, "text/plain", "Not Found (espalexa-internal)"); + DEBUGLN("Not-Found HTTP call:"); + DEBUGLN("URI: " + instance->server->uri()); + DEBUGLN("Body: " + instance->server->arg(0)); + if(!instance->handleAlexaApiCall(instance->server->uri(),instance->server->arg(0))) + instance->server->send(404, "text/plain", "Not Found (espalexa-internal)"); } void serveDescription() { - 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 = "" - "" - "10" - "http://"+ String(s) +":80/" - "" - "urn:schemas-upnp-org:device:Basic:1" - "Philips hue ("+ String(s) +")" - "Royal Philips Electronics" - "http://www.philips.com" - "Philips hue Personal Wireless Lighting" - "Philips hue bridge 2012" - "929000226503" - "http://www.meethue.com" - ""+ escapedMac +"" - "uuid:2f402f80-da50-11e1-9b23-"+ escapedMac +"" - "index.html" - "" - " " - " image/png" - " 48" - " 48" - " 24" - " hue_logo_0.png" - " " - " " - " image/png" - " 120" - " 120" - " 24" - " hue_logo_3.png" - " " - "" - "" - ""; - - instance->server->send(200, "text/xml", setup_xml.c_str()); + 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 = "" + "" + "10" + "http://"+ String(s) +":80/" + "" + "urn:schemas-upnp-org:device:Basic:1" + "Philips hue ("+ String(s) +")" + "Royal Philips Electronics" + "http://www.philips.com" + "Philips hue Personal Wireless Lighting" + "Philips hue bridge 2012" + "929000226503" + "http://www.meethue.com" + ""+ escapedMac +"" + "uuid:2f402f80-da50-11e1-9b23-"+ escapedMac +"" + "index.html" + "" + " " + " image/png" + " 48" + " 48" + " 24" + " hue_logo_0.png" + " " + " " + " image/png" + " 120" + " 120" + " 24" + " hue_logo_3.png" + " " + "" + "" + ""; - DEBUG("Sending :"); - DEBUGLN(setup_xml); + instance->server->send(200, "text/xml", setup_xml.c_str()); + + DEBUG("Sending :"); + DEBUGLN(setup_xml); } void Espalexa::startHttpServer() { - if (server == nullptr) { - #ifdef ARDUINO_ARCH_ESP32 - server = new WebServer(80); - #else - server = new ESP8266WebServer(80); - #endif - server->onNotFound(serveNotFound); - } - - server->on("/espalexa", HTTP_GET, servePage); - - server->on("/description.xml", HTTP_GET, serveDescription); - - server->begin(); + if (server == nullptr) { + #ifdef ARDUINO_ARCH_ESP32 + server = new WebServer(80); + #else + server = new ESP8266WebServer(80); + #endif + server->onNotFound(serveNotFound); + } + + server->on("/espalexa", HTTP_GET, servePage); + + server->on("/description.xml", HTTP_GET, serveDescription); + + server->begin(); } void Espalexa::alexaOn(uint8_t deviceId) { - String body = "[{\"success\":{\"/lights/"+ String(deviceId) +"/state/on\":true}}]"; - - server->send(200, "text/xml", body.c_str()); - devices[deviceId-1]->setValue(devices[deviceId-1]->getLastValue()); devices[deviceId-1]->doCallback(); } void Espalexa::alexaOff(uint8_t deviceId) { - String body = "[{\"success\":{\"/lights/"+ String(deviceId) +"/state/on\":false}}]"; - - server->send(200, "application/json", body.c_str()); devices[deviceId-1]->setValue(0); devices[deviceId-1]->doCallback(); } void Espalexa::alexaDim(uint8_t deviceId, uint8_t briL) { - String body = "[{\"success\":{\"/lights/"+ String(deviceId) +"/state/bri\":"+ String(briL) +"}}]"; - - server->send(200, "application/json", body.c_str()); - if (briL == 255) { - devices[deviceId-1]->setValue(255); + devices[deviceId-1]->setValue(255); } else { - devices[deviceId-1]->setValue(briL+1); + devices[deviceId-1]->setValue(briL+1); } devices[deviceId-1]->doCallback(); } +void Espalexa::alexaCol(uint8_t deviceId, uint16_t hue, uint8_t sat) +{ + devices[deviceId-1]->setColor(hue, sat); + devices[deviceId-1]->doCallback(); +} + +void Espalexa::alexaCt(uint8_t deviceId, uint16_t ct) +{ + devices[deviceId-1]->setColor(ct); + devices[deviceId-1]->doCallback(); +} + void Espalexa::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"; - - UDP.beginPacket(UDP.remoteIP(), UDP.remotePort()); - #ifdef ARDUINO_ARCH_ESP32 - UDP.write((uint8_t*)response.c_str(), response.length()); - #else - UDP.write(response.c_str()); - #endif - UDP.endPacket(); + 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"; + + UDP.beginPacket(UDP.remoteIP(), UDP.remotePort()); + #ifdef ARDUINO_ARCH_ESP32 + UDP.write((uint8_t*)response.c_str(), response.length()); + #else + UDP.write(response.c_str()); + #endif + UDP.endPacket(); } String Espalexa::boolString(bool st) @@ -341,50 +361,163 @@ bool Espalexa::connectUDP(){ #endif } +uint8_t Espalexa::toPercent(uint8_t bri) +{ + uint16_t perc = bri * 100; + return perc / 255; +} + //EspalexaDevice Class EspalexaDevice::EspalexaDevice(){} EspalexaDevice::EspalexaDevice(String deviceName, CallbackBriFunction gnCallback, uint8_t initialValue) { //constructor - - _deviceName = deviceName; - _callback = gnCallback; - _val = initialValue; - _val_last = _val; + + _deviceName = deviceName; + _callback = gnCallback; + _val = initialValue; + _val_last = _val; +} + +EspalexaDevice::EspalexaDevice(String deviceName, CallbackColFunction gnCallback, uint8_t initialValue) { //constructor for color device + + _deviceName = deviceName; + _callbackCol = gnCallback; + _callback = nullptr; + _val = initialValue; + _val_last = _val; } EspalexaDevice::~EspalexaDevice(){/*nothing to destruct*/} +bool EspalexaDevice::isColorDevice() +{ + //if brightness-only callback is null, we have color device + return (_callback == nullptr); +} + +bool EspalexaDevice::isColorTemperatureMode() +{ + return _ct; +} + String EspalexaDevice::getName() { - return _deviceName; + return _deviceName; } uint8_t EspalexaDevice::getValue() { - return _val; + return _val; +} + +uint16_t EspalexaDevice::getHue() +{ + return _hue; +} + +uint8_t EspalexaDevice::getSat() +{ + return _sat; +} + +uint16_t EspalexaDevice::getCt() +{ + if (_ct == 0) return 500; + return _ct; +} + +uint32_t EspalexaDevice::getColorRGB() +{ + uint8_t rgb[3]; + + if (isColorTemperatureMode()) + { + //this is only an approximation using WS2812B with gamma correction enabled + //TODO replace with better formula + if (_ct > 475) { + rgb[0]=255;rgb[1]=199;rgb[2]=92;//500 + } else if (_ct > 425) { + rgb[0]=255;rgb[1]=213;rgb[2]=118;//450 + } else if (_ct > 375) { + rgb[0]=255;rgb[1]=216;rgb[2]=118;//400 + } else if (_ct > 325) { + rgb[0]=255;rgb[1]=234;rgb[2]=140;//350 + } else if (_ct > 275) { + rgb[0]=255;rgb[1]=243;rgb[2]=160;//300 + } else if (_ct > 225) { + rgb[0]=250;rgb[1]=255;rgb[2]=188;//250 + } else if (_ct > 175) { + rgb[0]=247;rgb[1]=255;rgb[2]=215;//200 + } else { + rgb[0]=237;rgb[1]=255;rgb[2]=239;//150 + } + } else { //hue + sat mode + float h = ((float)_hue)/65535.0; + float s = ((float)_sat)/255.0; + byte i = floor(h*6); + float f = h * 6-i; + float p = 255 * (1-s); + float q = 255 * (1-f*s); + float t = 255 * (1-(1-f)*s); + switch (i%6) { + case 0: rgb[0]=255,rgb[1]=t,rgb[2]=p;break; + case 1: rgb[0]=q,rgb[1]=255,rgb[2]=p;break; + case 2: rgb[0]=p,rgb[1]=255,rgb[2]=t;break; + case 3: rgb[0]=p,rgb[1]=q,rgb[2]=255;break; + case 4: rgb[0]=t,rgb[1]=p,rgb[2]=255;break; + case 5: rgb[0]=255,rgb[1]=p,rgb[2]=q; + } + } + return ((rgb[0] << 16) | (rgb[1] << 8) | (rgb[2])); } uint8_t EspalexaDevice::getLastValue() { - if (_val_last == 0) return 255; - return _val_last; + if (_val_last == 0) return 255; + return _val_last; +} + +//you need to re-discover the device for the Alexa name to change +void EspalexaDevice::setName(String name) +{ + _deviceName = name; } void EspalexaDevice::setValue(uint8_t val) { - if (_val != 0) - { - _val_last = _val; - } - if (val != 0) - { - _val_last = val; - } - _val = val; + if (_val != 0) + { + _val_last = _val; + } + if (val != 0) + { + _val_last = val; + } + _val = val; +} + +void EspalexaDevice::setPercent(uint8_t perc) +{ + uint16_t val = perc * 255; + val /= 100; + if (val > 255) val = 255; + setValue(val); +} + +void EspalexaDevice::setColor(uint16_t hue, uint8_t sat) +{ + _hue = hue; + _sat = sat; + _ct = 0; +} + +void EspalexaDevice::setColor(uint16_t ct) +{ + _ct = ct; } void EspalexaDevice::doCallback() { - _callback(_val); + (_callback != nullptr) ? _callback(_val) : _callbackCol(_val, getColorRGB()); } \ No newline at end of file diff --git a/src/Espalexa.h b/src/Espalexa.h index 443233f..0bad0a0 100644 --- a/src/Espalexa.h +++ b/src/Espalexa.h @@ -10,56 +10,88 @@ #include #endif +#define ESPALEXA_MAXDEVICES 20 //this limit only has memory reasons, set it higher should you need to + +//#define ESPALEXA_DEBUG + +#ifdef ESPALEXA_DEBUG + #define DEBUG(x) Serial.print (x) + #define DEBUGLN(x) Serial.println (x) +#else + #define DEBUG(x) + #define DEBUGLN(x) +#endif + typedef void (*CallbackBriFunction) (uint8_t br); +typedef void (*CallbackColFunction) (uint8_t br, uint32_t col); class EspalexaDevice { private: - String _deviceName; - CallbackBriFunction _callback; - uint8_t _val, _val_last; + String _deviceName; + CallbackBriFunction _callback; + CallbackColFunction _callbackCol; + uint8_t _val, _val_last, _sat; + uint16_t _hue, _ct; + public: - EspalexaDevice(); - ~EspalexaDevice(); - EspalexaDevice(String deviceName, CallbackBriFunction gnCallback, uint8_t initialValue =0); - - String getName(); - uint8_t getValue(); - - void setValue(uint8_t bri); - - void doCallback(); - - uint8_t getLastValue(); //last value that was not off (1-255) + EspalexaDevice(); + ~EspalexaDevice(); + EspalexaDevice(String deviceName, CallbackBriFunction gnCallback, uint8_t initialValue =0); + EspalexaDevice(String deviceName, CallbackColFunction gnCallback, uint8_t initialValue =0); + + bool isColorDevice(); + bool isColorTemperatureMode(); + String getName(); + uint8_t getValue(); + uint16_t getHue(); + uint8_t getSat(); + uint16_t getCt(); + uint32_t getColorRGB(); + + void setValue(uint8_t bri); + void setPercent(uint8_t perc); + void setName(String name); + void setColor(uint16_t hue, uint8_t sat); + void setColor(uint16_t ct); + + void doCallback(); + + uint8_t getLastValue(); //last value that was not off (1-255) }; class Espalexa { private: - void startHttpServer(); - String deviceJsonString(uint8_t deviceId); - void handleDescriptionXml(); - void respondToSearch(); - String boolString(bool st); - bool connectUDP(); - void alexaOn(uint8_t deviceId); - void alexaOff(uint8_t deviceId); - void alexaDim(uint8_t deviceId, uint8_t briL); + void startHttpServer(); + String deviceJsonString(uint8_t deviceId); + void handleDescriptionXml(); + void respondToSearch(); + String boolString(bool st); + bool connectUDP(); + void alexaOn(uint8_t deviceId); + void alexaOff(uint8_t deviceId); + void alexaDim(uint8_t deviceId, uint8_t briL); + void alexaCol(uint8_t deviceId, uint16_t hue, uint8_t sat); + void alexaCt(uint8_t deviceId, uint16_t ct); public: - Espalexa(); - ~Espalexa(); - #ifdef ARDUINO_ARCH_ESP32 - WebServer* server; - bool begin(WebServer* externalServer=nullptr); - #else - ESP8266WebServer* server; - bool begin(ESP8266WebServer* externalServer=nullptr); - #endif + Espalexa(); + ~Espalexa(); + #ifdef ARDUINO_ARCH_ESP32 + WebServer* server; + bool begin(WebServer* externalServer=nullptr); + #else + ESP8266WebServer* server; + bool begin(ESP8266WebServer* externalServer=nullptr); + #endif - bool addDevice(EspalexaDevice* d); - bool addDevice(String deviceName, CallbackBriFunction callback, uint8_t initialValue=0); + bool addDevice(EspalexaDevice* d); + bool addDevice(String deviceName, CallbackBriFunction callback, uint8_t initialValue=0); + bool addDevice(String deviceName, CallbackColFunction callback, uint8_t initialValue=0); + + uint8_t toPercent(uint8_t bri); - void loop(); - - bool handleAlexaApiCall(String req, String body); + void loop(); + + bool handleAlexaApiCall(String req, String body); }; #endif