mirror of https://github.com/arendst/Tasmota.git
v3.9.20
3.9.20 20170221 * Add minimal basic authentication to Web Admin mode (#87) * Fix Hue and add HSB support (#89)
This commit is contained in:
parent
aaabe37f15
commit
3f73e48bfa
|
@ -1,7 +1,7 @@
|
||||||
## Sonoff-Tasmota
|
## Sonoff-Tasmota
|
||||||
Provide ESP8266 based Sonoff by [iTead Studio](https://www.itead.cc/) and ElectroDragon IoT Relay with Serial, Web and MQTT control allowing 'Over the Air' or OTA firmware updates using Arduino IDE.
|
Provide ESP8266 based Sonoff by [iTead Studio](https://www.itead.cc/) and ElectroDragon IoT Relay with Serial, Web and MQTT control allowing 'Over the Air' or OTA firmware updates using Arduino IDE.
|
||||||
|
|
||||||
Current version is **3.9.19** - See ```sonoff/_releasenotes.ino``` for change information.
|
Current version is **3.9.20** - See ```sonoff/_releasenotes.ino``` for change information.
|
||||||
|
|
||||||
- This version provides all (Sonoff) modules in one file and starts up with Sonoff Basic.
|
- This version provides all (Sonoff) modules in one file and starts up with Sonoff Basic.
|
||||||
- Once uploaded select module using the configuration webpage or the commands ```Modules``` and ```Module```.
|
- Once uploaded select module using the configuration webpage or the commands ```Modules``` and ```Module```.
|
||||||
|
|
Binary file not shown.
|
@ -1,4 +1,8 @@
|
||||||
/* 3.9.19 20170219
|
/* 3.9.20 20170221
|
||||||
|
* Add minimal basic authentication to Web Admin mode (#87)
|
||||||
|
* Fix Hue and add HSB support (#89)
|
||||||
|
*
|
||||||
|
* 3.9.19 20170219
|
||||||
* Sonoff Led: Made GPIO04, 05 and 15 available for user
|
* Sonoff Led: Made GPIO04, 05 and 15 available for user
|
||||||
* Sonoff Led: Add commands Fade, Speed, WakupDuration, Wakeup and LedTable
|
* Sonoff Led: Add commands Fade, Speed, WakupDuration, Wakeup and LedTable
|
||||||
*
|
*
|
||||||
|
|
|
@ -184,5 +184,6 @@ struct SYSCFG {
|
||||||
|
|
||||||
uint8_t emulation;
|
uint8_t emulation;
|
||||||
|
|
||||||
|
char web_password[33];
|
||||||
} sysCfg;
|
} sysCfg;
|
||||||
|
|
||||||
|
|
|
@ -438,6 +438,7 @@ void CFG_DefaultSet2()
|
||||||
|
|
||||||
sysCfg.emulation = EMULATION;
|
sysCfg.emulation = EMULATION;
|
||||||
|
|
||||||
|
strlcpy(sysCfg.web_password, WEB_PASSWORD, sizeof(sysCfg.web_password));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CFG_Default()
|
void CFG_Default()
|
||||||
|
@ -625,6 +626,9 @@ void CFG_Delta()
|
||||||
if (sysCfg.version < 0x03090700) { // 3.9.7 - Add parameter
|
if (sysCfg.version < 0x03090700) { // 3.9.7 - Add parameter
|
||||||
sysCfg.emulation = EMULATION;
|
sysCfg.emulation = EMULATION;
|
||||||
}
|
}
|
||||||
|
if (sysCfg.version < 0x03091301) {
|
||||||
|
strlcpy(sysCfg.web_password, WEB_PASSWORD, sizeof(sysCfg.web_password));
|
||||||
|
}
|
||||||
|
|
||||||
sysCfg.version = VERSION;
|
sysCfg.version = VERSION;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
* ====================================================
|
* ====================================================
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define VERSION 0x03091300 // 3.9.19
|
#define VERSION 0x03091400 // 3.9.20
|
||||||
|
|
||||||
//#define BE_MINIMAL // Compile a minimal version if upgrade memory gets tight (still 404k)
|
//#define BE_MINIMAL // Compile a minimal version if upgrade memory gets tight (still 404k)
|
||||||
// To be used as step 1. Next step is compile and use desired version
|
// To be used as step 1. Next step is compile and use desired version
|
||||||
|
@ -909,6 +909,16 @@ void mqttDataCb(char* topic, byte* data, unsigned int data_len)
|
||||||
snprintf_P(svalue, sizeof(svalue), PSTR("{\"Webserver\":\"%s\"}"), MQTT_STATUS_OFF);
|
snprintf_P(svalue, sizeof(svalue), PSTR("{\"Webserver\":\"%s\"}"), MQTT_STATUS_OFF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (!strcmp(type,"WEBPASSWORD")) {
|
||||||
|
if ((data_len > 0) && (data_len < sizeof(sysCfg.web_password))) {
|
||||||
|
if (payload == 0) {
|
||||||
|
sysCfg.web_password[0] = 0; // No password
|
||||||
|
} else {
|
||||||
|
strlcpy(sysCfg.web_password, (payload == 1) ? WEB_PASSWORD : dataBuf, sizeof(sysCfg.web_password));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
snprintf_P(svalue, sizeof(svalue), PSTR("{\"WebPassword\":\"%s\"}"), sysCfg.web_password);
|
||||||
|
}
|
||||||
else if (!strcmp(type,"WEBLOG")) {
|
else if (!strcmp(type,"WEBLOG")) {
|
||||||
if ((data_len > 0) && (payload >= LOG_LEVEL_NONE) && (payload <= LOG_LEVEL_ALL)) {
|
if ((data_len > 0) && (payload >= LOG_LEVEL_NONE) && (payload <= LOG_LEVEL_ALL)) {
|
||||||
sysCfg.weblog_level = payload;
|
sysCfg.weblog_level = payload;
|
||||||
|
|
|
@ -78,8 +78,10 @@
|
||||||
|
|
||||||
// -- HTTP ----------------------------------------
|
// -- HTTP ----------------------------------------
|
||||||
#define USE_WEBSERVER // Enable web server and wifi manager (+62k code, +4k mem) - Disable by //
|
#define USE_WEBSERVER // Enable web server and wifi manager (+62k code, +4k mem) - Disable by //
|
||||||
#define FRIENDLY_NAME "Sonoff" // [FriendlyName] Friendlyname up to 32 characters used by webpages and Alexa
|
|
||||||
#define WEB_SERVER 2 // [WebServer] Web server (0 = Off, 1 = Start as User, 2 = Start as Admin)
|
#define WEB_SERVER 2 // [WebServer] Web server (0 = Off, 1 = Start as User, 2 = Start as Admin)
|
||||||
|
#define WEB_USERNAME "admin" // Web server Admin mode user name
|
||||||
|
#define WEB_PASSWORD "" // [WebPassword] Web server Admin mode Password for WEB_USERNAME (empty string = Disable)
|
||||||
|
#define FRIENDLY_NAME "Sonoff" // [FriendlyName] Friendlyname up to 32 characters used by webpages and Alexa
|
||||||
#define USE_EMULATION // Enable Belkin WeMo and Hue Bridge emulation for Alexa (+11k code, +2k mem)
|
#define USE_EMULATION // Enable Belkin WeMo and Hue Bridge emulation for Alexa (+11k code, +2k mem)
|
||||||
#define EMULATION EMUL_NONE // [Emulation] Select Belkin WeMo or Hue Bridge emulation (EMUL_NONE, EMUL_WEMO or EMUL_HUE)
|
#define EMULATION EMUL_NONE // [Emulation] Select Belkin WeMo or Hue Bridge emulation (EMUL_NONE, EMUL_WEMO or EMUL_HUE)
|
||||||
|
|
||||||
|
|
|
@ -171,6 +171,7 @@ const char HTTP_FORM_LOG3[] PROGMEM =
|
||||||
const char HTTP_FORM_OTHER[] PROGMEM =
|
const char HTTP_FORM_OTHER[] PROGMEM =
|
||||||
"<fieldset><legend><b> Other parameters </b></legend><form method='post' action='sv'>"
|
"<fieldset><legend><b> Other parameters </b></legend><form method='post' action='sv'>"
|
||||||
"<input id='w' name='w' value='5' hidden><input id='r' name='r' value='1' hidden>"
|
"<input id='w' name='w' value='5' hidden><input id='r' name='r' value='1' hidden>"
|
||||||
|
"<br/><b>Web Admin Password</b><br/><input id='p1' name='p1' length=32 type='password' placeholder='" WEB_PASSWORD "' value='{p1}'><br/>"
|
||||||
"<br/><input style='width:10%;float:left' id='b1' name='b1' type='checkbox'{r1}><b>MQTT enable</b><br/>";
|
"<br/><input style='width:10%;float:left' id='b1' name='b1' type='checkbox'{r1}><b>MQTT enable</b><br/>";
|
||||||
const char HTTP_FORM_OTHER2[] PROGMEM =
|
const char HTTP_FORM_OTHER2[] PROGMEM =
|
||||||
"<br/><b>Friendly Name {1</b> ({2)<br/><input id='a{1' name='a{1' length=32 placeholder='{2' value='{3'><br/>";
|
"<br/><b>Friendly Name {1</b> ({2)<br/><input id='a{1' name='a{1' length=32 placeholder='{2' value='{3'><br/>";
|
||||||
|
@ -219,120 +220,6 @@ const char HTTP_END[] PROGMEM =
|
||||||
"</div>"
|
"</div>"
|
||||||
"</body>"
|
"</body>"
|
||||||
"</html>";
|
"</html>";
|
||||||
#ifdef USE_EMULATION
|
|
||||||
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\","
|
|
||||||
"\"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_NO_AUTH_JSON[] PROGMEM =
|
|
||||||
"[{\"error\":{\"type\":101,\"address\":\"/\",\"description\":\"link button not pressed\"}}]";
|
|
||||||
#endif // USE_EMULATION
|
|
||||||
|
|
||||||
#define DNS_PORT 53
|
#define DNS_PORT 53
|
||||||
enum http_t {HTTP_OFF, HTTP_USER, HTTP_ADMIN, HTTP_MANAGER};
|
enum http_t {HTTP_OFF, HTTP_USER, HTTP_ADMIN, HTTP_MANAGER};
|
||||||
|
@ -383,7 +270,9 @@ void startWebserver(int type, IPAddress ipweb)
|
||||||
webServer->on("/eventservice.xml", handleUPnPservice);
|
webServer->on("/eventservice.xml", handleUPnPservice);
|
||||||
webServer->on("/setup.xml", handleUPnPsetupWemo);
|
webServer->on("/setup.xml", handleUPnPsetupWemo);
|
||||||
}
|
}
|
||||||
if (sysCfg.emulation == EMUL_HUE) webServer->on("/description.xml", handleUPnPsetupHue);
|
if (sysCfg.emulation == EMUL_HUE) {
|
||||||
|
webServer->on("/description.xml", handleUPnPsetupHue);
|
||||||
|
}
|
||||||
#endif // USE_EMULATION
|
#endif // USE_EMULATION
|
||||||
webServer->onNotFound(handleNotFound);
|
webServer->onNotFound(handleNotFound);
|
||||||
}
|
}
|
||||||
|
@ -437,6 +326,9 @@ void pollDnsWeb()
|
||||||
|
|
||||||
void showPage(String &page)
|
void showPage(String &page)
|
||||||
{
|
{
|
||||||
|
if((_httpflag == HTTP_ADMIN) && (sysCfg.web_password[0] != 0) && !webServer->authenticate(WEB_USERNAME, sysCfg.web_password)) {
|
||||||
|
return webServer->requestAuthentication();
|
||||||
|
}
|
||||||
page.replace("{ha}", my_module.name);
|
page.replace("{ha}", my_module.name);
|
||||||
page.replace("{h}", sysCfg.friendlyname[0]);
|
page.replace("{h}", sysCfg.friendlyname[0]);
|
||||||
if (_httpflag == HTTP_MANAGER) {
|
if (_httpflag == HTTP_MANAGER) {
|
||||||
|
@ -783,6 +675,7 @@ void handleOther()
|
||||||
String page = FPSTR(HTTP_HEAD);
|
String page = FPSTR(HTTP_HEAD);
|
||||||
page.replace("{v}", "Configure Other");
|
page.replace("{v}", "Configure Other");
|
||||||
page += FPSTR(HTTP_FORM_OTHER);
|
page += FPSTR(HTTP_FORM_OTHER);
|
||||||
|
page.replace("{p1}", sysCfg.web_password);
|
||||||
page.replace("{r1}", (sysCfg.mqtt_enabled) ? " checked" : "");
|
page.replace("{r1}", (sysCfg.mqtt_enabled) ? " checked" : "");
|
||||||
page += FPSTR(HTTP_FORM_OTHER2);
|
page += FPSTR(HTTP_FORM_OTHER2);
|
||||||
page.replace("{1", "1");
|
page.replace("{1", "1");
|
||||||
|
@ -884,6 +777,8 @@ void handleSave()
|
||||||
break;
|
break;
|
||||||
#endif // USE_DOMOTICZ
|
#endif // USE_DOMOTICZ
|
||||||
case 5:
|
case 5:
|
||||||
|
strlcpy(sysCfg.web_password, (!strlen(webServer->arg("p1").c_str())) ? WEB_PASSWORD : webServer->arg("p1").c_str(), sizeof(sysCfg.web_password));
|
||||||
|
if (sysCfg.web_password[0] == '0') sysCfg.web_password[0] = '\0';
|
||||||
sysCfg.mqtt_enabled = webServer->hasArg("b1");
|
sysCfg.mqtt_enabled = webServer->hasArg("b1");
|
||||||
#ifdef USE_EMULATION
|
#ifdef USE_EMULATION
|
||||||
sysCfg.emulation = (!strlen(webServer->arg("b2").c_str())) ? 0 : atoi(webServer->arg("b2").c_str());
|
sysCfg.emulation = (!strlen(webServer->arg("b2").c_str())) ? 0 : atoi(webServer->arg("b2").c_str());
|
||||||
|
@ -1176,13 +1071,21 @@ void handleCmnd()
|
||||||
|
|
||||||
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle cmnd"));
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle cmnd"));
|
||||||
|
|
||||||
byte curridx = logidx;
|
uint8_t valid = 1;
|
||||||
if (strlen(webServer->arg(0).c_str())) {
|
if (sysCfg.web_password[0] != 0) {
|
||||||
snprintf_P(svalue, sizeof(svalue), webServer->arg(0).c_str());
|
if (!(!strcmp(webServer->arg("user").c_str(),WEB_USERNAME) && !strcmp(webServer->arg("password").c_str(),sysCfg.web_password))) {
|
||||||
do_cmnd(svalue);
|
valid = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String message = "";
|
String message = "";
|
||||||
|
if (valid) {
|
||||||
|
byte curridx = logidx;
|
||||||
|
if (strlen(webServer->arg("cmnd").c_str())) {
|
||||||
|
snprintf_P(svalue, sizeof(svalue), webServer->arg("cmnd").c_str());
|
||||||
|
do_cmnd(svalue);
|
||||||
|
}
|
||||||
|
|
||||||
if (logidx != curridx) {
|
if (logidx != curridx) {
|
||||||
byte counter = curridx;
|
byte counter = curridx;
|
||||||
do {
|
do {
|
||||||
|
@ -1203,6 +1106,9 @@ void handleCmnd()
|
||||||
} else {
|
} else {
|
||||||
message = F("Enable weblog 2 if response expected\n");
|
message = F("Enable weblog 2 if response expected\n");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
message = F("Need user=<username>&password=<password>\n");
|
||||||
|
}
|
||||||
|
|
||||||
webServer->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
webServer->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||||
webServer->sendHeader("Pragma", "no-cache");
|
webServer->sendHeader("Pragma", "no-cache");
|
||||||
|
@ -1313,15 +1219,9 @@ void handleInfo()
|
||||||
|
|
||||||
page += F("<tr><th>Emulation</th><td>");
|
page += F("<tr><th>Emulation</th><td>");
|
||||||
#ifdef USE_EMULATION
|
#ifdef USE_EMULATION
|
||||||
if (sysCfg.emulation == EMUL_WEMO) {
|
if (sysCfg.emulation == EMUL_WEMO) page += F("Belkin WeMo");
|
||||||
page += F("Belkin WeMo");
|
else if (sysCfg.emulation == EMUL_HUE) page += F("Hue Bridge");
|
||||||
}
|
else page += F("None");
|
||||||
else if (sysCfg.emulation == EMUL_HUE) {
|
|
||||||
page += F("Hue Bridge");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
page += F("None");
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
page += F("Disabled");
|
page += F("Disabled");
|
||||||
#endif // USE_EMULATION
|
#endif // USE_EMULATION
|
||||||
|
@ -1376,276 +1276,6 @@ void handleRestart()
|
||||||
|
|
||||||
/********************************************************************************************/
|
/********************************************************************************************/
|
||||||
|
|
||||||
#ifdef USE_EMULATION
|
|
||||||
void handleUPnPevent()
|
|
||||||
{
|
|
||||||
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle WeMo basic event"));
|
|
||||||
|
|
||||||
String request = webServer->arg(0);
|
|
||||||
if (request.indexOf("State>1</Binary") > 0) do_cmnd_power(1, 1);
|
|
||||||
if (request.indexOf("State>0</Binary") > 0) do_cmnd_power(1, 0);
|
|
||||||
webServer->send(200, "text/plain", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleUPnPservice()
|
|
||||||
{
|
|
||||||
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle WeMo event service"));
|
|
||||||
|
|
||||||
String eventservice_xml = FPSTR(WEMO_EVENTSERVICE_XML);
|
|
||||||
webServer->send(200, "text/plain", eventservice_xml);
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
webServer->send(200, "text/xml", setup_xml);
|
|
||||||
}
|
|
||||||
|
|
||||||
/********************************************************************************************/
|
|
||||||
|
|
||||||
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());
|
|
||||||
webServer->send(200, "text/xml", description_xml);
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
char buffer[21];
|
|
||||||
|
|
||||||
*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());
|
|
||||||
snprintf_P(buffer, sizeof(buffer), PSTR("%04d-%02d-%02dT%02d:%02d:%02d"),
|
|
||||||
rtcTime.Year, rtcTime.Month, rtcTime.Day, rtcTime.Hour, rtcTime.Minute, rtcTime.Second);
|
|
||||||
response->replace("{dt}", buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void hue_global_cfg(String *path)
|
|
||||||
{
|
|
||||||
String response;
|
|
||||||
|
|
||||||
path->remove(0,1); // cut leading / to get <id>
|
|
||||||
response = "{\"lights\":{\"";
|
|
||||||
for (uint8_t i = 1; i <= Maxdevice; i++)
|
|
||||||
{
|
|
||||||
response += i;
|
|
||||||
response += "\":";
|
|
||||||
response += FPSTR(HUE_LIGHT_STATUS_JSON);
|
|
||||||
if (i < Maxdevice) response += ",\"";
|
|
||||||
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 += F("},\"groups\":{},\"schedules\":{},\"config\":");
|
|
||||||
|
|
||||||
hue_config_response(&response);
|
|
||||||
response.replace("{id}", *path);
|
|
||||||
response += "}";
|
|
||||||
webServer->send(200, "application/json", response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void hue_auth(String *path)
|
|
||||||
{
|
|
||||||
char response[38];
|
|
||||||
|
|
||||||
snprintf_P(response, sizeof(response), PSTR("[{\"success\":{\"username\":\"%03x\"}}]"), ESP.getChipId());
|
|
||||||
webServer->send(200, "application/json", response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void hue_config(String *path)
|
|
||||||
{
|
|
||||||
String response = "";
|
|
||||||
|
|
||||||
path->remove(0,1); // cut leading / to get <id>
|
|
||||||
hue_config_response(&response);
|
|
||||||
response.replace("{id}", *path);
|
|
||||||
webServer->send(200, "application/json", response);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
int16_t pos = 0;
|
|
||||||
uint8_t bri = 0;
|
|
||||||
bool on = 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);
|
|
||||||
if (i < Maxdevice) response += ",\"";
|
|
||||||
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 += "}";
|
|
||||||
webServer->send(200, "application/json", response);
|
|
||||||
}
|
|
||||||
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());
|
|
||||||
if ((device < 1) || (device > Maxdevice)) device = 1;
|
|
||||||
response = "[";
|
|
||||||
response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
|
|
||||||
response.replace("{api}", "/lights");
|
|
||||||
response.replace("{id}", String(device));
|
|
||||||
response.replace("{cmd}", "state/on");
|
|
||||||
if (webServer->args() == 1)
|
|
||||||
{
|
|
||||||
StaticJsonBuffer<400> jsonBuffer;
|
|
||||||
JsonObject &hue_json = jsonBuffer.parseObject(webServer->arg(0));
|
|
||||||
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
|
|
||||||
bri = hue_json["bri"];
|
|
||||||
if (pin[GPIO_WS2812] < 99) {
|
|
||||||
ws2812_changeBrightness(bri);
|
|
||||||
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(bri));
|
|
||||||
}
|
|
||||||
#endif // USE_WS2812
|
|
||||||
response += "]";
|
|
||||||
webServer->send(200, "application/json", response);
|
|
||||||
}
|
|
||||||
else webServer->send(406, "application/json", "{}");
|
|
||||||
}
|
|
||||||
else if(path->indexOf("/lights/") >= 0) { // Got /lights/ID
|
|
||||||
path->remove(0,8); // Remove /lights/
|
|
||||||
device = atoi(path->c_str());
|
|
||||||
if ((device < 1) || (device > Maxdevice)) device = 1;
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
webServer->send(200, "application/json", response);
|
|
||||||
}
|
|
||||||
else webServer->send(406, "application/json", "{}");
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
snprintf_P(log, sizeof(log), PSTR("HTTP: Handle Hue API (%s)"),path->c_str());
|
|
||||||
addLog(LOG_LEVEL_DEBUG_MORE, log);
|
|
||||||
webServer->send(406, "application/json", "{}");
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
#endif // USE_EMULATION
|
|
||||||
|
|
||||||
/********************************************************************************************/
|
/********************************************************************************************/
|
||||||
|
|
||||||
|
|
|
@ -216,5 +216,417 @@ void pollUDP()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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);
|
||||||
|
if (request.indexOf("State>1</Binary") > 0) do_cmnd_power(1, 1);
|
||||||
|
if (request.indexOf("State>0</Binary") > 0) do_cmnd_power(1, 0);
|
||||||
|
webServer->send(200, "text/plain", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleUPnPservice()
|
||||||
|
{
|
||||||
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle WeMo event service"));
|
||||||
|
|
||||||
|
String eventservice_xml = FPSTR(WEMO_EVENTSERVICE_XML);
|
||||||
|
webServer->send(200, "text/plain", eventservice_xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
webServer->send(200, "text/xml", setup_xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
/********************************************************************************************/
|
||||||
|
|
||||||
|
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());
|
||||||
|
webServer->send(200, "text/xml", description_xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
char buffer[21];
|
||||||
|
|
||||||
|
*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());
|
||||||
|
snprintf_P(buffer, sizeof(buffer), PSTR("%04d-%02d-%02dT%02d:%02d:%02d"),
|
||||||
|
rtcTime.Year, rtcTime.Month, rtcTime.Day, rtcTime.Hour, rtcTime.Minute, rtcTime.Second);
|
||||||
|
response->replace("{dt}", buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hue_global_cfg(String *path)
|
||||||
|
{
|
||||||
|
String response;
|
||||||
|
|
||||||
|
path->remove(0,1); // cut leading / to get <id>
|
||||||
|
response = "{\"lights\":{\"";
|
||||||
|
for (uint8_t i = 1; i <= Maxdevice; i++)
|
||||||
|
{
|
||||||
|
response += i;
|
||||||
|
response += "\":";
|
||||||
|
response += FPSTR(HUE_LIGHT_STATUS_JSON);
|
||||||
|
if (i < Maxdevice) response += ",\"";
|
||||||
|
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 += F("},\"groups\":{},\"schedules\":{},\"config\":");
|
||||||
|
|
||||||
|
hue_config_response(&response);
|
||||||
|
response.replace("{id}", *path);
|
||||||
|
response += "}";
|
||||||
|
webServer->send(200, "application/json", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hue_auth(String *path)
|
||||||
|
{
|
||||||
|
char response[38];
|
||||||
|
|
||||||
|
snprintf_P(response, sizeof(response), PSTR("[{\"success\":{\"username\":\"%03x\"}}]"), ESP.getChipId());
|
||||||
|
webServer->send(200, "application/json", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hue_config(String *path)
|
||||||
|
{
|
||||||
|
String response = "";
|
||||||
|
|
||||||
|
path->remove(0,1); // cut leading / to get <id>
|
||||||
|
hue_config_response(&response);
|
||||||
|
response.replace("{id}", *path);
|
||||||
|
webServer->send(200, "application/json", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
if (i < Maxdevice) response += ",\"";
|
||||||
|
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 += "}";
|
||||||
|
webServer->send(200, "application/json", response);
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
if ((device < 1) || (device > Maxdevice)) device = 1;
|
||||||
|
response = "[";
|
||||||
|
response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
|
||||||
|
response.replace("{api}", "/lights");
|
||||||
|
response.replace("{id}", String(device));
|
||||||
|
response.replace("{cmd}", "state/on");
|
||||||
|
if (webServer->args() == 1) {
|
||||||
|
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)) {
|
||||||
|
ws2812_setHSB(hue,sat,bri);
|
||||||
|
change=false;
|
||||||
|
}
|
||||||
|
#endif // USE_WS2812
|
||||||
|
response += "]";
|
||||||
|
webServer->send(200, "application/json", response);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
response=FPSTR(HUE_ERROR_JSON);
|
||||||
|
webServer->send(200, "application/json", response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(path->indexOf("/lights/") >= 0) { // Got /lights/ID
|
||||||
|
path->remove(0,8); // Remove /lights/
|
||||||
|
device = atoi(path->c_str());
|
||||||
|
if ((device < 1) || (device > Maxdevice)) device = 1;
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
webServer->send(200, "application/json", response);
|
||||||
|
}
|
||||||
|
else webServer->send(406, "application/json", "{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
#endif // USE_EMULATION
|
#endif // USE_EMULATION
|
||||||
|
|
||||||
|
|
|
@ -44,8 +44,6 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||||
#endif // USE_WS2812_CTYPE
|
#endif // USE_WS2812_CTYPE
|
||||||
#endif // USE_WS2812_DMA
|
#endif // USE_WS2812_DMA
|
||||||
|
|
||||||
#define COLOR_SATURATION 254.0f
|
|
||||||
|
|
||||||
struct wsColor {
|
struct wsColor {
|
||||||
uint8_t red, green, blue;
|
uint8_t red, green, blue;
|
||||||
};
|
};
|
||||||
|
@ -168,20 +166,27 @@ void ws2812_replaceHSB(String *response)
|
||||||
ws2812_setDim(sysCfg.ws_dimmer);
|
ws2812_setDim(sysCfg.ws_dimmer);
|
||||||
HsbColor hsb=HsbColor(dcolor);
|
HsbColor hsb=HsbColor(dcolor);
|
||||||
response->replace("{h}", String((uint16_t)(65535.0f * hsb.H)));
|
response->replace("{h}", String((uint16_t)(65535.0f * hsb.H)));
|
||||||
response->replace("{s}", String((uint8_t)(COLOR_SATURATION * hsb.S)));
|
response->replace("{s}", String((uint8_t)(254.0f * hsb.S)));
|
||||||
response->replace("{b}", String((uint8_t)(COLOR_SATURATION * hsb.B)));
|
response->replace("{b}", String((uint8_t)(254.0f * hsb.B)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ws2812_changeBrightness(uint8_t bri)
|
void ws2812_getHSB(float *hue, float *sat, float *bri)
|
||||||
|
{
|
||||||
|
ws2812_setDim(sysCfg.ws_dimmer);
|
||||||
|
HsbColor hsb=HsbColor(dcolor);
|
||||||
|
*hue=hsb.H;
|
||||||
|
*sat=hsb.S;
|
||||||
|
*bri=hsb.B;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws2812_setHSB(float hue, float sat, float bri)
|
||||||
{
|
{
|
||||||
char rgb[7];
|
char rgb[7];
|
||||||
|
|
||||||
//sysCfg.ws_ledtable=1; // Switch on Gamma Correction for "natural" brightness controll
|
HsbColor hsb;
|
||||||
ws2812_setDim(sysCfg.ws_dimmer);
|
hsb.H=hue;
|
||||||
HsbColor hsb = HsbColor(dcolor);
|
hsb.S=sat;
|
||||||
if (!bri) bri=1;
|
hsb.B=bri;
|
||||||
if (bri==255) bri=252;
|
|
||||||
hsb.B=(float)(bri/COLOR_SATURATION);
|
|
||||||
RgbColor tmp = RgbColor(hsb);
|
RgbColor tmp = RgbColor(hsb);
|
||||||
sprintf(rgb,"%02X%02X%02X", tmp.R, tmp.G, tmp.B);
|
sprintf(rgb,"%02X%02X%02X", tmp.R, tmp.G, tmp.B);
|
||||||
ws2812_setColor(0,rgb);
|
ws2812_setColor(0,rgb);
|
||||||
|
|
Loading…
Reference in New Issue