// }2 =
WSContentSend_P(HTTP_SCRIPT_INFO_BEGIN);
WSContentSend_P(PSTR(""));
WSContentSend_P(PSTR(D_PROGRAM_VERSION "}2%s%s"), my_version, my_image);
WSContentSend_P(PSTR("}1" D_BUILD_DATE_AND_TIME "}2%s"), GetBuildDateAndTime().c_str());
WSContentSend_P(PSTR("}1" D_CORE_AND_SDK_VERSION "}2" ARDUINO_CORE_RELEASE "/%s"), ESP.getSdkVersion());
WSContentSend_P(PSTR("}1" D_UPTIME "}2%s"), GetUptime().c_str());
#ifdef ESP8266
WSContentSend_P(PSTR("}1" D_FLASH_WRITE_COUNT "}2%d at 0x%X"), Settings.save_flag, GetSettingsAddress());
#else
WSContentSend_P(PSTR("}1" D_FLASH_WRITE_COUNT "}2%d"), Settings.save_flag);
#endif
WSContentSend_P(PSTR("}1" D_BOOT_COUNT "}2%d"), Settings.bootcount);
WSContentSend_P(PSTR("}1" D_RESTART_REASON "}2%s"), GetResetReason().c_str());
uint32_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : devices_present;
#ifdef USE_SONOFF_IFAN
if (IsModuleIfan()) { maxfn = 1; }
#endif // USE_SONOFF_IFAN
for (uint32_t i = 0; i < maxfn; i++) {
WSContentSend_P(PSTR("}1" D_FRIENDLY_NAME " %d}2%s"), i +1, SettingsText(SET_FRIENDLYNAME1 +i));
}
WSContentSend_P(PSTR("}1}2 ")); // Empty line
#ifdef ESP32
#ifdef USE_ETHERNET
if (static_cast(EthernetLocalIP()) != 0) {
WSContentSend_P(PSTR("}1" D_HOSTNAME "}2%s%s"), EthernetHostname(), (Mdns.begun) ? ".local" : "");
WSContentSend_P(PSTR("}1" D_MAC_ADDRESS "}2%s"), EthernetMacAddress().c_str());
WSContentSend_P(PSTR("}1" D_IP_ADDRESS " (eth)}2%s"), EthernetLocalIP().toString().c_str());
WSContentSend_P(PSTR("}1 }2 "));
}
#endif
#endif
if (Settings.flag4.network_wifi) {
int32_t rssi = WiFi.RSSI();
WSContentSend_P(PSTR("}1" D_AP "%d " D_SSID " (" D_RSSI ")}2%s (%d%%, %d dBm)"), Settings.sta_active +1, HtmlEscape(SettingsText(SET_STASSID1 + Settings.sta_active)).c_str(), WifiGetRssiAsQuality(rssi), rssi);
WSContentSend_P(PSTR("}1" D_HOSTNAME "}2%s%s"), my_hostname, (Mdns.begun) ? ".local" : "");
#if LWIP_IPV6
String ipv6_addr = WifiGetIPv6();
if (ipv6_addr != "") {
WSContentSend_P(PSTR("}1 IPv6 Address }2%s"), ipv6_addr.c_str());
}
#endif
if (static_cast(WiFi.localIP()) != 0) {
WSContentSend_P(PSTR("}1" D_MAC_ADDRESS "}2%s"), WiFi.macAddress().c_str());
WSContentSend_P(PSTR("}1" D_IP_ADDRESS " (wifi)}2%s"), WiFi.localIP().toString().c_str());
WSContentSend_P(PSTR("}1 }2 "));
}
}
if (!global_state.network_down) {
WSContentSend_P(PSTR("}1" D_GATEWAY "}2%s"), IPAddress(Settings.ip_address[1]).toString().c_str());
WSContentSend_P(PSTR("}1" D_SUBNET_MASK "}2%s"), IPAddress(Settings.ip_address[2]).toString().c_str());
WSContentSend_P(PSTR("}1" D_DNS_SERVER "}2%s"), IPAddress(Settings.ip_address[3]).toString().c_str());
}
if ((WiFi.getMode() >= WIFI_AP) && (static_cast(WiFi.softAPIP()) != 0)) {
WSContentSend_P(PSTR("}1 }2 "));
WSContentSend_P(PSTR("}1" D_MAC_ADDRESS "}2%s"), WiFi.softAPmacAddress().c_str());
WSContentSend_P(PSTR("}1" D_IP_ADDRESS " (AP)}2%s"), WiFi.softAPIP().toString().c_str());
WSContentSend_P(PSTR("}1" D_GATEWAY "}2%s"), WiFi.softAPIP().toString().c_str());
}
WSContentSend_P(PSTR("}1}2 ")); // Empty line
if (Settings.flag.mqtt_enabled) { // SetOption3 - Enable MQTT
WSContentSend_P(PSTR("}1" D_MQTT_HOST "}2%s"), SettingsText(SET_MQTT_HOST));
WSContentSend_P(PSTR("}1" D_MQTT_PORT "}2%d"), Settings.mqtt_port);
#ifdef USE_MQTT_TLS
WSContentSend_P(PSTR("}1" D_MQTT_TLS_ENABLE "}2%s"), Settings.flag4.mqtt_tls ? PSTR(D_ENABLED) : PSTR(D_DISABLED));
#endif // USE_MQTT_TLS
WSContentSend_P(PSTR("}1" D_MQTT_USER "}2%s"), SettingsText(SET_MQTT_USER));
WSContentSend_P(PSTR("}1" D_MQTT_CLIENT "}2%s"), mqtt_client);
WSContentSend_P(PSTR("}1" D_MQTT_TOPIC "}2%s"), SettingsText(SET_MQTT_TOPIC));
uint32_t real_index = SET_MQTT_GRP_TOPIC;
for (uint32_t i = 0; i < MAX_GROUP_TOPICS; i++) {
if (1 == i) { real_index = SET_MQTT_GRP_TOPIC2 -1; }
if (strlen(SettingsText(real_index +i))) {
WSContentSend_P(PSTR("}1" D_MQTT_GROUP_TOPIC " %d}2%s"), 1 +i, GetGroupTopic_P(stopic, "", real_index +i));
}
}
WSContentSend_P(PSTR("}1" D_MQTT_FULL_TOPIC "}2%s"), GetTopic_P(stopic, CMND, mqtt_topic, ""));
WSContentSend_P(PSTR("}1" D_MQTT " " D_FALLBACK_TOPIC "}2%s"), GetFallbackTopic_P(stopic, ""));
WSContentSend_P(PSTR("}1" D_MQTT_NO_RETAIN "}2%s"), Settings.flag4.mqtt_no_retain ? PSTR(D_ENABLED) : PSTR(D_DISABLED));
} else {
WSContentSend_P(PSTR("}1" D_MQTT "}2" D_DISABLED));
}
WSContentSend_P(PSTR("}1}2 ")); // Empty line
#ifdef USE_EMULATION
WSContentSend_P(PSTR("}1" D_EMULATION "}2%s"), GetTextIndexed(stopic, sizeof(stopic), Settings.flag2.emulation, kEmulationOptions));
#else
WSContentSend_P(PSTR("}1" D_EMULATION "}2" D_DISABLED));
#endif // USE_EMULATION
#ifdef USE_DISCOVERY
WSContentSend_P(PSTR("}1" D_MDNS_DISCOVERY "}2%s"), (Settings.flag3.mdns_enabled) ? D_ENABLED : D_DISABLED); // SetOption55 - Control mDNS service
if (Settings.flag3.mdns_enabled) { // SetOption55 - Control mDNS service
#ifdef WEBSERVER_ADVERTISE
WSContentSend_P(PSTR("}1" D_MDNS_ADVERTISE "}2" D_WEB_SERVER));
#else
WSContentSend_P(PSTR("}1" D_MDNS_ADVERTISE "}2" D_DISABLED));
#endif // WEBSERVER_ADVERTISE
}
#else
WSContentSend_P(PSTR("}1" D_MDNS_DISCOVERY "}2" D_DISABLED));
#endif // USE_DISCOVERY
WSContentSend_P(PSTR("}1}2 ")); // Empty line
WSContentSend_P(PSTR("}1" D_ESP_CHIP_ID "}2%d"), ESP_getChipId());
#ifdef ESP8266
WSContentSend_P(PSTR("}1" D_FLASH_CHIP_ID "}20x%06X"), ESP.getFlashChipId());
#endif
WSContentSend_P(PSTR("}1" D_FLASH_CHIP_SIZE "}2%dkB"), ESP.getFlashChipRealSize() / 1024);
WSContentSend_P(PSTR("}1" D_PROGRAM_FLASH_SIZE "}2%dkB"), ESP.getFlashChipSize() / 1024);
WSContentSend_P(PSTR("}1" D_PROGRAM_SIZE "}2%dkB"), ESP_getSketchSize() / 1024);
WSContentSend_P(PSTR("}1" D_FREE_PROGRAM_SPACE "}2%dkB"), ESP.getFreeSketchSpace() / 1024);
WSContentSend_P(PSTR("}1" D_FREE_MEMORY "}2%dkB"), freeMem / 1024);
#ifdef ESP32
if (psramFound()) {
WSContentSend_P(PSTR("}1" D_PSR_MAX_MEMORY "}2%dkB"), ESP.getPsramSize() / 1024);
WSContentSend_P(PSTR("}1" D_PSR_FREE_MEMORY "}2%dkB"), ESP.getFreePsram() / 1024);
}
#endif
WSContentSend_P(PSTR("
"));
WSContentSend_P(HTTP_SCRIPT_INFO_END);
WSContentSendStyle();
// WSContentSend_P(PSTR(" Information "));
WSContentSend_P(PSTR(""
"
"));
// WSContentSend_P(PSTR(" "));
WSContentSpaceButton(BUTTON_MAIN);
WSContentStop();
}
#endif // Not FIRMWARE_MINIMAL
/*-------------------------------------------------------------------------------------------*/
void HandleUpgradeFirmware(void)
{
if (!HttpCheckPriviledgedAccess()) { return; }
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_FIRMWARE_UPGRADE);
WSContentStart_P(S_FIRMWARE_UPGRADE);
WSContentSendStyle();
WSContentSend_P(HTTP_FORM_UPG, SettingsText(SET_OTAURL));
WSContentSend_P(HTTP_FORM_RST_UPG, D_UPGRADE);
WSContentSpaceButton(BUTTON_MAIN);
WSContentStop();
Web.upload_error = 0;
Web.upload_file_type = UPL_TASMOTA;
}
void HandleUpgradeFirmwareStart(void)
{
if (!HttpCheckPriviledgedAccess()) { return; }
char command[TOPSZ + 10]; // OtaUrl
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPGRADE_STARTED));
WifiConfigCounter();
char otaurl[TOPSZ];
WebGetArg("o", otaurl, sizeof(otaurl));
if (strlen(otaurl)) {
snprintf_P(command, sizeof(command), PSTR(D_CMND_OTAURL " %s"), otaurl);
ExecuteWebCommand(command, SRC_WEBGUI);
}
WSContentStart_P(S_INFORMATION);
WSContentSend_P(HTTP_SCRIPT_RELOAD_TIME, HTTP_OTA_RESTART_RECONNECT_TIME);
WSContentSendStyle();
WSContentSend_P(PSTR("" D_UPGRADE_STARTED " ...
"));
WSContentSend_P(HTTP_MSG_RSTRT);
WSContentSpaceButton(BUTTON_MAIN);
WSContentStop();
snprintf_P(command, sizeof(command), PSTR(D_CMND_UPGRADE " 1"));
ExecuteWebCommand(command, SRC_WEBGUI);
}
void HandleUploadDone(void)
{
if (!HttpCheckPriviledgedAccess()) { return; }
#if defined(USE_ZIGBEE) && defined(USE_ZIGBEE_EZSP)
if (!Web.upload_error) {
// GUI xmodem
if (ZigbeeUploadOtaReady()) {
HandleZigbeeXfer();
return;
}
}
#endif
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPLOAD_DONE));
char error[100];
WifiConfigCounter();
restart_flag = 0;
MqttRetryCounter(0);
#ifdef USE_COUNTER
CounterInterruptDisable(false);
#endif // USE_COUNTER
WSContentStart_P(S_INFORMATION);
if (!Web.upload_error) {
uint32_t javascript_settimeout = HTTP_OTA_RESTART_RECONNECT_TIME;
#if defined(USE_ZIGBEE) && defined(USE_ZIGBEE_EZSP)
if (ZigbeeUploadFinish()) {
javascript_settimeout = 10000; // Refesh main web ui after transfer upgrade
}
#endif
WSContentSend_P(HTTP_SCRIPT_RELOAD_TIME, javascript_settimeout); // Refesh main web ui after OTA upgrade
}
WSContentSendStyle();
WSContentSend_P(PSTR("" D_UPLOAD " " D_FAILED " "), WebColor(COL_TEXT_WARNING));
#ifdef USE_RF_FLASH
if (Web.upload_error < 15) {
#else
if ((Web.upload_error < 10) || (14 == Web.upload_error)) {
if (14 == Web.upload_error) { Web.upload_error = 10; }
#endif
GetTextIndexed(error, sizeof(error), Web.upload_error -1, kUploadErrors);
} else {
snprintf_P(error, sizeof(error), PSTR(D_UPLOAD_ERROR_CODE " %d"), Web.upload_error);
}
WSContentSend_P(error);
DEBUG_CORE_LOG(PSTR("UPL: %s"), error);
stop_flash_rotate = Settings.flag.stop_flash_rotate; // SetOption12 - Switch between dynamic or fixed slot flash save location
} else {
WSContentSend_P(PSTR("%06x'>" D_SUCCESSFUL "
"), WebColor(COL_TEXT_SUCCESS));
restart_flag = 2; // Always restart to re-enable disabled features during update
#ifdef USE_TASMOTA_CLIENT
if (TasmotaClient_GetFlagFlashing()) {
WSContentSend_P(PSTR("
" D_TRANSFER_STARTED " ...
"));
restart_flag = 0; // Hold restart as code still needs to be transferred to Atmega
}
#endif // USE_TASMOTA_CLIENT
if (restart_flag) {
WSContentSend_P(HTTP_MSG_RSTRT);
ShowWebSource(SRC_WEBGUI);
}
}
SettingsBufferFree();
WSContentSend_P(PSTR("
"));
WSContentSpaceButton(BUTTON_MAIN);
WSContentStop();
#ifdef USE_TASMOTA_CLIENT
if (TasmotaClient_GetFlagFlashing()) {
TasmotaClient_Flash();
}
#endif
}
void HandleUploadLoop(void)
{
// Based on ESP8266HTTPUpdateServer.cpp uses ESP8266WebServer Parsing.cpp and Cores Updater.cpp (Update)
bool _serialoutput = (LOG_LEVEL_DEBUG <= seriallog_level);
if (HTTP_USER == Web.state) { return; }
if (Web.upload_error) {
if (UPL_TASMOTA == Web.upload_file_type) { Update.end(); }
return;
}
HTTPUpload& upload = Webserver->upload();
// ***** Step1: Start upload file
if (UPLOAD_FILE_START == upload.status) {
restart_flag = 60;
if (0 == upload.filename.c_str()[0]) {
Web.upload_error = 1; // No file selected
return;
}
SettingsSave(1); // Free flash for upload
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD D_FILE " %s ..."), upload.filename.c_str());
if (UPL_SETTINGS == Web.upload_file_type) {
if (!SettingsBufferAlloc()) {
Web.upload_error = 2; // Not enough space
return;
}
} else {
MqttRetryCounter(60);
#ifdef USE_COUNTER
CounterInterruptDisable(true); // Prevent OTA failures on 100Hz counter interrupts
#endif // USE_COUNTER
#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) { // SetOption3 - Enable MQTT
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");
// }
Web.upload_error = 2; // Not enough space
return;
}
}
Web.upload_progress_dot_count = 0;
}
// ***** Step2: Write upload file
else if (!Web.upload_error && (UPLOAD_FILE_WRITE == upload.status)) {
if (0 == upload.totalSize) {
if (UPL_SETTINGS == Web.upload_file_type) {
Web.config_block_count = 0;
}
else {
#if defined(USE_ZIGBEE) && defined(USE_ZIGBEE_EZSP)
#ifdef ESP8266
if ((SONOFF_ZB_BRIDGE == my_module_type) && (upload.buf[0] == 0xEB)) { // Check if this is a Zigbee bridge FW file
#else // ESP32
if (PinUsed(GPIO_ZIGBEE_RX) && PinUsed(GPIO_ZIGBEE_TX) && (upload.buf[0] == 0xEB)) { // Check if this is a Zigbee bridge FW file
#endif // ESP8266 or ESP32
Update.end(); // End esp8266 update session
Web.upload_file_type = UPL_EFR32;
Web.upload_error = ZigbeeUploadInit(); // 15
if (Web.upload_error != 0) { return; }
} else
#endif // USE_ZIGBEE and USE_ZIGBEE_EZSP
#ifdef USE_RF_FLASH
if ((SONOFF_BRIDGE == my_module_type) && (upload.buf[0] == ':')) { // Check if this is a RF bridge FW file
Update.end(); // End esp8266 update session
Web.upload_file_type = UPL_EFM8BB1;
Web.upload_error = SnfBrUpdateInit(); // 10, 11
if (Web.upload_error != 0) { return; }
} else
#endif // USE_RF_FLASH
#ifdef USE_TASMOTA_CLIENT
if (TasmotaClient_Available() && (upload.buf[0] == ':')) { // Check if this is a ARDUINO CLIENT hex file
Update.end(); // End esp8266 update session
Web.upload_file_type = UPL_TASMOTACLIENT;
Web.upload_error = TasmotaClient_UpdateInit(); // 0
if (Web.upload_error != 0) { return; }
} else
#endif
{
if ((upload.buf[0] != 0xE9) && (upload.buf[0] != 0x1F)) { // 0x1F is gzipped 0xE9
Web.upload_error = 3; // Magic byte is not 0xE9
return;
}
if (0xE9 == upload.buf[0]) {
uint32_t bin_flash_size = ESP.magicFlashChipSize((upload.buf[3] & 0xf0) >> 4);
if (bin_flash_size > ESP.getFlashChipRealSize()) {
Web.upload_error = 4; // Program flash size is larger than real flash size
return;
}
// upload.buf[2] = 3; // Force DOUT - ESP8285
}
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPLOAD "File type %d"), Web.upload_file_type);
}
}
if (UPL_SETTINGS == Web.upload_file_type) {
if (!Web.upload_error) {
if (upload.currentSize > (sizeof(Settings) - (Web.config_block_count * HTTP_UPLOAD_BUFLEN))) {
Web.upload_error = 9; // File too large
return;
}
memcpy(settings_buffer + (Web.config_block_count * HTTP_UPLOAD_BUFLEN), upload.buf, upload.currentSize);
Web.config_block_count++;
}
}
#if defined(USE_ZIGBEE) && defined(USE_ZIGBEE_EZSP)
else if (UPL_EFR32 == Web.upload_file_type) {
// Write buffers to MCU EFR32
if (!ZigbeeUploadWriteBuffer(upload.buf, upload.currentSize)) {
Web.upload_error = 9; // File too large
return;
}
}
#endif // USE_ZIGBEE and USE_ZIGBEE_EZSP
#ifdef USE_RF_FLASH
else if (UPL_EFM8BB1 == Web.upload_file_type) {
if (efm8bb1_update != nullptr) { // 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 = nullptr;
if (result != 0) {
Web.upload_error = abs(result); // 2 = Not enough space, 8 = File invalid, 12, 13
return;
}
}
ssize_t result = rf_search_and_write(upload.buf, upload.currentSize);
if (result < 0) {
Web.upload_error = abs(result); // 8, 12, 13
return;
} else if (result > 0) {
if ((size_t)result > upload.currentSize) {
// Offset is larger than the buffer supplied, this should not happen
Web.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 == nullptr) {
Web.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
#ifdef USE_TASMOTA_CLIENT
else if (UPL_TASMOTACLIENT == Web.upload_file_type) {
TasmotaClient_WriteBuffer(upload.buf, upload.currentSize);
}
#endif
else { // firmware
if (!Web.upload_error && (Update.write(upload.buf, upload.currentSize) != upload.currentSize)) {
Web.upload_error = 5; // Upload buffer miscompare
return;
}
if (_serialoutput) {
Serial.printf(".");
Web.upload_progress_dot_count++;
if (!(Web.upload_progress_dot_count % 80)) { Serial.println(); }
}
}
}
// ***** Step3: Finish upload file
else if(!Web.upload_error && (UPLOAD_FILE_END == upload.status)) {
if (_serialoutput && (Web.upload_progress_dot_count % 80)) {
Serial.println();
}
if (UPL_SETTINGS == Web.upload_file_type) {
if (Web.config_xor_on_set) {
for (uint32_t i = 2; i < sizeof(Settings); i++) {
settings_buffer[i] ^= (Web.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) {
uint32_t buffer_size = settings_buffer[3] << 8 | settings_buffer[2];
if (buffer_version > 0x0606000A) {
uint32_t buffer_crc32 = settings_buffer[4095] << 24 | settings_buffer[4094] << 16 | settings_buffer[4093] << 8 | settings_buffer[4092];
valid_settings = (GetCfgCrc32(settings_buffer, buffer_size -4) == buffer_crc32);
} else {
uint16_t buffer_crc16 = settings_buffer[15] << 8 | settings_buffer[14];
valid_settings = (GetCfgCrc16(settings_buffer, buffer_size) == buffer_crc16);
}
} else {
valid_settings = (settings_buffer[0] == CONFIG_FILE_SIGN);
}
if (valid_settings) {
#ifdef ESP8266
valid_settings = (0 == settings_buffer[0xF36]); // Settings.config_version
#endif // ESP8266
#ifdef ESP32
valid_settings = (1 == settings_buffer[0xF36]); // Settings.config_version
#endif // ESP32
}
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 {
Web.upload_error = 8; // File invalid
return;
}
}
#if defined(USE_ZIGBEE) && defined(USE_ZIGBEE_EZSP)
else if (UPL_EFR32 == Web.upload_file_type) {
// Zigbee FW upload to ESP8266 flash is done
ZigbeeUploadDone(); // Signal upload done and ready for delayed upload to MCU EFR32
Web.upload_file_type = UPL_TASMOTA;
}
#endif
#ifdef USE_RF_FLASH
else if (UPL_EFM8BB1 == Web.upload_file_type) {
// RF FW flash done
Web.upload_file_type = UPL_TASMOTA;
}
#endif // USE_RF_FLASH
#ifdef USE_TASMOTA_CLIENT
else if (UPL_TASMOTACLIENT == Web.upload_file_type) {
// Done writing the hex to SPI flash
TasmotaClient_SetFlagFlashing(true); // So we know on upload success page if it needs to flash hex or do a normal restart
Web.upload_file_type = UPL_TASMOTA;
}
#endif
else {
if (!Update.end(true)) { // true to set the size to the current progress
if (_serialoutput) { Update.printError(Serial); }
Web.upload_error = 6; // Upload failed. Enable logging 3
return;
}
if (!VersionCompatible()) {
Web.upload_error = 14; // Not compatible
return;
}
}
if (!Web.upload_error) {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD D_SUCCESSFUL " %u bytes"), upload.totalSize);
}
}
// ***** Step4: Abort upload file
else if (UPLOAD_FILE_ABORTED == upload.status) {
restart_flag = 0;
MqttRetryCounter(0);
#ifdef USE_COUNTER
CounterInterruptDisable(false);
#endif // USE_COUNTER
Web.upload_error = 7; // Upload aborted
if (UPL_TASMOTA == Web.upload_file_type) { Update.end(); }
}
delay(0);
}
/*-------------------------------------------------------------------------------------------*/
void HandlePreflightRequest(void)
{
HttpHeaderCors();
Webserver->sendHeader(F("Access-Control-Allow-Methods"), F("GET, POST"));
Webserver->sendHeader(F("Access-Control-Allow-Headers"), F("authorization"));
WSSend(200, CT_HTML, "");
}
/*-------------------------------------------------------------------------------------------*/
void HandleHttpCommand(void)
{
if (!HttpCheckPriviledgedAccess(false)) { return; }
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_COMMAND));
if (strlen(SettingsText(SET_WEBPWD))) {
char tmp1[33];
WebGetArg("user", tmp1, sizeof(tmp1));
char tmp2[strlen(SettingsText(SET_WEBPWD)) +1];
WebGetArg("password", tmp2, sizeof(tmp2));
if (!(!strcmp(tmp1, WEB_USERNAME) && !strcmp(tmp2, SettingsText(SET_WEBPWD)))) {
WSContentBegin(401, CT_JSON);
WSContentSend_P(PSTR("{\"" D_RSLT_WARNING "\":\"" D_NEED_USER_AND_PASSWORD "\"}"));
WSContentEnd();
return;
}
}
WSContentBegin(200, CT_JSON);
uint32_t curridx = web_log_index;
String svalue = Webserver->arg("cmnd");
if (svalue.length() && (svalue.length() < MQTT_MAX_PACKET_SIZE)) {
ExecuteWebCommand((char*)svalue.c_str(), SRC_WEBCOMMAND);
if (web_log_index != curridx) {
uint32_t counter = curridx;
WSContentSend_P(PSTR("{"));
bool cflg = false;
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])
size_t JSONlen = len - (JSON - tmp);
if (JSONlen > sizeof(mqtt_data)) { JSONlen = sizeof(mqtt_data); }
char stemp[JSONlen];
strlcpy(stemp, JSON +1, JSONlen -2);
WSContentSend_P(PSTR("%s%s"), (cflg) ? "," : "", stemp);
cflg = true;
}
}
counter++;
counter &= 0xFF;
if (!counter) counter++; // Skip 0 as it is not allowed
} while (counter != web_log_index);
WSContentSend_P(PSTR("}"));
} else {
WSContentSend_P(PSTR("{\"" D_RSLT_WARNING "\":\"" D_ENABLE_WEBLOG_FOR_RESPONSE "\"}"));
}
} else {
WSContentSend_P(PSTR("{\"" D_RSLT_WARNING "\":\"" D_ENTER_COMMAND " cmnd=\"}"));
}
WSContentEnd();
}
/*-------------------------------------------------------------------------------------------*/
void HandleConsole(void)
{
if (!HttpCheckPriviledgedAccess()) { return; }
if (Webserver->hasArg("c2")) { // Console refresh requested
HandleConsoleRefresh();
return;
}
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONSOLE);
WSContentStart_P(S_CONSOLE);
WSContentSend_P(HTTP_SCRIPT_CONSOL, Settings.web_refresh);
WSContentSendStyle();
WSContentSend_P(HTTP_FORM_CMND);
WSContentSpaceButton(BUTTON_MAIN);
WSContentStop();
}
void HandleConsoleRefresh(void)
{
bool cflg = true;
uint32_t counter = 0; // Initial start, should never be 0 again
String svalue = Webserver->arg("c1");
if (svalue.length() && (svalue.length() < MQTT_MAX_PACKET_SIZE)) {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_COMMAND "%s"), svalue.c_str());
ExecuteWebCommand((char*)svalue.c_str(), SRC_WEBCONSOLE);
}
char stmp[8];
WebGetArg("c2", stmp, sizeof(stmp));
if (strlen(stmp)) { counter = atoi(stmp); }
WSContentBegin(200, CT_PLAIN);
WSContentSend_P(PSTR("%d}1%d}1"), web_log_index, Web.reset_web_log_flag);
if (!Web.reset_web_log_flag) {
counter = 0;
Web.reset_web_log_flag = true;
}
if (counter != web_log_index) {
if (!counter) {
counter = web_log_index;
cflg = false;
}
do {
char* tmp;
size_t len;
GetLog(counter, &tmp, &len);
if (len) {
if (len > sizeof(mqtt_data) -2) { len = sizeof(mqtt_data); }
char stemp[len +1];
strlcpy(stemp, tmp, len);
WSContentSend_P(PSTR("%s%s"), (cflg) ? "\n" : "", stemp);
cflg = true;
}
counter++;
counter &= 0xFF;
if (!counter) { counter++; } // Skip log index 0 as it is not allowed
} while (counter != web_log_index);
}
WSContentSend_P(PSTR("}1"));
WSContentEnd();
}
/********************************************************************************************/
void HandleNotFound(void)
{
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP "Not found (%s)"), Webserver->uri().c_str());
if (CaptivePortal()) { return; } // If captive portal redirect instead of displaying the error page.
#ifdef USE_EMULATION
#ifdef USE_EMULATION_HUE
String path = Webserver->uri();
if ((EMUL_HUE == Settings.flag2.emulation) && (path.startsWith("/api"))) {
HandleHueApi(&path);
} else
#endif // USE_EMULATION_HUE
#endif // USE_EMULATION
{
WSContentBegin(404, CT_PLAIN);
WSContentSend_P(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 (uint32_t i = 0; i < Webserver->args(); i++) {
WSContentSend_P(PSTR(" %s: %s\n"), Webserver->argName(i).c_str(), Webserver->arg(i).c_str());
}
WSContentEnd();
}
}
/* 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. */
bool CaptivePortal(void)
{
// Possible hostHeader: connectivitycheck.gstatic.com or 192.168.4.1
if ((WifiIsInManagerMode()) && !ValidIpAddress(Webserver->hostHeader().c_str())) {
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_REDIRECTED));
Webserver->sendHeader(F("Location"), String("http://") + Webserver->client().localIP().toString(), true);
WSSend(302, CT_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;
}
/*********************************************************************************************/
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) || ('+' == decodedChar)) {
encoded += '%';
encoded += hex[decodedChar >> 4];
encoded += hex[decodedChar & 0xF];
} else {
encoded += decodedChar;
}
}
return encoded;
}
int WebSend(char *buffer)
{
// [tasmota] POWER1 ON --> Sends http://tasmota/cm?cmnd=POWER1 ON
// [192.168.178.86:80,admin:joker] POWER1 ON --> Sends http://hostname:80/cm?user=admin&password=joker&cmnd=POWER1 ON
// [tasmota] /any/link/starting/with/a/slash.php?log=123 --> Sends http://tasmota/any/link/starting/with/a/slash.php?log=123
// [tasmota,admin:joker] /any/link/starting/with/a/slash.php?log=123 --> Sends http://tasmota/any/link/starting/with/a/slash.php?log=123
char *host;
char *user;
char *password;
char *command;
int status = 1; // Wrong parameters
// buffer = | [ 192.168.178.86 : 80 , admin : joker ] POWER1 ON |
host = strtok_r(buffer, "]", &command); // host = | [ 192.168.178.86 : 80 , admin : joker |, command = | POWER1 ON |
if (host && command) {
RemoveSpace(host); // host = |[192.168.178.86:80,admin:joker|
host++; // host = |192.168.178.86:80,admin:joker| - Skip [
host = strtok_r(host, ",", &user); // host = |192.168.178.86:80|, user = |admin:joker|
String url = F("http://"); // url = |http://|
url += host; // url = |http://192.168.178.86:80|
command = Trim(command); // command = |POWER1 ON| or |/any/link/starting/with/a/slash.php?log=123|
if (command[0] != '/') {
url += F("/cm?"); // url = |http://192.168.178.86/cm?|
if (user) {
user = strtok_r(user, ":", &password); // user = |admin|, password = |joker|
if (user && password) {
char userpass[200];
snprintf_P(userpass, sizeof(userpass), PSTR("user=%s&password=%s&"), user, password);
url += userpass; // url = |http://192.168.178.86/cm?user=admin&password=joker&|
}
}
url += F("cmnd="); // url = |http://192.168.178.86/cm?cmnd=| or |http://192.168.178.86/cm?user=admin&password=joker&cmnd=|
}
url += command; // url = |http://192.168.178.86/cm?cmnd=POWER1 ON|
DEBUG_CORE_LOG(PSTR("WEB: Uri |%s|"), url.c_str());
WiFiClient http_client;
HTTPClient http;
if (http.begin(http_client, UrlEncode(url))) { // UrlEncode(url) = |http://192.168.178.86/cm?cmnd=POWER1%20ON|
int http_code = http.GET(); // Start connection and send HTTP header
if (http_code > 0) { // http_code will be negative on error
if (http_code == HTTP_CODE_OK || http_code == HTTP_CODE_MOVED_PERMANENTLY) {
#ifdef USE_WEBSEND_RESPONSE
// Return received data to the user - Adds 900+ bytes to the code
const char* read = http.getString().c_str(); // File found at server - may need lot of ram or trigger out of memory!
uint32_t j = 0;
char text = '.';
while (text != '\0') {
text = *read++;
if (text > 31) { // Remove control characters like linefeed
mqtt_data[j++] = text;
if (j == sizeof(mqtt_data) -2) { break; }
}
}
mqtt_data[j] = '\0';
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_WEBSEND));
#ifdef USE_SCRIPT
extern uint8_t tasm_cmd_activ;
// recursive call must be possible in this case
tasm_cmd_activ=0;
XdrvRulesProcess();
#endif // USE_SCRIPT
#endif // USE_WEBSEND_RESPONSE
}
status = 0; // No error - Done
} else {
status = 2; // Connection failed
}
http.end(); // Clean up connection data
} else {
status = 3; // Host not found or connection error
}
}
return status;
}
bool JsonWebColor(const char* dataBuf)
{
// Default (Dark theme)
// {"WebColor":["#eaeaea","#252525","#4f4f4f","#000","#ddd","#65c115","#1f1f1f","#ff5661","#008000","#faffff","#1fa3ec","#0e70a4","#d43535","#931f1f","#47c266","#5aaf6f","#faffff","#999","#eaeaea"]}
// Default pre v7 (Light theme)
// {"WebColor":["#000","#fff","#f2f2f2","#000","#fff","#000","#fff","#f00","#008000","#fff","#1fa3ec","#0e70a4","#d43535","#931f1f","#47c266","#5aaf6f","#fff","#999","#000"]} // {"WebColor":["#000000","#ffffff","#f2f2f2","#000000","#ffffff","#000000","#ffffff","#ff0000","#008000","#ffffff","#1fa3ec","#0e70a4","#d43535","#931f1f","#47c266","#5aaf6f","#ffffff","#999999","#000000"]}
char dataBufLc[strlen(dataBuf) +1];
LowerCase(dataBufLc, dataBuf);
RemoveSpace(dataBufLc);
if (strlen(dataBufLc) < 9) { return false; } // Workaround exception if empty JSON like {} - Needs checks
StaticJsonBuffer<450> jb; // 421 from https://arduinojson.org/v5/assistant/
JsonObject& obj = jb.parseObject(dataBufLc);
if (!obj.success()) { return false; }
char parm_lc[10];
if (obj[LowerCase(parm_lc, D_CMND_WEBCOLOR)].success()) {
for (uint32_t i = 0; i < COL_LAST; i++) {
const char* color = obj[parm_lc][i];
if (color != nullptr) {
WebHexCode(i, color);
}
}
}
return true;
}
const char kWebSendStatus[] PROGMEM = D_JSON_DONE "|" D_JSON_WRONG_PARAMETERS "|" D_JSON_CONNECT_FAILED "|" D_JSON_HOST_NOT_FOUND "|" D_JSON_MEMORY_ERROR;
const char kWebCommands[] PROGMEM = "|" // No prefix
#ifdef USE_EMULATION
D_CMND_EMULATION "|"
#endif
#ifdef USE_SENDMAIL
D_CMND_SENDMAIL "|"
#endif
D_CMND_WEBSERVER "|" D_CMND_WEBPASSWORD "|" D_CMND_WEBLOG "|" D_CMND_WEBREFRESH "|" D_CMND_WEBSEND "|" D_CMND_WEBCOLOR "|"
D_CMND_WEBSENSOR "|" D_CMND_WEBBUTTON "|" D_CMND_CORS;
void (* const WebCommand[])(void) PROGMEM = {
#ifdef USE_EMULATION
&CmndEmulation,
#endif
#ifdef USE_SENDMAIL
&CmndSendmail,
#endif
&CmndWebServer, &CmndWebPassword, &CmndWeblog, &CmndWebRefresh, &CmndWebSend, &CmndWebColor,
&CmndWebSensor, &CmndWebButton, &CmndCors };
/*********************************************************************************************\
* Commands
\*********************************************************************************************/
#ifdef USE_EMULATION
void CmndEmulation(void)
{
#if defined(USE_EMULATION_WEMO) || defined(USE_EMULATION_HUE)
#if defined(USE_EMULATION_WEMO) && defined(USE_EMULATION_HUE)
if ((XdrvMailbox.payload >= EMUL_NONE) && (XdrvMailbox.payload < EMUL_MAX)) {
#else
#ifndef USE_EMULATION_WEMO
if ((EMUL_NONE == XdrvMailbox.payload) || (EMUL_HUE == XdrvMailbox.payload)) {
#endif
#ifndef USE_EMULATION_HUE
if ((EMUL_NONE == XdrvMailbox.payload) || (EMUL_WEMO == XdrvMailbox.payload)) {
#endif
#endif
Settings.flag2.emulation = XdrvMailbox.payload;
restart_flag = 2;
}
#endif
ResponseCmndNumber(Settings.flag2.emulation);
}
#endif // USE_EMULATION
#ifdef USE_SENDMAIL
void CmndSendmail(void)
{
if (XdrvMailbox.data_len > 0) {
uint8_t result = SendMail(XdrvMailbox.data);
char stemp1[20];
ResponseCmndChar(GetTextIndexed(stemp1, sizeof(stemp1), result, kWebSendStatus));
}
}
#endif // USE_SENDMAIL
void CmndWebServer(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) {
Settings.webserver = XdrvMailbox.payload;
}
if (Settings.webserver) {
Response_P(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, NetworkHostname(), NetworkAddress().toString().c_str());
} else {
ResponseCmndStateText(0);
}
}
void CmndWebPassword(void)
{
if (XdrvMailbox.data_len > 0) {
SettingsUpdateText(SET_WEBPWD, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? WEB_PASSWORD : XdrvMailbox.data);
ResponseCmndChar(SettingsText(SET_WEBPWD));
} else {
Response_P(S_JSON_COMMAND_ASTERISK, XdrvMailbox.command);
}
}
void CmndWeblog(void)
{
if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_DEBUG_MORE)) {
Settings.weblog_level = XdrvMailbox.payload;
}
ResponseCmndNumber(Settings.weblog_level);
}
void CmndWebRefresh(void)
{
if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload <= 10000)) {
Settings.web_refresh = XdrvMailbox.payload;
}
ResponseCmndNumber(Settings.web_refresh);
}
void CmndWebSend(void)
{
if (XdrvMailbox.data_len > 0) {
uint32_t result = WebSend(XdrvMailbox.data);
char stemp1[20];
ResponseCmndChar(GetTextIndexed(stemp1, sizeof(stemp1), result, kWebSendStatus));
}
}
void CmndWebColor(void)
{
if (XdrvMailbox.data_len > 0) {
if (strstr(XdrvMailbox.data, "{") == nullptr) { // If no JSON it must be parameter
if ((XdrvMailbox.data_len > 3) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= COL_LAST)) {
WebHexCode(XdrvMailbox.index -1, XdrvMailbox.data);
}
else if (0 == XdrvMailbox.payload) {
SettingsDefaultWebColor();
}
}
else {
JsonWebColor(XdrvMailbox.data);
}
}
Response_P(PSTR("{\"" D_CMND_WEBCOLOR "\":["));
for (uint32_t i = 0; i < COL_LAST; i++) {
if (i) { ResponseAppend_P(PSTR(",")); }
ResponseAppend_P(PSTR("\"#%06x\""), WebColor(i));
}
ResponseAppend_P(PSTR("]}"));
}
void CmndWebSensor(void)
{
if (XdrvMailbox.index < MAX_XSNS_DRIVERS) {
if (XdrvMailbox.payload >= 0) {
bitWrite(Settings.sensors[XdrvMailbox.index / 32], XdrvMailbox.index % 32, XdrvMailbox.payload &1);
}
}
Response_P(PSTR("{\"" D_CMND_WEBSENSOR "\":"));
XsnsSensorState();
ResponseJsonEnd();
}
void CmndWebButton(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_BUTTON_TEXT)) {
if (!XdrvMailbox.usridx) {
ResponseCmndAll(SET_BUTTON1, MAX_BUTTON_TEXT);
} else {
if (XdrvMailbox.data_len > 0) {
SettingsUpdateText(SET_BUTTON1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data);
}
ResponseCmndIdxChar(SettingsText(SET_BUTTON1 + XdrvMailbox.index -1));
}
}
}
void CmndCors(void)
{
if (XdrvMailbox.data_len > 0) {
SettingsUpdateText(SET_CORS, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? WEB_PASSWORD : XdrvMailbox.data);
}
ResponseCmndChar(SettingsText(SET_CORS));
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xdrv01(uint8_t function)
{
bool result = false;
switch (function) {
case FUNC_LOOP:
PollDnsWebserver();
#ifdef USE_EMULATION
if (Settings.flag2.emulation) { PollUdp(); }
#endif // USE_EMULATION
break;
case FUNC_COMMAND:
result = DecodeCommand(kWebCommands, WebCommand);
break;
}
return result;
}
#endif // USE_WEBSERVER