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
alexa support
alexa settings
ntp bug
bri bug
simple slide transition
additional color picker field
implement HSB slider option

View File

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

View File

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

View File

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

View File

@ -35,7 +35,7 @@ void setLedsStandard()
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 (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");
}
}