2017-01-28 13:41:01 +00:00
|
|
|
/*
|
|
|
|
Copyright (c) 2017 Heiko Krupp and Theo Arends. All rights reserved.
|
|
|
|
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
|
|
|
|
- Redistributions of source code must retain the above copyright notice,
|
|
|
|
this list of conditions and the following disclaimer.
|
|
|
|
- Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
this list of conditions and the following disclaimer in the documentation
|
|
|
|
and/or other materials provided with the distribution.
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
|
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
2017-01-29 20:36:12 +00:00
|
|
|
#ifdef USE_EMULATION
|
2017-01-28 13:41:01 +00:00
|
|
|
|
|
|
|
#define UDP_BUFFER_SIZE 200 // Max UDP buffer size needed for M-SEARCH message
|
|
|
|
|
|
|
|
boolean udpConnected = false;
|
|
|
|
|
|
|
|
char packetBuffer[UDP_BUFFER_SIZE]; // buffer to hold incoming UDP packet
|
|
|
|
IPAddress ipMulticast(239, 255, 255, 250); // Simple Service Discovery Protocol (SSDP)
|
|
|
|
uint32_t portMulticast = 1900; // Multicast address and port
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* WeMo UPNP support routines
|
|
|
|
\*********************************************************************************************/
|
|
|
|
const char WEMO_MSEARCH[] PROGMEM =
|
|
|
|
"HTTP/1.1 200 OK\r\n"
|
|
|
|
"CACHE-CONTROL: max-age=86400\r\n"
|
|
|
|
"DATE: Fri, 15 Apr 2016 04:56:29 GMT\r\n"
|
|
|
|
"EXT:\r\n"
|
|
|
|
"LOCATION: http://{r1}:80/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:{r2}::urn:Belkin:device:**\r\n"
|
|
|
|
"X-User-Agent: redsonic\r\n"
|
|
|
|
"\r\n";
|
|
|
|
|
|
|
|
String wemo_serial()
|
|
|
|
{
|
2017-02-04 16:09:54 +00:00
|
|
|
char serial[16];
|
2017-04-25 17:24:42 +01:00
|
|
|
|
2017-02-04 16:09:54 +00:00
|
|
|
snprintf_P(serial, sizeof(serial), PSTR("201612K%08X"), ESP.getChipId());
|
2017-01-28 13:41:01 +00:00
|
|
|
return String(serial);
|
|
|
|
}
|
|
|
|
|
|
|
|
String wemo_UUID()
|
|
|
|
{
|
2017-02-04 16:09:54 +00:00
|
|
|
char uuid[27];
|
2017-04-25 17:24:42 +01:00
|
|
|
|
2017-01-28 13:41:01 +00:00
|
|
|
snprintf_P(uuid, sizeof(uuid), PSTR("Socket-1_0-%s"), wemo_serial().c_str());
|
|
|
|
return String(uuid);
|
|
|
|
}
|
|
|
|
|
|
|
|
void wemo_respondToMSearch()
|
|
|
|
{
|
2017-04-25 17:24:42 +01:00
|
|
|
char message[TOPSZ];
|
|
|
|
char log[LOGSZ];
|
2017-01-28 13:41:01 +00:00
|
|
|
|
|
|
|
if (portUDP.beginPacket(portUDP.remoteIP(), portUDP.remotePort())) {
|
|
|
|
String response = FPSTR(WEMO_MSEARCH);
|
|
|
|
response.replace("{r1}", WiFi.localIP().toString());
|
|
|
|
response.replace("{r2}", wemo_UUID());
|
|
|
|
portUDP.write(response.c_str());
|
|
|
|
portUDP.endPacket();
|
|
|
|
snprintf_P(message, sizeof(message), PSTR("Response sent"));
|
|
|
|
} else {
|
|
|
|
snprintf_P(message, sizeof(message), PSTR("Failed to send response"));
|
|
|
|
}
|
|
|
|
snprintf_P(log, sizeof(log), PSTR("UPnP: Wemo %s to %s:%d"),
|
|
|
|
message, portUDP.remoteIP().toString().c_str(), portUDP.remotePort());
|
|
|
|
addLog(LOG_LEVEL_DEBUG, log);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Hue Bridge UPNP support routines
|
|
|
|
* Need to send 3 response packets with varying ST and USN
|
|
|
|
\*********************************************************************************************/
|
|
|
|
const char HUE_RESPONSE[] PROGMEM =
|
|
|
|
"HTTP/1.0 200 OK\r\n"
|
|
|
|
"HOST: 239.255.255.250:1900\r\n"
|
|
|
|
"CACHE-CONTROL: max-age=100\r\n"
|
|
|
|
"EXT:\r\n"
|
|
|
|
"LOCATION: http://{r1}:80/description.xml\r\n"
|
|
|
|
"SERVER: FreeRTOS/7.4.2 UPnP/1.0 IpBridge/1.15.0\r\n"
|
|
|
|
"hue-bridgeid: {r2}\r\n";
|
|
|
|
const char HUE_ST1[] PROGMEM =
|
|
|
|
"ST: upnp:rootdevice\r\n";
|
|
|
|
const char HUE_USN1[] PROGMEM =
|
|
|
|
"USN: uuid:{r3}::upnp:rootdevice\r\n"
|
|
|
|
"\r\n";
|
|
|
|
|
|
|
|
const char HUE_ST2[] PROGMEM =
|
|
|
|
"ST: uuid:{r3}\r\n";
|
|
|
|
const char HUE_USN2[] PROGMEM =
|
|
|
|
"USN: uuid:{r3}\r\n"
|
|
|
|
"\r\n";
|
|
|
|
|
|
|
|
const char HUE_ST3[] PROGMEM =
|
|
|
|
"ST: urn:schemas-upnp-org:device:basic:1\r\n";
|
|
|
|
|
|
|
|
String hue_bridgeid()
|
|
|
|
{
|
|
|
|
char bridgeid[16];
|
2017-04-25 17:24:42 +01:00
|
|
|
|
2017-01-28 13:41:01 +00:00
|
|
|
snprintf_P(bridgeid, sizeof(bridgeid), PSTR("5CCF7FFFFE%03X"), ESP.getChipId());
|
|
|
|
return String(bridgeid);
|
|
|
|
}
|
|
|
|
|
|
|
|
String hue_UUID()
|
|
|
|
{
|
|
|
|
char serial[36];
|
2017-04-25 17:24:42 +01:00
|
|
|
|
2017-01-28 13:41:01 +00:00
|
|
|
snprintf_P(serial, sizeof(serial), PSTR("f6543a06-da50-11ba-8d8f-5ccf7f%03x"), ESP.getChipId());
|
|
|
|
return String(serial);
|
|
|
|
}
|
|
|
|
|
|
|
|
void hue_respondToMSearch()
|
|
|
|
{
|
2017-04-25 17:24:42 +01:00
|
|
|
char message[TOPSZ];
|
|
|
|
char log[LOGSZ];
|
2017-01-28 13:41:01 +00:00
|
|
|
|
|
|
|
if (portUDP.beginPacket(portUDP.remoteIP(), portUDP.remotePort())) {
|
|
|
|
String response = FPSTR(HUE_RESPONSE);
|
2017-04-25 17:24:42 +01:00
|
|
|
String response_st = FPSTR(HUE_ST1);
|
|
|
|
String response_usn = FPSTR(HUE_USN1);
|
2017-01-28 13:41:01 +00:00
|
|
|
response += response_st + response_usn;
|
|
|
|
response.replace("{r1}", WiFi.localIP().toString());
|
|
|
|
response.replace("{r2}", hue_bridgeid());
|
|
|
|
response.replace("{r3}", hue_UUID());
|
|
|
|
portUDP.write(response.c_str());
|
|
|
|
portUDP.endPacket();
|
|
|
|
// addLog(LOG_LEVEL_DEBUG_MORE, response.c_str());
|
|
|
|
|
|
|
|
response = FPSTR(HUE_RESPONSE);
|
2017-04-25 17:24:42 +01:00
|
|
|
response_st = FPSTR(HUE_ST2);
|
|
|
|
response_usn = FPSTR(HUE_USN2);
|
2017-01-28 13:41:01 +00:00
|
|
|
response += response_st + response_usn;
|
|
|
|
response.replace("{r1}", WiFi.localIP().toString());
|
|
|
|
response.replace("{r2}", hue_bridgeid());
|
|
|
|
response.replace("{r3}", hue_UUID());
|
|
|
|
portUDP.write(response.c_str());
|
|
|
|
portUDP.endPacket();
|
|
|
|
// addLog(LOG_LEVEL_DEBUG_MORE, response.c_str());
|
|
|
|
|
|
|
|
response = FPSTR(HUE_RESPONSE);
|
2017-04-25 17:24:42 +01:00
|
|
|
response_st = FPSTR(HUE_ST3);
|
2017-01-28 13:41:01 +00:00
|
|
|
response += response_st + response_usn;
|
|
|
|
response.replace("{r1}", WiFi.localIP().toString());
|
|
|
|
response.replace("{r2}", hue_bridgeid());
|
|
|
|
response.replace("{r3}", hue_UUID());
|
|
|
|
portUDP.write(response.c_str());
|
|
|
|
portUDP.endPacket();
|
2017-02-04 16:09:54 +00:00
|
|
|
|
|
|
|
snprintf_P(message, sizeof(message), PSTR("3 response packets sent"));
|
2017-01-28 13:41:01 +00:00
|
|
|
// addLog(LOG_LEVEL_DEBUG_MORE, response.c_str());
|
|
|
|
|
|
|
|
} else {
|
|
|
|
snprintf_P(message, sizeof(message), PSTR("Failed to send response"));
|
|
|
|
}
|
|
|
|
snprintf_P(log, sizeof(log), PSTR("UPnP: HUE %s to %s:%d"),
|
|
|
|
message, portUDP.remoteIP().toString().c_str(), portUDP.remotePort());
|
|
|
|
addLog(LOG_LEVEL_DEBUG, log);
|
|
|
|
}
|
|
|
|
|
|
|
|
/********************************************************************************************/
|
|
|
|
|
|
|
|
boolean UDP_Disconnect()
|
|
|
|
{
|
|
|
|
if (udpConnected) {
|
|
|
|
WiFiUDP::stopAll();
|
|
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("UPnP: Multicast disabled"));
|
|
|
|
udpConnected = false;
|
|
|
|
}
|
|
|
|
return udpConnected;
|
|
|
|
}
|
|
|
|
|
|
|
|
boolean UDP_Connect()
|
|
|
|
{
|
|
|
|
if (!udpConnected) {
|
|
|
|
if (portUDP.beginMulticast(WiFi.localIP(), ipMulticast, portMulticast)) {
|
|
|
|
addLog_P(LOG_LEVEL_INFO, PSTR("UPnP: Multicast (re)joined"));
|
|
|
|
udpConnected = true;
|
|
|
|
} else {
|
|
|
|
addLog_P(LOG_LEVEL_INFO, PSTR("UPnP: Multicast join failed"));
|
|
|
|
udpConnected = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return udpConnected;
|
|
|
|
}
|
|
|
|
|
|
|
|
void pollUDP()
|
|
|
|
{
|
|
|
|
if (udpConnected) {
|
|
|
|
if (portUDP.parsePacket()) {
|
|
|
|
int len = portUDP.read(packetBuffer, UDP_BUFFER_SIZE -1);
|
2017-04-25 17:24:42 +01:00
|
|
|
if (len > 0) {
|
|
|
|
packetBuffer[len] = 0;
|
|
|
|
}
|
2017-01-28 13:41:01 +00:00
|
|
|
String request = packetBuffer;
|
2017-05-11 16:47:34 +01:00
|
|
|
|
|
|
|
// addLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet received"));
|
2017-01-28 13:41:01 +00:00
|
|
|
// addLog_P(LOG_LEVEL_DEBUG_MORE, packetBuffer);
|
2017-05-11 16:47:34 +01:00
|
|
|
|
2017-01-28 13:41:01 +00:00
|
|
|
if (request.indexOf("M-SEARCH") >= 0) {
|
2017-05-03 17:19:13 +01:00
|
|
|
if ((EMUL_WEMO == sysCfg.flag.emulation) &&(request.indexOf("urn:Belkin:device:**") > 0)) {
|
2017-01-28 13:41:01 +00:00
|
|
|
wemo_respondToMSearch();
|
|
|
|
}
|
2017-05-03 17:19:13 +01:00
|
|
|
else if ((EMUL_HUE == sysCfg.flag.emulation) && ((request.indexOf("ST: urn:schemas-upnp-org:device:basic:1") > 0) || (request.indexOf("ST: upnp:rootdevice") > 0) || (request.indexOf("ST: ssdp:all") > 0))) {
|
2017-01-28 13:41:01 +00:00
|
|
|
hue_respondToMSearch();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-02-21 17:14:33 +00:00
|
|
|
|
|
|
|
#ifdef USE_WEBSERVER
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Web server additions
|
|
|
|
\*********************************************************************************************/
|
|
|
|
const char WEMO_EVENTSERVICE_XML[] PROGMEM =
|
|
|
|
"<?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";
|
|
|
|
const char WEMO_SETUP_XML[] PROGMEM =
|
|
|
|
"<?xml version=\"1.0\"?>"
|
|
|
|
"<root>"
|
|
|
|
"<device>"
|
|
|
|
"<deviceType>urn:Belkin:device:controllee:1</deviceType>"
|
|
|
|
"<friendlyName>{x1}</friendlyName>"
|
|
|
|
"<manufacturer>Belkin International Inc.</manufacturer>"
|
|
|
|
"<modelName>Sonoff Socket</modelName>"
|
|
|
|
"<modelNumber>3.1415</modelNumber>"
|
|
|
|
"<UDN>uuid:{x2}</UDN>"
|
|
|
|
"<serialNumber>{x3}</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";
|
|
|
|
const char HUE_DESCRIPTION_XML[] PROGMEM =
|
|
|
|
"<?xml version=\"1.0\"?>"
|
|
|
|
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
|
|
|
|
"<specVersion>"
|
|
|
|
"<major>1</major>"
|
|
|
|
"<minor>0</minor>"
|
|
|
|
"</specVersion>"
|
|
|
|
"<URLBase>http://{x1}/</URLBase>"
|
|
|
|
"<device>"
|
|
|
|
"<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>"
|
|
|
|
"<friendlyName>Amazon-Echo-HA-Bridge ({x1})</friendlyName>"
|
|
|
|
"<manufacturer>Royal Philips Electronics</manufacturer>"
|
|
|
|
"<modelName>Philips hue bridge 2012</modelName>"
|
|
|
|
"<modelNumber>929000226503</modelNumber>"
|
|
|
|
"<UDN>uuid:{x2}</UDN>"
|
|
|
|
"</device>"
|
|
|
|
"</root>\r\n"
|
|
|
|
"\r\n";
|
|
|
|
const char HUE_LIGHT_STATUS_JSON[] PROGMEM =
|
|
|
|
"{\"state\":"
|
|
|
|
"{\"on\":{state},"
|
|
|
|
"\"bri\":{b},"
|
|
|
|
"\"hue\":{h},"
|
|
|
|
"\"sat\":{s},"
|
|
|
|
"\"effect\":\"none\","
|
|
|
|
"\"ct\":0,"
|
|
|
|
"\"alert\":\"none\","
|
|
|
|
"\"colormode\":\"hs\","
|
|
|
|
"\"reachable\":true"
|
|
|
|
"},"
|
|
|
|
"\"type\":\"Dimmable light\","
|
|
|
|
"\"name\":\"{j1}\","
|
|
|
|
"\"modelid\":\"LWB004\","
|
|
|
|
"\"manufacturername\":\"Philips\","
|
|
|
|
"\"uniqueid\":\"{j2}\","
|
|
|
|
"\"swversion\":\"66012040\""
|
|
|
|
"}";
|
|
|
|
const char HUE_LIGHT_RESPONSE_JSON[] PROGMEM =
|
|
|
|
"{\"success\":{\"{api}/{id}/{cmd}\":{res}}}";
|
|
|
|
const char HUE_CONFIG_RESPONSE_JSON[] PROGMEM =
|
|
|
|
"{\"name\":\"Philips hue\","
|
|
|
|
"\"mac\":\"{mac}\","
|
|
|
|
"\"dhcp\":true,"
|
|
|
|
"\"ipaddress\":\"{ip}\","
|
|
|
|
"\"netmask\":\"{mask}\","
|
|
|
|
"\"gateway\":\"{gw}\","
|
|
|
|
"\"proxyaddress\":\"\","
|
|
|
|
"\"proxyport\":0,"
|
|
|
|
"\"UTC\":\"{dt}\","
|
|
|
|
"\"whitelist\":{\"{id}\":{"
|
|
|
|
"\"last use date\":\"{dt}\","
|
|
|
|
"\"create date\":\"{dt}\","
|
|
|
|
"\"name\":\"Remote\"}},"
|
|
|
|
"\"swversion\":\"01036659\","
|
|
|
|
"\"apiversion\":\"1.16.0\","
|
|
|
|
"\"swupdate\":{\"updatestate\":0,\"url\":\"\",\"text\":\"\",\"notify\": false},"
|
|
|
|
"\"linkbutton\":false,"
|
|
|
|
"\"portalservices\":false"
|
|
|
|
"}";
|
|
|
|
const char HUE_ERROR_JSON[] PROGMEM =
|
|
|
|
"[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]";
|
|
|
|
|
|
|
|
void handleUPnPevent()
|
|
|
|
{
|
|
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle WeMo basic event"));
|
|
|
|
|
|
|
|
String request = webServer->arg(0);
|
2017-04-25 17:24:42 +01:00
|
|
|
if (request.indexOf("State>1</Binary") > 0) {
|
|
|
|
do_cmnd_power(1, 1);
|
|
|
|
}
|
|
|
|
if (request.indexOf("State>0</Binary") > 0) {
|
|
|
|
do_cmnd_power(1, 0);
|
|
|
|
}
|
2017-05-11 16:47:34 +01:00
|
|
|
webServer->send(200, F("text/plain"), "");
|
2017-02-21 17:14:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void handleUPnPservice()
|
|
|
|
{
|
|
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle WeMo event service"));
|
2017-05-11 16:47:34 +01:00
|
|
|
webServer->send_P(200, PSTR("text/plain"), WEMO_EVENTSERVICE_XML);
|
2017-02-21 17:14:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void handleUPnPsetupWemo()
|
|
|
|
{
|
|
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle WeMo setup"));
|
|
|
|
|
|
|
|
String setup_xml = FPSTR(WEMO_SETUP_XML);
|
|
|
|
setup_xml.replace("{x1}", sysCfg.friendlyname[0]);
|
|
|
|
setup_xml.replace("{x2}", wemo_UUID());
|
|
|
|
setup_xml.replace("{x3}", wemo_serial());
|
2017-05-11 16:47:34 +01:00
|
|
|
webServer->send(200, F("text/xml"), setup_xml);
|
2017-02-21 17:14:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/********************************************************************************************/
|
|
|
|
|
|
|
|
String hue_deviceId(uint8_t id)
|
|
|
|
{
|
|
|
|
char deviceid[16];
|
|
|
|
|
|
|
|
snprintf_P(deviceid, sizeof(deviceid), PSTR("5CCF7F%03X-%0d"), ESP.getChipId(), id);
|
|
|
|
return String(deviceid);
|
|
|
|
}
|
|
|
|
|
|
|
|
void handleUPnPsetupHue()
|
|
|
|
{
|
|
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle Hue Bridge setup"));
|
|
|
|
|
|
|
|
String description_xml = FPSTR(HUE_DESCRIPTION_XML);
|
|
|
|
description_xml.replace("{x1}", WiFi.localIP().toString());
|
|
|
|
description_xml.replace("{x2}", hue_UUID());
|
2017-05-11 16:47:34 +01:00
|
|
|
webServer->send(200, F("text/xml"), description_xml);
|
2017-02-21 17:14:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void hue_todo(String *path)
|
|
|
|
{
|
|
|
|
char log[LOGSZ];
|
|
|
|
|
|
|
|
snprintf_P(log, sizeof(log), PSTR("HTTP: HUE API not implemented (%s)"),path->c_str());
|
|
|
|
addLog(LOG_LEVEL_DEBUG_MORE, log);
|
|
|
|
}
|
|
|
|
|
|
|
|
void hue_config_response(String *response)
|
|
|
|
{
|
|
|
|
*response += FPSTR(HUE_CONFIG_RESPONSE_JSON);
|
|
|
|
response->replace("{mac}", WiFi.macAddress());
|
|
|
|
response->replace("{ip}", WiFi.localIP().toString());
|
|
|
|
response->replace("{mask}", WiFi.subnetMask().toString());
|
|
|
|
response->replace("{gw}", WiFi.gatewayIP().toString());
|
2017-03-08 15:20:45 +00:00
|
|
|
response->replace("{dt}", getDateTime());
|
2017-02-21 17:14:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void hue_global_cfg(String *path)
|
|
|
|
{
|
|
|
|
String response;
|
|
|
|
|
|
|
|
path->remove(0,1); // cut leading / to get <id>
|
|
|
|
response = "{\"lights\":{\"";
|
2017-04-25 17:24:42 +01:00
|
|
|
for (uint8_t i = 1; i <= Maxdevice; i++) {
|
2017-02-21 17:14:33 +00:00
|
|
|
response += i;
|
|
|
|
response += "\":";
|
|
|
|
response += FPSTR(HUE_LIGHT_STATUS_JSON);
|
2017-04-25 17:24:42 +01:00
|
|
|
if (i < Maxdevice) {
|
|
|
|
response += ",\"";
|
|
|
|
}
|
2017-02-21 17:14:33 +00:00
|
|
|
response.replace("{state}", (power & (0x01 << (i-1))) ? "true" : "false");
|
|
|
|
response.replace("{j1}", sysCfg.friendlyname[i-1]);
|
|
|
|
response.replace("{j2}", hue_deviceId(i));
|
|
|
|
if (pin[GPIO_WS2812] < 99) {
|
|
|
|
#ifdef USE_WS2812
|
|
|
|
ws2812_replaceHSB(&response);
|
|
|
|
#endif // USE_WS2812
|
2017-04-25 17:24:42 +01:00
|
|
|
} else {
|
2017-02-21 17:14:33 +00:00
|
|
|
response.replace("{h}", "0");
|
|
|
|
response.replace("{s}", "0");
|
|
|
|
response.replace("{b}", "0");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
response += F("},\"groups\":{},\"schedules\":{},\"config\":");
|
|
|
|
|
|
|
|
hue_config_response(&response);
|
|
|
|
response.replace("{id}", *path);
|
|
|
|
response += "}";
|
2017-05-11 16:47:34 +01:00
|
|
|
webServer->send(200, F("application/json"), response);
|
2017-02-21 17:14:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void hue_auth(String *path)
|
|
|
|
{
|
|
|
|
char response[38];
|
|
|
|
|
|
|
|
snprintf_P(response, sizeof(response), PSTR("[{\"success\":{\"username\":\"%03x\"}}]"), ESP.getChipId());
|
2017-05-11 16:47:34 +01:00
|
|
|
webServer->send(200, F("application/json"), response);
|
2017-02-21 17:14:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void hue_config(String *path)
|
|
|
|
{
|
|
|
|
String response = "";
|
|
|
|
|
|
|
|
path->remove(0,1); // cut leading / to get <id>
|
|
|
|
hue_config_response(&response);
|
|
|
|
response.replace("{id}", *path);
|
2017-05-11 16:47:34 +01:00
|
|
|
webServer->send(200, F("application/json"), response);
|
2017-02-21 17:14:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void hue_lights(String *path)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* http://sonoff/api/username/lights/1/state?1={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
|
|
|
|
*/
|
|
|
|
String response;
|
|
|
|
uint8_t device = 1;
|
|
|
|
uint16_t tmp=0;
|
|
|
|
int16_t pos = 0;
|
|
|
|
float bri = 0;
|
|
|
|
float hue = 0;
|
|
|
|
float sat = 0;
|
|
|
|
bool on = false;
|
|
|
|
bool change = false;
|
|
|
|
char id[4];
|
|
|
|
|
|
|
|
path->remove(0,path->indexOf("/lights")); // Remove until /lights
|
|
|
|
if (path->endsWith("/lights")) { // Got /lights
|
|
|
|
response = "{\"";
|
|
|
|
for (uint8_t i = 1; i <= Maxdevice; i++) {
|
|
|
|
response += i;
|
|
|
|
response += "\":";
|
|
|
|
response += FPSTR(HUE_LIGHT_STATUS_JSON);
|
2017-04-25 17:24:42 +01:00
|
|
|
if (i < Maxdevice) {
|
|
|
|
response += ",\"";
|
|
|
|
}
|
2017-02-21 17:14:33 +00:00
|
|
|
response.replace("{state}", (power & (0x01 << (i-1))) ? "true" : "false");
|
|
|
|
response.replace("{j1}", sysCfg.friendlyname[i-1]);
|
|
|
|
response.replace("{j2}", hue_deviceId(i));
|
|
|
|
if (pin[GPIO_WS2812] < 99) {
|
|
|
|
#ifdef USE_WS2812
|
|
|
|
ws2812_replaceHSB(&response);
|
|
|
|
#endif // USE_WS2812
|
|
|
|
} else {
|
|
|
|
response.replace("{h}", "0");
|
|
|
|
response.replace("{s}", "0");
|
|
|
|
response.replace("{b}", "0");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
response += "}";
|
2017-05-11 16:47:34 +01:00
|
|
|
webServer->send(200, F("application/json"), response);
|
2017-02-21 17:14:33 +00:00
|
|
|
}
|
|
|
|
else if (path->endsWith("/state")) { // Got ID/state
|
|
|
|
path->remove(0,8); // Remove /lights/
|
|
|
|
path->remove(path->indexOf("/state")); // Remove /state
|
|
|
|
device = atoi(path->c_str());
|
2017-04-25 17:24:42 +01:00
|
|
|
if ((device < 1) || (device > Maxdevice)) {
|
|
|
|
device = 1;
|
|
|
|
}
|
2017-02-21 17:14:33 +00:00
|
|
|
response = "[";
|
|
|
|
response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
|
|
|
|
response.replace("{api}", "/lights");
|
|
|
|
response.replace("{id}", String(device));
|
|
|
|
response.replace("{cmd}", "state/on");
|
2017-04-25 17:24:42 +01:00
|
|
|
if (1 == webServer->args()) {
|
2017-02-21 17:14:33 +00:00
|
|
|
StaticJsonBuffer<400> jsonBuffer;
|
|
|
|
JsonObject &hue_json = jsonBuffer.parseObject(webServer->arg(0));
|
|
|
|
if (hue_json.containsKey("on")) {
|
|
|
|
on = hue_json["on"];
|
|
|
|
switch(on)
|
|
|
|
{
|
|
|
|
case false : do_cmnd_power(device, 0);
|
|
|
|
response.replace("{res}", "false");
|
|
|
|
break;
|
|
|
|
case true : do_cmnd_power(device, 1);
|
|
|
|
response.replace("{res}", "true");
|
|
|
|
break;
|
|
|
|
default : response.replace("{res}", (power & (0x01 << (device-1))) ? "true" : "false");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#ifdef USE_WS2812
|
|
|
|
ws2812_getHSB(&hue,&sat,&bri);
|
|
|
|
if (hue_json.containsKey("bri")) {
|
|
|
|
tmp = hue_json["bri"];
|
|
|
|
bri = (float)tmp/254.0f;
|
|
|
|
response += ",";
|
|
|
|
response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
|
|
|
|
response.replace("{api}", "/lights");
|
|
|
|
response.replace("{id}", String(device));
|
|
|
|
response.replace("{cmd}", "state/bri");
|
|
|
|
response.replace("{res}", String(tmp));
|
|
|
|
change = true;
|
|
|
|
}
|
|
|
|
if (hue_json.containsKey("hue")) {
|
|
|
|
tmp = hue_json["hue"];
|
|
|
|
hue = (float)tmp/65535.0f;
|
|
|
|
response += ",";
|
|
|
|
response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
|
|
|
|
response.replace("{api}", "/lights");
|
|
|
|
response.replace("{id}", String(device));
|
|
|
|
response.replace("{cmd}", "state/hue");
|
|
|
|
response.replace("{res}", String(tmp));
|
|
|
|
change = true;
|
|
|
|
}
|
|
|
|
if (hue_json.containsKey("sat")) {
|
|
|
|
tmp = hue_json["sat"];
|
|
|
|
sat = (float)tmp/254.0f;
|
|
|
|
response += ",";
|
|
|
|
response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
|
|
|
|
response.replace("{api}", "/lights");
|
|
|
|
response.replace("{id}", String(device));
|
|
|
|
response.replace("{cmd}", "state/sat");
|
|
|
|
response.replace("{res}", String(tmp));
|
|
|
|
change = true;
|
|
|
|
}
|
|
|
|
if (change && (pin[GPIO_WS2812] < 99)) {
|
2017-04-25 17:24:42 +01:00
|
|
|
ws2812_setHSB(hue, sat, bri);
|
|
|
|
change = false;
|
2017-02-21 17:14:33 +00:00
|
|
|
}
|
|
|
|
#endif // USE_WS2812
|
|
|
|
response += "]";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
response=FPSTR(HUE_ERROR_JSON);
|
|
|
|
}
|
2017-05-11 16:47:34 +01:00
|
|
|
webServer->send(200, F("application/json"), response);
|
2017-02-21 17:14:33 +00:00
|
|
|
}
|
|
|
|
else if(path->indexOf("/lights/") >= 0) { // Got /lights/ID
|
|
|
|
path->remove(0,8); // Remove /lights/
|
|
|
|
device = atoi(path->c_str());
|
2017-04-25 17:24:42 +01:00
|
|
|
if ((device < 1) || (device > Maxdevice)) {
|
|
|
|
device = 1;
|
|
|
|
}
|
2017-02-21 17:14:33 +00:00
|
|
|
response = FPSTR(HUE_LIGHT_STATUS_JSON);
|
|
|
|
response.replace("{state}", (power & (0x01 << (device -1))) ? "true" : "false");
|
|
|
|
response.replace("{j1}", sysCfg.friendlyname[device -1]);
|
|
|
|
response.replace("{j2}", hue_deviceId(device));
|
|
|
|
if (pin[GPIO_WS2812] < 99) {
|
|
|
|
#ifdef USE_WS2812
|
|
|
|
ws2812_replaceHSB(&response);
|
|
|
|
#endif // USE_WS2812
|
|
|
|
} else {
|
|
|
|
response.replace("{h}", "0");
|
|
|
|
response.replace("{s}", "0");
|
|
|
|
response.replace("{b}", "0");
|
|
|
|
}
|
2017-05-11 16:47:34 +01:00
|
|
|
webServer->send(200, F("application/json"), response);
|
2017-02-21 17:14:33 +00:00
|
|
|
}
|
2017-05-11 16:47:34 +01:00
|
|
|
else webServer->send(406, F("application/json"), "{}");
|
2017-02-21 17:14:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void handle_hue_api(String *path)
|
|
|
|
{
|
|
|
|
/* HUE API uses /api/<userid>/<command> syntax. The userid is created by the echo device and
|
|
|
|
* on original HUE the pressed button allows for creation of this user. We simply ignore the
|
|
|
|
* user part and allow every caller as with Web or WeMo.
|
|
|
|
*
|
|
|
|
* (c) Heiko Krupp, 2017
|
|
|
|
*/
|
|
|
|
|
|
|
|
char log[LOGSZ];
|
|
|
|
uint8_t args = 0;
|
|
|
|
|
|
|
|
path->remove(0, 4); // remove /api
|
|
|
|
snprintf_P(log, sizeof(log), PSTR("HTTP: Handle Hue API (%s)"), path->c_str());
|
|
|
|
addLog(LOG_LEVEL_DEBUG_MORE, log);
|
|
|
|
for (args = 0; args < webServer->args(); args++) {
|
|
|
|
String json = webServer->arg(args);
|
|
|
|
snprintf_P(log, sizeof(log), PSTR("HTTP: Hue POST args (%s)"), json.c_str());
|
|
|
|
addLog(LOG_LEVEL_DEBUG_MORE, log);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (path->endsWith("/invalid/")) {} // Just ignore
|
|
|
|
else if (path->endsWith("/")) hue_auth(path); // New HUE App setup
|
|
|
|
else if (path->endsWith("/config")) hue_config(path);
|
|
|
|
else if (path->indexOf("/lights") >= 0) hue_lights(path);
|
|
|
|
else if (path->endsWith("/groups")) hue_todo(path);
|
|
|
|
else if (path->endsWith("/schedules")) hue_todo(path);
|
|
|
|
else if (path->endsWith("/sensors")) hue_todo(path);
|
|
|
|
else if (path->endsWith("/scenes")) hue_todo(path);
|
|
|
|
else if (path->endsWith("/rules")) hue_todo(path);
|
|
|
|
else hue_global_cfg(path);
|
|
|
|
}
|
|
|
|
#endif // USE_WEBSERVER
|
2017-01-29 20:36:12 +00:00
|
|
|
#endif // USE_EMULATION
|
2017-01-28 13:41:01 +00:00
|
|
|
|