/* asyncwebserver.cpp - ESP3D sync functions class Copyright (c) 2014 Luc Lebosse. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "config.h" #if defined(ASYNCWEBSERVER) #include "webinterface.h" #include "wificonf.h" #include #include #include #include #ifndef FS_NO_GLOBALS #define FS_NO_GLOBALS #endif #include #include #ifdef ARDUINO_ARCH_ESP8266 #include "ESP8266WiFi.h" #include #else //ESP32 #include #include #include "SPIFFS.h" #include "Update.h" #endif #include "GenLinkedList.h" #include "command.h" #include "espcom.h" #ifdef SSDP_FEATURE #ifdef ARDUINO_ARCH_ESP32 #include #else #include #endif #endif //embedded response file if no files on SPIFFS #include "nofile.h" bool can_process_serial = true; extern bool deleteRecursive(String path); extern bool sendLine2Serial (String & line, int32_t linenb, int32_t * newlinenb); extern void CloseSerialUpload (bool iserror, String & filename, int32_t linenb); extern bool purge_serial(); extern long id_connection; const uint8_t PAGE_404 [] PROGMEM = "\n\nRedirecting... \n\n\n
Unknown page : $QUERY$- you will be redirected...\n

\nif not redirected, click here\n

\n\n\n\n
\n\n\n\n"; const uint8_t PAGE_CAPTIVE [] PROGMEM = "\n\nCaptive Portal \n\n\n
Captive Portal page : $QUERY$- you will be redirected...\n

\nif not redirected, click here\n

\n\n\n\n
\n\n\n\n"; #define CONTENT_TYPE_HTML "text/html" //filter to intercept command line on root bool filterOnRoot (AsyncWebServerRequest *request) { if (request->hasArg ("forcefallback") ) { String stmp = request->arg ("forcefallback"); //to use all case stmp.toLowerCase(); if ( stmp == "yes") { return false; } } return true; } void handle_login(AsyncWebServerRequest *request) { #ifdef AUTHENTICATION_FEATURE #else AsyncWebServerResponse * response = request->beginResponse (200, "application/json", "{\"status\":\"Ok\",\"authentication_lvl\":\"admin\"}"); response->addHeader("Cache-Control","no-cache"); request->send(response); #endif } #ifdef SSDP_FEATURE void handle_SSDP (AsyncWebServerRequest *request) { StreamString sschema ; if (sschema.reserve (1024) ) { String templ = "" "" "" "1" "0" "" "http://%s:%u/" "" "upnp:rootdevice" "%s" "/" "%s" #ifdef ARDUINO_ARCH_ESP32 "ESP32" #else "ESP8266" #endif "ESP3D" #ifdef ARDUINO_ARCH_ESP32 "http://espressif.com/en/products/hardware/esp-wroom-32/overview" #else "http://espressif.com/en/products/esp8266" #endif "Espressif Systems" "http://espressif.com" "uuid:%s" "" "\r\n" "\r\n"; char uuid[37]; String sip = WiFi.localIP().toString(); #ifdef ARDUINO_ARCH_ESP8266 uint32_t chipId = ESP.getChipId(); #else uint32_t chipId = (uint16_t) (ESP.getEfuseMac() >> 32); #endif sprintf (uuid, "38323636-4558-4dda-9188-cda0e6%02x%02x%02x", (uint16_t) ( (chipId >> 16) & 0xff), (uint16_t) ( (chipId >> 8) & 0xff), (uint16_t) chipId & 0xff ); String friendlyName; if (!CONFIG::read_string (EP_HOSTNAME, friendlyName, MAX_HOSTNAME_LENGTH) ) { friendlyName = wifi_config.get_default_hostname(); } String serialNumber = String (chipId); sschema.printf (templ.c_str(), sip.c_str(), wifi_config.iweb_port, friendlyName.c_str(), serialNumber.c_str(), uuid); request->send (200, "text/xml", (String) sschema); } else { request->send (500); } } #endif //SPIFFS //SPIFFS files list and file commands void handleFileList (AsyncWebServerRequest *request) { level_authenticate_type auth_level = web_interface->is_authenticated(); if (auth_level == LEVEL_GUEST) { web_interface->_upload_status = UPLOAD_STATUS_NONE; request->send (401, "text/plain", "Authentication failed!\n"); return; } String path ; String status = "Ok"; if ( (web_interface->_upload_status == UPLOAD_STATUS_FAILED) || (web_interface->_upload_status == UPLOAD_STATUS_FAILED) ) { status = "Upload failed"; } //be sure root is correct according authentication if (auth_level == LEVEL_ADMIN) { path = "/"; } else { path = "/user"; } //get current path if (request->hasArg ("path") ) { path += request->arg ("path") ; } //to have a clean path path.trim(); path.replace ("//", "/"); if (path[path.length() - 1] != '/') { path += "/"; } //check if query need some action if (request->hasArg ("action") ) { //delete a file if (request->arg ("action") == "delete" && request->hasArg ("filename") ) { String filename; String shortname = request->arg ("filename"); shortname.replace ("/", ""); filename = path + request->arg ("filename"); filename.replace ("//", "/"); if (!SPIFFS.exists (filename) ) { status = shortname + F (" does not exists!"); } else { if (SPIFFS.remove (filename) ) { status = shortname + F (" deleted"); //what happen if no "/." and no other subfiles ? #ifdef ARDUINO_ARCH_ESP8266 FS_DIR dir = SPIFFS.openDir (path); if (!dir.next() ) { #else String ptmp = path; if ( (path != "/") && (path[path.length() - 1] = '/') ) { ptmp = path.substring (0, path.length() - 1); } FS_FILE dir = SPIFFS.open (ptmp); FS_FILE dircontent = dir.openNextFile(); if (!dircontent) { #endif //keep directory alive even empty FS_FILE r = SPIFFS.open (path + "/.", SPIFFS_FILE_WRITE); if (r) { r.close(); } } } else { status = F ("Cannot deleted ") ; status += shortname ; } } } //delete a directory if (request->arg ("action") == "deletedir" && request->hasArg ("filename") ) { String filename; String shortname = request->arg ("filename"); shortname.replace ("/", ""); filename = path + request->arg ("filename"); filename += "/"; filename.replace ("//", "/"); if (filename != "/") { bool delete_error = false; #ifdef ARDUINO_ARCH_ESP8266 FS_DIR dir = SPIFFS.openDir (path + shortname); { while (dir.next() ) { #else FS_FILE dir = SPIFFS.open (path + shortname); { FS_FILE file2deleted = dir.openNextFile(); while (file2deleted) { #endif #ifdef ARDUINO_ARCH_ESP8266 String fullpath = dir.fileName(); #else String fullpath = file2deleted.name(); #endif if (!SPIFFS.remove (fullpath) ) { delete_error = true; status = F ("Cannot deleted ") ; status += fullpath; } #ifdef ARDUINO_ARCH_ESP32 file2deleted = dir.openNextFile(); #endif } } if (!delete_error) { status = shortname ; status += " deleted"; } } } //create a directory if (request->arg ("action") == "createdir" && request->hasArg ("filename") ) { String filename; filename = path + request->arg ("filename") + "/."; String shortname = request->arg ("filename"); shortname.replace ("/", ""); filename.replace ("//", "/"); if (SPIFFS.exists (filename) ) { status = shortname + F (" already exists!"); } else { FS_FILE r = SPIFFS.open (filename, SPIFFS_FILE_WRITE); if (!r) { status = F ("Cannot create "); status += shortname ; } else { r.close(); status = shortname + F (" created"); } } } } String jsonfile = "{"; #ifdef ARDUINO_ARCH_ESP8266 FS_DIR dir = SPIFFS.openDir (path); #else String ptmp = path; if ( (path != "/") && (path[path.length() - 1] = '/') ) { ptmp = path.substring (0, path.length() - 1); } FS_FILE dir = SPIFFS.open (ptmp); #endif jsonfile += "\"files\":["; bool firstentry = true; String subdirlist = ""; #ifdef ARDUINO_ARCH_ESP8266 while (dir.next() ) { String filename = dir.fileName(); #else File fileparsed = dir.openNextFile(); while (fileparsed) { String filename = fileparsed.name(); #endif String size = ""; bool addtolist = true; //remove path from name filename = filename.substring (path.length(), filename.length() ); //check if file or subfile if (filename.indexOf ("/") > -1) { //Do not rely on "/." to define directory as SPIFFS upload won't create it but directly files //and no need to overload SPIFFS if not necessary to create "/." if no need //it will reduce SPIFFS available space so limit it to creation filename = filename.substring (0, filename.indexOf ("/") ); String tag = "*"; tag += filename + "*"; if (subdirlist.indexOf (tag) > -1 || filename.length() == 0) { //already in list addtolist = false; //no need to add } else { size = "-1"; //it is subfile so display only directory, size will be -1 to describe it is directory if (subdirlist.length() == 0) { subdirlist += "*"; } subdirlist += filename + "*"; //add to list } } else { //do not add "." file if (! ( (filename == ".") || (filename == "") ) ) { #ifdef ARDUINO_ARCH_ESP8266 size = CONFIG::formatBytes (dir.fileSize() ); #else size = CONFIG::formatBytes (fileparsed.size() ); #endif } else { addtolist = false; } } if (addtolist) { if (!firstentry) { jsonfile += ","; } else { firstentry = false; } jsonfile += "{"; jsonfile += "\"name\":\""; jsonfile += filename; jsonfile += "\",\"size\":\""; jsonfile += size; jsonfile += "\""; jsonfile += "}"; } #ifdef ARDUINO_ARCH_ESP32 fileparsed = dir.openNextFile(); #endif } jsonfile += "],"; jsonfile += "\"path\":\"" + path + "\","; jsonfile += "\"status\":\"" + status + "\","; size_t totalBytes; size_t usedBytes; #ifdef ARDUINO_ARCH_ESP8266 fs::FSInfo info; SPIFFS.info (info); totalBytes = info.totalBytes; usedBytes = info.usedBytes; #else totalBytes = SPIFFS.totalBytes(); usedBytes = SPIFFS.usedBytes(); #endif jsonfile += "\"total\":\"" + CONFIG::formatBytes (totalBytes) + "\","; jsonfile += "\"used\":\"" + CONFIG::formatBytes (usedBytes) + "\","; jsonfile.concat (F ("\"occupation\":\"") ); jsonfile += CONFIG::intTostr (100 * usedBytes / totalBytes); jsonfile += "\""; jsonfile += "}"; path = ""; AsyncResponseStream *response = request->beginResponseStream ("application/json"); response->addHeader ("Cache-Control", "no-cache"); response->print (jsonfile.c_str() ); request->send (response); web_interface->_upload_status = UPLOAD_STATUS_NONE; } //SPIFFS files uploader handle void SPIFFSFileupload (AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { LOG ("Uploading: ") LOG (filename) LOG ("\n") //get authentication status level_authenticate_type auth_level= web_interface->is_authenticated(); //Guest cannot upload - only admin if (auth_level == LEVEL_GUEST) { web_interface->_upload_status = UPLOAD_STATUS_FAILED; ESPCOM::println (F ("Upload rejected"), PRINTER_PIPE); LOG ("Upload rejected\r\n"); request->client()->abort(); return; } String upload_filename = filename; if (upload_filename[0] != '/') { upload_filename = "/" + filename; LOG ("Fix :") LOG (upload_filename) LOG ("\n") } if(auth_level != LEVEL_ADMIN) { String filename = upload_filename; upload_filename = "/user" + filename; } //Upload start //************** if (!index) { LOG ("Upload start: ") LOG ("filename") LOG ("\n") LOG (upload_filename) if (SPIFFS.exists (upload_filename) ) { SPIFFS.remove (upload_filename); } if (request->_tempFile) { request->_tempFile.close(); } request->_tempFile = SPIFFS.open (upload_filename, SPIFFS_FILE_WRITE); if (!request->_tempFile) { LOG ("Error") request->client()->abort(); web_interface->_upload_status = UPLOAD_STATUS_FAILED; } else { web_interface->_upload_status = UPLOAD_STATUS_ONGOING; } } //Upload write //************** if ( request->_tempFile) { if ( ( web_interface->_upload_status = UPLOAD_STATUS_ONGOING) && len) { request->_tempFile.write (data, len); LOG ("Write file\n") } } //Upload end //************** if (final) { request->_tempFile.flush(); request->_tempFile.close(); request->_tempFile = SPIFFS.open (upload_filename, SPIFFS_FILE_READ); uint32_t filesize = request->_tempFile.size(); request->_tempFile.close(); String sizeargname = filename + "S"; if (request->hasArg (sizeargname.c_str()) ) { if (request->arg (sizeargname.c_str()) != String(filesize)) { web_interface->_upload_status = UPLOAD_STATUS_FAILED; SPIFFS.remove (upload_filename); } } LOG ("Close file\n") if (web_interface->_upload_status == UPLOAD_STATUS_ONGOING) { web_interface->_upload_status = UPLOAD_STATUS_SUCCESSFUL; } } } //FW update using Web interface #ifdef WEB_UPDATE_FEATURE void handleUpdate (AsyncWebServerRequest *request) { level_authenticate_type auth_level = web_interface->is_authenticated(); if (auth_level != LEVEL_ADMIN) { web_interface->_upload_status = UPLOAD_STATUS_NONE; request->send (403, "text/plain", "Not allowed, log in first!\n"); return; } String jsonfile = "{\"status\":\"" ; jsonfile += CONFIG::intTostr (web_interface->_upload_status); jsonfile += "\"}"; //send status AsyncResponseStream *response = request->beginResponseStream ("application/json"); response->addHeader ("Cache-Control", "no-cache"); response->print (jsonfile.c_str() ); request->send (response); //if success restart if (web_interface->_upload_status == UPLOAD_STATUS_SUCCESSFUL) { web_interface->restartmodule = true; } else { web_interface->_upload_status = UPLOAD_STATUS_NONE; } } void WebUpdateUpload (AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { static size_t last_upload_update; static size_t totalSize; static uint32_t maxSketchSpace ; //only admin can update FW if (web_interface->is_authenticated() != LEVEL_ADMIN) { web_interface->_upload_status = UPLOAD_STATUS_FAILED; request->client()->abort(); ESPCOM::println (F ("Update rejected"), PRINTER_PIPE); LOG ("Update failed\r\n"); return; } //Upload start //************** if (!index) { ESPCOM::println (F ("Update Firmware"), PRINTER_PIPE); web_interface->_upload_status = UPLOAD_STATUS_ONGOING; #ifdef ARDUINO_ARCH_ESP8266 Update.runAsync (true); WiFiUDP::stopAll(); #endif #ifdef ARDUINO_ARCH_ESP8266 maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; #else //Not sure can do OTA on 2Mb board maxSketchSpace = (ESP.getFlashChipSize() > 0x20000) ? 0x140000 : 0x140000 / 2; #endif last_upload_update = 0; totalSize = 0; if (!Update.begin (maxSketchSpace) ) { //start with max available size web_interface->_upload_status = UPLOAD_STATUS_FAILED; ESPCOM::println (F ("Update Cancelled"), PRINTER_PIPE); request->client()->abort(); return; } else { if ( ( CONFIG::GetFirmwareTarget() == REPETIER4DV) || (CONFIG::GetFirmwareTarget() == REPETIER) ) { ESPCOM::println (F ("Update 0%%"), PRINTER_PIPE); } else { ESPCOM::println (F ("Update 0%"), PRINTER_PIPE); } } } //Upload write //************** if (web_interface->_upload_status == UPLOAD_STATUS_ONGOING) { //we do not know the total file size yet but we know the available space so let's use it totalSize += len; if ( ( (100 * totalSize) / maxSketchSpace) != last_upload_update) { last_upload_update = (100 * totalSize) / maxSketchSpace; String s = "Update "; s+= String(last_upload_update); if ( ( CONFIG::GetFirmwareTarget() == REPETIER4DV) || (CONFIG::GetFirmwareTarget() == REPETIER) ) { s+="%%"; } else { s+="%"; } ESPCOM::println (s, PRINTER_PIPE); } if (Update.write (data, len) != len) { web_interface->_upload_status = UPLOAD_STATUS_FAILED; ESPCOM::println(F("Update Error"), PRINTER_PIPE); Update.end(); request->client()->abort(); } } //Upload end //************** if (final) { String sizeargname = filename + "S"; if (request->hasArg (sizeargname.c_str()) ) { ESPCOM::println (F ("Check integrity"), PRINTER_PIPE); if (request->arg (sizeargname.c_str()) != String(totalSize)) { web_interface->_upload_status = UPLOAD_STATUS_FAILED; ESPCOM::println (F ("Update Error"), PRINTER_PIPE); Update.end(); request->client()->abort(); } } if (Update.end (true) ) { //true to set the size to the current progress //Update is done if ( ( CONFIG::GetFirmwareTarget() == REPETIER4DV) || (CONFIG::GetFirmwareTarget() == REPETIER) ) { ESPCOM::println (F ("Update 100%%"), PRINTER_PIPE); } else { ESPCOM::println (F ("Update 100%"), PRINTER_PIPE); } web_interface->_upload_status = UPLOAD_STATUS_SUCCESSFUL; } } } #endif //Not found Page handler ////////////////////////////////////////////////////////// void handle_not_found (AsyncWebServerRequest *request) { //if we are here it means no index.html if (request->url() == "/") { AsyncWebServerResponse * response = request->beginResponse_P (200, CONTENT_TYPE_HTML, PAGE_NOFILES, PAGE_NOFILES_SIZE); response->addHeader ("Content-Encoding", "gzip"); request->send (response); } else { String path = F ("/404.htm"); String pathWithGz = path + F (".gz"); if (SPIFFS.exists (pathWithGz) || SPIFFS.exists (path) ) { request->send (SPIFFS, path); } else { //if not template use default page String contentpage = FPSTR (PAGE_404); String stmp; if (WiFi.getMode() == WIFI_STA ) { stmp = WiFi.localIP().toString(); } else { stmp = WiFi.softAPIP().toString(); } //Web address = ip + port String KEY_IP = F ("$WEB_ADDRESS$"); String KEY_QUERY = F ("$QUERY$"); if (wifi_config.iweb_port != 80) { stmp += ":"; stmp += CONFIG::intTostr (wifi_config.iweb_port); } contentpage.replace (KEY_IP, stmp); contentpage.replace (KEY_QUERY, request->url() ); request->send (404, CONTENT_TYPE_HTML, contentpage.c_str() ); } } } //Handle web command query and send answer////////////////////////////// void handle_web_command (AsyncWebServerRequest *request) { //to save time if already disconnected if (request->hasArg ("PAGEID") ) { if (request->arg ("PAGEID").length() > 0 ) { if (request->arg ("PAGEID").toInt() != id_connection) { request->send (200, "text/plain", "Invalid command"); return; } } } level_authenticate_type auth_level = web_interface->is_authenticated(); LOG (" Web command\r\n") #ifdef DEBUG_ESP3D int nb = request->args(); for (int i = 0 ; i < nb; i++) { LOG (request->argName (i) ) LOG (":") LOG (request->arg (i) ) LOG ("\r\n") } #endif String cmd = ""; if (request->hasArg ("plain") || request->hasArg ("commandText") ) { if (request->hasArg ("plain") ) { cmd = request->arg ("plain"); } else { cmd = request->arg ("commandText"); } LOG ("Web Command:") LOG (cmd) LOG ("\r\n") } else { LOG ("invalid argument\r\n") request->send (200, "text/plain", "Invalid command"); return; } //if it is for ESP module [ESPXXX] cmd.trim(); int ESPpos = cmd.indexOf ("[ESP"); if (ESPpos > -1) { //is there the second part? int ESPpos2 = cmd.indexOf ("]", ESPpos); if (ESPpos2 > -1) { //Split in command and parameters String cmd_part1 = cmd.substring (ESPpos + 4, ESPpos2); String cmd_part2 = ""; //only [ESP800] is allowed login free if authentication is enabled if ( (auth_level == LEVEL_GUEST) && (cmd_part1.toInt() != 800) ) { request->send (401, "text/plain", "Authentication failed!\n"); return; } //is there space for parameters? if (ESPpos2 < cmd.length() ) { cmd_part2 = cmd.substring (ESPpos2 + 1); } //if command is a valid number then execute command if (cmd_part1.toInt() != 0) { AsyncResponseStream *response = request->beginResponseStream ("text/html"); response->addHeader ("Cache-Control", "no-cache"); COMMAND::execute_command (cmd_part1.toInt(), cmd_part2, WEB_PIPE, auth_level, response); request->send (response); } //if not is not a valid [ESPXXX] command } } else { if (auth_level == LEVEL_GUEST) { request->send (401, "text/plain", "Authentication failed!\n"); return; } //send command to serial as no need to transfer ESP command //to avoid any pollution if Uploading file to SDCard if ( (web_interface->blockserial) == false) { //block every query web_interface->blockserial = true; LOG ("Block Serial\r\n") //empty the serial buffer and incoming data LOG ("Start PurgeSerial\r\n") ESPCOM::processFromSerial (true); LOG ("End PurgeSerial\r\n") LOG ("Start PurgeSerial\r\n") ESPCOM::processFromSerial (true); LOG ("End PurgeSerial\r\n") can_process_serial = false; request->onDisconnect([request]() { can_process_serial = true; }); //send command LOG ("Send Command\r\n") ESPCOM::println (cmd, DEFAULT_PRINTER_PIPE); CONFIG::wait (1); AsyncWebServerResponse *response = request->beginChunkedResponse ("text/plain", [] (uint8_t *buffer, size_t maxLen, size_t index) -> size_t { static bool finish_check; static String current_line; static int multiple_counter = 0; static uint8_t *active_serial_buffer = NULL; static size_t active_serial_buffer_size = 0; uint32_t timeout = millis(); size_t count = 0; LOG ("Entering\n") //this is the start if (!index) { //we are not done finish_check = false; //no error / multiple heat multiple_counter = 0; //if somehow the buffer is not empty - clean it if (active_serial_buffer != NULL) { delete active_serial_buffer; active_serial_buffer = NULL; } active_serial_buffer_size = 0; current_line = ""; } //current_line is not empty because previous buffer was full if (current_line.length() > 0 ) { //does it have a end already ? if (current_line[current_line.length() - 1] == '\n') { LOG (current_line.c_str() ) //if yes fill the buffer String tmp = ""; for (int p = 0 ; p < current_line.length() ; p++) { CONFIG::wdtFeed(); //Just in case line is too long if (count < maxLen) { buffer[count] = current_line[p]; count++; } //too long let's copy to buffer what's left for next time else { tmp += current_line[p]; } } //this will be sent next time current_line = tmp; } } LOG (" Max Len Size ") LOG (String (maxLen) ) LOG (" Index ") LOG (String (index) ) LOG (" initial count ") LOG (String (count) ) LOG (" buffer ") LOG (String (active_serial_buffer_size) ) LOG (" line ") LOG (String (current_line.length() ) ) LOG ("\n") int packet_size = (maxLen >= 1460) ? 1200 : maxLen; //now check if serial has data if have space in send buffer while (!finish_check && (count < maxLen) ) { CONFIG::wdtFeed(); size_t len = ESPCOM::available(DEFAULT_PRINTER_PIPE); LOG ("Input Size ") LOG (String (len) ) LOG ("\n") if (len > 0) { //prepare serial buffer uint8_t * tmp_buf = new uint8_t[active_serial_buffer_size + len]; //copy current buffer in new buffer if ( (active_serial_buffer_size > 0) && (active_serial_buffer != NULL) ) { for (int p = 0; p < active_serial_buffer_size; p++) { tmp_buf[p] = active_serial_buffer[p]; } delete active_serial_buffer; } //new sized buffer active_serial_buffer = tmp_buf; //read full buffer instead of read char by char //so it give time to refill when processing ESPCOM::readBytes (DEFAULT_PRINTER_PIPE, &active_serial_buffer[active_serial_buffer_size], len); //new size of current buffer active_serial_buffer_size += len; } LOG ("buffer Size ") LOG (String (active_serial_buffer_size) ) LOG ("\n") //if buffer is not empty let's process it if (active_serial_buffer_size > 0) { for (int p = 0; p < active_serial_buffer_size ; p++) { //reset timeout timeout = millis(); //feed WDT CONFIG::wdtFeed(); //be sure there is some space if (count < maxLen) { //read next char uint8_t c = active_serial_buffer[p]; //if it is end line if ( (c == 13) || (c == 10) ) { //it is an ok or a wait so this is the end LOG ("line ") LOG (current_line.c_str() ) if ( (current_line == "ok") || (current_line == "wait") || ( ( (CONFIG::GetFirmwareTarget() == REPETIER) || (CONFIG::GetFirmwareTarget() == REPETIER4DV) ) && ( (current_line.indexOf ("busy:") > -1) || (current_line.startsWith ( "ok ") ) ) ) ) { LOG ("ignore ") LOG (current_line.c_str() ) current_line = ""; //check we have something before we leave and it is not //an ack for the command if ( (index == 0) && (count == 0) ) { multiple_counter ++ ; } else { finish_check = true; } //we do not ignore it } else { if (current_line.length() > 0) { current_line += "\n"; //do we add current line to buffer or wait for next callback ? if ( (count + current_line.length() ) < maxLen) { LOG ("line added\n") //have space so copy to buffer for (int pp = 0 ; pp < current_line.length() ; pp++) { buffer[count] = current_line[pp]; count++; } //CONFIG::wait(1); timeout = millis(); if (COMMAND::check_command (current_line, NO_PIPE, false, false) ) { multiple_counter ++ ; } //reset line current_line = ""; //no space return and add next time } else { //we should never reach here - but better prevent if (p < active_serial_buffer_size) { uint8_t * tmp_buf = new uint8_t[active_serial_buffer_size - p]; //copy old one to new one for (int pp = 0; pp < active_serial_buffer_size - p; pp++) { tmp_buf[pp] = active_serial_buffer[p + pp]; } //delete old one if (active_serial_buffer != NULL) { delete active_serial_buffer; } //now new is the active one active_serial_buffer = tmp_buf; //reset size active_serial_buffer_size = active_serial_buffer_size - p; } return count; } } } } else { current_line += char (c); //case of long line that won't fit if (current_line.length() >= maxLen) { //we push out what we have and what we can //no need tp check if it is command as it is too long for (int pp = 0 ; pp < current_line.length() ; pp++) { CONFIG::wdtFeed(); if (count < maxLen) { buffer[count] = current_line[pp]; count++; } else { //put what is left for next time //remove part already sent String tmp = current_line.substring (pp); current_line = tmp; //save left buffer if (p < active_serial_buffer_size) { uint8_t * tmp_buf = new uint8_t[active_serial_buffer_size - p]; //copy old one to new one for (int pp = 0; pp < active_serial_buffer_size - p; pp++) { tmp_buf[pp] = active_serial_buffer[p + pp]; } //delete old one if (active_serial_buffer != NULL) { delete active_serial_buffer; } //now new is the active one active_serial_buffer = tmp_buf; //reset size active_serial_buffer_size = active_serial_buffer_size - p; } return count; } } } } //no space return and add next time } else { //we should never reach here - but better prevent //save unprocessed buffer //create a resized buffer uint8_t * tmp_buf = new uint8_t[active_serial_buffer_size - p]; //copy old one to new one for (int pp = 0; pp < active_serial_buffer_size - p; pp++) { tmp_buf[pp] = active_serial_buffer[p + pp]; } //delete old one if (active_serial_buffer != NULL) { delete active_serial_buffer; } //now new is the active one active_serial_buffer = tmp_buf; //reset size active_serial_buffer_size = active_serial_buffer_size - p; return count; } }//end processing buffer Serial active_serial_buffer_size = 0; if (active_serial_buffer != NULL) { delete active_serial_buffer; } active_serial_buffer = NULL; //we chop to fill packect size not maxLen if (count >= packet_size) { //buffer is empty so time to clean return count; } timeout = millis(); } //end of processing serial buffer //we got several ok / wait /busy or temperature, so we should stop to avoid a dead loop if (multiple_counter > 5) { LOG ("Multiple counter reached\n\r") finish_check = true; } //no input during 1 s so consider we are done and we missed the end flag if (millis() - timeout > 1000) { finish_check = true; LOG ("time out\r\n") } } // we are done for this call : buffer is full or everything is finished //if we are done if (finish_check) { //do some cleaning if needed active_serial_buffer_size = 0; if (active_serial_buffer != NULL) { delete active_serial_buffer; } active_serial_buffer = NULL; } return count; }); response->addHeader ("Cache-Control", "no-cache"); request->send (response); LOG ("Start PurgeSerial\r\n") ESPCOM::processFromSerial (true); LOG ("End PurgeSerial\r\n") web_interface->blockserial = false; LOG ("Release Serial\r\n") } else { request->send (409, "text/plain", "Serial is busy, retry later!"); } } } //Handle web command query and sent ack or fail instead of answer/////// void handle_web_command_silent (AsyncWebServerRequest *request) { //to save time if already disconnected if (request->hasArg ("PAGEID") ) { if (request->arg ("PAGEID").length() > 0 ) { if (request->arg ("PAGEID").toInt() != id_connection) { request->send (200, "text/plain", "Invalid command"); return; } } } level_authenticate_type auth_level = web_interface->is_authenticated(); if (auth_level == LEVEL_GUEST) { request->send (401, "text/plain", "Authentication failed!\n"); return; } LOG (String (request->args() ) ) LOG (" Web silent command\r\n") #ifdef DEBUG_ESP3D int nb = request->args(); for (int i = 0 ; i < nb; i++) { LOG (request->argName (i) ) LOG (":") LOG (request->arg (i) ) LOG ("\r\n") } #endif String cmd = ""; //int count ; if (request->hasArg ("plain") || request->hasArg ("commandText") ) { if (request->hasArg ("plain") ) { cmd = request->arg ("plain"); } else { cmd = request->arg ("commandText"); } LOG ("Web Command:") LOG (cmd) LOG ("\r\n") } else { LOG ("invalid argument\r\n") request->send (200, "text/plain", "Invalid command"); return; } //if it is for ESP module [ESPXXX] cmd.trim(); int ESPpos = cmd.indexOf ("[ESP"); if (ESPpos > -1) { //is there the second part? int ESPpos2 = cmd.indexOf ("]", ESPpos); if (ESPpos2 > -1) { //Split in command and parameters String cmd_part1 = cmd.substring (ESPpos + 4, ESPpos2); String cmd_part2 = ""; //is there space for parameters? if (ESPpos2 < cmd.length() ) { cmd_part2 = cmd.substring (ESPpos2 + 1); } //if command is a valid number then execute command if (cmd_part1.toInt() != 0) { if (COMMAND::execute_command (cmd_part1.toInt(), cmd_part2, NO_PIPE, auth_level) ) { request->send (200, "text/plain", "ok"); } else { request->send (500, "text/plain", "error"); } } //if not is not a valid [ESPXXX] command } } else { //send command to serial as no need to transfer ESP command //to avoid any pollution if Uploading file to SDCard if ( (web_interface->blockserial) == false) { LOG ("Send Command\r\n") //send command ESPCOM::println (cmd, DEFAULT_PRINTER_PIPE); request->send (200, "text/plain", "ok"); } else { request->send (200, "text/plain", "Serial is busy, retry later!"); } } } //serial SD files list////////////////////////////////////////////////// void handle_serial_SDFileList (AsyncWebServerRequest *request) { //this is only for admin and user if (web_interface->is_authenticated() == LEVEL_GUEST) { web_interface->_upload_status = UPLOAD_STATUS_NONE; request->send (401, "application/json", "{\"status\":\"Authentication failed!\"}"); return; } LOG ("serial SD upload done\r\n") String sstatus = "Ok"; if ( (web_interface->_upload_status == UPLOAD_STATUS_FAILED) || (web_interface->_upload_status == UPLOAD_STATUS_FAILED) ) { sstatus = "Upload failed"; web_interface->_upload_status = UPLOAD_STATUS_NONE; } String jsonfile = "{\"status\":\"" + sstatus + "\"}"; AsyncResponseStream *response = request->beginResponseStream ("application/json"); response->addHeader ("Cache-Control", "no-cache"); response->print (jsonfile.c_str() ); request->send (response); web_interface->blockserial = false; web_interface->_upload_status = UPLOAD_STATUS_NONE; } //SD file upload by serial #define NB_RETRY 5 #define MAX_RESEND_BUFFER 128 #define SERIAL_CHECK_TIMEOUT 2000 void SDFile_serial_upload (AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { LOG ("Uploading: ") LOG (filename) LOG ("\n") static int32_t lineNb =-1; static String current_line; static bool is_comment = false; static String current_filename; String response; //Guest cannot upload - only admin and user if (web_interface->is_authenticated() == LEVEL_GUEST) { web_interface->_upload_status = UPLOAD_STATUS_FAILED; ESPCOM::println (F ("SD upload rejected"), PRINTER_PIPE); LOG ("SD upload rejected\r\n"); request->client()->abort(); return; } //Upload start //************** if (!index) { LOG("Upload Start\r\n") String command = "M29"; String resetcmd = "M110 N0"; if (CONFIG::GetFirmwareTarget() == SMOOTHIEWARE) { resetcmd = "N0 M110"; } lineNb=1; //close any ongoing upload and get current line number if(!sendLine2Serial (command,1, &lineNb)) { //it can failed for repetier if ( ( CONFIG::GetFirmwareTarget() == REPETIER4DV) || (CONFIG::GetFirmwareTarget() == REPETIER) ) { if(!sendLine2Serial (command,-1, NULL)) { LOG("Start Upload failed") web_interface->_upload_status= UPLOAD_STATUS_FAILED; return; } } else { LOG("Start Upload failed") web_interface->_upload_status= UPLOAD_STATUS_FAILED; return; } } //Mount SD card command = "M21"; if(!sendLine2Serial (command,-1, NULL)) { LOG("Mounting SD failed") web_interface->_upload_status= UPLOAD_STATUS_FAILED; return; } //Reset line numbering if(!sendLine2Serial (resetcmd,-1, NULL)) { LOG("Reset Numbering failed") web_interface->_upload_status= UPLOAD_STATUS_FAILED; return; } lineNb=1; //need to lock serial out to avoid garbage in file (web_interface->blockserial) = true; current_line =""; current_filename = filename; is_comment = false; String response; ESPCOM::println (F ("Uploading..."), PRINTER_PIPE); //Clear all serial ESPCOM::flush (DEFAULT_PRINTER_PIPE); purge_serial(); //besure nothing left again purge_serial(); command = "M28 " + current_filename; //send start upload //no correction allowed because it means reset numbering was failed if (sendLine2Serial(command, lineNb, NULL)) { CONFIG::wait(1200); //additional purge, in case it is slow to answer purge_serial(); web_interface->_upload_status= UPLOAD_STATUS_ONGOING; LOG("Creation Ok\r\n") } else { web_interface->_upload_status= UPLOAD_STATUS_FAILED; LOG("Creation failed\r\n"); } } //Upload write //************** if ( ( web_interface->_upload_status = UPLOAD_STATUS_ONGOING) && len) { LOG ("Writing to serial\n") for (int pos = 0; pos < len; pos++) { //parse full post data //feed watchdog CONFIG::wdtFeed(); //it is a comment if (data[pos] == ';') { LOG ("Comment\n") is_comment = true; } //it is an end line else if ( (data[pos] == 13) || (data[pos] == 10) ) { is_comment = false; //does line fit the buffer ? if (current_line.length() < 126) { //do we have something in buffer ? if (current_line.length() > 0 ) { lineNb++; if (!sendLine2Serial (current_line, lineNb, NULL) ) { LOG ("Error sending line\n") CloseSerialUpload (true, current_filename,lineNb); request->client()->abort(); return; } //reset line current_line = ""; //if comment line then reset } else { LOG ("Empy line\n") } } else { //error buffer overload LOG ("Error over buffer\n") lineNb++; CloseSerialUpload (true, current_filename, lineNb); request->client()->abort(); return; } } else if (!is_comment) { if (current_line.length() < 126) { current_line += char (data[pos]); //copy current char to buffer to send/resend } else { LOG ("Error over buffer\n") lineNb++; CloseSerialUpload (true, current_filename, lineNb); request->client()->abort(); return; } } } LOG ("Parsing Done\n") } else { LOG ("Nothing to write\n") } //Upload end //************** if (final) { LOG ("Final is reached\n") //if last part does not have '\n' if (current_line.length() > 0) { lineNb++; if (!sendLine2Serial (current_line, lineNb, NULL) ) { LOG ("Error sending buffer\n") lineNb++; CloseSerialUpload (true, current_filename, lineNb); request->client()->abort(); return; } } LOG ("Upload finished "); lineNb++; CloseSerialUpload (false, current_filename, lineNb); } LOG ("Exit fn\n") } //on event connect function void handle_onevent_connect(AsyncEventSourceClient *client) { if (!client->lastId()) { //Init active ID id_connection++; client->send(String(id_connection).c_str(), "InitID", id_connection, 1000); //Dispatch who is active ID web_interface->web_events.send( String(id_connection).c_str(),"ActiveID"); } } void handle_Websocket_Event(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) { //Handle WebSocket event } #endif