"
+ "
");
+#endif // USE_EMULATION
+ page += FPSTR(HTTP_FORM_END);
+ page += FPSTR(HTTP_BTN_CONF);
+ showPage(page);
+}
+
+void handleDownload()
+{
+ if (httpUser()) {
+ return;
+ }
+ addLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_BACKUP_CONFIGURATION));
+
+ uint8_t buffer[sizeof(sysCfg)];
+
+ WiFiClient myClient = webServer->client();
+ webServer->setContentLength(4096);
+
+ char attachment[100];
+ snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=Config_%s_%s.dmp"),
+ sysCfg.friendlyname[0], Version);
+ webServer->sendHeader(F("Content-Disposition"), attachment);
+ webServer->send(200, FPSTR(HDR_CTYPE_STREAM), "");
+ memcpy(buffer, &sysCfg, sizeof(sysCfg));
+ buffer[0] = CONFIG_FILE_SIGN;
+ buffer[1] = (!CONFIG_FILE_XOR)?0:1;
+ if (buffer[1]) {
+ for (uint16_t i = 2; i < sizeof(buffer); i++) {
+ buffer[i] ^= (CONFIG_FILE_XOR +i);
+ }
+ }
+ myClient.write((const char*)buffer, sizeof(buffer));
+}
+
+void handleSave()
+{
+ if (httpUser()) {
+ return;
+ }
+
+ char log[LOGSZ +20];
+ char stemp[TOPSZ];
+ char stemp2[TOPSZ];
+ byte what = 0;
+ byte restart;
+ String result = "";
+
+ addLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_SAVE_CONFIGURATION);
+
+ if (strlen(webServer->arg("w").c_str())) {
+ what = atoi(webServer->arg("w").c_str());
+ }
+ switch (what) {
+ case 1:
+ strlcpy(sysCfg.hostname, (!strlen(webServer->arg("h").c_str())) ? WIFI_HOSTNAME : webServer->arg("h").c_str(), sizeof(sysCfg.hostname));
+ if (strstr(sysCfg.hostname,"%")) {
+ strlcpy(sysCfg.hostname, WIFI_HOSTNAME, sizeof(sysCfg.hostname));
+ }
+ strlcpy(sysCfg.sta_ssid[0], (!strlen(webServer->arg("s1").c_str())) ? STA_SSID1 : webServer->arg("s1").c_str(), sizeof(sysCfg.sta_ssid[0]));
+ strlcpy(sysCfg.sta_pwd[0], (!strlen(webServer->arg("p1").c_str())) ? STA_PASS1 : webServer->arg("p1").c_str(), sizeof(sysCfg.sta_pwd[0]));
+ strlcpy(sysCfg.sta_ssid[1], (!strlen(webServer->arg("s2").c_str())) ? STA_SSID2 : webServer->arg("s2").c_str(), sizeof(sysCfg.sta_ssid[1]));
+ strlcpy(sysCfg.sta_pwd[1], (!strlen(webServer->arg("p2").c_str())) ? STA_PASS2 : webServer->arg("p2").c_str(), sizeof(sysCfg.sta_pwd[1]));
+ snprintf_P(log, sizeof(log), PSTR(D_LOG_WIFI D_CMND_HOSTNAME " %s, " D_CMND_SSID "1 %s, " D_CMND_PASSWORD "1 %s, " D_CMND_SSID "2 %s, " D_CMND_PASSWORD "2 %s"),
+ sysCfg.hostname, sysCfg.sta_ssid[0], sysCfg.sta_pwd[0], sysCfg.sta_ssid[1], sysCfg.sta_pwd[1]);
+ addLog(LOG_LEVEL_INFO, log);
+ result += F("
" D_TRYING_TO_CONNECT "
");
+ break;
+ case 2:
+ strlcpy(stemp, (!strlen(webServer->arg("mt").c_str())) ? MQTT_TOPIC : webServer->arg("mt").c_str(), sizeof(stemp));
+ mqttfy(0, stemp);
+ strlcpy(stemp2, (!strlen(webServer->arg("mf").c_str())) ? MQTT_FULLTOPIC : webServer->arg("mf").c_str(), sizeof(stemp2));
+ mqttfy(1,stemp2);
+ if ((strcmp(stemp, sysCfg.mqtt_topic)) || (strcmp(stemp2, sysCfg.mqtt_fulltopic))) {
+ mqtt_publish_topic_P(2, S_LWT, (sysCfg.flag.mqtt_offline) ? S_OFFLINE : "", true); // Offline or remove previous retained topic
+ }
+ strlcpy(sysCfg.mqtt_topic, stemp, sizeof(sysCfg.mqtt_topic));
+ strlcpy(sysCfg.mqtt_fulltopic, stemp2, sizeof(sysCfg.mqtt_fulltopic));
+ strlcpy(sysCfg.mqtt_host, (!strlen(webServer->arg("mh").c_str())) ? MQTT_HOST : webServer->arg("mh").c_str(), sizeof(sysCfg.mqtt_host));
+ sysCfg.mqtt_port = (!strlen(webServer->arg("ml").c_str())) ? MQTT_PORT : atoi(webServer->arg("ml").c_str());
+ strlcpy(sysCfg.mqtt_client, (!strlen(webServer->arg("mc").c_str())) ? MQTT_CLIENT_ID : webServer->arg("mc").c_str(), sizeof(sysCfg.mqtt_client));
+ strlcpy(sysCfg.mqtt_user, (!strlen(webServer->arg("mu").c_str())) ? MQTT_USER : (!strcmp(webServer->arg("mu").c_str(),"0")) ? "" : webServer->arg("mu").c_str(), sizeof(sysCfg.mqtt_user));
+ strlcpy(sysCfg.mqtt_pwd, (!strlen(webServer->arg("mp").c_str())) ? MQTT_PASS : (!strcmp(webServer->arg("mp").c_str(),"0")) ? "" : webServer->arg("mp").c_str(), sizeof(sysCfg.mqtt_pwd));
+ snprintf_P(log, sizeof(log), PSTR(D_LOG_MQTT D_CMND_MQTTHOST " %s, " D_CMND_MQTTPORT " %d, " D_CMND_MQTTCLIENT " %s, " D_CMND_MQTTUSER " %s, " D_CMND_MQTTPASSWORD " %s, " D_CMND_TOPIC " %s, " D_CMND_FULLTOPIC " %s"),
+ sysCfg.mqtt_host, sysCfg.mqtt_port, sysCfg.mqtt_client, sysCfg.mqtt_user, sysCfg.mqtt_pwd, sysCfg.mqtt_topic, sysCfg.mqtt_fulltopic);
+ addLog(LOG_LEVEL_INFO, log);
+ break;
+ case 3:
+ sysCfg.seriallog_level = (!strlen(webServer->arg("ls").c_str())) ? SERIAL_LOG_LEVEL : atoi(webServer->arg("ls").c_str());
+ sysCfg.weblog_level = (!strlen(webServer->arg("lw").c_str())) ? WEB_LOG_LEVEL : atoi(webServer->arg("lw").c_str());
+ sysCfg.syslog_level = (!strlen(webServer->arg("ll").c_str())) ? SYS_LOG_LEVEL : atoi(webServer->arg("ll").c_str());
+ syslog_level = sysCfg.syslog_level;
+ syslog_timer = 0;
+ strlcpy(sysCfg.syslog_host, (!strlen(webServer->arg("lh").c_str())) ? SYS_LOG_HOST : webServer->arg("lh").c_str(), sizeof(sysCfg.syslog_host));
+ sysCfg.syslog_port = (!strlen(webServer->arg("lp").c_str())) ? SYS_LOG_PORT : atoi(webServer->arg("lp").c_str());
+ sysCfg.tele_period = (!strlen(webServer->arg("lt").c_str())) ? TELE_PERIOD : atoi(webServer->arg("lt").c_str());
+ snprintf_P(log, sizeof(log), PSTR(D_LOG_LOG D_CMND_SERIALLOG " %d, " D_CMND_WEBLOG " %d, " D_CMND_SYSLOG " %d, " D_CMND_LOGHOST " %s, " D_CMND_LOGPORT " %d, " D_CMND_TELEPERIOD " %d"),
+ sysCfg.seriallog_level, sysCfg.weblog_level, sysCfg.syslog_level, sysCfg.syslog_host, sysCfg.syslog_port, sysCfg.tele_period);
+ addLog(LOG_LEVEL_INFO, log);
+ break;
+#ifdef USE_DOMOTICZ
+ case 4:
+ domoticz_saveSettings();
+ break;
+#endif // USE_DOMOTICZ
+ case 5:
+ strlcpy(sysCfg.web_password, (!strlen(webServer->arg("p1").c_str())) ? WEB_PASSWORD : (!strcmp(webServer->arg("p1").c_str(),"0")) ? "" : webServer->arg("p1").c_str(), sizeof(sysCfg.web_password));
+ sysCfg.flag.mqtt_enabled = webServer->hasArg("b1");
+#ifdef USE_EMULATION
+ sysCfg.flag.emulation = (!strlen(webServer->arg("b2").c_str())) ? 0 : atoi(webServer->arg("b2").c_str());
+#endif // USE_EMULATION
+ strlcpy(sysCfg.friendlyname[0], (!strlen(webServer->arg("a1").c_str())) ? FRIENDLY_NAME : webServer->arg("a1").c_str(), sizeof(sysCfg.friendlyname[0]));
+ strlcpy(sysCfg.friendlyname[1], (!strlen(webServer->arg("a2").c_str())) ? FRIENDLY_NAME"2" : webServer->arg("a2").c_str(), sizeof(sysCfg.friendlyname[1]));
+ strlcpy(sysCfg.friendlyname[2], (!strlen(webServer->arg("a3").c_str())) ? FRIENDLY_NAME"3" : webServer->arg("a3").c_str(), sizeof(sysCfg.friendlyname[2]));
+ strlcpy(sysCfg.friendlyname[3], (!strlen(webServer->arg("a4").c_str())) ? FRIENDLY_NAME"4" : webServer->arg("a4").c_str(), sizeof(sysCfg.friendlyname[3]));
+ snprintf_P(log, sizeof(log), PSTR(D_LOG_OTHER D_MQTT_ENABLE " %s, " D_CMND_EMULATION " %d, " D_CMND_FRIENDLYNAME " %s, %s, %s, %s"),
+ getStateText(sysCfg.flag.mqtt_enabled), sysCfg.flag.emulation, sysCfg.friendlyname[0], sysCfg.friendlyname[1], sysCfg.friendlyname[2], sysCfg.friendlyname[3]);
+ addLog(LOG_LEVEL_INFO, log);
+ break;
+ case 6:
+ byte new_module = (!strlen(webServer->arg("g99").c_str())) ? MODULE : atoi(webServer->arg("g99").c_str());
+ byte new_modflg = (sysCfg.module != new_module);
+ sysCfg.module = new_module;
+ mytmplt cmodule;
+ memcpy_P(&cmodule, &modules[sysCfg.module], sizeof(cmodule));
+ String gpios = "";
+ for (byte i = 0; i < MAX_GPIO_PIN; i++) {
+ if (new_modflg) {
+ sysCfg.my_module.gp.io[i] = 0;
+ }
+ if (GPIO_USER == cmodule.gp.io[i]) {
+ snprintf_P(stemp, sizeof(stemp), PSTR("g%d"), i);
+ sysCfg.my_module.gp.io[i] = (!strlen(webServer->arg(stemp).c_str())) ? 0 : atoi(webServer->arg(stemp).c_str());
+ gpios += F(", " D_GPIO ); gpios += String(i); gpios += F(" "); gpios += String(sysCfg.my_module.gp.io[i]);
+ }
+ }
+ snprintf_P(stemp, sizeof(stemp), modules[sysCfg.module].name);
+ snprintf_P(log, sizeof(log), PSTR(D_LOG_MODULE "%s " D_CMND_MODULE "%s"), stemp, gpios.c_str());
+ addLog(LOG_LEVEL_INFO, log);
+ break;
+ }
+
+ restart = (!strlen(webServer->arg("r").c_str())) ? 1 : atoi(webServer->arg("r").c_str());
+ if (restart) {
+ String page = FPSTR(HTTP_HEAD);
+ page.replace(F("{v}"), FPSTR(S_SAVE_CONFIGURATION));
+ page += F("
" D_CONFIGURATION_SAVED "
");
+ page += result;
+ page += F("
");
+ page += FPSTR(HTTP_MSG_RSTRT);
+ if (HTTP_MANAGER == _httpflag) {
+ _httpflag = HTTP_ADMIN;
+ } else {
+ page += FPSTR(HTTP_BTN_MAIN);
+ }
+ showPage(page);
+
+ restartflag = 2;
+ } else {
+ handleConfig();
+ }
+}
+
+void handleReset()
+{
+ if (httpUser()) {
+ return;
+ }
+
+ char svalue[16]; // was MESSZ
+
+ addLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_RESET_CONFIGURATION);
+
+ String page = FPSTR(HTTP_HEAD);
+ page.replace(F("{v}"), FPSTR(S_RESET_CONFIGURATION));
+ page += F("
" D_CONFIGURATION_RESET "
");
+ page += FPSTR(HTTP_MSG_RSTRT);
+ page += FPSTR(HTTP_BTN_MAIN);
+ showPage(page);
+
+ snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_RESET " 1"));
+ do_cmnd(svalue);
+}
+
+void handleRestore()
+{
+ if (httpUser()) {
+ return;
+ }
+ addLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_RESTORE_CONFIGURATION);
+
+ String page = FPSTR(HTTP_HEAD);
+ page.replace(F("{v}"), FPSTR(S_RESTORE_CONFIGURATION));
+ page += FPSTR(HTTP_FORM_RST);
+ page += FPSTR(HTTP_FORM_RST_UPG);
+ page.replace(F("{r1}"), F(D_RESTORE));
+ page += FPSTR(HTTP_BTN_CONF);
+ showPage(page);
+
+ _uploaderror = 0;
+ _uploadfiletype = 1;
+}
+
+void handleUpgrade()
+{
+ if (httpUser()) {
+ return;
+ }
+ addLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_FIRMWARE_UPGRADE);
+
+ String page = FPSTR(HTTP_HEAD);
+ page.replace(F("{v}"), FPSTR(S_FIRMWARE_UPGRADE));
+ page += FPSTR(HTTP_FORM_UPG);
+ page.replace(F("{o1}"), sysCfg.otaUrl);
+ page += FPSTR(HTTP_FORM_RST_UPG);
+ page.replace(F("{r1}"), F(D_UPGRADE));
+ page += FPSTR(HTTP_BTN_MAIN);
+ showPage(page);
+
+ _uploaderror = 0;
+ _uploadfiletype = 0;
+}
+
+void handleUpgradeStart()
+{
+ if (httpUser()) {
+ return;
+ }
+ char svalue[100]; // was MESSZ
+
+ addLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPGRADE_STARTED));
+ WIFI_configCounter();
+
+ if (strlen(webServer->arg("o").c_str())) {
+ snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_OTAURL " %s"), webServer->arg("o").c_str());
+ do_cmnd(svalue);
+ }
+
+ String page = FPSTR(HTTP_HEAD);
+ page.replace(F("{v}"), FPSTR(S_INFORMATION));
+ page += F("
" D_UPGRADE_STARTED " ...
");
+ page += FPSTR(HTTP_MSG_RSTRT);
+ page += FPSTR(HTTP_BTN_MAIN);
+ showPage(page);
+
+ snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_UPGRADE " 1"));
+ do_cmnd(svalue);
+}
+
+void handleUploadDone()
+{
+ if (httpUser()) {
+ return;
+ }
+ addLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPLOAD_DONE));
+
+ char error[100];
+ char log[LOGSZ];
+
+ WIFI_configCounter();
+ restartflag = 0;
+ mqttcounter = 0;
+
+ String page = FPSTR(HTTP_HEAD);
+ page.replace(F("{v}"), FPSTR(S_INFORMATION));
+ page += F("
" D_UPLOAD " " D_FAILED "
");
+ switch (_uploaderror) {
+ case 1: strncpy_P(error, PSTR(D_UPLOAD_ERR_1), sizeof(error)); break;
+ case 2: strncpy_P(error, PSTR(D_UPLOAD_ERR_2), sizeof(error)); break;
+ case 3: strncpy_P(error, PSTR(D_UPLOAD_ERR_3), sizeof(error)); break;
+ case 4: strncpy_P(error, PSTR(D_UPLOAD_ERR_4), sizeof(error)); break;
+ case 5: strncpy_P(error, PSTR(D_UPLOAD_ERR_5), sizeof(error)); break;
+ case 6: strncpy_P(error, PSTR(D_UPLOAD_ERR_6), sizeof(error)); break;
+ case 7: strncpy_P(error, PSTR(D_UPLOAD_ERR_7), sizeof(error)); break;
+ case 8: strncpy_P(error, PSTR(D_UPLOAD_ERR_8), sizeof(error)); break;
+ case 9: strncpy_P(error, PSTR(D_UPLOAD_ERR_9), sizeof(error)); break;
+ default:
+ snprintf_P(error, sizeof(error), PSTR(D_UPLOAD_ERROR_CODE " %d"), _uploaderror);
+ }
+ page += error;
+ snprintf_P(log, sizeof(log), PSTR(D_UPLOAD ": %s"), error);
+ addLog(LOG_LEVEL_DEBUG, log);
+ stop_flash_rotate = sysCfg.flag.stop_flash_rotate;
+ } else {
+ page += F("green'>" D_SUCCESSFUL "
");
+ page += FPSTR(HTTP_MSG_RSTRT);
+ restartflag = 2;
+ }
+ page += F("
");
+ page += FPSTR(HTTP_BTN_MAIN);
+ showPage(page);
+}
+
+void handleUploadLoop()
+{
+ // Based on ESP8266HTTPUpdateServer.cpp uses ESP8266WebServer Parsing.cpp and Cores Updater.cpp (Update)
+ char log[LOGSZ];
+ boolean _serialoutput = (LOG_LEVEL_DEBUG <= seriallog_level);
+
+ if (HTTP_USER == _httpflag) {
+ return;
+ }
+ if (_uploaderror) {
+ if (!_uploadfiletype) {
+ Update.end();
+ }
+ return;
+ }
+
+ HTTPUpload& upload = webServer->upload();
+
+ if (UPLOAD_FILE_START == upload.status) {
+ restartflag = 60;
+ if (0 == upload.filename.c_str()[0]) {
+ _uploaderror = 1;
+ return;
+ }
+ CFG_Save(1); // Free flash for upload
+ snprintf_P(log, sizeof(log), PSTR(D_LOG_UPLOAD D_FILE " %s ..."), upload.filename.c_str());
+ addLog(LOG_LEVEL_INFO, log);
+ if (!_uploadfiletype) {
+ mqttcounter = 60;
+#ifdef USE_EMULATION
+ UDP_Disconnect();
+#endif // USE_EMULATION
+ if (sysCfg.flag.mqtt_enabled) {
+ mqttClient.disconnect();
+ }
+ uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
+ if (!Update.begin(maxSketchSpace)) { //start with max available size
+ _uploaderror = 2;
+ return;
+ }
+ }
+ _colcount = 0;
+ } else if (!_uploaderror && (UPLOAD_FILE_WRITE == upload.status)) {
+ if (0 == upload.totalSize)
+ {
+ if (_uploadfiletype) {
+ if (upload.buf[0] != CONFIG_FILE_SIGN) {
+ _uploaderror = 8;
+ return;
+ }
+ if (upload.currentSize > sizeof(sysCfg)) {
+ _uploaderror = 9;
+ return;
+ }
+ } else {
+ if (upload.buf[0] != 0xE9) {
+ _uploaderror = 3;
+ return;
+ }
+ uint32_t bin_flash_size = ESP.magicFlashChipSize((upload.buf[3] & 0xf0) >> 4);
+ if(bin_flash_size > ESP.getFlashChipRealSize()) {
+ _uploaderror = 4;
+ return;
+ }
+ upload.buf[2] = 3; // Force DOUT - ESP8285
+ }
+ }
+ if (_uploadfiletype) { // config
+ if (!_uploaderror) {
+ if (upload.buf[1]) {
+ for (uint16_t i = 2; i < upload.currentSize; i++) {
+ upload.buf[i] ^= (CONFIG_FILE_XOR +i);
+ }
+ }
+ CFG_DefaultSet2();
+ memcpy((char*)&sysCfg +16, upload.buf +16, upload.currentSize -16);
+ memcpy((char*)&sysCfg +8, upload.buf +8, 4); // Restore version and auto upgrade
+ }
+ } else { // firmware
+ if (!_uploaderror && (Update.write(upload.buf, upload.currentSize) != upload.currentSize)) {
+ _uploaderror = 5;
+ return;
+ }
+ if (_serialoutput) {
+ Serial.printf(".");
+ _colcount++;
+ if (!(_colcount % 80)) {
+ Serial.println();
+ }
+ }
+ }
+ } else if(!_uploaderror && (UPLOAD_FILE_END == upload.status)) {
+ if (_serialoutput && (_colcount % 80)) {
+ Serial.println();
+ }
+ if (!_uploadfiletype) {
+ if (!Update.end(true)) { // true to set the size to the current progress
+ if (_serialoutput) {
+ Update.printError(Serial);
+ }
+ _uploaderror = 6;
+ return;
+ }
+ }
+ if (!_uploaderror) {
+ snprintf_P(log, sizeof(log), PSTR(D_LOG_UPLOAD D_SUCCESSFUL " %u bytes. " D_RESTARTING), upload.totalSize);
+ addLog(LOG_LEVEL_INFO, log);
+ }
+ } else if (UPLOAD_FILE_ABORTED == upload.status) {
+ restartflag = 0;
+ mqttcounter = 0;
+ _uploaderror = 7;
+ if (!_uploadfiletype) {
+ Update.end();
+ }
+ }
+ delay(0);
+}
+
+void handleCmnd()
+{
+ if (httpUser()) {
+ return;
+ }
+ char svalue[INPUT_BUFFER_SIZE]; // big to serve Backlog
+
+ addLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_COMMAND));
+
+ uint8_t valid = 1;
+ if (sysCfg.web_password[0] != 0) {
+ if (!(!strcmp(webServer->arg("user").c_str(),WEB_USERNAME) && !strcmp(webServer->arg("password").c_str(),sysCfg.web_password))) {
+ valid = 0;
+ }
+ }
+
+ String message = "";
+ if (valid) {
+ byte curridx = logidx;
+ if (strlen(webServer->arg("cmnd").c_str())) {
+// snprintf_P(svalue, sizeof(svalue), webServer->arg("cmnd").c_str());
+ snprintf_P(svalue, sizeof(svalue), PSTR("%s"), webServer->arg("cmnd").c_str());
+ byte syslog_now = syslog_level;
+ syslog_level = 0; // Disable UDP syslog to not trigger hardware WDT
+ do_cmnd(svalue);
+ syslog_level = syslog_now;
+ }
+
+ if (logidx != curridx) {
+ byte counter = curridx;
+ do {
+ if (Log[counter].length()) {
+ if (message.length()) {
+ message += F("\n");
+ }
+ if (sysCfg.flag.mqtt_enabled) {
+ // [14:49:36 MQTT: stat/wemos5/RESULT = {"POWER":"OFF"}] > [RESULT = {"POWER":"OFF"}]
+// message += Log[counter].substring(17 + strlen(PUB_PREFIX) + strlen(sysCfg.mqtt_topic));
+ message += Log[counter].substring(Log[counter].lastIndexOf("/",Log[counter].indexOf("="))+1);
+ } else {
+ // [14:49:36 RSLT: RESULT = {"POWER":"OFF"}] > [RESULT = {"POWER":"OFF"}]
+ message += Log[counter].substring(Log[counter].indexOf(": ")+2);
+ }
+ }
+ counter++;
+ if (counter > MAX_LOG_LINES -1) {
+ counter = 0;
+ }
+ } while (counter != logidx);
+ } else {
+ message = F(D_ENABLE_WEBLOG_FOR_RESPONSE "\n");
+ }
+ } else {
+ message = F(D_NEED_USER_AND_PASSWORD "\n");
+ }
+ webServer->send(200, FPSTR(HDR_CTYPE_PLAIN), message);
+}
+
+void handleConsole()
+{
+ if (httpUser()) {
+ return;
+ }
+
+ addLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONSOLE);
+
+ String page = FPSTR(HTTP_HEAD);
+ page.replace(F("{v}"), FPSTR(S_CONSOLE));
+ page.replace(F(""), FPSTR(HTTP_SCRIPT_CONSOL));
+ page.replace(F(""), F(""));
+ page += FPSTR(HTTP_FORM_CMND);
+ page += FPSTR(HTTP_BTN_MAIN);
+ showPage(page);
+}
+
+void handleAjax()
+{
+ if (httpUser()) {
+ return;
+ }
+ char log[LOGSZ];
+ char svalue[INPUT_BUFFER_SIZE]; // big to serve Backlog
+ byte cflg = 1;
+ byte counter = 99;
+
+ if (strlen(webServer->arg("c1").c_str())) {
+ snprintf_P(svalue, sizeof(svalue), PSTR("%s"), webServer->arg("c1").c_str());
+ snprintf_P(log, sizeof(log), PSTR(D_LOG_COMMAND "%s"), svalue);
+ addLog(LOG_LEVEL_INFO, log);
+ byte syslog_now = syslog_level;
+ syslog_level = 0; // Disable UDP syslog to not trigger hardware WDT
+ do_cmnd(svalue);
+ syslog_level = syslog_now;
+ }
+
+ if (strlen(webServer->arg("c2").c_str())) {
+ counter = atoi(webServer->arg("c2").c_str());
+ }
+
+ String message = F("
");
+ message += String(logidx);
+ message += F("");
+ message += String(logajaxflg);
+ if (!logajaxflg) {
+ counter = 99;
+ logajaxflg = 1;
+ }
+ message += F("");
+ if (counter != logidx) {
+ if (99 == counter) {
+ counter = logidx;
+ cflg = 0;
+ }
+ do {
+ if (Log[counter].length()) {
+ if (cflg) {
+ message += F("\n");
+ } else {
+ cflg = 1;
+ }
+ message += Log[counter];
+ }
+ counter++;
+ if (counter > MAX_LOG_LINES -1) {
+ counter = 0;
+ }
+ } while (counter != logidx);
+ }
+ message += F("");
+ webServer->send(200, FPSTR(HDR_CTYPE_XML), message);
+}
+
+void handleInfo()
+{
+ if (httpUser()) {
+ return;
+ }
+ addLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_INFORMATION);
+
+ char stopic[TOPSZ];
+
+ int freeMem = ESP.getFreeHeap();
+
+ String page = FPSTR(HTTP_HEAD);
+ page.replace(F("{v}"), FPSTR(S_INFORMATION));
+// page += F("
");
+ page += F("");
+ page += F("");
+ page += F("" D_PROGRAM_VERSION " | "); page += Version; page += F(" |
");
+ page += F("" D_BUILD_DATE_AND_TIME " | "); page += getBuildDateTime(); page += F(" |
");
+ page += F("" D_CORE_AND_SDK_VERSION " | "); page += ESP.getCoreVersion(); page += F("/"); page += String(ESP.getSdkVersion()); page += F(" |
");
+ page += F("" D_UPTIME " | "); page += String(uptime); page += F(" Hours |
");
+ snprintf_P(stopic, sizeof(stopic), PSTR(" at %X"), CFG_Address());
+ page += F("" D_FLASH_WRITE_COUNT " | "); page += String(sysCfg.saveFlag); page += stopic; page += F(" |
");
+ page += F("" D_BOOT_COUNT " | "); page += String(sysCfg.bootcount); page += F(" |
");
+ page += F("" D_RESTART_REASON " | "); page += getResetReason(); page += F(" |
");
+ for (byte i = 0; i < Maxdevice; i++) {
+ page += F("" D_FRIENDLY_NAME " ");
+ page += i +1;
+ page += F(" | "); page += sysCfg.friendlyname[i]; page += F(" |
");
+ }
+ page += F(" |
");
+ page += F("" D_AP); page += String(sysCfg.sta_active +1);
+ page += F(" " D_SSID " (" D_RSSI ") | "); page += sysCfg.sta_ssid[sysCfg.sta_active]; page += F(" ("); page += WIFI_getRSSIasQuality(WiFi.RSSI()); page += F("%) |
");
+ page += F("" D_HOSTNAME " | "); page += Hostname; page += F(" |
");
+ if (static_cast(WiFi.localIP()) != 0) {
+ page += F("" D_IP_ADDRESS " | "); page += WiFi.localIP().toString(); page += F(" |
");
+ page += F("" D_GATEWAY " | "); page += IPAddress(sysCfg.ip_address[1]).toString(); page += F(" |
");
+ page += F("" D_SUBNET_MASK " | "); page += IPAddress(sysCfg.ip_address[2]).toString(); page += F(" |
");
+ page += F("" D_DNS_SERVER " | "); page += IPAddress(sysCfg.ip_address[3]).toString(); page += F(" |
");
+ page += F("" D_MAC_ADDRESS " | "); page += WiFi.macAddress(); page += F(" |
");
+ }
+ if (static_cast(WiFi.softAPIP()) != 0) {
+ page += F("" D_AP " " D_IP_ADDRESS " | "); page += WiFi.softAPIP().toString(); page += F(" |
");
+ page += F("" D_AP " " D_GATEWAY " | "); page += WiFi.softAPIP().toString(); page += F(" |
");
+ page += F("" D_AP " " D_MAC_ADDRESS " | "); page += WiFi.softAPmacAddress(); page += F(" |
");
+ }
+ page += F(" |
");
+ if (sysCfg.flag.mqtt_enabled) {
+ page += F("" D_MQTT_HOST " | "); page += sysCfg.mqtt_host; page += F(" |
");
+ page += F("" D_MQTT_PORT " | "); page += String(sysCfg.mqtt_port); page += F(" |
");
+ page += F("" D_MQTT_CLIENT " & " D_FALLBACK_TOPIC " | "); page += MQTTClient; page += F(" |
");
+ page += F("" D_MQTT_USER " | "); page += sysCfg.mqtt_user; page += F(" |
");
+ page += F("" D_MQTT_TOPIC " | "); page += sysCfg.mqtt_topic; page += F(" |
");
+ page += F("" D_MQTT_GROUP_TOPIC " | "); page += sysCfg.mqtt_grptopic; page += F(" |
");
+ getTopic_P(stopic, 0, sysCfg.mqtt_topic, "");
+ page += F("" D_MQTT_FULL_TOPIC " | "); page += stopic; page += F(" |
");
+
+ } else {
+ page += F("" D_MQTT " | " D_DISABLED " |
");
+ }
+ page += F(" |
");
+ page += F("" D_EMULATION " | ");
+#ifdef USE_EMULATION
+ if (EMUL_WEMO == sysCfg.flag.emulation) {
+ page += F(D_BELKIN_WEMO);
+ }
+ else if (EMUL_HUE == sysCfg.flag.emulation) {
+ page += F(D_HUE_BRIDGE);
+ }
+ else {
+ page += F(D_NONE);
+ }
+#else
+ page += F(D_DISABLED);
+#endif // USE_EMULATION
+ page += F(" |
");
+
+ page += F("" D_MDNS_DISCOVERY " | ");
+#ifdef USE_DISCOVERY
+ page += F(D_ENABLED);
+ page += F(" |
");
+ page += F("" D_MDNS_ADVERTISE " | ");
+#ifdef WEBSERVER_ADVERTISE
+ page += F(D_WEB_SERVER);
+#else
+ page += F(D_DISABLED);
+#endif // WEBSERVER_ADVERTISE
+#else
+ page += F(D_DISABLED);
+#endif // USE_DISCOVERY
+ page += F(" |
");
+
+ page += F(" |
");
+ page += F("" D_ESP_CHIP_ID " | "); page += String(ESP.getChipId()); page += F(" |
");
+ page += F("" D_FLASH_CHIP_ID " | "); page += String(ESP.getFlashChipId()); page += F(" |
");
+ page += F("" D_FLASH_CHIP_SIZE " | "); page += String(ESP.getFlashChipRealSize() / 1024); page += F("kB |
");
+ page += F("" D_PROGRAM_FLASH_SIZE " | "); page += String(ESP.getFlashChipSize() / 1024); page += F("kB |
");
+ page += F("" D_PROGRAM_SIZE " | "); page += String(ESP.getSketchSize() / 1024); page += F("kB |
");
+ page += F("" D_FREE_PROGRAM_SPACE " | "); page += String(ESP.getFreeSketchSpace() / 1024); page += F("kB |
");
+ page += F("" D_FREE_MEMORY " | "); page += String(freeMem / 1024); page += F("kB |
");
+ page += F("
");
+// page += F("");
+ page += FPSTR(HTTP_BTN_MAIN);
+ showPage(page);
+}
+
+void handleRestart()
+{
+ if (httpUser()) {
+ return;
+ }
+ addLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_RESTART);
+
+ String page = FPSTR(HTTP_HEAD);
+ page.replace(F("{v}"), FPSTR(S_RESTART));
+ page += FPSTR(HTTP_MSG_RSTRT);
+ if (HTTP_MANAGER == _httpflag) {
+ _httpflag = HTTP_ADMIN;
+ } else {
+ page += FPSTR(HTTP_BTN_MAIN);
+ }
+ showPage(page);
+
+ restartflag = 2;
+}
+
+/********************************************************************************************/
+
+void handleNotFound()
+{
+ if (captivePortal()) { // If captive portal redirect instead of displaying the error page.
+ return;
+ }
+
+#ifdef USE_EMULATION
+ String path = webServer->uri();
+ if ((EMUL_HUE == sysCfg.flag.emulation) && (path.startsWith("/api"))) {
+ handle_hue_api(&path);
+ } else
+#endif // USE_EMULATION
+ {
+ String message = F(D_FILE_NOT_FOUND "\n\nURI: ");
+ message += webServer->uri();
+ message += F("\nMethod: ");
+ message += (webServer->method() == HTTP_GET) ? F("GET") : F("POST");
+ message += F("\nArguments: ");
+ message += webServer->args();
+ message += F("\n");
+ for ( uint8_t i = 0; i < webServer->args(); i++ ) {
+ message += " " + webServer->argName(i) + ": " + webServer->arg(i) + "\n";
+ }
+ setHeader();
+ webServer->send(404, FPSTR(HDR_CTYPE_PLAIN), message);
+ }
+}
+
+/* Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */
+boolean captivePortal()
+{
+ if ((HTTP_MANAGER == _httpflag) && !isIp(webServer->hostHeader())) {
+ addLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_REDIRECTED));
+
+ webServer->sendHeader(F("Location"), String("http://") + webServer->client().localIP().toString(), true);
+ webServer->send(302, FPSTR(HDR_CTYPE_PLAIN), ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
+ webServer->client().stop(); // Stop is needed because we sent no content length
+ return true;
+ }
+ return false;
+}
+
+/** Is this an IP? */
+boolean isIp(String str)
+{
+ for (uint16_t i = 0; i < str.length(); i++) {
+ int c = str.charAt(i);
+ if (c != '.' && (c < '0' || c > '9')) {
+ return false;
+ }
+ }
+ return true;
+}
+#endif // USE_WEBSERVER
diff --git a/sonoff/xdrv_snfled.ino b/sonoff/xdrv_snfled.ino
new file mode 100644
index 000000000..90e5e1a77
--- /dev/null
+++ b/sonoff/xdrv_snfled.ino
@@ -0,0 +1,574 @@
+/*
+ xdrv_snfled.ino - sonoff led support for Sonoff-Tasmota
+
+ Copyright (C) 2017 Theo Arends
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
.
+*/
+
+/*********************************************************************************************\
+ * Sonoff B1, AiLight, Sonoff Led and BN-SZ01
+ *
+ * sfl_flg Module Color ColorTemp
+ * 1 Sonoff BN-SZ W no
+ * 2 Sonoff Led CW yes
+ * 3 not used
+ * 4 AiLight RGBW no
+ * 5 Sonoff B1 RGBCW yes
+\*********************************************************************************************/
+
+uint8_t ledTable[] = {
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4,
+ 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8,
+ 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 12, 12, 12, 13, 13, 14,
+ 14, 15, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 22,
+ 22, 23, 23, 24, 25, 25, 26, 26, 27, 28, 28, 29, 30, 30, 31, 32,
+ 33, 33, 34, 35, 36, 36, 37, 38, 39, 40, 40, 41, 42, 43, 44, 45,
+ 46, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
+ 61, 62, 63, 64, 65, 67, 68, 69, 70, 71, 72, 73, 75, 76, 77, 78,
+ 80, 81, 82, 83, 85, 86, 87, 89, 90, 91, 93, 94, 95, 97, 98, 99,
+ 101,102,104,105,107,108,110,111,113,114,116,117,119,121,122,124,
+ 125,127,129,130,132,134,135,137,139,141,142,144,146,148,150,151,
+ 153,155,157,159,161,163,165,166,168,170,172,174,176,178,180,182,
+ 184,186,189,191,193,195,197,199,201,204,206,208,210,212,215,217,
+ 219,221,224,226,228,231,233,235,238,240,243,245,248,250,253,255 };
+
+uint8_t sl_dcolor[5];
+uint8_t sl_tcolor[5];
+uint8_t sl_lcolor[5];
+
+uint8_t sl_power;
+uint8_t sl_any;
+uint8_t sl_wakeupActive = 0;
+uint8_t sl_wakeupDimmer = 0;
+uint16_t sl_wakeupCntr = 0;
+
+/*********************************************************************************************\
+ * Sonoff B1 and AiLight inspired by OpenLight https://github.com/icamgo/noduino-sdk
+\*********************************************************************************************/
+
+extern "C" {
+ void os_delay_us(unsigned int);
+}
+
+uint8_t sl_pdi;
+uint8_t sl_pdcki;
+
+void sl_di_pulse(uint8_t times)
+{
+ for (uint8_t i = 0; i < times; i++) {
+ digitalWrite(sl_pdi, HIGH);
+ digitalWrite(sl_pdi, LOW);
+ }
+}
+
+void sl_dcki_pulse(uint8_t times)
+{
+ for (uint8_t i = 0; i < times; i++) {
+ digitalWrite(sl_pdcki, HIGH);
+ digitalWrite(sl_pdcki, LOW);
+ }
+}
+
+void sl_my92x1_write(uint8_t data)
+{
+ for (uint8_t i = 0; i < 4; i++) { // Send 8bit Data
+ digitalWrite(sl_pdcki, LOW);
+ digitalWrite(sl_pdi, (data & 0x80));
+ digitalWrite(sl_pdcki, HIGH);
+ data = data << 1;
+ digitalWrite(sl_pdi, (data & 0x80));
+ digitalWrite(sl_pdcki, LOW);
+ digitalWrite(sl_pdi, LOW);
+ data = data << 1;
+ }
+}
+
+void sl_my92x1_init()
+{
+ uint8_t chips = sfl_flg -3; // 1 (AiLight) or 2 (Sonoff B1)
+
+ sl_dcki_pulse(chips * 32); // Clear all duty register
+ os_delay_us(12); // TStop > 12us.
+ // Send 12 DI pulse, after 6 pulse's falling edge store duty data, and 12
+ // pulse's rising edge convert to command mode.
+ sl_di_pulse(12);
+ os_delay_us(12); // Delay >12us, begin send CMD data
+ for (uint8_t n = 0; n < chips; n++) { // Send CMD data
+ sl_my92x1_write(0x18); // ONE_SHOT_DISABLE, REACTION_FAST, BIT_WIDTH_8, FREQUENCY_DIVIDE_1, SCATTER_APDM
+ }
+ os_delay_us(12); // TStart > 12us. Delay 12 us.
+ // Send 16 DI pulse, at 14 pulse's falling edge store CMD data, and
+ // at 16 pulse's falling edge convert to duty mode.
+ sl_di_pulse(16);
+ os_delay_us(12); // TStop > 12us.
+}
+
+void sl_my92x1_duty(uint8_t duty_r, uint8_t duty_g, uint8_t duty_b, uint8_t duty_w, uint8_t duty_c)
+{
+ uint8_t channels[2] = { 4, 6 };
+
+ uint8_t didx = sfl_flg -4; // 0 or 1
+
+ uint8_t duty[2][6] = {{ duty_r, duty_g, duty_b, duty_w, 0, 0 }, // Definition for RGBW channels
+ { duty_w, duty_c, 0, duty_g, duty_r, duty_b }}; // Definition for RGBWC channels
+
+ os_delay_us(12); // TStop > 12us.
+ for (uint8_t channel = 0; channel < channels[didx]; channel++) {
+ sl_my92x1_write(duty[didx][channel]); // Send 8bit Data
+ }
+ os_delay_us(12); // TStart > 12us. Ready for send DI pulse.
+ sl_di_pulse(8); // Send 8 DI pulse. After 8 pulse falling edge, store old data.
+ os_delay_us(12); // TStop > 12us.
+}
+
+/********************************************************************************************/
+
+void sl_init(void)
+{
+ pin[GPIO_WS2812] = 99; // I do not allow both Sonoff Led AND WS2812 led
+ if (sfl_flg < 4) {
+ if (!my_module.gp.io[4]) {
+ pinMode(4, OUTPUT); // Stop floating outputs
+ digitalWrite(4, LOW);
+ }
+ if (!my_module.gp.io[5]) {
+ pinMode(5, OUTPUT); // Stop floating outputs
+ digitalWrite(5, LOW);
+ }
+ if (!my_module.gp.io[14]) {
+ pinMode(14, OUTPUT); // Stop floating outputs
+ digitalWrite(14, LOW);
+ }
+ sysCfg.pwmvalue[0] = 0; // We use dimmer / led_color
+ if (2 == sfl_flg) {
+ sysCfg.pwmvalue[1] = 0; // We use led_color
+ }
+ } else {
+ sl_pdi = pin[GPIO_DI];
+ sl_pdcki = pin[GPIO_DCKI];
+
+ pinMode(sl_pdi, OUTPUT);
+ pinMode(sl_pdcki, OUTPUT);
+ digitalWrite(sl_pdi, LOW);
+ digitalWrite(sl_pdcki, LOW);
+
+ sl_my92x1_init();
+ }
+
+ sl_power = 0;
+ sl_any = 0;
+ sl_wakeupActive = 0;
+}
+
+void sl_setColorTemp(uint16_t ct)
+{
+/* Color Temperature (https://developers.meethue.com/documentation/core-concepts)
+ *
+ * ct = 153 = 2000K = Warm = CCWW = 00FF
+ * ct = 500 = 6500K = Cold = CCWW = FF00
+ */
+ uint16_t my_ct = ct - 153;
+ if (my_ct > 347) {
+ my_ct = 347;
+ }
+ uint16_t icold = (100 * (347 - my_ct)) / 136;
+ uint16_t iwarm = (100 * my_ct) / 136;
+ if (5 == sfl_flg) {
+ sysCfg.led_color[0] = 0;
+ sysCfg.led_color[1] = 0;
+ sysCfg.led_color[2] = 0;
+ sysCfg.led_color[3] = (uint8_t)icold;
+ sysCfg.led_color[4] = (uint8_t)iwarm;
+ } else {
+ sysCfg.led_color[0] = (uint8_t)icold;
+ sysCfg.led_color[1] = (uint8_t)iwarm;
+ }
+}
+
+uint16_t sl_getColorTemp()
+{
+ uint8_t ct_idx = 0;
+ if (5 == sfl_flg) {
+ ct_idx = 3;
+ }
+ uint16_t my_ct = sysCfg.led_color[ct_idx +1];
+ if (my_ct > 0) {
+ return ((my_ct * 136) / 100) + 154;
+ } else {
+ my_ct = sysCfg.led_color[ct_idx];
+ return 499 - ((my_ct * 136) / 100);
+ }
+}
+
+void sl_setDim(uint8_t myDimmer)
+{
+ float temp;
+
+ if ((1 == sfl_flg) && (100 == myDimmer)) {
+ myDimmer = 99; // BN-SZ01 starts flickering at dimmer = 100
+ }
+ float newDim = 100 / (float)myDimmer;
+ for (byte i = 0; i < sfl_flg; i++) {
+ temp = (float)sysCfg.led_color[i] / newDim;
+ sl_dcolor[i] = (uint8_t)temp;
+ }
+}
+
+void sl_setColor()
+{
+ uint8_t highest = 0;
+ float temp;
+
+ for (byte i = 0; i < sfl_flg; i++) {
+ if (highest < sl_dcolor[i]) {
+ highest = sl_dcolor[i];
+ }
+ }
+ float mDim = (float)highest / 2.55;
+ sysCfg.led_dimmer[0] = (uint8_t)mDim;
+ float newDim = 100 / mDim;
+ for (byte i = 0; i < sfl_flg; i++) {
+ temp = (float)sl_dcolor[i] * newDim;
+ sysCfg.led_color[i] = (uint8_t)temp;
+ }
+}
+
+char* sl_getColor(char* scolor)
+{
+ sl_setDim(sysCfg.led_dimmer[0]);
+ scolor[0] = '\0';
+ for (byte i = 0; i < sfl_flg; i++) {
+ snprintf_P(scolor, 11, PSTR("%s%02X"), scolor, sl_dcolor[i]);
+ }
+ return scolor;
+}
+
+void sl_prepPower(char *svalue, uint16_t ssvalue)
+{
+ char scolor[11];
+
+// do_cmnd_power(index, (sysCfg.led_dimmer[0]>0));
+ if (sysCfg.led_dimmer[0] && !(power&1)) {
+ do_cmnd_power(1, 7); // No publishPowerState
+ }
+ else if (!sysCfg.led_dimmer[0] && (power&1)) {
+ do_cmnd_power(1, 6); // No publishPowerState
+ }
+#ifdef USE_DOMOTICZ
+ mqtt_publishDomoticzPowerState(1);
+#endif // USE_DOMOTICZ
+ if (sfl_flg > 1) {
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_RSLT_POWER "\":\"%s\", \"" D_CMND_DIMMER "\":%d, \"" D_CMND_COLOR "\":\"%s\"}"),
+ getStateText(power &1), sysCfg.led_dimmer[0], sl_getColor(scolor));
+ } else {
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_RSLT_POWER "\":\"%s\", \"" D_CMND_DIMMER "\":%d}"),
+ getStateText(power &1), sysCfg.led_dimmer[0]);
+ }
+}
+
+void sl_setPower(uint8_t power)
+{
+ sl_power = power &1;
+ if (sl_wakeupActive) {
+ sl_wakeupActive--;
+ }
+ sl_animate();
+}
+
+void sl_animate()
+{
+// {"Wakeup":"Done"}
+ char svalue[32]; // was MESSZ
+ uint8_t fadeValue;
+ uint8_t cur_col[5];
+
+ if (0 == sl_power) { // Power Off
+ for (byte i = 0; i < sfl_flg; i++) {
+ sl_tcolor[i] = 0;
+ }
+ }
+ else {
+ if (!sl_wakeupActive) { // Power On
+ sl_setDim(sysCfg.led_dimmer[0]);
+ if (0 == sysCfg.led_fade) {
+ for (byte i = 0; i < sfl_flg; i++) {
+ sl_tcolor[i] = sl_dcolor[i];
+ }
+ } else {
+ for (byte i = 0; i < sfl_flg; i++) {
+ if (sl_tcolor[i] != sl_dcolor[i]) {
+ if (sl_tcolor[i] < sl_dcolor[i]) {
+ sl_tcolor[i] += ((sl_dcolor[i] - sl_tcolor[i]) >> sysCfg.led_speed) +1;
+ }
+ if (sl_tcolor[i] > sl_dcolor[i]) {
+ sl_tcolor[i] -= ((sl_tcolor[i] - sl_dcolor[i]) >> sysCfg.led_speed) +1;
+ }
+ }
+ }
+ }
+ } else { // Power On using wake up duration
+ if (2 == sl_wakeupActive) {
+ sl_wakeupActive = 1;
+ for (byte i = 0; i < sfl_flg; i++) {
+ sl_tcolor[i] = 0;
+ }
+ sl_wakeupCntr = 0;
+ sl_wakeupDimmer = 0;
+ }
+ sl_wakeupCntr++;
+ if (sl_wakeupCntr > ((sysCfg.led_wakeup * STATES) / sysCfg.led_dimmer[0])) {
+ sl_wakeupCntr = 0;
+ sl_wakeupDimmer++;
+ if (sl_wakeupDimmer <= sysCfg.led_dimmer[0]) {
+ sl_setDim(sl_wakeupDimmer);
+ for (byte i = 0; i < sfl_flg; i++) {
+ sl_tcolor[i] = sl_dcolor[i];
+ }
+ } else {
+ snprintf_P(svalue, sizeof(svalue), PSTR("{\"" D_CMND_WAKEUP "\":\"" D_DONE "\"}"));
+ mqtt_publish_topic_P(2, PSTR(D_CMND_WAKEUP), svalue);
+ sl_wakeupActive = 0;
+ }
+ }
+ }
+ }
+ for (byte i = 0; i < sfl_flg; i++) {
+ if (sl_lcolor[i] != sl_tcolor[i]) {
+ sl_any = 1;
+ }
+ }
+ if (sl_any) {
+ sl_any = 0;
+ for (byte i = 0; i < sfl_flg; i++) {
+ sl_lcolor[i] = sl_tcolor[i];
+ cur_col[i] = (sysCfg.led_table) ? ledTable[sl_lcolor[i]] : sl_lcolor[i];
+ if (sfl_flg < 4) {
+ if (pin[GPIO_PWM1 +i] < 99) {
+ analogWrite(pin[GPIO_PWM1 +i], cur_col[i] * (PWM_RANGE / 255));
+ }
+ }
+ }
+ if (sfl_flg > 3) {
+ sl_my92x1_duty(cur_col[0], cur_col[1], cur_col[2], cur_col[3], cur_col[4]);
+ }
+ }
+}
+
+/*********************************************************************************************\
+ * Hue support
+\*********************************************************************************************/
+
+void sl_rgb2hsb(float *hue, float *sat, float *bri)
+{
+ RgbColor dcolor;
+
+ sl_setDim(sysCfg.led_dimmer[0]);
+ dcolor.R = sl_dcolor[0];
+ dcolor.G = sl_dcolor[1];
+ dcolor.B = sl_dcolor[2];
+ HsbColor hsb = HsbColor(dcolor);
+ *hue = hsb.H;
+ *sat = hsb.S;
+ *bri = hsb.B;
+}
+
+/********************************************************************************************/
+
+void sl_replaceHSB(String *response)
+{
+ float hue;
+ float sat;
+ float bri;
+
+ if (sfl_flg > 2) {
+ sl_rgb2hsb(&hue, &sat, &bri);
+ response->replace("{h}", String((uint16_t)(65535.0f * hue)));
+ response->replace("{s}", String((uint8_t)(254.0f * sat)));
+ response->replace("{b}", String((uint8_t)(254.0f * bri)));
+ } else {
+ response->replace("{h}", "0");
+ response->replace("{s}", "0");
+// response->replace("{b}", String((uint8_t)(2.54f * (float)sysCfg.led_dimmer[0])));
+ response->replace("{b}", String((uint8_t)(0.01f * (float)sysCfg.led_dimmer[0])));
+ }
+}
+
+void sl_getHSB(float *hue, float *sat, float *bri)
+{
+ if (sfl_flg > 2) {
+ sl_rgb2hsb(hue, sat, bri);
+ } else {
+ *hue = 0;
+ *sat = 0;
+// *bri = (2.54f * (float)sysCfg.led_dimmer[0]);
+ *bri = (0.01f * (float)sysCfg.led_dimmer[0]);
+ }
+}
+
+void sl_setHSB(float hue, float sat, float bri, uint16_t ct)
+{
+ char svalue[MESSZ];
+ HsbColor hsb;
+
+/*
+ char log[LOGSZ];
+ char stemp1[10];
+ char stemp2[10];
+ char stemp3[10];
+ dtostrfi(hue, 3, stemp1);
+ dtostrfi(sat, 3, stemp2);
+ dtostrfi(bri, 3, stemp3);
+ snprintf_P(log, sizeof(log), PSTR("HUE: Set Hue %s, Sat %s, Bri %s, Ct %d"), stemp1, stemp2, stemp3, ct);
+ addLog(LOG_LEVEL_DEBUG, log);
+*/
+
+ if (sfl_flg > 2) {
+ if ((5 == sfl_flg) && (ct > 0)) {
+ sl_setColorTemp(ct);
+ } else {
+ hsb.H = hue;
+ hsb.S = sat;
+ hsb.B = bri;
+ RgbColor tmp = RgbColor(hsb);
+ sl_dcolor[0] = tmp.R;
+ sl_dcolor[1] = tmp.G;
+ sl_dcolor[2] = tmp.B;
+ sl_setColor();
+ }
+ sl_prepPower(svalue, sizeof(svalue));
+ mqtt_publish_topic_P(5, PSTR(D_CMND_COLOR), svalue);
+ } else {
+ uint8_t tmp = (uint8_t)(bri * 100);
+ sysCfg.led_dimmer[0] = tmp;
+ if (2 == sfl_flg) {
+ if (ct > 0) {
+ sl_setColorTemp(ct);
+ }
+ sl_prepPower(svalue, sizeof(svalue));
+ mqtt_publish_topic_P(5, PSTR(D_CMND_COLOR), svalue);
+ } else {
+ sl_prepPower(svalue, sizeof(svalue));
+ mqtt_publish_topic_P(5, PSTR(D_CMND_DIMMER), svalue);
+ }
+ }
+}
+
+/*********************************************************************************************\
+ * Commands
+\*********************************************************************************************/
+
+boolean sl_command(char *type, uint16_t index, char *dataBufUc, uint16_t data_len, int16_t payload, char *svalue, uint16_t ssvalue)
+{
+ boolean serviced = true;
+ boolean coldim = false;
+ char scolor[11];
+ char *p;
+
+ if ((sfl_flg > 1) && !strcasecmp_P(type, PSTR(D_CMND_COLOR))) {
+ if (dataBufUc[0] == '#') {
+ dataBufUc++;
+ data_len--;
+ }
+ if ((2 * sfl_flg) == data_len) {
+ for (byte i = 0; i < sfl_flg; i++) {
+ strlcpy(scolor, dataBufUc + (i *2), 3);
+ sl_dcolor[i] = (uint8_t)strtol(scolor, &p, 16);
+ }
+ sl_setColor();
+ coldim = true;
+ } else {
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_CMND_COLOR "\":\"%s\"}"), sl_getColor(scolor));
+ }
+ }
+ else if (!strcasecmp_P(type, PSTR(D_CMND_COLORTEMPERATURE)) && ((2 == sfl_flg) || (5 == sfl_flg))) { // ColorTemp
+ if ((payload >= 153) && (payload <= 500)) { // https://developers.meethue.com/documentation/core-concepts
+ sl_setColorTemp(payload);
+ coldim = true;
+ } else {
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_CMND_COLORTEMPERATURE "\":%d}"), sl_getColorTemp());
+ }
+ }
+ else if (!strcasecmp_P(type, PSTR(D_CMND_DIMMER))) {
+ if ((payload >= 0) && (payload <= 100)) {
+ sysCfg.led_dimmer[0] = payload;
+ coldim = true;
+ } else {
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_CMND_DIMMER "\":%d}"), sysCfg.led_dimmer[0]);
+ }
+ }
+ else if (!strcasecmp_P(type, PSTR(D_CMND_LEDTABLE))) {
+ if ((payload >= 0) && (payload <= 2)) {
+ switch (payload) {
+ case 0: // Off
+ case 1: // On
+ sysCfg.led_table = payload;
+ break;
+ case 2: // Toggle
+ sysCfg.led_table ^= 1;
+ break;
+ }
+ sl_any = 1;
+ }
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_CMND_LEDTABLE "\":\"%s\"}"), getStateText(sysCfg.led_table));
+ }
+ else if (!strcasecmp_P(type, PSTR(D_CMND_FADE))) {
+ switch (payload) {
+ case 0: // Off
+ case 1: // On
+ sysCfg.led_fade = payload;
+ break;
+ case 2: // Toggle
+ sysCfg.led_fade ^= 1;
+ break;
+ }
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_CMND_FADE "\":\"%s\"}"), getStateText(sysCfg.led_fade));
+ }
+ else if (!strcasecmp_P(type, PSTR(D_CMND_SPEED))) { // 1 - fast, 8 - slow
+ if ((payload > 0) && (payload <= 8)) {
+ sysCfg.led_speed = payload;
+ }
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_CMND_SPEED "\":%d}"), sysCfg.led_speed);
+ }
+ else if (!strcasecmp_P(type, PSTR(D_CMND_WAKEUPDURATION))) {
+ if ((payload > 0) && (payload < 3001)) {
+ sysCfg.led_wakeup = payload;
+ sl_wakeupActive = 0;
+ }
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_CMND_WAKEUPDURATION "\":%d}"), sysCfg.led_wakeup);
+ }
+ else if (!strcasecmp_P(type, PSTR(D_CMND_WAKEUP))) {
+ sl_wakeupActive = 3;
+ do_cmnd_power(1, 1);
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_CMND_WAKEUP "\":\"" D_STARTED "\"}"));
+ }
+ else if (!strcasecmp_P(type, PSTR("UNDOCA"))) { // Theos legacy status
+ sl_getColor(scolor);
+ scolor[6] = '\0'; // RGB only
+ snprintf_P(svalue, ssvalue, PSTR("%s, %d, %d, 1, %d, 1"),
+ scolor, sysCfg.led_fade, sysCfg.led_table, sysCfg.led_speed);
+ mqtt_publish_topic_P(1, type, svalue);
+ svalue[0] = '\0';
+ }
+ else {
+ serviced = false; // Unknown command
+ }
+ if (coldim) {
+ sl_prepPower(svalue, ssvalue);
+ }
+ return serviced;
+}
+
diff --git a/sonoff/xdrv_ws2812.ino b/sonoff/xdrv_ws2812.ino
new file mode 100644
index 000000000..39d181186
--- /dev/null
+++ b/sonoff/xdrv_ws2812.ino
@@ -0,0 +1,630 @@
+/*
+ xdrv_ws2812.ino - ws2812 led string support for Sonoff-Tasmota
+
+ Copyright (C) 2017 Heiko Krupp and Theo Arends
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
.
+*/
+
+#ifdef USE_WS2812
+/*********************************************************************************************\
+ * WS2812 Leds using NeopixelBus library
+\*********************************************************************************************/
+
+//#include
// Global defined as also used by Sonoff Led
+
+#ifdef USE_WS2812_DMA
+#if (USE_WS2812_CTYPE == 1)
+ NeoPixelBus *strip = NULL;
+#else // USE_WS2812_CTYPE
+ NeoPixelBus *strip = NULL;
+#endif // USE_WS2812_CTYPE
+#else // USE_WS2812_DMA
+#if (USE_WS2812_CTYPE == 1)
+ NeoPixelBus *strip = NULL;
+#else // USE_WS2812_CTYPE
+ NeoPixelBus *strip = NULL;
+#endif // USE_WS2812_CTYPE
+#endif // USE_WS2812_DMA
+
+struct wsColor {
+ uint8_t red, green, blue;
+};
+
+struct ColorScheme {
+ wsColor* colors;
+ uint8_t count;
+};
+
+wsColor incandescent[2] = { 255, 140, 20, 0, 0, 0 };
+wsColor rgb[3] = { 255, 0, 0, 0, 255, 0, 0, 0, 255 };
+wsColor christmas[2] = { 255, 0, 0, 0, 255, 0 };
+wsColor hanukkah[2] = { 0, 0, 255, 255, 255, 255 };
+wsColor kwanzaa[3] = { 255, 0, 0, 0, 0, 0, 0, 255, 0 };
+wsColor rainbow[7] = { 255, 0, 0, 255, 128, 0, 255, 255, 0, 0, 255, 0, 0, 0, 255, 128, 0, 255, 255, 0, 255 };
+wsColor fire[3] = { 255, 0, 0, 255, 102, 0, 255, 192, 0 };
+ColorScheme schemes[7] = {
+ incandescent, 2,
+ rgb, 3,
+ christmas, 2,
+ hanukkah, 2,
+ kwanzaa, 3,
+ rainbow, 7,
+ fire, 3 };
+
+uint8_t widthValues[5] = {
+ 1, // Small
+ 2, // Medium
+ 4, // Large
+ 8, // Largest
+ 255 }; // All
+uint8_t repeatValues[5] = {
+ 8, // Small
+ 6, // Medium
+ 4, // Large
+ 2, // Largest
+ 1 }; // All
+uint8_t speedValues[6] = {
+ 0, // None
+ 9 * (STATES / 10), // Slowest
+ 7 * (STATES / 10), // Slower
+ 5 * (STATES / 10), // Slow
+ 3 * (STATES / 10), // Fast
+ 1 * (STATES / 10) }; // Fastest
+
+uint8_t lany = 0;
+RgbColor dcolor;
+RgbColor tcolor;
+RgbColor lcolor;
+
+uint8_t wakeupDimmer = 0;
+uint8_t ws_bit = 0;
+uint16_t wakeupCntr = 0;
+unsigned long stripTimerCntr = 0; // Bars and Gradient
+
+void ws2812_setDim(uint8_t myDimmer)
+{
+ float newDim = 100 / (float)myDimmer;
+ float fmyRed = (float)sysCfg.ws_red / newDim;
+ float fmyGrn = (float)sysCfg.ws_green / newDim;
+ float fmyBlu = (float)sysCfg.ws_blue / newDim;
+ dcolor.R = (uint8_t)fmyRed;
+ dcolor.G = (uint8_t)fmyGrn;
+ dcolor.B = (uint8_t)fmyBlu;
+}
+
+void ws2812_setColor(uint16_t led, char* colstr)
+{
+ HtmlColor hcolor;
+ char log[LOGSZ];
+ char lcolstr[8];
+
+ snprintf_P(lcolstr, sizeof(lcolstr), PSTR("#%s"), colstr);
+ uint8_t result = hcolor.Parse((char *)lcolstr, 7);
+ if (result) {
+ if (led) {
+ strip->SetPixelColor(led -1, RgbColor(hcolor)); // Led 1 is strip Led 0 -> substract offset 1
+ strip->Show();
+ } else {
+ dcolor = RgbColor(hcolor);
+
+// snprintf_P(log, sizeof(log), PSTR("DBG: Red %02X, Green %02X, Blue %02X"), dcolor.R, dcolor.G, dcolor.B);
+// addLog(LOG_LEVEL_DEBUG, log);
+
+ uint16_t temp = dcolor.R;
+ if (temp < dcolor.G) {
+ temp = dcolor.G;
+ }
+ if (temp < dcolor.B) {
+ temp = dcolor.B;
+ }
+ float mDim = (float)temp / 2.55;
+ sysCfg.ws_dimmer = (uint8_t)mDim;
+
+ float newDim = 100 / mDim;
+ float fmyRed = (float)dcolor.R * newDim;
+ float fmyGrn = (float)dcolor.G * newDim;
+ float fmyBlu = (float)dcolor.B * newDim;
+ sysCfg.ws_red = (uint8_t)fmyRed;
+ sysCfg.ws_green = (uint8_t)fmyGrn;
+ sysCfg.ws_blue = (uint8_t)fmyBlu;
+
+ lany = 1;
+ }
+ }
+}
+
+void ws2812_getColor(uint16_t led, char* svalue, uint16_t ssvalue)
+{
+ RgbColor mcolor;
+ char stemp[20];
+
+ if (led) {
+ mcolor = strip->GetPixelColor(led -1);
+ snprintf_P(stemp, sizeof(stemp), PSTR(D_CMND_LED "%d"), led);
+ } else {
+ ws2812_setDim(sysCfg.ws_dimmer);
+ mcolor = dcolor;
+ snprintf_P(stemp, sizeof(stemp), PSTR(D_CMND_COLOR));
+ }
+ uint32_t color = (uint32_t)mcolor.R << 16;
+ color += (uint32_t)mcolor.G << 8;
+ color += (uint32_t)mcolor.B;
+ snprintf_P(svalue, ssvalue, PSTR("{\"%s\":\"%06X\"}"), stemp, color);
+}
+
+void ws2812_stripShow()
+{
+ RgbColor c;
+
+ if (sysCfg.ws_ledtable) {
+ for (uint16_t i = 0; i < sysCfg.ws_pixels; i++) {
+ c = strip->GetPixelColor(i);
+ strip->SetPixelColor(i, RgbColor(ledTable[c.R], ledTable[c.G], ledTable[c.B]));
+ }
+ }
+ strip->Show();
+}
+
+void ws2812_resetWakupState()
+{
+ wakeupDimmer = 0;
+ wakeupCntr = 0;
+}
+
+void ws2812_resetStripTimer()
+{
+ stripTimerCntr = 0;
+}
+
+int mod(int a, int b)
+{
+ int ret = a % b;
+ if (ret < 0) {
+ ret += b;
+ }
+ return ret;
+}
+
+void ws2812_clock()
+{
+ RgbColor c;
+
+ strip->ClearTo(0); // Reset strip
+ float newDim = 100 / (float)sysCfg.ws_dimmer;
+ float f1 = 255 / newDim;
+ uint8_t i1 = (uint8_t)f1;
+ float f2 = 127 / newDim;
+ uint8_t i2 = (uint8_t)f2;
+ float f3 = 63 / newDim;
+ uint8_t i3 = (uint8_t)f3;
+
+ int j = sysCfg.ws_pixels;
+ int clksize = 600 / j;
+ int i = (rtcTime.Second * 10) / clksize;
+
+ c = strip->GetPixelColor(mod(i, j)); c.B = i1; strip->SetPixelColor(mod(i, j), c);
+ i = (rtcTime.Minute * 10) / clksize;
+ c = strip->GetPixelColor(mod(i -1, j)); c.G = i3; strip->SetPixelColor(mod(i -1, j), c);
+ c = strip->GetPixelColor(mod(i, j)); c.G = i1; strip->SetPixelColor(mod(i, j), c);
+ c = strip->GetPixelColor(mod(i +1, j)); c.G = i3; strip->SetPixelColor(mod(i +1, j), c);
+ i = (rtcTime.Hour % 12) * (50 / clksize);
+ c = strip->GetPixelColor(mod(i -2, j)); c.R = i3; strip->SetPixelColor(mod(i -2, j), c);
+ c = strip->GetPixelColor(mod(i -1, j)); c.R = i2; strip->SetPixelColor(mod(i -1, j), c);
+ c = strip->GetPixelColor(mod(i, j)); c.R = i1; strip->SetPixelColor(mod(i, j), c);
+ c = strip->GetPixelColor(mod(i +1, j)); c.R = i2; strip->SetPixelColor(mod(i +1, j), c);
+ c = strip->GetPixelColor(mod(i +2, j)); c.R = i3; strip->SetPixelColor(mod(i +2, j), c);
+ ws2812_stripShow();
+}
+
+void ws2812_gradientColor(struct wsColor* mColor, uint16_t range, uint16_t gradRange, uint16_t i)
+{
+/*
+ * Compute the color of a pixel at position i using a gradient of the color scheme.
+ * This function is used internally by the gradient function.
+ */
+ ColorScheme scheme = schemes[sysCfg.ws_scheme -3];
+ uint16_t curRange = i / range;
+ uint16_t rangeIndex = i % range;
+ uint16_t colorIndex = rangeIndex / gradRange;
+ uint16_t start = colorIndex;
+ uint16_t end = colorIndex +1;
+ if (curRange % 2 != 0) {
+ start = (scheme.count -1) - start;
+ end = (scheme.count -1) - end;
+ }
+ float newDim = 100 / (float)sysCfg.ws_dimmer;
+ float fmyRed = (float)map(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].red, scheme.colors[end].red) / newDim;
+ float fmyGrn = (float)map(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].green, scheme.colors[end].green) / newDim;
+ float fmyBlu = (float)map(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].blue, scheme.colors[end].blue) / newDim;
+ mColor->red = (uint8_t)fmyRed;
+ mColor->green = (uint8_t)fmyGrn;
+ mColor->blue = (uint8_t)fmyBlu;
+}
+
+void ws2812_gradient()
+{
+/*
+ * This routine courtesy Tony DiCola (Adafruit)
+ * Display a gradient of colors for the current color scheme.
+ * Repeat is the number of repetitions of the gradient (pick a multiple of 2 for smooth looping of the gradient).
+ */
+ RgbColor c;
+
+ ColorScheme scheme = schemes[sysCfg.ws_scheme -3];
+ if (scheme.count < 2) {
+ return;
+ }
+
+ uint8_t repeat = repeatValues[sysCfg.ws_width]; // number of scheme.count per ledcount
+ uint16_t range = (uint16_t)ceil((float)sysCfg.ws_pixels / (float)repeat);
+ uint16_t gradRange = (uint16_t)ceil((float)range / (float)(scheme.count - 1));
+ uint16_t offset = speedValues[sysCfg.ws_speed] > 0 ? stripTimerCntr / speedValues[sysCfg.ws_speed] : 0;
+
+ wsColor oldColor, currentColor;
+ ws2812_gradientColor(&oldColor, range, gradRange, offset);
+ currentColor = oldColor;
+ for (uint16_t i = 0; i < sysCfg.ws_pixels; i++) {
+ if (repeatValues[sysCfg.ws_width] > 1) {
+ ws2812_gradientColor(¤tColor, range, gradRange, i +offset);
+ }
+ if (sysCfg.ws_speed > 0) {
+ // Blend old and current color based on time for smooth movement.
+ c.R = map(stripTimerCntr % speedValues[sysCfg.ws_speed], 0, speedValues[sysCfg.ws_speed], oldColor.red, currentColor.red);
+ c.G = map(stripTimerCntr % speedValues[sysCfg.ws_speed], 0, speedValues[sysCfg.ws_speed], oldColor.green, currentColor.green);
+ c.B = map(stripTimerCntr % speedValues[sysCfg.ws_speed], 0, speedValues[sysCfg.ws_speed], oldColor.blue, currentColor.blue);
+ }
+ else {
+ // No animation, just use the current color.
+ c.R = currentColor.red;
+ c.G = currentColor.green;
+ c.B = currentColor.blue;
+ }
+ strip->SetPixelColor(i, c);
+ oldColor = currentColor;
+ }
+ ws2812_stripShow();
+}
+
+void ws2812_bars()
+{
+/*
+ * This routine courtesy Tony DiCola (Adafruit)
+ * Display solid bars of color for the current color scheme.
+ * Width is the width of each bar in pixels/lights.
+ */
+ RgbColor c;
+ uint16_t i;
+
+ ColorScheme scheme = schemes[sysCfg.ws_scheme -3];
+
+ uint16_t maxSize = sysCfg.ws_pixels / scheme.count;
+ if (widthValues[sysCfg.ws_width] > maxSize) {
+ maxSize = 0;
+ }
+
+ uint8_t offset = speedValues[sysCfg.ws_speed] > 0 ? stripTimerCntr / speedValues[sysCfg.ws_speed] : 0;
+
+ wsColor mcolor[scheme.count];
+ memcpy(mcolor, scheme.colors, sizeof(mcolor));
+ float newDim = 100 / (float)sysCfg.ws_dimmer;
+ for (i = 0; i < scheme.count; i++) {
+ float fmyRed = (float)mcolor[i].red / newDim;
+ float fmyGrn = (float)mcolor[i].green / newDim;
+ float fmyBlu = (float)mcolor[i].blue / newDim;
+ mcolor[i].red = (uint8_t)fmyRed;
+ mcolor[i].green = (uint8_t)fmyGrn;
+ mcolor[i].blue = (uint8_t)fmyBlu;
+ }
+ uint8_t colorIndex = offset % scheme.count;
+ for (i = 0; i < sysCfg.ws_pixels; i++) {
+ if (maxSize) {
+ colorIndex = ((i + offset) % (scheme.count * widthValues[sysCfg.ws_width])) / widthValues[sysCfg.ws_width];
+ }
+ c.R = mcolor[colorIndex].red;
+ c.G = mcolor[colorIndex].green;
+ c.B = mcolor[colorIndex].blue;
+ strip->SetPixelColor(i, c);
+ }
+ ws2812_stripShow();
+}
+
+void ws2812_animate()
+{
+ char log[LOGSZ];
+ uint8_t fadeValue;
+
+ stripTimerCntr++;
+ if (0 == bitRead(power, ws_bit)) { // Power Off
+ sleep = sysCfg.sleep;
+ stripTimerCntr = 0;
+ tcolor = 0;
+ }
+ else {
+ sleep = 0;
+ switch (sysCfg.ws_scheme) {
+ case 0: // Power On
+ ws2812_setDim(sysCfg.ws_dimmer);
+ if (0 == sysCfg.ws_fade) {
+ tcolor = dcolor;
+ } else {
+ if (tcolor != dcolor) {
+ uint8_t ws_speed = speedValues[sysCfg.ws_speed];
+ if (tcolor.R < dcolor.R) {
+ tcolor.R += ((dcolor.R - tcolor.R) / ws_speed) +1;
+ }
+ if (tcolor.G < dcolor.G) {
+ tcolor.G += ((dcolor.G - tcolor.G) / ws_speed) +1;
+ }
+ if (tcolor.B < dcolor.B) {
+ tcolor.B += ((dcolor.B - tcolor.B) / ws_speed) +1;
+ }
+ if (tcolor.R > dcolor.R) {
+ tcolor.R -= ((tcolor.R - dcolor.R) / ws_speed) +1;
+ }
+ if (tcolor.G > dcolor.G) {
+ tcolor.G -= ((tcolor.G - dcolor.G) / ws_speed) +1;
+ }
+ if (tcolor.B > dcolor.B) {
+ tcolor.B -= ((tcolor.B - dcolor.B) / ws_speed) +1;
+ }
+ }
+ }
+ break;
+ case 1: // Wake up light
+ wakeupCntr++;
+ if (0 == wakeupDimmer) {
+ tcolor = 0;
+ wakeupDimmer++;
+ }
+ else {
+ if (wakeupCntr > ((sysCfg.ws_wakeup * STATES) / sysCfg.ws_dimmer)) {
+ wakeupCntr = 0;
+ wakeupDimmer++;
+ if (wakeupDimmer <= sysCfg.ws_dimmer) {
+ ws2812_setDim(wakeupDimmer);
+ tcolor = dcolor;
+ } else {
+ sysCfg.ws_scheme = 0;
+ }
+ }
+ }
+ break;
+ case 2: // Clock
+ if (((STATES/10)*2 == state) || (lany != 2)) {
+ ws2812_clock();
+ }
+ lany = 2;
+ break;
+ default:
+ if (1 == sysCfg.ws_fade) {
+ ws2812_gradient();
+ } else {
+ ws2812_bars();
+ }
+ lany = 1;
+ break;
+ }
+ }
+
+ if ((sysCfg.ws_scheme <= 1) || (0 == bitRead(power, ws_bit))) {
+ if ((lcolor != tcolor) || lany) {
+ lany = 0;
+ lcolor = tcolor;
+
+// snprintf_P(log, sizeof(log), PSTR("DBG: StripPixels %d, CfgPixels %d, Red %02X, Green %02X, Blue %02X"), strip->PixelCount(), sysCfg.ws_pixels, lcolor.R, lcolor.G, lcolor.B);
+// addLog(LOG_LEVEL_DEBUG, log);
+
+ if (sysCfg.ws_ledtable) {
+ for (uint16_t i = 0; i < sysCfg.ws_pixels; i++) {
+ strip->SetPixelColor(i, RgbColor(ledTable[lcolor.R],ledTable[lcolor.G],ledTable[lcolor.B]));
+ }
+ } else {
+ for (uint16_t i = 0; i < sysCfg.ws_pixels; i++) {
+ strip->SetPixelColor(i, lcolor);
+ }
+ }
+ strip->Show();
+ }
+ }
+}
+
+void ws2812_update()
+{
+ lany = 1;
+}
+
+void ws2812_pixels()
+{
+ strip->ClearTo(0);
+ strip->Show();
+ tcolor = 0;
+ lany = 1;
+}
+
+void ws2812_init(uint8_t powerbit)
+{
+ ws_bit = powerbit -1;
+#ifdef USE_WS2812_DMA
+#if (USE_WS2812_CTYPE == 1)
+ strip = new NeoPixelBus(WS2812_MAX_LEDS); // For Esp8266, the Pin is omitted and it uses GPIO3 due to DMA hardware use.
+#else // USE_WS2812_CTYPE
+ strip = new NeoPixelBus(WS2812_MAX_LEDS); // For Esp8266, the Pin is omitted and it uses GPIO3 due to DMA hardware use.
+#endif // USE_WS2812_CTYPE
+#else // USE_WS2812_DMA
+#if (USE_WS2812_CTYPE == 1)
+ strip = new NeoPixelBus(WS2812_MAX_LEDS, pin[GPIO_WS2812]);
+#else // USE_WS2812_CTYPE
+ strip = new NeoPixelBus(WS2812_MAX_LEDS, pin[GPIO_WS2812]);
+#endif // USE_WS2812_CTYPE
+#endif // USE_WS2812_DMA
+ strip->Begin();
+ ws2812_pixels();
+}
+
+/*********************************************************************************************\
+ * Hue support
+\*********************************************************************************************/
+
+void ws2812_replaceHSB(String *response)
+{
+ ws2812_setDim(sysCfg.ws_dimmer);
+ HsbColor hsb = HsbColor(dcolor);
+ response->replace("{h}", String((uint16_t)(65535.0f * hsb.H)));
+ response->replace("{s}", String((uint8_t)(254.0f * hsb.S)));
+ response->replace("{b}", String((uint8_t)(254.0f * hsb.B)));
+}
+
+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];
+
+ HsbColor hsb;
+ hsb.H = hue;
+ hsb.S = sat;
+ hsb.B = bri;
+ RgbColor tmp = RgbColor(hsb);
+ sprintf(rgb,"%02X%02X%02X", tmp.R, tmp.G, tmp.B);
+ ws2812_setColor(0,rgb);
+}
+
+/*********************************************************************************************\
+ * Commands
+\*********************************************************************************************/
+
+boolean ws2812_command(char *type, uint16_t index, char *dataBuf, uint16_t data_len, int16_t payload, char *svalue, uint16_t ssvalue)
+{
+ boolean serviced = true;
+
+ if (!strcasecmp_P(type, PSTR(D_CMND_PIXELS))) {
+ if ((payload > 0) && (payload <= WS2812_MAX_LEDS)) {
+ sysCfg.ws_pixels = payload;
+ ws2812_pixels();
+ }
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_CMND_PIXELS "\":%d}"), sysCfg.ws_pixels);
+ }
+ else if (!strcasecmp_P(type, PSTR(D_CMND_LED)) && (index > 0) && (index <= sysCfg.ws_pixels)) {
+ if (6 == data_len) {
+ ws2812_setColor(index, dataBuf);
+ }
+ ws2812_getColor(index, svalue, ssvalue);
+ }
+ else if (!strcasecmp_P(type, PSTR(D_CMND_COLOR))) {
+ if (dataBuf[0] == '#') {
+ dataBuf++;
+ data_len--;
+ }
+ if (6 == data_len) {
+ ws2812_setColor(0, dataBuf);
+ bitSet(power, ws_bit);
+ }
+ ws2812_getColor(0, svalue, ssvalue);
+ }
+ else if (!strcasecmp_P(type, PSTR(D_CMND_DIMMER))) {
+ if ((payload >= 0) && (payload <= 100)) {
+ sysCfg.ws_dimmer = payload;
+ bitSet(power, ws_bit);
+#ifdef USE_DOMOTICZ
+// mqtt_publishDomoticzPowerState(index);
+ mqtt_publishDomoticzPowerState(ws_bit +1);
+#endif // USE_DOMOTICZ
+ }
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_CMND_DIMMER "\":%d}"), sysCfg.ws_dimmer);
+ }
+ else if (!strcasecmp_P(type, PSTR(D_CMND_LEDTABLE))) {
+ if ((payload >= 0) && (payload <= 2)) {
+ switch (payload) {
+ case 0: // Off
+ case 1: // On
+ sysCfg.ws_ledtable = payload;
+ break;
+ case 2: // Toggle
+ sysCfg.ws_ledtable ^= 1;
+ break;
+ }
+ ws2812_update();
+ }
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_CMND_LEDTABLE "\":\"%s\"}"), getStateText(sysCfg.ws_ledtable));
+ }
+ else if (!strcasecmp_P(type, PSTR(D_CMND_FADE))) {
+ switch (payload) {
+ case 0: // Off
+ case 1: // On
+ sysCfg.ws_fade = payload;
+ break;
+ case 2: // Toggle
+ sysCfg.ws_fade ^= 1;
+ break;
+ }
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_CMND_FADE "\":\"%s\"}"), getStateText(sysCfg.ws_fade));
+ }
+ else if (!strcasecmp_P(type, PSTR(D_CMND_SPEED))) { // 1 - fast, 5 - slow
+ if ((payload > 0) && (payload <= 5)) {
+ sysCfg.ws_speed = payload;
+ }
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_CMND_SPEED "\":%d}"), sysCfg.ws_speed);
+ }
+ else if (!strcasecmp_P(type, PSTR(D_CMND_WIDTH))) {
+ if ((payload >= 0) && (payload <= 4)) {
+ sysCfg.ws_width = payload;
+ }
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_CMND_WIDTH "\":%d}"), sysCfg.ws_width);
+ }
+ else if (!strcasecmp_P(type, PSTR(D_CMND_WAKEUP))) {
+ if ((payload > 0) && (payload < 3001)) {
+ sysCfg.ws_wakeup = payload;
+ if (1 == sysCfg.ws_scheme) {
+ sysCfg.ws_scheme = 0;
+ }
+ }
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_CMND_WAKEUP "\":%d}"), sysCfg.ws_wakeup);
+ }
+ else if (!strcasecmp_P(type, PSTR(D_CMND_SCHEME))) {
+ if ((payload >= 0) && (payload <= 9)) {
+ sysCfg.ws_scheme = payload;
+ if (1 == sysCfg.ws_scheme) {
+ ws2812_resetWakupState();
+ }
+ bitSet(power, ws_bit);
+ ws2812_resetStripTimer();
+ }
+ snprintf_P(svalue, ssvalue, PSTR("{\"" D_CMND_SCHEME "\":%d}"), sysCfg.ws_scheme);
+ }
+ else if (!strcasecmp_P(type, PSTR("UNDOCA"))) { // Theos legacy status
+ RgbColor mcolor;
+ ws2812_setDim(sysCfg.ws_dimmer);
+ mcolor = dcolor;
+ uint32_t color = (uint32_t)mcolor.R << 16;
+ color += (uint32_t)mcolor.G << 8;
+ color += (uint32_t)mcolor.B;
+ snprintf_P(svalue, ssvalue, PSTR("%06X, %d, %d, %d, %d, %d"),
+ color, sysCfg.ws_fade, sysCfg.ws_ledtable, sysCfg.ws_scheme, sysCfg.ws_speed, sysCfg.ws_width);
+ mqtt_publish_topic_P(1, type, svalue);
+ svalue[0] = '\0';
+ }
+ else {
+ serviced = false; // Unknown command
+ }
+ return serviced;
+}
+#endif // USE_WS2812