ESP3D/esp3d/asyncwebserver.cpp
Luc 02264b8da5 Update Readme
Apply style to code
2019-06-21 19:10:33 +02:00

1275 lines
52 KiB
C++

/*
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 <pgmspace.h>
#include "config.h"
#if defined(ASYNCWEBSERVER)
#include "webinterface.h"
#include "wificonf.h"
#include <WiFiClient.h>
#include <WiFiServer.h>
#include <WiFiUdp.h>
#include <StreamString.h>
#ifndef FS_NO_GLOBALS
#define FS_NO_GLOBALS
#endif
#include <FS.h>
#include <ESPAsyncWebServer.h>
#ifdef ARDUINO_ARCH_ESP8266
#include "ESP8266WiFi.h"
#include <ESPAsyncTCP.h>
#else //ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#include "SPIFFS.h"
#include "Update.h"
#endif
#include "GenLinkedList.h"
#include "command.h"
#include "espcom.h"
#ifdef SSDP_FEATURE
#ifdef ARDUINO_ARCH_ESP32
#include <ESP32SSDP.h>
#else
#include <ESP8266SSDP.h>
#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 = "<HTML>\n<HEAD>\n<title>Redirecting...</title> \n</HEAD>\n<BODY>\n<CENTER>Unknown page : $QUERY$- you will be redirected...\n<BR><BR>\nif not redirected, <a href='http://$WEB_ADDRESS$'>click here</a>\n<BR><BR>\n<PROGRESS name='prg' id='prg'></PROGRESS>\n\n<script>\nvar i = 0; \nvar x = document.getElementById(\"prg\"); \nx.max=5; \nvar interval=setInterval(function(){\ni=i+1; \nvar x = document.getElementById(\"prg\"); \nx.value=i; \nif (i>5) \n{\nclearInterval(interval);\nwindow.location.href='/';\n}\n},1000);\n</script>\n</CENTER>\n</BODY>\n</HTML>\n\n";
const uint8_t PAGE_CAPTIVE [] PROGMEM = "<HTML>\n<HEAD>\n<title>Captive Portal</title> \n</HEAD>\n<BODY>\n<CENTER>Captive Portal page : $QUERY$- you will be redirected...\n<BR><BR>\nif not redirected, <a href='http://$WEB_ADDRESS$'>click here</a>\n<BR><BR>\n<PROGRESS name='prg' id='prg'></PROGRESS>\n\n<script>\nvar i = 0; \nvar x = document.getElementById(\"prg\"); \nx.max=5; \nvar interval=setInterval(function(){\ni=i+1; \nvar x = document.getElementById(\"prg\"); \nx.value=i; \nif (i>5) \n{\nclearInterval(interval);\nwindow.location.href='/';\n}\n},1000);\n</script>\n</CENTER>\n</BODY>\n</HTML>\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 = "<?xml version=\"1.0\"?>"
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
"<specVersion>"
"<major>1</major>"
"<minor>0</minor>"
"</specVersion>"
"<URLBase>http://%s:%u/</URLBase>"
"<device>"
"<deviceType>upnp:rootdevice</deviceType>"
"<friendlyName>%s</friendlyName>"
"<presentationURL>/</presentationURL>"
"<serialNumber>%s</serialNumber>"
#ifdef ARDUINO_ARCH_ESP32
"<modelName>ESP32</modelName>"
#else
"<modelName>ESP8266</modelName>"
#endif
"<modelNumber>ESP3D</modelNumber>"
#ifdef ARDUINO_ARCH_ESP32
"<modelURL>http://espressif.com/en/products/hardware/esp-wroom-32/overview</modelURL>"
#else
"<modelURL>http://espressif.com/en/products/esp8266</modelURL>"
#endif
"<manufacturer>Espressif Systems</manufacturer>"
"<manufacturerURL>http://espressif.com</manufacturerURL>"
"<UDN>uuid:%s</UDN>"
"</device>"
"</root>\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_CANCELLED) ) {
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_CANCELLED;
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_CANCELLED;
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_CANCELLED;
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_CANCELLED;
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]<parameter>
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]<parameter>
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_CANCELLED) ) {
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_CANCELLED;
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