Added Alexa Color support
This commit is contained in:
parent
bec745d095
commit
5d1993935e
@ -27,7 +27,7 @@ lib_deps_external =
|
||||
#E131@1.0.0
|
||||
#webserver
|
||||
FastLED@3.2.1
|
||||
NeoPixelBus@2.3.4
|
||||
NeoPixelBus@2.4.1
|
||||
#PubSubClient@2.7
|
||||
#Time@1.5
|
||||
#Timezone@1.2.1
|
||||
|
@ -1,6 +1,6 @@
|
||||
![WLED logo](https://raw.githubusercontent.com/Aircoookie/WLED/master/wled_logo.png)
|
||||
|
||||
## Welcome to my project WLED! (v0.8.2)
|
||||
## Welcome to my project WLED! (v0.8.3-dev)
|
||||
|
||||
A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B) LEDs!
|
||||
|
||||
@ -25,7 +25,7 @@ A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control
|
||||
- E1.31
|
||||
- Hyperion
|
||||
- UDP realtime
|
||||
- Alexa smart device (including dimming)
|
||||
- Alexa voice control (including dimming and color)
|
||||
- Sync to Philips hue lights
|
||||
- Adalight (PC ambilight via serial)
|
||||
- Sync color of multiple WLED devices (UDP notifier)
|
||||
|
@ -24,7 +24,7 @@
|
||||
#else //esp8266
|
||||
//autoselect the right method depending on strip pin
|
||||
#if LEDPIN == 2
|
||||
#define PIXELMETHOD NeoEsp8266Uart800KbpsMethod
|
||||
#define PIXELMETHOD NeoEsp8266Uart1Ws2813Method //if you get an error here, please update to Neopixelbus v2.4.0+
|
||||
#elif LEDPIN == 3
|
||||
#define PIXELMETHOD NeoEsp8266Dma800KbpsMethod
|
||||
#else
|
||||
|
@ -42,6 +42,9 @@
|
||||
#include "WS2812FX.h"
|
||||
#include "palettes.h"
|
||||
|
||||
|
||||
#define LED_SKIP_AMOUNT 1
|
||||
|
||||
void WS2812FX::init(bool supportWhite, uint16_t countPixels, bool skipFirst)
|
||||
{
|
||||
if (supportWhite == _rgbwMode && countPixels == _length && _locked != NULL) return;
|
||||
@ -49,14 +52,19 @@ void WS2812FX::init(bool supportWhite, uint16_t countPixels, bool skipFirst)
|
||||
_rgbwMode = supportWhite;
|
||||
_skipFirstMode = skipFirst;
|
||||
_length = countPixels;
|
||||
if (_skipFirstMode) _length++;
|
||||
|
||||
uint8_t ty = 1;
|
||||
if (supportWhite) ty =2;
|
||||
bus->Begin((NeoPixelType)ty, _length);
|
||||
uint16_t lengthRaw = _length;
|
||||
if (_skipFirstMode) lengthRaw += LED_SKIP_AMOUNT;
|
||||
bus->Begin((NeoPixelType)ty, lengthRaw);
|
||||
|
||||
if (_locked != NULL) delete _locked;
|
||||
_locked = new byte[_length];
|
||||
|
||||
_segments[0].start = 0;
|
||||
_segments[0].stop = _length -1;
|
||||
|
||||
unlockAll();
|
||||
setBrightness(_brightness);
|
||||
_running = true;
|
||||
@ -119,7 +127,12 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
|
||||
}
|
||||
if (!_cronixieMode)
|
||||
{
|
||||
if (_skipFirstMode) {i++;if(i==1)bus->SetPixelColor(i, RgbwColor(0,0,0,0));}
|
||||
if (_skipFirstMode)
|
||||
{
|
||||
if (i < LED_SKIP_AMOUNT) bus->SetPixelColor(i, RgbwColor(0,0,0,0));
|
||||
i += LED_SKIP_AMOUNT;
|
||||
}
|
||||
|
||||
bus->SetPixelColor(i, RgbwColor(r,g,b,w));
|
||||
} else {
|
||||
if(i>6)return;
|
||||
@ -132,27 +145,28 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
|
||||
byte w2 = (_segments[0].colors[1] >>24) & 0xFF;
|
||||
for (int j=o; j< o+19; j++)
|
||||
{
|
||||
bus->SetPixelColor((_skipFirstMode)?j+1:j,RgbwColor(r2,g2,b2,w2));
|
||||
bus->SetPixelColor(j, RgbwColor(r2,g2,b2,w2));
|
||||
}
|
||||
} else
|
||||
{
|
||||
for (int j=o; j< o+19; j++)
|
||||
{
|
||||
bus->SetPixelColor((_skipFirstMode)?j+1:j,RgbwColor(0,0,0,0));
|
||||
bus->SetPixelColor(j, RgbwColor(0,0,0,0));
|
||||
}
|
||||
}
|
||||
if (_skipFirstMode) o += LED_SKIP_AMOUNT;
|
||||
switch(_cronixieDigits[i])
|
||||
{
|
||||
case 0: bus->SetPixelColor((_skipFirstMode)?o+6:o+5,RgbwColor(r,g,b,w)); break;
|
||||
case 1: bus->SetPixelColor((_skipFirstMode)?o+1:o+0,RgbwColor(r,g,b,w)); break;
|
||||
case 2: bus->SetPixelColor((_skipFirstMode)?o+7:o+6,RgbwColor(r,g,b,w)); break;
|
||||
case 3: bus->SetPixelColor((_skipFirstMode)?o+2:o+1,RgbwColor(r,g,b,w)); break;
|
||||
case 4: bus->SetPixelColor((_skipFirstMode)?o+8:o+7,RgbwColor(r,g,b,w)); break;
|
||||
case 5: bus->SetPixelColor((_skipFirstMode)?o+3:o+2,RgbwColor(r,g,b,w)); break;
|
||||
case 6: bus->SetPixelColor((_skipFirstMode)?o+9:o+8,RgbwColor(r,g,b,w)); break;
|
||||
case 7: bus->SetPixelColor((_skipFirstMode)?o+4:o+3,RgbwColor(r,g,b,w)); break;
|
||||
case 8: bus->SetPixelColor((_skipFirstMode)?o+10:o+9,RgbwColor(r,g,b,w)); break;
|
||||
case 9: bus->SetPixelColor((_skipFirstMode)?o+5:o+4,RgbwColor(r,g,b,w)); break;
|
||||
case 0: bus->SetPixelColor(o+5, RgbwColor(r,g,b,w)); break;
|
||||
case 1: bus->SetPixelColor(o+0, RgbwColor(r,g,b,w)); break;
|
||||
case 2: bus->SetPixelColor(o+6, RgbwColor(r,g,b,w)); break;
|
||||
case 3: bus->SetPixelColor(o+1, RgbwColor(r,g,b,w)); break;
|
||||
case 4: bus->SetPixelColor(o+7, RgbwColor(r,g,b,w)); break;
|
||||
case 5: bus->SetPixelColor(o+2, RgbwColor(r,g,b,w)); break;
|
||||
case 6: bus->SetPixelColor(o+8, RgbwColor(r,g,b,w)); break;
|
||||
case 7: bus->SetPixelColor(o+3, RgbwColor(r,g,b,w)); break;
|
||||
case 8: bus->SetPixelColor(o+9, RgbwColor(r,g,b,w)); break;
|
||||
case 9: bus->SetPixelColor(o+4, RgbwColor(r,g,b,w)); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -340,7 +354,7 @@ uint32_t WS2812FX::getColor(void) {
|
||||
uint32_t WS2812FX::getPixelColor(uint16_t i)
|
||||
{
|
||||
if (_reverseMode) i = _length- 1 -i;
|
||||
if (_skipFirstMode) i++;
|
||||
if (_skipFirstMode) i += LED_SKIP_AMOUNT;
|
||||
if (_cronixieMode)
|
||||
{
|
||||
if(i>6)return 0;
|
||||
|
@ -437,7 +437,7 @@ HTTP traffic is unencrypted. An attacker in the same network can intercept form
|
||||
<button type="button" onclick="U()">Manual OTA Update</button><br>
|
||||
Enable ArduinoOTA: <input type="checkbox" name="AO"><br>
|
||||
<h3>About</h3>
|
||||
<a href="https://github.com/Aircoookie/WLED" target="_blank">WLED</a> version 0.8.2<br><br>
|
||||
<a href="https://github.com/Aircoookie/WLED" target="_blank">WLED</a> version 0.8.3<br><br>
|
||||
<a href="https://github.com/Aircoookie/WLED/wiki/Contributors-&-About" target="_blank">Contributors, dependencies and special thanks</a><br>
|
||||
A huge thank you to everyone who helped me create WLED!<br><br>
|
||||
(c) 2016-2018 Christian Schwinne <br>
|
||||
|
486
wled00/src/dependencies/espalexa/Espalexa.h
Normal file
486
wled00/src/dependencies/espalexa/Espalexa.h
Normal file
@ -0,0 +1,486 @@
|
||||
#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
|
||||
* @version 2.3.3
|
||||
* @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
|
||||
|
||||
#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>
|
||||
#include <WebServer.h> //if you get an error here please update to ESP32 arduino core 1.0.0
|
||||
#else
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#endif
|
||||
#endif
|
||||
#include <WiFiUdp.h>
|
||||
|
||||
#ifdef ESPALEXA_DEBUG
|
||||
#pragma message "Espalexa 2.3.3 debug mode"
|
||||
#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"
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
String deviceJsonString(uint8_t deviceId)
|
||||
{
|
||||
if (deviceId < 1 || deviceId > currentDeviceCount) return "{}"; //error
|
||||
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;
|
||||
}
|
||||
|
||||
//Espalexa status page /espalexa
|
||||
void servePage()
|
||||
{
|
||||
EA_DEBUGLN("HTTP Req espalexa ...\n");
|
||||
String res = "Hello from Espalexa!\r\n\r\n";
|
||||
for (int i=0; i<currentDeviceCount; i++)
|
||||
{
|
||||
res += "Value of device " + String(i+1) + " (" + devices[i]->getName() + "): " + 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.3.3 by Christian Schwinne 2019";
|
||||
server->send(200, "text/plain", res);
|
||||
}
|
||||
|
||||
//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>"
|
||||
"<friendlyName>Philips hue ("+ String(s) +")</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>"+ escapedMac +"</serialNumber>"
|
||||
"<UDN>uuid:2f402f80-da50-11e1-9b23-"+ escapedMac +"</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", 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);
|
||||
});
|
||||
serverAsync->on("/espalexa", HTTP_GET, [=](AsyncWebServerRequest *request){server = request; servePage();});
|
||||
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();});
|
||||
}
|
||||
|
||||
server->on("/espalexa", HTTP_GET, [=](){servePage();});
|
||||
server->on("/description.xml", HTTP_GET, [=](){serveDescription();});
|
||||
server->begin();
|
||||
#endif
|
||||
}
|
||||
|
||||
//called when Alexa sends ON command
|
||||
void alexaOn(uint8_t deviceId)
|
||||
{
|
||||
devices[deviceId-1]->setValue(devices[deviceId-1]->getLastValue());
|
||||
devices[deviceId-1]->setPropertyChanged(1);
|
||||
devices[deviceId-1]->doCallback();
|
||||
}
|
||||
|
||||
//called when Alexa sends OFF command
|
||||
void alexaOff(uint8_t deviceId)
|
||||
{
|
||||
devices[deviceId-1]->setValue(0);
|
||||
devices[deviceId-1]->setPropertyChanged(2);
|
||||
devices[deviceId-1]->doCallback();
|
||||
}
|
||||
|
||||
//called when Alexa sends BRI command
|
||||
void alexaDim(uint8_t deviceId, uint8_t briL)
|
||||
{
|
||||
if (briL == 255)
|
||||
{
|
||||
devices[deviceId-1]->setValue(255);
|
||||
} else {
|
||||
devices[deviceId-1]->setValue(briL+1);
|
||||
}
|
||||
devices[deviceId-1]->setPropertyChanged(3);
|
||||
devices[deviceId-1]->doCallback();
|
||||
}
|
||||
|
||||
//called when Alexa sends HUE command
|
||||
void alexaCol(uint8_t deviceId, uint16_t hue, uint8_t sat)
|
||||
{
|
||||
devices[deviceId-1]->setColor(hue, sat);
|
||||
devices[deviceId-1]->setPropertyChanged(4);
|
||||
devices[deviceId-1]->doCallback();
|
||||
}
|
||||
|
||||
//called when Alexa sends CT command (color temperature)
|
||||
void alexaCt(uint8_t deviceId, uint16_t ct)
|
||||
{
|
||||
devices[deviceId-1]->setColor(ct);
|
||||
devices[deviceId-1]->setPropertyChanged(5);
|
||||
devices[deviceId-1]->doCallback();
|
||||
}
|
||||
|
||||
//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();
|
||||
}
|
||||
|
||||
String boolString(bool st)
|
||||
{
|
||||
return(st)?"true":"false";
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
String request = packetBuffer;
|
||||
EA_DEBUGLN(request);
|
||||
if(request.indexOf("M-SEARCH") >= 0) {
|
||||
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;
|
||||
devices[currentDeviceCount] = d;
|
||||
currentDeviceCount++;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool addDevice(String deviceName, CallbackBriFunction 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);
|
||||
}
|
||||
|
||||
bool addDevice(String deviceName, CallbackColFunction 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);
|
||||
}
|
||||
|
||||
//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");
|
||||
|
||||
if (body.indexOf("devicetype") > 0) //client wants a hue api username, we dont care and give static
|
||||
{
|
||||
EA_DEBUGLN("devType");
|
||||
body = "";
|
||||
server->send(200, "application/json", "[{\"success\":{\"username\": \"2WLEDHardQrI3WHYTHoMcXHgEspsM8ZZRpSKtBQr\"}}]");
|
||||
return true;
|
||||
}
|
||||
|
||||
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();
|
||||
EA_DEBUG("ls"); EA_DEBUGLN(tempDeviceId);
|
||||
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;
|
||||
}
|
||||
|
||||
int pos = req.indexOf("lights");
|
||||
if (pos > 0) //client wants light info
|
||||
{
|
||||
int tempDeviceId = req.substring(pos+7).toInt();
|
||||
EA_DEBUG("l"); EA_DEBUGLN(tempDeviceId);
|
||||
|
||||
if (tempDeviceId == 0) //client wants all lights
|
||||
{
|
||||
EA_DEBUGLN("lAll");
|
||||
String jsonTemp = "{";
|
||||
for (int i = 0; i<currentDeviceCount; i++)
|
||||
{
|
||||
jsonTemp += "\"" + String(i+1) + "\":";
|
||||
jsonTemp += deviceJsonString(i+1);
|
||||
if (i < currentDeviceCount-1) jsonTemp += ",";
|
||||
}
|
||||
jsonTemp += "}";
|
||||
server->send(200, "application/json", jsonTemp);
|
||||
} else //client wants one light (tempDeviceId)
|
||||
{
|
||||
server->send(200, "application/json", deviceJsonString(tempDeviceId));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//we dont care about other api commands at this time and send empty JSON
|
||||
server->send(200, "application/json", "{}");
|
||||
return true;
|
||||
}
|
||||
|
||||
//is an unique device ID
|
||||
String getEscapedMac()
|
||||
{
|
||||
return escapedMac;
|
||||
}
|
||||
|
||||
//convert brightness (0-255) to percentage
|
||||
uint8_t toPercent(uint8_t bri)
|
||||
{
|
||||
uint16_t perc = bri * 100;
|
||||
return perc / 255;
|
||||
}
|
||||
|
||||
~Espalexa(){delete devices;} //note: Espalexa is NOT meant to be destructed
|
||||
};
|
||||
|
||||
#endif
|
||||
|
176
wled00/src/dependencies/espalexa/EspalexaDevice.cpp
Normal file
176
wled00/src/dependencies/espalexa/EspalexaDevice.cpp
Normal file
@ -0,0 +1,176 @@
|
||||
//EspalexaDevice Class
|
||||
|
||||
#include "EspalexaDevice.h"
|
||||
|
||||
EspalexaDevice::EspalexaDevice(){}
|
||||
|
||||
EspalexaDevice::EspalexaDevice(String deviceName, CallbackBriFunction gnCallback, uint8_t initialValue) { //constructor
|
||||
|
||||
_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;
|
||||
}
|
||||
|
||||
uint8_t EspalexaDevice::getLastChangedProperty()
|
||||
{
|
||||
return _changed;
|
||||
}
|
||||
|
||||
uint8_t EspalexaDevice::getValue()
|
||||
{
|
||||
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())
|
||||
{
|
||||
//TODO tweak a bit to match hue lamp characteristics
|
||||
//based on https://gist.github.com/paulkaplan/5184275
|
||||
float temp = 10000/ _ct; //kelvins = 1,000,000/mired (and that /100)
|
||||
float r, g, b;
|
||||
|
||||
if( temp <= 66 ){
|
||||
r = 255;
|
||||
g = temp;
|
||||
g = 99.470802 * log(g) - 161.119568;
|
||||
if( temp <= 19){
|
||||
b = 0;
|
||||
} else {
|
||||
b = temp-10;
|
||||
b = 138.517731 * log(b) - 305.044793;
|
||||
}
|
||||
} else {
|
||||
r = temp - 60;
|
||||
r = 329.698727 * pow(r, -0.13320476);
|
||||
g = temp - 60;
|
||||
g = 288.12217 * pow(g, -0.07551485 );
|
||||
b = 255;
|
||||
}
|
||||
|
||||
rgb[0] = (byte)constrain(r,0.1,255.1);
|
||||
rgb[1] = (byte)constrain(g,0.1,255.1);
|
||||
rgb[2] = (byte)constrain(b,0.1,255.1);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
void EspalexaDevice::setPropertyChanged(uint8_t p)
|
||||
{
|
||||
//0: initial 1: on 2: off 3: bri 4: col 5: ct
|
||||
_changed = p;
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
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 != nullptr) ? _callback(_val) : _callbackCol(_val, getColorRGB());
|
||||
}
|
46
wled00/src/dependencies/espalexa/EspalexaDevice.h
Normal file
46
wled00/src/dependencies/espalexa/EspalexaDevice.h
Normal file
@ -0,0 +1,46 @@
|
||||
#ifndef EspalexaDevice_h
|
||||
#define EspalexaDevice_h
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
typedef void (*CallbackBriFunction) (uint8_t br);
|
||||
typedef void (*CallbackColFunction) (uint8_t br, uint32_t col);
|
||||
|
||||
class EspalexaDevice {
|
||||
private:
|
||||
String _deviceName;
|
||||
CallbackBriFunction _callback;
|
||||
CallbackColFunction _callbackCol;
|
||||
uint8_t _val, _val_last, _sat = 0;
|
||||
uint16_t _hue = 0, _ct = 0;
|
||||
uint8_t _changed = 0;
|
||||
|
||||
public:
|
||||
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 getLastChangedProperty();
|
||||
uint8_t getValue();
|
||||
uint16_t getHue();
|
||||
uint8_t getSat();
|
||||
uint16_t getCt();
|
||||
uint32_t getColorRGB();
|
||||
|
||||
void setPropertyChanged(uint8_t p);
|
||||
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)
|
||||
};
|
||||
|
||||
#endif
|
21
wled00/src/dependencies/espalexa/LICENSE
Normal file
21
wled00/src/dependencies/espalexa/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Christian Schwinne
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -16,7 +16,7 @@
|
||||
#else
|
||||
#include <WProgram.h>
|
||||
#endif
|
||||
#include <Time.h> //http://www.arduino.cc/playground/Code/Time
|
||||
#include "../time/Time.h" //http://www.arduino.cc/playground/Code/Time
|
||||
|
||||
//convenient constants for dstRules
|
||||
enum week_t {Last, First, Second, Third, Fourth};
|
||||
|
@ -3,7 +3,7 @@
|
||||
*/
|
||||
/*
|
||||
* @title WLED project sketch
|
||||
* @version 0.8.2
|
||||
* @version 0.8.3
|
||||
* @author Christian Schwinne
|
||||
*/
|
||||
|
||||
@ -60,6 +60,10 @@
|
||||
#include "src/dependencies/time/Time.h"
|
||||
#include "src/dependencies/time/TimeLib.h"
|
||||
#include "src/dependencies/timezone/Timezone.h"
|
||||
#ifndef WLED_DISABLE_ALEXA
|
||||
#define ESPALEXA_MAXDEVICES 1
|
||||
#include "src/dependencies/espalexa/Espalexa.h"
|
||||
#endif
|
||||
#ifndef WLED_DISABLE_BLYNK
|
||||
#include "src/dependencies/blynk/BlynkSimpleEsp.h"
|
||||
#endif
|
||||
@ -74,8 +78,8 @@
|
||||
|
||||
|
||||
//version code in format yymmddb (b = daily build)
|
||||
#define VERSION 1812141
|
||||
char versionString[] = "0.8.2";
|
||||
#define VERSION 1901091
|
||||
char versionString[] = "0.8.3-dev";
|
||||
|
||||
|
||||
//AP and OTA default passwords (for maximum change them!)
|
||||
@ -370,11 +374,11 @@ unsigned long auxStartTime = 0;
|
||||
bool auxActive = false, auxActiveBefore = false;
|
||||
|
||||
//alexa udp
|
||||
WiFiUDP alexaUDP;
|
||||
bool alexaUdpConnected = false;
|
||||
IPAddress ipMulti(239, 255, 255, 250);
|
||||
unsigned int portMulti = 1900;
|
||||
String escapedMac;
|
||||
#ifndef WLED_DISABLE_ALEXA
|
||||
Espalexa espalexa;
|
||||
EspalexaDevice* espalexaDevice;
|
||||
#endif
|
||||
|
||||
//dns server
|
||||
DNSServer dnsServer;
|
||||
@ -465,6 +469,7 @@ void serveMessage(int,String,String,int=255);
|
||||
void reset()
|
||||
{
|
||||
briT = 0;
|
||||
delay(250); //enough time to send response to client
|
||||
setAllLeds();
|
||||
DEBUG_PRINTLN("MODULE RESET");
|
||||
ESP.restart();
|
||||
|
@ -461,7 +461,7 @@ void loadSettingsFromEEPROM(bool first)
|
||||
strip.colorOrder = EEPROM.read(383);
|
||||
irEnabled = EEPROM.read(385);
|
||||
strip.ablMilliampsMax = EEPROM.read(387) + ((EEPROM.read(388) << 8) & 0xFF00);
|
||||
} else if (lastEEPROMversion > 1) //ABL is off by default when updating from version older than 0.8.2
|
||||
} else if (lastEEPROMversion > 1) //ABL is off by default when updating from version older than 0.8.3
|
||||
{
|
||||
strip.ablMilliampsMax = 65000;
|
||||
} else {
|
||||
|
@ -309,6 +309,7 @@ void handleSettingsSet(byte subPage)
|
||||
}
|
||||
saveSettingsToEEPROM();
|
||||
if (subPage == 2) strip.init(useRGBW,ledCount,skipFirstLed);
|
||||
if (subPage == 4) alexaInit();
|
||||
}
|
||||
|
||||
|
||||
|
@ -271,7 +271,7 @@ void getBuildInfo()
|
||||
oappend("\r\nstrip-pin: gpio");
|
||||
oappendi(LEDPIN);
|
||||
oappend("\r\nbrand: wled");
|
||||
oappend("\r\nbuild-type: src\r\n");
|
||||
oappend("\r\nbuild-type: dev\r\n");
|
||||
}
|
||||
|
||||
|
||||
|
@ -19,6 +19,7 @@ void notify(byte callMode, bool followUp=false)
|
||||
case 7: if (!notifyHue) return; break;
|
||||
case 8: if (!notifyDirect) return; break;
|
||||
case 9: if (!notifyDirect) return; break;
|
||||
case 10: if (!notifyAlexa) return; break;
|
||||
default: return;
|
||||
}
|
||||
byte udpOut[WLEDPACKETSIZE];
|
||||
|
@ -36,7 +36,7 @@ void setAllLeds() {
|
||||
}
|
||||
whiteSecT = whiteSec;
|
||||
}
|
||||
if (autoRGBtoRGBW)
|
||||
if (useRGBW && autoRGBtoRGBW)
|
||||
{
|
||||
colorRGBtoRGBW(colT,&whiteT);
|
||||
colorRGBtoRGBW(colSecT,&whiteSecT);
|
||||
@ -87,7 +87,7 @@ bool colorChanged()
|
||||
void colorUpdated(int callMode)
|
||||
{
|
||||
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification)
|
||||
// 6: fx changed 7: hue 8: preset cycle 9: blynk
|
||||
// 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa
|
||||
bool fxChanged = strip.setEffectConfig(effectCurrent, effectSpeed, effectIntensity, effectPalette);
|
||||
if (!colorChanged())
|
||||
{
|
||||
@ -146,6 +146,9 @@ void colorUpdated(int callMode)
|
||||
}
|
||||
|
||||
if (callMode == 8) return;
|
||||
#ifndef WLED_DISABLE_ALEXA
|
||||
if (espalexaDevice != nullptr) espalexaDevice->setValue(bri);
|
||||
#endif
|
||||
//only update Blynk and mqtt every 2 seconds to reduce lag
|
||||
if (millis() - lastInterfaceUpdate <= 2000)
|
||||
{
|
||||
|
@ -12,280 +12,69 @@ void prepareIds() {
|
||||
}
|
||||
|
||||
#ifndef WLED_DISABLE_ALEXA
|
||||
void onAlexaChange(byte b, uint32_t color);
|
||||
|
||||
void alexaInit()
|
||||
{
|
||||
if (alexaEnabled && WiFi.status() == WL_CONNECTED)
|
||||
{
|
||||
alexaUdpConnected = connectUDP();
|
||||
|
||||
if (alexaUdpConnected) alexaInitPages();
|
||||
if (espalexaDevice == nullptr) //only init once
|
||||
{
|
||||
espalexaDevice = new EspalexaDevice(alexaInvocationName, onAlexaChange);
|
||||
espalexa.addDevice(espalexaDevice);
|
||||
espalexa.begin(&server);
|
||||
} else {
|
||||
espalexaDevice->setName(alexaInvocationName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
if (!alexaEnabled || WiFi.status() != WL_CONNECTED) return;
|
||||
espalexa.loop();
|
||||
}
|
||||
|
||||
void alexaOn()
|
||||
void onAlexaChange(byte b, uint32_t color)
|
||||
{
|
||||
if (macroAlexaOn == 0)
|
||||
{
|
||||
handleSet((notifyAlexa)?"win&T=1&IN":"win&T=1&NN&IN");
|
||||
} else
|
||||
{
|
||||
applyMacro(macroAlexaOn);
|
||||
}
|
||||
byte m = espalexaDevice->getLastChangedProperty();
|
||||
|
||||
server.send(200, "application/json", "[{\"success\":{\"/lights/1/state/on\":true}}]");
|
||||
if (m == 1){ //ON
|
||||
if (!macroAlexaOn)
|
||||
{
|
||||
if (bri == 0)
|
||||
{
|
||||
bri = briLast;
|
||||
colorUpdated(10);
|
||||
}
|
||||
} else applyMacro(macroAlexaOn);
|
||||
} else if (m == 2) //OFF
|
||||
{
|
||||
if (!macroAlexaOff)
|
||||
{
|
||||
if (bri > 0)
|
||||
{
|
||||
briLast = bri;
|
||||
bri = 0;
|
||||
colorUpdated(10);
|
||||
}
|
||||
} else applyMacro(macroAlexaOff);
|
||||
} else if (m == 3) //brightness
|
||||
{
|
||||
bri = b;
|
||||
colorUpdated(10);
|
||||
} else //color
|
||||
{
|
||||
col[0] = ((color >> 16) & 0xFF);
|
||||
col[1] = ((color >> 8) & 0xFF);
|
||||
col[2] = (color & 0xFF);
|
||||
if (useRGBW) colorRGBtoRGBW(col,&white);
|
||||
colorUpdated(10);
|
||||
}
|
||||
}
|
||||
|
||||
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("<?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(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
|
||||
|
@ -178,7 +178,9 @@ void initServer()
|
||||
}
|
||||
|
||||
if(!handleSet(server.uri())){
|
||||
if(!handleAlexaApiCall(server.uri(),server.arg(0)))
|
||||
#ifndef WLED_DISABLE_ALEXA
|
||||
if(!espalexa.handleAlexaApiCall(server.uri(),server.arg(0)))
|
||||
#endif
|
||||
server.send(404, "text/plain", "Not Found");
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user