\").replace(/}2/g,\" | \");"
+ "eb('i').innerHTML=s;"
+ "}"
+ "";
+const char HTTP_MSG_SLIDER1[] PROGMEM =
+ " " D_COLDLIGHT "" D_WARMLIGHT " "
+ "";
+const char HTTP_MSG_SLIDER2[] PROGMEM =
+ "" D_DARKLIGHT "" D_BRIGHTLIGHT " "
+ "";
+const char HTTP_MSG_RSTRT[] PROGMEM =
+ "
" D_DEVICE_WILL_RESTART " ";
+const char HTTP_BTN_MENU1[] PROGMEM =
+#ifndef BE_MINIMAL
+ " "
+ " "
+#endif
+ " "
+ " ";
+const char HTTP_BTN_RSTRT[] PROGMEM =
+ " ";
+const char HTTP_BTN_MENU_MODULE[] PROGMEM =
+ " "
+ " ";
+const char HTTP_BTN_MENU4[] PROGMEM =
+ " "
+ " "
+ " "
+ " "
+ " "
+ " ";
+const char HTTP_BTN_MAIN[] PROGMEM =
+ "
";
+const char HTTP_FORM_LOGIN[] PROGMEM =
+ "";
+const char HTTP_BTN_CONF[] PROGMEM =
+ "
";
+const char HTTP_FORM_MODULE[] PROGMEM =
+ " |
---|
+ // }2 = |
+ String func = FPSTR(HTTP_SCRIPT_INFO_BEGIN);
+ func += F("");
+ func += F(D_PROGRAM_VERSION "}2"); func += my_version;
+ func += F("}1" D_BUILD_DATE_AND_TIME "}2"); func += GetBuildDateAndTime();
+ func += F("}1" D_CORE_AND_SDK_VERSION "}2" ARDUINO_ESP8266_RELEASE "/"); func += String(ESP.getSdkVersion());
+ func += F("}1" D_UPTIME "}2"); func += GetUptime();
+ snprintf_P(stopic, sizeof(stopic), PSTR(" at %X"), GetSettingsAddress());
+ func += F("}1" D_FLASH_WRITE_COUNT "}2"); func += String(Settings.save_flag); func += stopic;
+ func += F("}1" D_BOOT_COUNT "}2"); func += String(Settings.bootcount);
+ func += F("}1" D_RESTART_REASON "}2"); func += GetResetReason();
+ uint8_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : devices_present;
+ if (SONOFF_IFAN02 == Settings.module) { maxfn = 1; }
+ for (byte i = 0; i < maxfn; i++) {
+ func += F("}1" D_FRIENDLY_NAME " "); func += i +1; func += F("}2"); func += Settings.friendlyname[i];
+ }
+
+ func += F("}1}2 "); // Empty line
+ func += F("}1" D_AP); func += String(Settings.sta_active +1);
+ func += F(" " D_SSID " (" D_RSSI ")}2"); func += Settings.sta_ssid[Settings.sta_active]; func += F(" ("); func += WifiGetRssiAsQuality(WiFi.RSSI()); func += F("%)");
+ func += F("}1" D_HOSTNAME "}2"); func += my_hostname;
+ if (static_cast(WiFi.localIP()) != 0) {
+ func += F("}1" D_IP_ADDRESS "}2"); func += WiFi.localIP().toString();
+ func += F("}1" D_GATEWAY "}2"); func += IPAddress(Settings.ip_address[1]).toString();
+ func += F("}1" D_SUBNET_MASK "}2"); func += IPAddress(Settings.ip_address[2]).toString();
+ func += F("}1" D_DNS_SERVER "}2"); func += IPAddress(Settings.ip_address[3]).toString();
+ func += F("}1" D_MAC_ADDRESS "}2"); func += WiFi.macAddress();
+ }
+ if (static_cast(WiFi.softAPIP()) != 0) {
+ func += F("}1" D_AP " " D_IP_ADDRESS "}2"); func += WiFi.softAPIP().toString();
+ func += F("}1" D_AP " " D_GATEWAY "}2"); func += WiFi.softAPIP().toString();
+ func += F("}1" D_AP " " D_MAC_ADDRESS "}2"); func += WiFi.softAPmacAddress();
+ }
+
+ func += F("}1}2 "); // Empty line
+ if (Settings.flag.mqtt_enabled) {
+ func += F("}1" D_MQTT_HOST "}2"); func += Settings.mqtt_host;
+ func += F("}1" D_MQTT_PORT "}2"); func += String(Settings.mqtt_port);
+ func += F("}1" D_MQTT_CLIENT " & " D_FALLBACK_TOPIC "}2"); func += mqtt_client;
+ func += F("}1" D_MQTT_USER "}2"); func += Settings.mqtt_user;
+ func += F("}1" D_MQTT_TOPIC "}2"); func += Settings.mqtt_topic;
+ func += F("}1" D_MQTT_GROUP_TOPIC "}2"); func += Settings.mqtt_grptopic;
+ GetTopic_P(stopic, CMND, mqtt_topic, "");
+ func += F("}1" D_MQTT_FULL_TOPIC "}2"); func += stopic;
+
+ } else {
+ func += F("}1" D_MQTT "}2" D_DISABLED);
+ }
+
+ func += F("}1}2 "); // Empty line
+ func += F("}1" D_EMULATION "}2");
+#ifdef USE_EMULATION
+ if (EMUL_WEMO == Settings.flag2.emulation) {
+ func += F(D_BELKIN_WEMO);
+ }
+ else if (EMUL_HUE == Settings.flag2.emulation) {
+ func += F(D_HUE_BRIDGE);
+ }
+ else {
+ func += F(D_NONE);
+ }
+#else
+ func += F(D_DISABLED);
+#endif // USE_EMULATION
+
+ func += F("}1" D_MDNS_DISCOVERY "}2");
+#ifdef USE_DISCOVERY
+ func += F(D_ENABLED);
+ func += F("}1" D_MDNS_ADVERTISE "}2");
+#ifdef WEBSERVER_ADVERTISE
+ func += F(D_WEB_SERVER);
+#else
+ func += F(D_DISABLED);
+#endif // WEBSERVER_ADVERTISE
+#else
+ func += F(D_DISABLED);
+#endif // USE_DISCOVERY
+
+ func += F("}1}2 "); // Empty line
+ func += F("}1" D_ESP_CHIP_ID "}2"); func += String(ESP.getChipId());
+ func += F("}1" D_FLASH_CHIP_ID "}2"); func += String(ESP.getFlashChipId());
+ func += F("}1" D_FLASH_CHIP_SIZE "}2"); func += String(ESP.getFlashChipRealSize() / 1024); func += F("kB");
+ func += F("}1" D_PROGRAM_FLASH_SIZE "}2"); func += String(ESP.getFlashChipSize() / 1024); func += F("kB");
+ func += F("}1" D_PROGRAM_SIZE "}2"); func += String(ESP.getSketchSize() / 1024); func += F("kB");
+ func += F("}1" D_FREE_PROGRAM_SPACE "}2"); func += String(ESP.getFreeSketchSpace() / 1024); func += F("kB");
+ func += F("}1" D_FREE_MEMORY "}2"); func += String(freeMem / 1024); func += F("kB");
+ func += F(" |
---|
");
+ func += FPSTR(HTTP_SCRIPT_INFO_END);
+ page.replace(F(""), func);
+ page.replace(F(""), F(""));
+
+ // page += F("");
+ page += FPSTR(HTTP_BTN_MAIN);
+ ShowPage(page);
+}
+#endif // Not BE_MINIMAL
+
+/*-------------------------------------------------------------------------------------------*/
+
+void HandleUpgradeFirmware()
+{
+ if (HttpUser()) { return; }
+ if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
+ 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_HEAD_STYLE);
+ page += FPSTR(HTTP_FORM_UPG);
+ page.replace(F("{o1"), Settings.ota_url);
+ page += FPSTR(HTTP_FORM_RST_UPG);
+ page.replace(F("{r1"), F(D_UPGRADE));
+ page += FPSTR(HTTP_BTN_MAIN);
+ ShowPage(page);
+
+ upload_error = 0;
+ upload_file_type = UPL_TASMOTA;
+}
+
+void HandleUpgradeFirmwareStart()
+{
+ if (HttpUser()) { return; }
+ if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
+ char svalue[100];
+
+ AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPGRADE_STARTED));
+ WifiConfigCounter();
+
+ char tmp[100];
+ WebGetArg("o", tmp, sizeof(tmp));
+ if (strlen(tmp)) {
+ snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_OTAURL " %s"), tmp);
+ ExecuteWebCommand(svalue, SRC_WEBGUI);
+ }
+
+ String page = FPSTR(HTTP_HEAD);
+ page.replace(F("{v}"), FPSTR(S_INFORMATION));
+ page += FPSTR(HTTP_HEAD_STYLE);
+ 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"));
+ ExecuteWebCommand(svalue, SRC_WEBGUI);
+}
+
+void HandleUploadDone()
+{
+ if (HttpUser()) { return; }
+ if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
+ AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPLOAD_DONE));
+
+ char error[100];
+
+ WifiConfigCounter();
+ restart_flag = 0;
+ MqttRetryCounter(0);
+
+ String page = FPSTR(HTTP_HEAD);
+ page.replace(F("{v}"), FPSTR(S_INFORMATION));
+ page += FPSTR(HTTP_HEAD_STYLE);
+ page += F("" D_UPLOAD " " D_FAILED "
");
+ switch (upload_error) {
+ 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;
+#ifdef USE_RF_FLASH
+ case 10: strncpy_P(error, PSTR(D_UPLOAD_ERR_10), sizeof(error)); break;
+ case 11: strncpy_P(error, PSTR(D_UPLOAD_ERR_11), sizeof(error)); break;
+ case 12: strncpy_P(error, PSTR(D_UPLOAD_ERR_12), sizeof(error)); break;
+ case 13: strncpy_P(error, PSTR(D_UPLOAD_ERR_13), sizeof(error)); break;
+#endif
+ default:
+ snprintf_P(error, sizeof(error), PSTR(D_UPLOAD_ERROR_CODE " %d"), upload_error);
+ }
+ page += error;
+ snprintf_P(log_data, sizeof(log_data), PSTR(D_UPLOAD ": %s"), error);
+ AddLog(LOG_LEVEL_DEBUG);
+ stop_flash_rotate = Settings.flag.stop_flash_rotate;
+ } else {
+ page += F("green'>" D_SUCCESSFUL " ");
+ page += FPSTR(HTTP_MSG_RSTRT);
+ ShowWebSource(SRC_WEBGUI);
+ restart_flag = 2; // Always restart to re-enable disabled features during update
+ }
+ SettingsBufferFree();
+ page += F(" ");
+ page += FPSTR(HTTP_BTN_MAIN);
+ ShowPage(page);
+}
+
+void HandleUploadLoop()
+{
+ // Based on ESP8266HTTPUpdateServer.cpp uses ESP8266WebServer Parsing.cpp and Cores Updater.cpp (Update)
+ boolean _serialoutput = (LOG_LEVEL_DEBUG <= seriallog_level);
+
+ if (HTTP_USER == webserver_state) { return; }
+ if (upload_error) {
+ if (UPL_TASMOTA == upload_file_type) { Update.end(); }
+ return;
+ }
+
+ HTTPUpload& upload = WebServer->upload();
+
+ if (UPLOAD_FILE_START == upload.status) {
+ restart_flag = 60;
+ if (0 == upload.filename.c_str()[0]) {
+ upload_error = 1; // No file selected
+ return;
+ }
+ SettingsSave(1); // Free flash for upload
+ snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_UPLOAD D_FILE " %s ..."), upload.filename.c_str());
+ AddLog(LOG_LEVEL_INFO);
+ if (UPL_SETTINGS == upload_file_type) {
+ if (!SettingsBufferAlloc()) {
+ upload_error = 2; // Not enough space
+ return;
+ }
+ } else {
+ MqttRetryCounter(60);
+#ifdef USE_EMULATION
+ UdpDisconnect();
+#endif // USE_EMULATION
+#ifdef USE_ARILUX_RF
+ AriluxRfDisable(); // Prevent restart exception on Arilux Interrupt routine
+#endif // USE_ARILUX_RF
+ if (Settings.flag.mqtt_enabled) MqttDisconnect();
+ uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
+ if (!Update.begin(maxSketchSpace)) { //start with max available size
+
+// if (_serialoutput) Update.printError(Serial);
+// if (Update.getError() == UPDATE_ERROR_BOOTSTRAP) {
+// if (_serialoutput) Serial.println("Device still in UART update mode, perform powercycle");
+// }
+
+ upload_error = 2; // Not enough space
+ return;
+ }
+ }
+ upload_progress_dot_count = 0;
+ } else if (!upload_error && (UPLOAD_FILE_WRITE == upload.status)) {
+ if (0 == upload.totalSize) {
+ if (UPL_SETTINGS == upload_file_type) {
+ config_block_count = 0;
+ }
+ else {
+#ifdef USE_RF_FLASH
+ if ((SONOFF_BRIDGE == Settings.module) && (upload.buf[0] == ':')) { // Check if this is a RF bridge FW file
+ Update.end(); // End esp8266 update session
+ upload_file_type = UPL_EFM8BB1;
+
+ upload_error = SnfBrUpdateInit();
+ if (upload_error != 0) { return; }
+ } else
+#endif // USE_RF_FLASH
+ {
+ if (upload.buf[0] != 0xE9) {
+ upload_error = 3; // Magic byte is not 0xE9
+ return;
+ }
+ uint32_t bin_flash_size = ESP.magicFlashChipSize((upload.buf[3] & 0xf0) >> 4);
+ if(bin_flash_size > ESP.getFlashChipRealSize()) {
+ upload_error = 4; // Program flash size is larger than real flash size
+ return;
+ }
+ upload.buf[2] = 3; // Force DOUT - ESP8285
+ }
+ }
+ }
+ if (UPL_SETTINGS == upload_file_type) {
+ if (!upload_error) {
+ if (upload.currentSize > (sizeof(Settings) - (config_block_count * HTTP_UPLOAD_BUFLEN))) {
+ upload_error = 9; // File too large
+ return;
+ }
+ memcpy(settings_buffer + (config_block_count * HTTP_UPLOAD_BUFLEN), upload.buf, upload.currentSize);
+ config_block_count++;
+ }
+ }
+#ifdef USE_RF_FLASH
+ else if (UPL_EFM8BB1 == upload_file_type) {
+ if (efm8bb1_update != NULL) { // We have carry over data since last write, i. e. a start but not an end
+ ssize_t result = rf_glue_remnant_with_new_data_and_write(efm8bb1_update, upload.buf, upload.currentSize);
+ free(efm8bb1_update);
+ efm8bb1_update = NULL;
+ if (result != 0) {
+ upload_error = abs(result); // 2 = Not enough space, 8 = File invalid
+ return;
+ }
+ }
+ ssize_t result = rf_search_and_write(upload.buf, upload.currentSize);
+ if (result < 0) {
+ upload_error = abs(result);
+ return;
+ } else if (result > 0) {
+ if (result > upload.currentSize) {
+ // Offset is larger than the buffer supplied, this should not happen
+ upload_error = 9; // File too large - Failed to decode RF firmware
+ return;
+ }
+ // A remnant has been detected, allocate data for it plus a null termination byte
+ size_t remnant_sz = upload.currentSize - result;
+ efm8bb1_update = (uint8_t *) malloc(remnant_sz + 1);
+ if (efm8bb1_update == NULL) {
+ upload_error = 2; // Not enough space - Unable to allocate memory to store new RF firmware
+ return;
+ }
+ memcpy(efm8bb1_update, upload.buf + result, remnant_sz);
+ // Add null termination at the end of of remnant buffer
+ efm8bb1_update[remnant_sz] = '\0';
+ }
+ }
+#endif // USE_RF_FLASH
+ else { // firmware
+ if (!upload_error && (Update.write(upload.buf, upload.currentSize) != upload.currentSize)) {
+ upload_error = 5; // Upload buffer miscompare
+ return;
+ }
+ if (_serialoutput) {
+ Serial.printf(".");
+ upload_progress_dot_count++;
+ if (!(upload_progress_dot_count % 80)) { Serial.println(); }
+ }
+ }
+ } else if(!upload_error && (UPLOAD_FILE_END == upload.status)) {
+ if (_serialoutput && (upload_progress_dot_count % 80)) {
+ Serial.println();
+ }
+ if (UPL_SETTINGS == upload_file_type) {
+ if (config_xor_on_set) {
+ for (uint16_t i = 2; i < sizeof(Settings); i++) {
+ settings_buffer[i] ^= (config_xor_on_set +i);
+ }
+ }
+ bool valid_settings = false;
+ unsigned long buffer_version = settings_buffer[11] << 24 | settings_buffer[10] << 16 | settings_buffer[9] << 8 | settings_buffer[8];
+ if (buffer_version > 0x06000000) {
+ uint16_t buffer_size = settings_buffer[3] << 8 | settings_buffer[2];
+ uint16_t buffer_crc = settings_buffer[15] << 8 | settings_buffer[14];
+ uint16_t crc = 0;
+ for (uint16_t i = 0; i < buffer_size; i++) {
+ if ((i < 14) || (i > 15)) { crc += settings_buffer[i]*(i+1); } // Skip crc
+ }
+ valid_settings = (buffer_crc == crc);
+ } else {
+ valid_settings = (settings_buffer[0] == CONFIG_FILE_SIGN);
+ }
+ if (valid_settings) {
+ SettingsDefaultSet2();
+ memcpy((char*)&Settings +16, settings_buffer +16, sizeof(Settings) -16);
+ Settings.version = buffer_version; // Restore version and auto upgrade after restart
+ SettingsBufferFree();
+ } else {
+ upload_error = 8; // File invalid
+ return;
+ }
+ }
+#ifdef USE_RF_FLASH
+ else if (UPL_EFM8BB1 == upload_file_type) {
+ // RF FW flash done
+ upload_file_type = UPL_TASMOTA;
+ }
+#endif // USE_RF_FLASH
+ else {
+ if (!Update.end(true)) { // true to set the size to the current progress
+ if (_serialoutput) { Update.printError(Serial); }
+ upload_error = 6; // Upload failed. Enable logging 3
+ return;
+ }
+ }
+ if (!upload_error) {
+ snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_UPLOAD D_SUCCESSFUL " %u bytes. " D_RESTARTING), upload.totalSize);
+ AddLog(LOG_LEVEL_INFO);
+ }
+ } else if (UPLOAD_FILE_ABORTED == upload.status) {
+ restart_flag = 0;
+ MqttRetryCounter(0);
+ upload_error = 7; // Upload aborted
+ if (UPL_TASMOTA == upload_file_type) { Update.end(); }
+ }
+ delay(0);
+}
+
+/*-------------------------------------------------------------------------------------------*/
+
+void HandlePreflightRequest()
+{
+ WebServer->sendHeader(F("Access-Control-Allow-Origin"), F("*"));
+ WebServer->sendHeader(F("Access-Control-Allow-Methods"), F("GET, POST"));
+ WebServer->sendHeader(F("Access-Control-Allow-Headers"), F("authorization"));
+ WebServer->send(200, FPSTR(HDR_CTYPE_HTML), "");
+}
+
+/*-------------------------------------------------------------------------------------------*/
+
+void HandleHttpCommand()
+{
+ if (HttpUser()) { return; }
+// if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
+ char svalue[INPUT_BUFFER_SIZE]; // Large to serve Backlog
+
+ AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_COMMAND));
+
+ uint8_t valid = 1;
+ if (Settings.web_password[0] != 0) {
+ char tmp1[100];
+ WebGetArg("user", tmp1, sizeof(tmp1));
+ char tmp2[100];
+ WebGetArg("password", tmp2, sizeof(tmp2));
+ if (!(!strcmp(tmp1, WEB_USERNAME) && !strcmp(tmp2, Settings.web_password))) { valid = 0; }
+ }
+
+ String message = F("{\"" D_RSLT_WARNING "\":\"");
+ if (valid) {
+ byte curridx = web_log_index;
+ WebGetArg("cmnd", svalue, sizeof(svalue));
+ if (strlen(svalue)) {
+ ExecuteWebCommand(svalue, SRC_WEBCOMMAND);
+
+ if (web_log_index != curridx) {
+ byte counter = curridx;
+ message = F("{");
+ do {
+ char* tmp;
+ size_t len;
+ GetLog(counter, &tmp, &len);
+ if (len) {
+ // [14:49:36 MQTT: stat/wemos5/RESULT = {"POWER":"OFF"}] > [{"POWER":"OFF"}]
+ char* JSON = (char*)memchr(tmp, '{', len);
+ if (JSON) { // Is it a JSON message (and not only [15:26:08 MQT: stat/wemos5/POWER = O])
+ if (message.length() > 1) { message += F(","); }
+ size_t JSONlen = len - (JSON - tmp);
+ strlcpy(mqtt_data, JSON +1, JSONlen -2);
+ message += mqtt_data;
+ }
+ }
+ counter++;
+ if (!counter) counter++; // Skip 0 as it is not allowed
+ } while (counter != web_log_index);
+ message += F("}");
+ } else {
+ message += F(D_ENABLE_WEBLOG_FOR_RESPONSE "\"}");
+ }
+ } else {
+ message += F(D_ENTER_COMMAND " cmnd=\"}");
+ }
+ } else {
+ message += F(D_NEED_USER_AND_PASSWORD "\"}");
+ }
+ SetHeader();
+ WebServer->send(200, FPSTR(HDR_CTYPE_JSON), message);
+}
+
+/*-------------------------------------------------------------------------------------------*/
+
+void HandleConsole()
+{
+ if (HttpUser()) { return; }
+ if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
+ AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONSOLE);
+
+ String page = FPSTR(HTTP_HEAD);
+ page.replace(F("{v}"), FPSTR(S_CONSOLE));
+ page += FPSTR(HTTP_HEAD_STYLE);
+ page.replace(F(""), FPSTR(HTTP_SCRIPT_CONSOL));
+ page.replace(F(""), F(""));
+ page += FPSTR(HTTP_FORM_CMND);
+ page += FPSTR(HTTP_BTN_MAIN);
+ ShowPage(page);
+}
+
+void HandleAjaxConsoleRefresh()
+{
+ if (HttpUser()) { return; }
+ if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
+ char svalue[INPUT_BUFFER_SIZE]; // Large to serve Backlog
+ byte cflg = 1;
+ byte counter = 0; // Initial start, should never be 0 again
+
+ WebGetArg("c1", svalue, sizeof(svalue));
+ if (strlen(svalue)) {
+ snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_COMMAND "%s"), svalue);
+ AddLog(LOG_LEVEL_INFO);
+ ExecuteWebCommand(svalue, SRC_WEBCONSOLE);
+ }
+
+ WebGetArg("c2", svalue, sizeof(svalue));
+ if (strlen(svalue)) { counter = atoi(svalue); }
+
+ byte last_reset_web_log_flag = reset_web_log_flag;
+ String message = F("}9"); // Cannot load mqtt_data here as <> will be encoded by replacements below
+ if (!reset_web_log_flag) {
+ counter = 0;
+ reset_web_log_flag = 1;
+ }
+ if (counter != web_log_index) {
+ if (!counter) {
+ counter = web_log_index;
+ cflg = 0;
+ }
+ do {
+ char* tmp;
+ size_t len;
+ GetLog(counter, &tmp, &len);
+ if (len) {
+ if (cflg) {
+ message += F("\n");
+ } else {
+ cflg = 1;
+ }
+ strlcpy(mqtt_data, tmp, len);
+ message += mqtt_data;
+ }
+ counter++;
+ if (!counter) { counter++; } // Skip 0 as it is not allowed
+ } while (counter != web_log_index);
+ // XML encoding to fix blank console log in concert with javascript decodeURIComponent
+ message.replace(F("%"), F("%25")); // Needs to be done first as otherwise the % in %26 will also be converted
+ message.replace(F("&"), F("%26"));
+ message.replace(F("<"), F("%3C"));
+ message.replace(F(">"), F("%3E"));
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%d%d"), web_log_index, last_reset_web_log_flag);
+ message.replace(F("}9"), mqtt_data); // Save to load here
+ message += F("");
+ WebServer->send(200, FPSTR(HDR_CTYPE_XML), message);
+}
+
+/*-------------------------------------------------------------------------------------------*/
+
+void HandleRestart()
+{
+ if (HttpUser()) { return; }
+ if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
+ 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_HEAD_STYLE);
+ page += FPSTR(HTTP_MSG_RSTRT);
+ if (HTTP_MANAGER == webserver_state) {
+ webserver_state = HTTP_ADMIN;
+ } else {
+ page += FPSTR(HTTP_BTN_MAIN);
+ }
+ ShowPage(page);
+
+ ShowWebSource(SRC_WEBGUI);
+ restart_flag = 2;
+}
+
+/********************************************************************************************/
+
+void HandleNotFound()
+{
+// snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_HTTP "Not fount (%s)"), WebServer->uri().c_str());
+// AddLog(LOG_LEVEL_DEBUG);
+
+ if (CaptivePortal()) { return; } // If captive portal redirect instead of displaying the error page.
+
+#ifdef USE_EMULATION
+ String path = WebServer->uri();
+ if ((EMUL_HUE == Settings.flag2.emulation) && (path.startsWith("/api"))) {
+ HandleHueApi(&path);
+ } else
+#endif // USE_EMULATION
+ {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR(D_FILE_NOT_FOUND "\n\nURI: %s\nMethod: %s\nArguments: %d\n"),
+ WebServer->uri().c_str(), (WebServer->method() == HTTP_GET) ? "GET" : "POST", WebServer->args());
+ for (uint8_t i = 0; i < WebServer->args(); i++) {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s %s: %s\n"), mqtt_data, WebServer->argName(i).c_str(), WebServer->arg(i).c_str());
+ }
+ SetHeader();
+ WebServer->send(404, FPSTR(HDR_CTYPE_PLAIN), mqtt_data);
+ }
+}
+
+/* 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 == webserver_state) && !ValidIpAddress(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 ValidIpAddress(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;
+}
+
+/*********************************************************************************************/
+
+String UrlEncode(const String& text)
+{
+ const char hex[] = "0123456789ABCDEF";
+
+ String encoded = "";
+ int len = text.length();
+ int i = 0;
+ while (i < len) {
+ char decodedChar = text.charAt(i++);
+
+/*
+ if (('a' <= decodedChar && decodedChar <= 'z') ||
+ ('A' <= decodedChar && decodedChar <= 'Z') ||
+ ('0' <= decodedChar && decodedChar <= '9') ||
+ ('=' == decodedChar)) {
+ encoded += decodedChar;
+ } else {
+ encoded += '%';
+ encoded += hex[decodedChar >> 4];
+ encoded += hex[decodedChar & 0xF];
+ }
+*/
+ if (' ' == decodedChar) {
+ encoded += '%';
+ encoded += hex[decodedChar >> 4];
+ encoded += hex[decodedChar & 0xF];
+ } else {
+ encoded += decodedChar;
+ }
+
+ }
+ return encoded;
+}
+
+int WebSend(char *buffer)
+{
+ // http://192.168.178.86:80/cm?user=admin&password=joker&cmnd=POWER1 ON
+ // http://192.168.178.86:80/cm?cmnd=POWER1 ON
+ // [192.168.178.86:80,admin:joker] POWER1 ON
+
+ char *host;
+ char *port;
+ char *user;
+ char *password;
+ char *command;
+ uint16_t nport = 80;
+ int status = 1; // Wrong parameters
+
+ host = strtok_r(buffer, "]", &command); // buffer = [192.168.178.86:80,admin:joker] POWER1 ON
+ if (host && command) {
+ host = LTrim(host);
+ host++; // Skip [
+ host = strtok_r(host, ",", &user); // host = 192.168.178.86:80,admin:joker > 192.168.178.86:80
+ host = strtok_r(host, ":", &port); // host = 192.168.178.86:80 > 192.168.178.86
+ if (user) {
+ user = strtok_r(user, ":", &password); // user = admin:joker > admin
+ }
+
+//snprintf_P(log_data, sizeof(log_data), PSTR("DBG: Buffer |%X|, Host |%X|, Port |%X|, User |%X|, Password |%X|, Command |%X|"), buffer, host, port, user, password, command);
+//AddLog(LOG_LEVEL_DEBUG);
+
+ if (port) { nport = atoi(port); }
+
+ String nuri = "";
+ if (user && password) {
+ nuri += F("user=");
+ nuri += user;
+ nuri += F("&password=");
+ nuri += password;
+ nuri += F("&");
+ }
+ nuri += F("cmnd=");
+ nuri += LTrim(command);
+ String uri = UrlEncode(nuri);
+
+ IPAddress host_ip;
+ if (WiFi.hostByName(host, host_ip)) {
+ WiFiClient client;
+
+ bool connected = false;
+ byte retry = 2;
+ while ((retry > 0) && !connected) {
+ --retry;
+ connected = client.connect(host_ip, nport);
+ if (connected) break;
+ }
+
+ if (connected) {
+ String url = F("GET /cm?");
+ url += uri;
+ url += F(" HTTP/1.1\r\n Host: ");
+ url += IPAddress(host_ip).toString();
+ if (port) {
+ url += F(" \r\n Port: ");
+ url += port;
+ }
+ url += F(" \r\n Connection: close\r\n\r\n");
+
+//snprintf_P(log_data, sizeof(log_data), PSTR("DBG: Url |%s|"), url.c_str());
+//AddLog(LOG_LEVEL_DEBUG);
+
+ client.print(url.c_str());
+ client.flush();
+ client.stop();
+ status = 0; // No error - Done
+ } else {
+ status = 2; // Connection failed
+ }
+ } else {
+ status = 3; // Host not found
+ }
+ }
+ return status;
+}
+
+/*********************************************************************************************/
+
+enum WebCommands { CMND_WEBSERVER, CMND_WEBPASSWORD, CMND_WEBLOG, CMND_WEBREFRESH, CMND_WEBSEND, CMND_EMULATION };
+const char kWebCommands[] PROGMEM = D_CMND_WEBSERVER "|" D_CMND_WEBPASSWORD "|" D_CMND_WEBLOG "|" D_CMND_WEBREFRESH "|" D_CMND_WEBSEND "|" D_CMND_EMULATION ;
+const char kWebSendStatus[] PROGMEM = D_JSON_DONE "|" D_JSON_WRONG_PARAMETERS "|" D_JSON_CONNECT_FAILED "|" D_JSON_HOST_NOT_FOUND ;
+
+bool WebCommand()
+{
+ char command[CMDSZ];
+ bool serviced = true;
+
+ int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kWebCommands);
+ if (-1 == command_code) {
+ serviced = false; // Unknown command
+ }
+ if (CMND_WEBSERVER == command_code) {
+ if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) { Settings.webserver = XdrvMailbox.payload; }
+ if (Settings.webserver) {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_WEBSERVER "\":\"" D_JSON_ACTIVE_FOR " %s " D_JSON_ON_DEVICE " %s " D_JSON_WITH_IP_ADDRESS " %s\"}"),
+ (2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str());
+ } else {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetStateText(0));
+ }
+ }
+ else if (CMND_WEBPASSWORD == command_code) {
+ if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.web_password))) {
+ strlcpy(Settings.web_password, (SC_CLEAR == Shortcut(XdrvMailbox.data)) ? "" : (SC_DEFAULT == Shortcut(XdrvMailbox.data)) ? WEB_PASSWORD : XdrvMailbox.data, sizeof(Settings.web_password));
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.web_password);
+ } else {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_ASTERIX, command);
+ }
+ }
+ else if (CMND_WEBLOG == command_code) {
+ if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_ALL)) { Settings.weblog_level = XdrvMailbox.payload; }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.weblog_level);
+ }
+ else if (CMND_WEBREFRESH == command_code) {
+ if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload <= 10000)) { Settings.web_refresh = XdrvMailbox.payload; }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.web_refresh);
+ }
+ else if (CMND_WEBSEND == command_code) {
+ if (XdrvMailbox.data_len > 0) {
+ uint8_t result = WebSend(XdrvMailbox.data);
+ char stemp1[20];
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetTextIndexed(stemp1, sizeof(stemp1), result, kWebSendStatus));
+ }
+ }
+#ifdef USE_EMULATION
+ else if (CMND_EMULATION == command_code) {
+ if ((XdrvMailbox.payload >= EMUL_NONE) && (XdrvMailbox.payload < EMUL_MAX)) {
+ Settings.flag2.emulation = XdrvMailbox.payload;
+ restart_flag = 2;
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.flag2.emulation);
+ }
+#endif // USE_EMULATION
+ else serviced = false; // Unknown command
+
+ return serviced;
+}
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+
+#define XDRV_01
+
+boolean Xdrv01(byte function)
+{
+ boolean result = false;
+
+ switch (function) {
+ case FUNC_LOOP:
+ PollDnsWebserver();
+#ifdef USE_EMULATION
+ if (Settings.flag2.emulation) PollUdp();
+#endif // USE_EMULATION
+ break;
+ case FUNC_COMMAND:
+ result = WebCommand();
+ break;
+ }
+ return result;
+}
+#endif // USE_WEBSERVER
diff --git a/sonoff/xdrv_02_mqtt.ino b/sonoff/xdrv_02_mqtt.ino
new file mode 100644
index 000000000..1cd6d9aff
--- /dev/null
+++ b/sonoff/xdrv_02_mqtt.ino
@@ -0,0 +1,903 @@
+/*
+ xdrv_02_mqtt.ino - mqtt support for Sonoff-Tasmota
+
+ Copyright (C) 2018 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 .
+*/
+
+/*********************************************************************************************\
+ * Select ONE of possible MQTT library types below
+\*********************************************************************************************/
+// Default MQTT driver for both non-TLS and TLS connections. Blocks network if MQTT server is unavailable.
+//#define MQTT_LIBRARY_TYPE MQTT_PUBSUBCLIENT // Use PubSubClient library
+// Alternative MQTT driver does not block network when MQTT server is unavailable. No TLS support
+//#define MQTT_LIBRARY_TYPE MQTT_TASMOTAMQTT // Use TasmotaMqtt library (+4k4 (core 2.3.0), +14k4 (core 2.4.2 lwip2) code, +4k mem) - non-TLS only
+// Alternative MQTT driver does not block network when MQTT server is unavailable. TLS should work but needs to be tested.
+//#define MQTT_LIBRARY_TYPE MQTT_ARDUINOMQTT // Use arduino-mqtt (lwmqtt) library (+3k3 code, +2k mem)
+
+#if (MQTT_LIBRARY_TYPE == MQTT_ESPMQTTARDUINO) // Obsolete as of v6.2.1.11
+#undef MQTT_LIBRARY_TYPE
+#define MQTT_LIBRARY_TYPE MQTT_ARDUINOMQTT
+#endif
+
+/*
+#if (MQTT_LIBRARY_TYPE == MQTT_TASMOTAMQTT)
+#undef MQTT_LIBRARY_TYPE
+#define MQTT_LIBRARY_TYPE MQTT_ARDUINOMQTT // Obsolete in near future
+#endif
+*/
+
+#ifdef USE_MQTT_TLS
+
+#if (MQTT_LIBRARY_TYPE == MQTT_TASMOTAMQTT)
+#undef MQTT_LIBRARY_TYPE
+#endif
+
+#ifndef MQTT_LIBRARY_TYPE
+#define MQTT_LIBRARY_TYPE MQTT_PUBSUBCLIENT // Use PubSubClient library as it only supports TLS
+#endif
+
+#endif
+
+/*********************************************************************************************/
+
+enum MqttCommands {
+ CMND_MQTTHOST, CMND_MQTTPORT, CMND_MQTTRETRY, CMND_STATETEXT, CMND_MQTTFINGERPRINT, CMND_MQTTCLIENT,
+ CMND_MQTTUSER, CMND_MQTTPASSWORD, CMND_FULLTOPIC, CMND_PREFIX, CMND_GROUPTOPIC, CMND_TOPIC, CMND_PUBLISH,
+ CMND_BUTTONTOPIC, CMND_SWITCHTOPIC, CMND_BUTTONRETAIN, CMND_SWITCHRETAIN, CMND_POWERRETAIN, CMND_SENSORRETAIN };
+const char kMqttCommands[] PROGMEM =
+ D_CMND_MQTTHOST "|" D_CMND_MQTTPORT "|" D_CMND_MQTTRETRY "|" D_CMND_STATETEXT "|" D_CMND_MQTTFINGERPRINT "|" D_CMND_MQTTCLIENT "|"
+ D_CMND_MQTTUSER "|" D_CMND_MQTTPASSWORD "|" D_CMND_FULLTOPIC "|" D_CMND_PREFIX "|" D_CMND_GROUPTOPIC "|" D_CMND_TOPIC "|" D_CMND_PUBLISH "|"
+ D_CMND_BUTTONTOPIC "|" D_CMND_SWITCHTOPIC "|" D_CMND_BUTTONRETAIN "|" D_CMND_SWITCHRETAIN "|" D_CMND_POWERRETAIN "|" D_CMND_SENSORRETAIN ;
+
+uint8_t mqtt_retry_counter = 1; // MQTT connection retry counter
+uint8_t mqtt_initial_connection_state = 2; // MQTT connection messages state
+bool mqtt_connected = false; // MQTT virtual connection status
+
+/*********************************************************************************************\
+ * MQTT driver specific code need to provide the following functions:
+ *
+ * bool MqttIsConnected()
+ * void MqttDisconnect()
+ * void MqttSubscribeLib(char *topic)
+ * bool MqttPublishLib(const char* topic, boolean retained)
+ * void MqttLoop()
+\*********************************************************************************************/
+
+#if (MQTT_LIBRARY_TYPE == MQTT_PUBSUBCLIENT) /***********************************************/
+
+#include
+
+// Max message size calculated by PubSubClient is (MQTT_MAX_PACKET_SIZE < 5 + 2 + strlen(topic) + plength)
+#if (MQTT_MAX_PACKET_SIZE -TOPSZ -7) < MIN_MESSZ // If the max message size is too small, throw an error at compile time. See PubSubClient.cpp line 359
+ #error "MQTT_MAX_PACKET_SIZE is too small in libraries/PubSubClient/src/PubSubClient.h, increase it to at least 1000"
+#endif
+
+PubSubClient MqttClient(EspClient);
+
+bool MqttIsConnected()
+{
+ return MqttClient.connected();
+}
+
+void MqttDisconnect()
+{
+ MqttClient.disconnect();
+}
+
+void MqttSubscribeLib(char *topic)
+{
+ MqttClient.subscribe(topic);
+ MqttClient.loop(); // Solve LmacRxBlk:1 messages
+}
+
+bool MqttPublishLib(const char* topic, boolean retained)
+{
+ bool result = MqttClient.publish(topic, mqtt_data, retained);
+ yield(); // #3313
+ return result;
+}
+
+void MqttLoop()
+{
+ MqttClient.loop();
+}
+
+#elif (MQTT_LIBRARY_TYPE == MQTT_TASMOTAMQTT) /**********************************************/
+
+#include
+TasmotaMqtt MqttClient;
+
+bool MqttIsConnected()
+{
+ return MqttClient.Connected();
+}
+
+void MqttDisconnect()
+{
+ MqttClient.Disconnect();
+}
+
+void MqttDisconnectedCb()
+{
+ MqttDisconnected(MqttClient.State()); // status codes are documented in file mqtt.h as tConnState
+}
+
+void MqttSubscribeLib(char *topic)
+{
+ MqttClient.Subscribe(topic, 0);
+}
+
+bool MqttPublishLib(const char* topic, boolean retained)
+{
+ return MqttClient.Publish(topic, mqtt_data, strlen(mqtt_data), 0, retained);
+}
+
+void MqttLoop()
+{
+}
+
+#elif (MQTT_LIBRARY_TYPE == MQTT_ARDUINOMQTT) /**********************************************/
+
+#include
+MQTTClient MqttClient(MQTT_MAX_PACKET_SIZE);
+
+bool MqttIsConnected()
+{
+ return MqttClient.connected();
+}
+
+void MqttDisconnect()
+{
+ MqttClient.disconnect();
+}
+
+/*
+void MqttMyDataCb(MQTTClient* client, char* topic, char* data, int data_len)
+//void MqttMyDataCb(MQTTClient *client, char topic[], char data[], int data_len)
+{
+// MqttDataHandler((char*)topic, (byte*)data, data_len);
+}
+*/
+
+void MqttMyDataCb(String &topic, String &data)
+{
+ MqttDataHandler((char*)topic.c_str(), (byte*)data.c_str(), data.length());
+}
+
+void MqttSubscribeLib(char *topic)
+{
+ MqttClient.subscribe(topic, 0);
+}
+
+bool MqttPublishLib(const char* topic, boolean retained)
+{
+ return MqttClient.publish(topic, mqtt_data, strlen(mqtt_data), retained, 0);
+}
+
+void MqttLoop()
+{
+ MqttClient.loop();
+// delay(10);
+}
+
+#endif // MQTT_LIBRARY_TYPE
+
+/*********************************************************************************************/
+
+int MqttLibraryType()
+{
+ return (int)MQTT_LIBRARY_TYPE;
+}
+
+void MqttRetryCounter(uint8_t value)
+{
+ mqtt_retry_counter = value;
+}
+
+void MqttSubscribe(char *topic)
+{
+ snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_MQTT D_SUBSCRIBE_TO " %s"), topic);
+ AddLog(LOG_LEVEL_DEBUG);
+ MqttSubscribeLib(topic);
+}
+
+void MqttPublishDirect(const char* topic, boolean retained)
+{
+ char sretained[CMDSZ];
+ char slog_type[10];
+
+ ShowFreeMem(PSTR("MqttPublishDirect"));
+
+ sretained[0] = '\0';
+ snprintf_P(slog_type, sizeof(slog_type), PSTR(D_LOG_RESULT));
+
+ if (Settings.flag.mqtt_enabled) {
+ if (MqttIsConnected()) {
+ if (MqttPublishLib(topic, retained)) {
+ snprintf_P(slog_type, sizeof(slog_type), PSTR(D_LOG_MQTT));
+ if (retained) {
+ snprintf_P(sretained, sizeof(sretained), PSTR(" (" D_RETAINED ")"));
+ }
+ }
+ }
+ }
+
+ snprintf_P(log_data, sizeof(log_data), PSTR("%s%s = %s"), slog_type, (Settings.flag.mqtt_enabled) ? topic : strrchr(topic,'/')+1, mqtt_data);
+ if (strlen(log_data) >= (sizeof(log_data) - strlen(sretained) -1)) {
+ log_data[sizeof(log_data) - strlen(sretained) -5] = '\0';
+ snprintf_P(log_data, sizeof(log_data), PSTR("%s ..."), log_data);
+ }
+ snprintf_P(log_data, sizeof(log_data), PSTR("%s%s"), log_data, sretained);
+
+ AddLog(LOG_LEVEL_INFO);
+ if (Settings.ledstate &0x04) {
+ blinks++;
+ }
+}
+
+void MqttPublish(const char* topic, boolean retained)
+{
+ char *me;
+
+ if (!strcmp(Settings.mqtt_prefix[0],Settings.mqtt_prefix[1])) {
+ me = strstr(topic,Settings.mqtt_prefix[0]);
+ if (me == topic) {
+ mqtt_cmnd_publish += 3;
+ }
+ }
+ MqttPublishDirect(topic, retained);
+}
+
+void MqttPublish(const char* topic)
+{
+ MqttPublish(topic, false);
+}
+
+void MqttPublishPrefixTopic_P(uint8_t prefix, const char* subtopic, boolean retained)
+{
+/* prefix 0 = cmnd using subtopic
+ * prefix 1 = stat using subtopic
+ * prefix 2 = tele using subtopic
+ * prefix 4 = cmnd using subtopic or RESULT
+ * prefix 5 = stat using subtopic or RESULT
+ * prefix 6 = tele using subtopic or RESULT
+ */
+ char romram[33];
+ char stopic[TOPSZ];
+
+ snprintf_P(romram, sizeof(romram), ((prefix > 3) && !Settings.flag.mqtt_response) ? S_RSLT_RESULT : subtopic);
+ for (byte i = 0; i < strlen(romram); i++) {
+ romram[i] = toupper(romram[i]);
+ }
+ prefix &= 3;
+ GetTopic_P(stopic, prefix, mqtt_topic, romram);
+ MqttPublish(stopic, retained);
+}
+
+void MqttPublishPrefixTopic_P(uint8_t prefix, const char* subtopic)
+{
+ MqttPublishPrefixTopic_P(prefix, subtopic, false);
+}
+
+void MqttPublishPowerState(byte device)
+{
+ char stopic[TOPSZ];
+ char scommand[33];
+
+ if ((device < 1) || (device > devices_present)) { device = 1; }
+
+ if ((SONOFF_IFAN02 == Settings.module) && (device > 1)) {
+ if (GetFanspeed() < 4) { // 4 occurs when fanspeed is 3 and RC button 2 is pressed
+ snprintf_P(scommand, sizeof(scommand), PSTR(D_CMND_FANSPEED));
+ GetTopic_P(stopic, STAT, mqtt_topic, (Settings.flag.mqtt_response) ? scommand : S_RSLT_RESULT);
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, scommand, GetFanspeed());
+ MqttPublish(stopic);
+ }
+ } else {
+ GetPowerDevice(scommand, device, sizeof(scommand), Settings.flag.device_index_enable);
+ GetTopic_P(stopic, STAT, mqtt_topic, (Settings.flag.mqtt_response) ? scommand : S_RSLT_RESULT);
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, scommand, GetStateText(bitRead(power, device -1)));
+ MqttPublish(stopic);
+
+ GetTopic_P(stopic, STAT, mqtt_topic, scommand);
+ snprintf_P(mqtt_data, sizeof(mqtt_data), GetStateText(bitRead(power, device -1)));
+ MqttPublish(stopic, Settings.flag.mqtt_power_retain);
+ }
+}
+
+void MqttPublishPowerBlinkState(byte device)
+{
+ char scommand[33];
+
+ if ((device < 1) || (device > devices_present)) {
+ device = 1;
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":\"" D_JSON_BLINK " %s\"}"),
+ GetPowerDevice(scommand, device, sizeof(scommand), Settings.flag.device_index_enable), GetStateText(bitRead(blink_mask, device -1)));
+
+ MqttPublishPrefixTopic_P(RESULT_OR_STAT, S_RSLT_POWER);
+}
+
+/*********************************************************************************************/
+
+void MqttDisconnected(int state)
+{
+ mqtt_connected = false;
+ mqtt_retry_counter = Settings.mqtt_retry;
+
+ snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_MQTT D_CONNECT_FAILED_TO " %s:%d, rc %d. " D_RETRY_IN " %d " D_UNIT_SECOND),
+ Settings.mqtt_host, Settings.mqtt_port, state, mqtt_retry_counter);
+ AddLog(LOG_LEVEL_INFO);
+ rules_flag.mqtt_disconnected = 1;
+}
+
+void MqttConnected()
+{
+ char stopic[TOPSZ];
+
+ if (Settings.flag.mqtt_enabled) {
+ AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR(D_CONNECTED));
+ mqtt_connected = true;
+ mqtt_retry_counter = 0;
+
+ GetTopic_P(stopic, TELE, mqtt_topic, S_LWT);
+ snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR(D_ONLINE));
+ MqttPublish(stopic, true);
+
+ // Satisfy iobroker (#299)
+ mqtt_data[0] = '\0';
+ MqttPublishPrefixTopic_P(CMND, S_RSLT_POWER);
+
+ GetTopic_P(stopic, CMND, mqtt_topic, PSTR("#"));
+ MqttSubscribe(stopic);
+ if (strstr(Settings.mqtt_fulltopic, MQTT_TOKEN_TOPIC) != NULL) {
+ GetTopic_P(stopic, CMND, Settings.mqtt_grptopic, PSTR("#"));
+ MqttSubscribe(stopic);
+ fallback_topic_flag = 1;
+ GetTopic_P(stopic, CMND, mqtt_client, PSTR("#"));
+ fallback_topic_flag = 0;
+ MqttSubscribe(stopic);
+ }
+
+ XdrvCall(FUNC_MQTT_SUBSCRIBE);
+ }
+
+ if (mqtt_initial_connection_state) {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_MODULE "\":\"%s\",\"" D_JSON_VERSION "\":\"%s\",\"" D_JSON_FALLBACKTOPIC "\":\"%s\",\"" D_CMND_GROUPTOPIC "\":\"%s\"}"),
+ my_module.name, my_version, mqtt_client, Settings.mqtt_grptopic);
+ MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "1"));
+#ifdef USE_WEBSERVER
+ if (Settings.webserver) {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_JSON_WEBSERVER_MODE "\":\"%s\",\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\"}"),
+ (2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str());
+ MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "2"));
+ }
+#endif // USE_WEBSERVER
+ snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_JSON_RESTARTREASON "\":\"%s\"}"),
+ (GetResetReason() == "Exception") ? ESP.getResetInfo().c_str() : GetResetReason().c_str());
+ MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "3"));
+ for (byte i = 1; i <= devices_present; i++) {
+ MqttPublishPowerState(i);
+ if (SONOFF_IFAN02 == Settings.module) { break; } // Only report status of light relay
+ }
+ if (Settings.tele_period) { tele_period = Settings.tele_period -9; } // Enable TelePeriod in 9 seconds
+ rules_flag.system_boot = 1;
+ XdrvCall(FUNC_MQTT_INIT);
+ }
+ mqtt_initial_connection_state = 0;
+ rules_flag.mqtt_connected = 1;
+ global_state.mqtt_down = 0;
+}
+
+#ifdef USE_MQTT_TLS
+boolean MqttCheckTls()
+{
+ char fingerprint1[60];
+ char fingerprint2[60];
+ boolean result = false;
+
+ fingerprint1[0] = '\0';
+ fingerprint2[0] = '\0';
+ for (byte i = 0; i < sizeof(Settings.mqtt_fingerprint[0]); i++) {
+ snprintf_P(fingerprint1, sizeof(fingerprint1), PSTR("%s%s%02X"), fingerprint1, (i) ? " " : "", Settings.mqtt_fingerprint[0][i]);
+ snprintf_P(fingerprint2, sizeof(fingerprint2), PSTR("%s%s%02X"), fingerprint2, (i) ? " " : "", Settings.mqtt_fingerprint[1][i]);
+ }
+
+ AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR(D_FINGERPRINT));
+
+//#ifdef ARDUINO_ESP8266_RELEASE_2_4_1
+ EspClient = WiFiClientSecure(); // Wifi Secure Client reconnect issue 4497 (https://github.com/esp8266/Arduino/issues/4497)
+//#endif
+
+ if (!EspClient.connect(Settings.mqtt_host, Settings.mqtt_port)) {
+ snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_MQTT D_TLS_CONNECT_FAILED_TO " %s:%d. " D_RETRY_IN " %d " D_UNIT_SECOND),
+ Settings.mqtt_host, Settings.mqtt_port, mqtt_retry_counter);
+ AddLog(LOG_LEVEL_DEBUG);
+ } else {
+ if (EspClient.verify(fingerprint1, Settings.mqtt_host)) {
+ AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR(D_VERIFIED "1"));
+ result = true;
+ }
+ else if (EspClient.verify(fingerprint2, Settings.mqtt_host)) {
+ AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR(D_VERIFIED "2"));
+ result = true;
+ }
+ }
+ if (!result) AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR(D_FAILED));
+ EspClient.stop();
+ yield();
+ return result;
+}
+#endif // USE_MQTT_TLS
+
+void MqttReconnect()
+{
+ char stopic[TOPSZ];
+
+ if (!Settings.flag.mqtt_enabled) {
+ MqttConnected();
+ return;
+ }
+
+#if defined(USE_WEBSERVER) && defined(USE_EMULATION)
+ UdpDisconnect();
+#endif // USE_EMULATION
+
+ AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR(D_ATTEMPTING_CONNECTION));
+
+ mqtt_connected = false;
+ mqtt_retry_counter = Settings.mqtt_retry;
+ global_state.mqtt_down = 1;
+
+#ifndef USE_MQTT_TLS
+#ifdef USE_DISCOVERY
+#ifdef MQTT_HOST_DISCOVERY
+ if (!strlen(Settings.mqtt_host)) {
+ MdnsDiscoverMqttServer();
+ }
+#endif // MQTT_HOST_DISCOVERY
+#endif // USE_DISCOVERY
+#endif // USE_MQTT_TLS
+
+ char *mqtt_user = NULL;
+ char *mqtt_pwd = NULL;
+ if (strlen(Settings.mqtt_user) > 0) mqtt_user = Settings.mqtt_user;
+ if (strlen(Settings.mqtt_pwd) > 0) mqtt_pwd = Settings.mqtt_pwd;
+
+ GetTopic_P(stopic, TELE, mqtt_topic, S_LWT);
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_OFFLINE);
+
+//#ifdef ARDUINO_ESP8266_RELEASE_2_4_1
+#ifdef USE_MQTT_TLS
+ EspClient = WiFiClientSecure(); // Wifi Secure Client reconnect issue 4497 (https://github.com/esp8266/Arduino/issues/4497)
+#else
+ EspClient = WiFiClient(); // Wifi Client reconnect issue 4497 (https://github.com/esp8266/Arduino/issues/4497)
+#endif
+//#endif
+
+ if (2 == mqtt_initial_connection_state) { // Executed once just after power on and wifi is connected
+#ifdef USE_MQTT_TLS
+ if (!MqttCheckTls()) return;
+#endif // USE_MQTT_TLS
+
+#if (MQTT_LIBRARY_TYPE == MQTT_TASMOTAMQTT)
+ MqttClient.InitConnection(Settings.mqtt_host, Settings.mqtt_port);
+ MqttClient.InitClient(mqtt_client, mqtt_user, mqtt_pwd, MQTT_KEEPALIVE);
+ MqttClient.InitLWT(stopic, mqtt_data, 1, true);
+ MqttClient.OnConnected(MqttConnected);
+ MqttClient.OnDisconnected(MqttDisconnectedCb);
+ MqttClient.OnData(MqttDataHandler);
+#elif (MQTT_LIBRARY_TYPE == MQTT_ARDUINOMQTT)
+ MqttClient.begin(Settings.mqtt_host, Settings.mqtt_port, EspClient);
+ MqttClient.setWill(stopic, mqtt_data, true, 1);
+ MqttClient.setOptions(MQTT_KEEPALIVE, true, MQTT_TIMEOUT);
+// MqttClient.onMessageAdvanced(MqttMyDataCb);
+ MqttClient.onMessage(MqttMyDataCb);
+#endif
+
+ mqtt_initial_connection_state = 1;
+ }
+
+#if (MQTT_LIBRARY_TYPE == MQTT_PUBSUBCLIENT)
+ MqttClient.setCallback(MqttDataHandler);
+ MqttClient.setServer(Settings.mqtt_host, Settings.mqtt_port);
+ if (MqttClient.connect(mqtt_client, mqtt_user, mqtt_pwd, stopic, 1, true, mqtt_data)) {
+ MqttConnected();
+ } else {
+ MqttDisconnected(MqttClient.state()); // status codes are documented here http://pubsubclient.knolleary.net/api.html#state
+ }
+#elif (MQTT_LIBRARY_TYPE == MQTT_TASMOTAMQTT)
+ MqttClient.Connect();
+#elif (MQTT_LIBRARY_TYPE == MQTT_ARDUINOMQTT)
+ if (MqttClient.connect(mqtt_client, mqtt_user, mqtt_pwd)) {
+ MqttConnected();
+ } else {
+ MqttDisconnected(MqttClient.lastError()); // status codes are documented here https://github.com/256dpi/lwmqtt/blob/master/include/lwmqtt.h#L11
+ }
+#endif // MQTT_LIBRARY_TYPE
+}
+
+void MqttCheck()
+{
+ if (Settings.flag.mqtt_enabled) {
+ if (!MqttIsConnected()) {
+ global_state.mqtt_down = 1;
+ if (!mqtt_retry_counter) {
+ MqttReconnect();
+ } else {
+ mqtt_retry_counter--;
+ }
+ } else {
+ global_state.mqtt_down = 0;
+ }
+ } else {
+ global_state.mqtt_down = 0;
+ if (mqtt_initial_connection_state) MqttReconnect();
+ }
+}
+
+/*********************************************************************************************/
+
+bool MqttCommand()
+{
+ char command [CMDSZ];
+ bool serviced = true;
+ char stemp1[TOPSZ];
+ char scommand[CMDSZ];
+ uint16_t i;
+
+ uint16_t index = XdrvMailbox.index;
+ uint16_t data_len = XdrvMailbox.data_len;
+ uint16_t payload16 = XdrvMailbox.payload16;
+ int16_t payload = XdrvMailbox.payload;
+ uint8_t grpflg = XdrvMailbox.grpflg;
+ char *type = XdrvMailbox.topic;
+ char *dataBuf = XdrvMailbox.data;
+
+ int command_code = GetCommandCode(command, sizeof(command), type, kMqttCommands);
+ if (-1 == command_code) {
+ serviced = false; // Unknown command
+ }
+ else if (CMND_MQTTHOST == command_code) {
+ if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_host))) {
+ strlcpy(Settings.mqtt_host, (SC_CLEAR == Shortcut(dataBuf)) ? "" : (SC_DEFAULT == Shortcut(dataBuf)) ? MQTT_HOST : dataBuf, sizeof(Settings.mqtt_host));
+ restart_flag = 2;
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.mqtt_host);
+ }
+ else if (CMND_MQTTPORT == command_code) {
+ if (payload16 > 0) {
+ Settings.mqtt_port = (1 == payload16) ? MQTT_PORT : payload16;
+ restart_flag = 2;
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.mqtt_port);
+ }
+ else if (CMND_MQTTRETRY == command_code) {
+ if ((payload >= MQTT_RETRY_SECS) && (payload < 32001)) {
+ Settings.mqtt_retry = payload;
+ mqtt_retry_counter = Settings.mqtt_retry;
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.mqtt_retry);
+ }
+ else if ((CMND_STATETEXT == command_code) && (index > 0) && (index <= 4)) {
+ if ((data_len > 0) && (data_len < sizeof(Settings.state_text[0]))) {
+ for(i = 0; i <= data_len; i++) {
+ if (dataBuf[i] == ' ') dataBuf[i] = '_';
+ }
+ strlcpy(Settings.state_text[index -1], dataBuf, sizeof(Settings.state_text[0]));
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, GetStateText(index -1));
+ }
+#ifdef USE_MQTT_TLS
+ else if ((CMND_MQTTFINGERPRINT == command_code) && (index > 0) && (index <= 2)) {
+ char fingerprint[60];
+ if ((data_len > 0) && (data_len < sizeof(fingerprint))) {
+ strlcpy(fingerprint, (SC_CLEAR == Shortcut(dataBuf)) ? "" : (SC_DEFAULT == Shortcut(dataBuf)) ? (1 == index) ? MQTT_FINGERPRINT1 : MQTT_FINGERPRINT2 : dataBuf, sizeof(fingerprint));
+ char *p = fingerprint;
+ for (byte i = 0; i < 20; i++) {
+ Settings.mqtt_fingerprint[index -1][i] = strtol(p, &p, 16);
+ }
+ restart_flag = 2;
+ }
+ fingerprint[0] = '\0';
+ for (byte i = 0; i < sizeof(Settings.mqtt_fingerprint[index -1]); i++) {
+ snprintf_P(fingerprint, sizeof(fingerprint), PSTR("%s%s%02X"), fingerprint, (i) ? " " : "", Settings.mqtt_fingerprint[index -1][i]);
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, fingerprint);
+ }
+#endif
+ else if ((CMND_MQTTCLIENT == command_code) && !grpflg) {
+ if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_client))) {
+ strlcpy(Settings.mqtt_client, (SC_DEFAULT == Shortcut(dataBuf)) ? MQTT_CLIENT_ID : dataBuf, sizeof(Settings.mqtt_client));
+ restart_flag = 2;
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.mqtt_client);
+ }
+ else if (CMND_MQTTUSER == command_code) {
+ if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_user))) {
+ strlcpy(Settings.mqtt_user, (SC_CLEAR == Shortcut(dataBuf)) ? "" : (SC_DEFAULT == Shortcut(dataBuf)) ? MQTT_USER : dataBuf, sizeof(Settings.mqtt_user));
+ restart_flag = 2;
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.mqtt_user);
+ }
+ else if (CMND_MQTTPASSWORD == command_code) {
+ if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_pwd))) {
+ strlcpy(Settings.mqtt_pwd, (SC_CLEAR == Shortcut(dataBuf)) ? "" : (SC_DEFAULT == Shortcut(dataBuf)) ? MQTT_PASS : dataBuf, sizeof(Settings.mqtt_pwd));
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.mqtt_pwd);
+ restart_flag = 2;
+ } else {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_ASTERIX, command);
+ }
+ }
+ else if (CMND_FULLTOPIC == command_code) {
+ if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_fulltopic))) {
+ MakeValidMqtt(1, dataBuf);
+ if (!strcmp(dataBuf, mqtt_client)) SetShortcut(dataBuf, SC_DEFAULT);
+ strlcpy(stemp1, (SC_DEFAULT == Shortcut(dataBuf)) ? MQTT_FULLTOPIC : dataBuf, sizeof(stemp1));
+ if (strcmp(stemp1, Settings.mqtt_fulltopic)) {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), (Settings.flag.mqtt_offline) ? S_OFFLINE : "");
+ MqttPublishPrefixTopic_P(TELE, PSTR(D_LWT), true); // Offline or remove previous retained topic
+ strlcpy(Settings.mqtt_fulltopic, stemp1, sizeof(Settings.mqtt_fulltopic));
+ restart_flag = 2;
+ }
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.mqtt_fulltopic);
+ }
+ else if ((CMND_PREFIX == command_code) && (index > 0) && (index <= 3)) {
+ if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_prefix[0]))) {
+ MakeValidMqtt(0, dataBuf);
+ strlcpy(Settings.mqtt_prefix[index -1], (SC_DEFAULT == Shortcut(dataBuf)) ? (1==index)?SUB_PREFIX:(2==index)?PUB_PREFIX:PUB_PREFIX2 : dataBuf, sizeof(Settings.mqtt_prefix[0]));
+// if (Settings.mqtt_prefix[index -1][strlen(Settings.mqtt_prefix[index -1])] == '/') Settings.mqtt_prefix[index -1][strlen(Settings.mqtt_prefix[index -1])] = 0;
+ restart_flag = 2;
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, Settings.mqtt_prefix[index -1]);
+ }
+ else if (CMND_PUBLISH == command_code) {
+ if (data_len > 0) {
+ char *mqtt_part = strtok(dataBuf, " ");
+ if (mqtt_part) {
+ snprintf(stemp1, sizeof(stemp1), mqtt_part);
+ mqtt_part = strtok(NULL, " ");
+ if (mqtt_part) {
+ snprintf(mqtt_data, sizeof(mqtt_data), mqtt_part);
+ } else {
+ mqtt_data[0] = '\0';
+ }
+ MqttPublishDirect(stemp1, (index == 2));
+// snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, D_JSON_DONE);
+ mqtt_data[0] = '\0';
+ }
+ }
+ }
+ else if (CMND_GROUPTOPIC == command_code) {
+ if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_grptopic))) {
+ MakeValidMqtt(0, dataBuf);
+ if (!strcmp(dataBuf, mqtt_client)) SetShortcut(dataBuf, SC_DEFAULT);
+ strlcpy(Settings.mqtt_grptopic, (SC_DEFAULT == Shortcut(dataBuf)) ? MQTT_GRPTOPIC : dataBuf, sizeof(Settings.mqtt_grptopic));
+ restart_flag = 2;
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.mqtt_grptopic);
+ }
+ else if ((CMND_TOPIC == command_code) && !grpflg) {
+ if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_topic))) {
+ MakeValidMqtt(0, dataBuf);
+ if (!strcmp(dataBuf, mqtt_client)) SetShortcut(dataBuf, SC_DEFAULT);
+ strlcpy(stemp1, (SC_DEFAULT == Shortcut(dataBuf)) ? MQTT_TOPIC : dataBuf, sizeof(stemp1));
+ if (strcmp(stemp1, Settings.mqtt_topic)) {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), (Settings.flag.mqtt_offline) ? S_OFFLINE : "");
+ MqttPublishPrefixTopic_P(TELE, PSTR(D_LWT), true); // Offline or remove previous retained topic
+ strlcpy(Settings.mqtt_topic, stemp1, sizeof(Settings.mqtt_topic));
+ restart_flag = 2;
+ }
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.mqtt_topic);
+ }
+ else if ((CMND_BUTTONTOPIC == command_code) && !grpflg) {
+ if ((data_len > 0) && (data_len < sizeof(Settings.button_topic))) {
+ MakeValidMqtt(0, dataBuf);
+ if (!strcmp(dataBuf, mqtt_client)) SetShortcut(dataBuf, SC_DEFAULT);
+ switch (Shortcut(dataBuf)) {
+ case SC_CLEAR: strlcpy(Settings.button_topic, "", sizeof(Settings.button_topic)); break;
+ case SC_DEFAULT: strlcpy(Settings.button_topic, mqtt_topic, sizeof(Settings.button_topic)); break;
+ case SC_USER: strlcpy(Settings.button_topic, MQTT_BUTTON_TOPIC, sizeof(Settings.button_topic)); break;
+ default: strlcpy(Settings.button_topic, dataBuf, sizeof(Settings.button_topic));
+ }
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.button_topic);
+ }
+ else if (CMND_SWITCHTOPIC == command_code) {
+ if ((data_len > 0) && (data_len < sizeof(Settings.switch_topic))) {
+ MakeValidMqtt(0, dataBuf);
+ if (!strcmp(dataBuf, mqtt_client)) SetShortcut(dataBuf, SC_DEFAULT);
+ switch (Shortcut(dataBuf)) {
+ case SC_CLEAR: strlcpy(Settings.switch_topic, "", sizeof(Settings.switch_topic)); break;
+ case SC_DEFAULT: strlcpy(Settings.switch_topic, mqtt_topic, sizeof(Settings.switch_topic)); break;
+ case SC_USER: strlcpy(Settings.switch_topic, MQTT_SWITCH_TOPIC, sizeof(Settings.switch_topic)); break;
+ default: strlcpy(Settings.switch_topic, dataBuf, sizeof(Settings.switch_topic));
+ }
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.switch_topic);
+ }
+ else if (CMND_BUTTONRETAIN == command_code) {
+ if ((payload >= 0) && (payload <= 1)) {
+ if (!payload) {
+ for(i = 1; i <= MAX_KEYS; i++) {
+ SendKey(0, i, 9); // Clear MQTT retain in broker
+ }
+ }
+ Settings.flag.mqtt_button_retain = payload;
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetStateText(Settings.flag.mqtt_button_retain));
+ }
+ else if (CMND_SWITCHRETAIN == command_code) {
+ if ((payload >= 0) && (payload <= 1)) {
+ if (!payload) {
+ for(i = 1; i <= MAX_SWITCHES; i++) {
+ SendKey(1, i, 9); // Clear MQTT retain in broker
+ }
+ }
+ Settings.flag.mqtt_switch_retain = payload;
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetStateText(Settings.flag.mqtt_switch_retain));
+ }
+ else if (CMND_POWERRETAIN == command_code) {
+ if ((payload >= 0) && (payload <= 1)) {
+ if (!payload) {
+ for(i = 1; i <= devices_present; i++) { // Clear MQTT retain in broker
+ GetTopic_P(stemp1, STAT, mqtt_topic, GetPowerDevice(scommand, i, sizeof(scommand), Settings.flag.device_index_enable));
+ mqtt_data[0] = '\0';
+ MqttPublish(stemp1, Settings.flag.mqtt_power_retain);
+ }
+ }
+ Settings.flag.mqtt_power_retain = payload;
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetStateText(Settings.flag.mqtt_power_retain));
+ }
+ else if (CMND_SENSORRETAIN == command_code) {
+ if ((payload >= 0) && (payload <= 1)) {
+ if (!payload) {
+ mqtt_data[0] = '\0';
+ MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
+ MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_ENERGY), Settings.flag.mqtt_sensor_retain);
+ }
+ Settings.flag.mqtt_sensor_retain = payload;
+ }
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetStateText(Settings.flag.mqtt_sensor_retain));
+ }
+ else serviced = false; // Unknown command
+
+ return serviced;
+}
+
+/*********************************************************************************************\
+ * Presentation
+\*********************************************************************************************/
+
+#ifdef USE_WEBSERVER
+
+#define WEB_HANDLE_MQTT "mq"
+
+const char S_CONFIGURE_MQTT[] PROGMEM = D_CONFIGURE_MQTT;
+
+const char HTTP_BTN_MENU_MQTT[] PROGMEM =
+ " ";
+
+const char HTTP_FORM_MQTT[] PROGMEM =
+ " |
---|