Added Alexa support

This commit is contained in:
cschwinne 2017-02-21 23:59:47 +01:00
parent bdaa8f22b8
commit 255cef8685
12 changed files with 435 additions and 7 deletions

View File

@ -1,7 +1,6 @@
captive portal for ap captive portal for ap
alexa support alexa settings
ntp bug ntp bug
bri bug
simple slide transition simple slide transition
additional color picker field additional color picker field
implement HSB slider option implement HSB slider option

View File

@ -16,7 +16,8 @@ Additions for V0.3 (nearly complete!)
- Support for power pushbutton - Support for power pushbutton
- Full OTA software update capability - Full OTA software update capability
- Password protected OTA page for added security (OTA lock) - Password protected OTA page for added security (OTA lock)
- NTP and experimental analog clock function - Alexa smart home device server
- (not working) NTP and experimental analog clock function
Compile settings: Compile settings:
Board: WeMos D1 mini Board: WeMos D1 mini
@ -66,6 +67,14 @@ Add one or multiple of the following parameters after the base url to change val
("&I=<0-255>&I2=<0-255>" experimental individual LED range control) ("&I=<0-255>&I2=<0-255>" experimental individual LED range control)
Licensed under the MIT license
Uses libraries:
ESP8266 Arduino Core
WS2812FX by kitesurfer1404 (Aircoookie fork)
Timezone library by JChristensen
arduino-esp8266-alexa-multiple-wemo-switch by kakopappa
Software update procedure: Software update procedure:
Method 1: Reflash the new update source via USB. Method 1: Reflash the new update source via USB.

View File

@ -0,0 +1,8 @@
#ifndef CALLBACKFUNCTION_H
#define CALLBACKFUNCTION_H
#include <Arduino.h>
typedef void (*CallbackFunction) ();
#endif

201
wled00/Switch.cpp Normal file
View File

@ -0,0 +1,201 @@
#include "Switch.h"
#include "CallbackFunction.h"
//<<constructor>>
Switch::Switch(){
Serial.println("default constructor called");
}
//Switch::Switch(String alexaInvokeName,unsigned int port){
Switch::Switch(String alexaInvokeName, unsigned int port, CallbackFunction oncb, CallbackFunction offcb){
uint32_t chipId = ESP.getChipId();
char uuid[64];
sprintf_P(uuid, PSTR("38323636-4558-4dda-9188-cda0e6%02x%02x%02x"),
(uint16_t) ((chipId >> 16) & 0xff),
(uint16_t) ((chipId >> 8) & 0xff),
(uint16_t) chipId & 0xff);
serial = String(uuid);
persistent_uuid = "Socket-1_0-" + serial+"-"+ String(port);
device_name = alexaInvokeName;
localPort = port;
onCallback = oncb;
offCallback = offcb;
startWebServer();
}
//<<destructor>>
Switch::~Switch(){/*nothing to destruct*/}
void Switch::serverLoop(){
if (server != NULL) {
server->handleClient();
delay(1);
}
}
void Switch::startWebServer(){
server = new ESP8266WebServer(localPort);
server->on("/", [&]() {
handleRoot();
});
server->on("/setup.xml", [&]() {
handleSetupXml();
});
server->on("/upnp/control/basicevent1", [&]() {
handleUpnpControl();
});
server->on("/eventservice.xml", [&]() {
handleEventservice();
});
//server->onNotFound(handleNotFound);
server->begin();
Serial.println("WebServer started on port: ");
Serial.println(localPort);
}
void Switch::handleEventservice(){
Serial.println(" ########## Responding to eventservice.xml ... ########\n");
String eventservice_xml = "<?scpd xmlns=\"urn:Belkin:service-1-0\"?>"
"<actionList>"
"<action>"
"<name>SetBinaryState</name>"
"<argumentList>"
"<argument>"
"<retval/>"
"<name>BinaryState</name>"
"<relatedStateVariable>BinaryState</relatedStateVariable>"
"<direction>in</direction>"
"</argument>"
"</argumentList>"
"<serviceStateTable>"
"<stateVariable sendEvents=\"yes\">"
"<name>BinaryState</name>"
"<dataType>Boolean</dataType>"
"<defaultValue>0</defaultValue>"
"</stateVariable>"
"<stateVariable sendEvents=\"yes\">"
"<name>level</name>"
"<dataType>string</dataType>"
"<defaultValue>0</defaultValue>"
"</stateVariable>"
"</serviceStateTable>"
"</action>"
"</scpd>\r\n"
"\r\n";
server->send(200, "text/plain", eventservice_xml.c_str());
}
void Switch::handleUpnpControl(){
Serial.println("########## Responding to /upnp/control/basicevent1 ... ##########");
//for (int x=0; x <= HTTP.args(); x++) {
// Serial.println(HTTP.arg(x));
//}
String request = server->arg(0);
Serial.print("request:");
Serial.println(request);
if(request.indexOf("<BinaryState>1</BinaryState>") > 0) {
Serial.println("Got Turn on request");
onCallback();
}
if(request.indexOf("<BinaryState>0</BinaryState>") > 0) {
Serial.println("Got Turn off request");
offCallback();
}
server->send(200, "text/plain", "");
}
void Switch::handleRoot(){
server->send(200, "text/plain", "You should tell Alexa to discover devices");
}
void Switch::handleSetupXml(){
Serial.println(" ########## Responding to setup.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>"
"<device>"
"<deviceType>urn:Belkin:device:controllee:1</deviceType>"
"<friendlyName>"+ device_name +"</friendlyName>"
"<manufacturer>Belkin International Inc.</manufacturer>"
"<modelName>Emulated Socket</modelName>"
"<modelNumber>3.1415</modelNumber>"
"<UDN>uuid:"+ persistent_uuid +"</UDN>"
"<serialNumber>221517K0101769</serialNumber>"
"<binaryState>0</binaryState>"
"<serviceList>"
"<service>"
"<serviceType>urn:Belkin:service:basicevent:1</serviceType>"
"<serviceId>urn:Belkin:serviceId:basicevent1</serviceId>"
"<controlURL>/upnp/control/basicevent1</controlURL>"
"<eventSubURL>/upnp/event/basicevent1</eventSubURL>"
"<SCPDURL>/eventservice.xml</SCPDURL>"
"</service>"
"</serviceList>"
"</device>"
"</root>\r\n"
"\r\n";
server->send(200, "text/xml", setup_xml.c_str());
Serial.print("Sending :");
Serial.println(setup_xml);
}
String Switch::getAlexaInvokeName() {
return device_name;
}
void Switch::respondToSearch(IPAddress& senderIP, unsigned int senderPort) {
Serial.println("");
Serial.print("Sending response to ");
Serial.println(senderIP);
Serial.print("Port : ");
Serial.println(senderPort);
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"
"CACHE-CONTROL: max-age=86400\r\n"
"DATE: Sat, 26 Nov 2016 04:56:29 GMT\r\n"
"EXT:\r\n"
"LOCATION: http://" + String(s) + ":" + String(localPort) + "/setup.xml\r\n"
"OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n"
"01-NLS: b9200ebb-736d-4b93-bf03-835149d13983\r\n"
"SERVER: Unspecified, UPnP/1.0, Unspecified\r\n"
"ST: urn:Belkin:device:**\r\n"
"USN: uuid:" + persistent_uuid + "::urn:Belkin:device:**\r\n"
"X-User-Agent: redsonic\r\n\r\n";
UDP.beginPacket(senderIP, senderPort);
UDP.write(response.c_str());
UDP.endPacket();
Serial.println("Response sent !");
}

36
wled00/Switch.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef SWITCH_H
#define SWITCH_H
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <WiFiUDP.h>
#include "CallbackFunction.h"
class Switch {
private:
ESP8266WebServer *server = NULL;
WiFiUDP UDP;
String serial;
String persistent_uuid;
String device_name;
unsigned int localPort;
CallbackFunction onCallback;
CallbackFunction offCallback;
void startWebServer();
void handleEventservice();
void handleUpnpControl();
void handleRoot();
void handleSetupXml();
public:
Switch();
Switch(String alexaInvokeName, unsigned int port, CallbackFunction onCallback, CallbackFunction offCallback);
~Switch();
String getAlexaInvokeName();
void serverLoop();
void respondToSearch(IPAddress& senderIP, unsigned int senderPort);
};
#endif

View File

@ -0,0 +1,86 @@
#include "UpnpBroadcastResponder.h"
#include "Switch.h"
#include <functional>
// Multicast declarations
IPAddress ipMulti(239, 255, 255, 250);
const unsigned int portMulti = 1900;
char packetBuffer[512];
#define MAX_SWITCHES 14
Switch switches[MAX_SWITCHES] = {};
int numOfSwitchs = 0;
//#define numOfSwitchs (sizeof(switches)/sizeof(Switch)) //array size
//<<constructor>>
UpnpBroadcastResponder::UpnpBroadcastResponder(){
}
//<<destructor>>
UpnpBroadcastResponder::~UpnpBroadcastResponder(){/*nothing to destruct*/}
bool UpnpBroadcastResponder::beginUdpMulticast(){
boolean state = false;
Serial.println("Begin multicast ..");
if(UDP.beginMulticast(WiFi.localIP(), ipMulti, portMulti)) {
Serial.print("Udp multicast server started at ");
Serial.print(ipMulti);
Serial.print(":");
Serial.println(portMulti);
state = true;
}
else{
Serial.println("Connection failed");
}
return state;
}
//Switch *ptrArray;
void UpnpBroadcastResponder::addDevice(Switch& device) {
Serial.print("Adding switch : ");
Serial.print(device.getAlexaInvokeName());
Serial.print(" index : ");
Serial.println(numOfSwitchs);
switches[numOfSwitchs] = device;
numOfSwitchs++;
}
void UpnpBroadcastResponder::serverLoop(){
int packetSize = UDP.parsePacket();
if (packetSize <= 0)
return;
IPAddress senderIP = UDP.remoteIP();
unsigned int senderPort = UDP.remotePort();
// read the packet into the buffer
UDP.read(packetBuffer, packetSize);
// check if this is a M-SEARCH for WeMo device
String request = String((char *)packetBuffer);
if(request.indexOf('M-SEARCH') > 0) {
if(request.indexOf("urn:Belkin:device:**") > 0) {
Serial.println("Got UDP Belkin Request..");
// int arrSize = sizeof(switchs) / sizeof(Switch);
for(int n = 0; n < numOfSwitchs; n++) {
Switch &sw = switches[n];
if (&sw != NULL) {
sw.respondToSearch(senderIP, senderPort);
}
}
}
}
}

View File

@ -0,0 +1,20 @@
#ifndef UPNPBROADCASTRESPONDER_H
#define UPNPBROADCASTRESPONDER_H
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <WiFiUDP.h>
#include "Switch.h"
class UpnpBroadcastResponder {
private:
WiFiUDP UDP;
public:
UpnpBroadcastResponder();
~UpnpBroadcastResponder();
bool beginUdpMulticast();
void serverLoop();
void addDevice(Switch& device);
};
#endif

View File

@ -17,6 +17,9 @@
#include <Timezone.h> #include <Timezone.h>
#include "htmls00.h" #include "htmls00.h"
#include "htmls01.h" #include "htmls01.h"
#include "switch.h"
#include "UpnpBroadcastResponder.h"
#include "CallbackFunction.h"
//to toggle usb serial debug (un)comment following line //to toggle usb serial debug (un)comment following line
#define DEBUG #define DEBUG
@ -37,7 +40,7 @@
* @author Christian Schwinne * @author Christian Schwinne
*/ */
//Hardware-settings (only changeble via code) //Hardware-settings (only changeble via code)
#define LEDCOUNT 11 #define LEDCOUNT 9
#define MAXDIRECT 52 //for direct access like arls, should be >= LEDCOUNT #define MAXDIRECT 52 //for direct access like arls, should be >= LEDCOUNT
uint8_t buttonPin = 0; //needs pull-up uint8_t buttonPin = 0; //needs pull-up
uint8_t auxPin = 15; //use e.g. for external relay uint8_t auxPin = 15; //use e.g. for external relay
@ -99,9 +102,14 @@ boolean overlayReverse = true;
uint8_t overlaySpeed = 200; uint8_t overlaySpeed = 200;
boolean useGammaCorrectionBri = true; boolean useGammaCorrectionBri = true;
boolean useGammaCorrectionRGB = true; boolean useGammaCorrectionRGB = true;
int arlsOffset = -21; //10: -22 assuming arls52 int arlsOffset = -22; //10: -22 assuming arls52
boolean realtimeEnabled = true; boolean realtimeEnabled = true;
//alexa
boolean alexaEnabled = true;
String alexaInvocationName = "Schloss";
boolean alexaNotify = false;
double transitionResolution = 0.011; double transitionResolution = 0.011;
//Internal vars //Internal vars
@ -153,6 +161,10 @@ uint8_t auxTime = 0;
unsigned long auxStartTime; unsigned long auxStartTime;
boolean auxActive, auxActiveBefore; boolean auxActive, auxActiveBefore;
//alexa
Switch *alexa = NULL;
UpnpBroadcastResponder upnpBroadcastResponder;
ESP8266WebServer server(80); ESP8266WebServer server(80);
ESP8266HTTPUpdateServer httpUpdater; ESP8266HTTPUpdateServer httpUpdater;
WiFiUDP notifierUdp; WiFiUDP notifierUdp;
@ -220,6 +232,7 @@ void loop() {
handleButton(); handleButton();
handleNetworkTime(); handleNetworkTime();
handleOverlays(); handleOverlays();
handleAlexa();
strip.service(); strip.service();
//DEBUG //DEBUG

View File

@ -305,7 +305,17 @@ boolean handleSet(String req)
} }
} }
} }
pos = req.indexOf("IN");
if (pos < 1)
{
XML_response(); XML_response();
}
pos = req.indexOf("NN");
if (pos > 0)
{
colorUpdated(5);
return true;
}
if (effectUpdated) if (effectUpdated)
{ {
colorUpdated(6); colorUpdated(6);

View File

@ -131,6 +131,8 @@ void wledInit()
DEBUG_PRINTLN("HTTP server started"); DEBUG_PRINTLN("HTTP server started");
// Add service to MDNS // Add service to MDNS
MDNS.addService("http", "tcp", 80); MDNS.addService("http", "tcp", 80);
//Init alexa service
alexaInit();
// Initialize NeoPixel Strip // Initialize NeoPixel Strip
strip.init(); strip.init();
strip.setMode(effectCurrent); strip.setMode(effectCurrent);

View File

@ -35,7 +35,7 @@ void setLedsStandard()
void colorUpdated(int callMode) void colorUpdated(int callMode)
{ {
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (no not.) 6: fx changed //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (no not.) (NN)6: fx changed
if (col[0] == col_it[0] && col[1] == col_it[1] && col[2] == col_it[2] && bri == bri_it) if (col[0] == col_it[0] && col[1] == col_it[1] && col[2] == col_it[2] && bri == bri_it)
{ {
if (callMode == 6) notify(6); if (callMode == 6) notify(6);

44
wled00/wled12_alexa.ino Normal file
View File

@ -0,0 +1,44 @@
void alexaOn();
void alexaOff();
void alexaInit()
{
if (alexaEnabled && WiFi.status() == WL_CONNECTED)
{
upnpBroadcastResponder.beginUdpMulticast();
alexa = new Switch(alexaInvocationName, 81, alexaOn, alexaOff);
upnpBroadcastResponder.addDevice(*alexa);
}
}
void handleAlexa()
{
if (alexaEnabled && WiFi.status() == WL_CONNECTED)
{
upnpBroadcastResponder.serverLoop();
alexa->serverLoop();
}
}
void alexaOn()
{
if (alexaNotify)
{
handleSet("win&T=1&IN");
} else
{
handleSet("win&T=1&NN&IN");
}
}
void alexaOff()
{
if (alexaNotify)
{
handleSet("win&T=0&IN");
} else
{
handleSet("win&T=0&NN&IN");
}
}