mirror of
https://git.mirrors.martin98.com/https://github.com/luc-github/ESP3D.git
synced 2025-10-12 15:21:29 +08:00

Add EEPROM version Check Update embedded page content and build process Move check sum ESP commands from ESP6XX to ESP5XX to let Notifications use ESP6XX Update command.txt with latest commands
548 lines
18 KiB
C++
548 lines
18 KiB
C++
/*
|
|
webinterface.cpp - ESP3D configuration 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"
|
|
#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>
|
|
#if defined (ASYNCWEBSERVER)
|
|
#include <ESPAsyncWebServer.h>
|
|
#endif
|
|
#ifdef ARDUINO_ARCH_ESP8266
|
|
#include "ESP8266WiFi.h"
|
|
#if defined (ASYNCWEBSERVER)
|
|
#include <ESPAsyncTCP.h>
|
|
#else
|
|
#include <ESP8266WebServer.h>
|
|
#endif
|
|
#else //ESP32
|
|
#include <WiFi.h>
|
|
#if defined (ASYNCWEBSERVER)
|
|
#include <AsyncTCP.h>
|
|
#else
|
|
#include <WebServer.h>
|
|
#endif
|
|
#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
|
|
|
|
#if defined(ASYNCWEBSERVER)
|
|
#include "asyncwebserver.h"
|
|
#else
|
|
#include "syncwebserver.h"
|
|
#endif
|
|
|
|
#define MAX_AUTH_IP 10
|
|
|
|
long id_connection = 0;
|
|
|
|
uint8_t Checksum(const char * line, uint16_t lineSize)
|
|
{
|
|
uint8_t checksum_val =0;
|
|
for (uint16_t i=0; i < lineSize; i++) checksum_val = checksum_val ^ ((uint8_t)line[i]);
|
|
return checksum_val;
|
|
}
|
|
|
|
String CheckSumLine(const char* line, uint32_t linenb){
|
|
String linechecksum = "N" + String(linenb)+ " " + line;
|
|
uint8_t crc = Checksum(linechecksum.c_str(), linechecksum.length());
|
|
linechecksum+="*"+String(crc);
|
|
return linechecksum;
|
|
}
|
|
|
|
bool purge_serial(){
|
|
uint32_t start = millis();
|
|
uint8_t buf [51];
|
|
ESPCOM::flush (DEFAULT_PRINTER_PIPE);
|
|
CONFIG::wait (5);
|
|
LOG("Purge Serial\r\n")
|
|
while (ESPCOM::available(DEFAULT_PRINTER_PIPE) > 0 ){
|
|
if ((millis() - start ) > 2000) {
|
|
LOG("Purge timeout\r\n")
|
|
return false;
|
|
}
|
|
size_t len = ESPCOM::readBytes (DEFAULT_PRINTER_PIPE, buf, 50);
|
|
buf[len] = '\0';
|
|
LOG("Purge: \r\n************\r\n")
|
|
LOG((const char *)buf)
|
|
LOG("\r\n************\r\n")
|
|
if ( ( CONFIG::GetFirmwareTarget() == REPETIER4DV) || (CONFIG::GetFirmwareTarget() == REPETIER) ) {
|
|
String s = (const char *)buf;
|
|
//repetier never stop sending data so no need to wait if have 'wait' or 'busy'
|
|
if((s.indexOf ("wait") > -1) || (s.indexOf ("busy") > -1))return true;
|
|
LOG("Purge interrupted\r\n")
|
|
}
|
|
CONFIG::wait (5);
|
|
}
|
|
CONFIG::wait (0);
|
|
LOG("Purge done\r\n")
|
|
return true;
|
|
}
|
|
|
|
size_t wait_for_data(uint32_t timeout){
|
|
uint32_t start = millis();
|
|
while ((ESPCOM::available(DEFAULT_PRINTER_PIPE) < 2) && ((millis()-start) < timeout))CONFIG::wait (10);
|
|
return ESPCOM::available(DEFAULT_PRINTER_PIPE);
|
|
}
|
|
|
|
uint32_t Get_lineNumber(String & response){
|
|
int32_t l = 0;
|
|
String sresend = "Resend:";
|
|
if ( CONFIG::GetFirmwareTarget() == SMOOTHIEWARE){
|
|
sresend = "rs N";
|
|
}
|
|
int pos = response.indexOf(sresend);
|
|
if (pos == -1 ) {
|
|
return -1;
|
|
}
|
|
pos+=sresend.length();
|
|
int pos2 = response.indexOf("\n", pos);
|
|
String snum = response.substring(pos, pos2);
|
|
//remove potential unwished char
|
|
snum.replace("\r", "");
|
|
l = snum.toInt();
|
|
LOG("Line requested is ")
|
|
LOG(String(l))
|
|
LOG("\r\n")
|
|
return l;
|
|
}
|
|
|
|
//function to send line to serial///////////////////////////////////////
|
|
//if newlinenb is NULL no auto correction of line number in case of resend
|
|
bool sendLine2Serial (String & line, int32_t linenb, int32_t * newlinenb)
|
|
{
|
|
LOG ("Send line ")
|
|
LOG (line )
|
|
LOG (" NB:")
|
|
LOG (String(linenb))
|
|
LOG ("\r\n")
|
|
String line2send;
|
|
String sok = "ok";
|
|
String sresend = "Resend:";
|
|
if (newlinenb) *newlinenb = linenb;
|
|
if ( CONFIG::GetFirmwareTarget() == SMOOTHIEWARE)sresend = "rs N";
|
|
if (linenb != -1) {
|
|
if ( ( CONFIG::GetFirmwareTarget() == REPETIER4DV) || (CONFIG::GetFirmwareTarget() == REPETIER) ) sok+=" " + String(linenb);
|
|
line2send = CheckSumLine(line.c_str(),linenb);
|
|
} else line2send = line;
|
|
//purge serial as nothing is supposed to interfere with upload
|
|
purge_serial();
|
|
LOG ("Send line ")
|
|
LOG (line2send )
|
|
LOG ("\r\n")
|
|
//send line
|
|
ESPCOM::println (line2send, DEFAULT_PRINTER_PIPE);
|
|
ESPCOM::flush(DEFAULT_PRINTER_PIPE);
|
|
//check answer
|
|
if (wait_for_data(2000) > 0 ) {
|
|
bool done = false;
|
|
uint32_t timeout = millis();
|
|
uint8_t count = 0;
|
|
//got data check content
|
|
String response ;
|
|
while (!done) {
|
|
size_t len = ESPCOM::available(DEFAULT_PRINTER_PIPE);
|
|
//get size of buffer
|
|
if (len > 0) {
|
|
uint8_t * sbuf = (uint8_t *)malloc(len+1);
|
|
if(!sbuf){
|
|
return false;
|
|
}
|
|
//read buffer
|
|
ESPCOM::readBytes (DEFAULT_PRINTER_PIPE, sbuf, len);
|
|
//convert buffer to zero end array
|
|
sbuf[len] = '\0';
|
|
//use string because easier to handle and allow to re-assemble cutted answer
|
|
response += (const char*) sbuf;
|
|
LOG ("Response:\r\n************\r\n")
|
|
LOG (response)
|
|
LOG ("\r\n************\r\n")
|
|
//in that case there is no way to know what is the right number to use and so send should be failed
|
|
if (( ( CONFIG::GetFirmwareTarget() == REPETIER4DV) || (CONFIG::GetFirmwareTarget() == REPETIER) ) && (response.indexOf ("skip") != -1)) {
|
|
LOG ("Wrong line requested\r\n")
|
|
count = 5;
|
|
}
|
|
//it is resend ?
|
|
int pos = response.indexOf (sresend);
|
|
//be sure we get full line to be able to process properly
|
|
if (( pos > -1) && (response.lastIndexOf("\n") > pos)){
|
|
LOG ("Resend detected\r\n")
|
|
uint32_t line_number = Get_lineNumber(response);
|
|
//this part is only if have newlinenb variable
|
|
if (newlinenb != nullptr){
|
|
*newlinenb = line_number;
|
|
free(sbuf);
|
|
//no need newlinenb in this one in theory, but just in case...
|
|
return sendLine2Serial (line, line_number, newlinenb);
|
|
} else {
|
|
//the line requested is not the current one so we stop
|
|
if (line_number !=linenb) {
|
|
LOG ("Wrong line requested\r\n")
|
|
count = 5;
|
|
}
|
|
}
|
|
count++;
|
|
if (count > 5) {
|
|
free(sbuf);
|
|
LOG ("Exit too many resend or wrong line\r\n")
|
|
return false;
|
|
}
|
|
purge_serial();
|
|
LOG ("Resend ")
|
|
LOG (String(count))
|
|
LOG ("\r\n")
|
|
response="";
|
|
ESPCOM::println (line2send, DEFAULT_PRINTER_PIPE);
|
|
ESPCOM::flush (DEFAULT_PRINTER_PIPE);
|
|
wait_for_data(1000);
|
|
timeout = millis();
|
|
|
|
} else {
|
|
if ( (response.indexOf (sok) > -1) ) { //we have ok so it is done
|
|
free(sbuf);
|
|
LOG ("Got ok\r\n")
|
|
purge_serial();
|
|
return true;
|
|
}
|
|
}
|
|
free(sbuf);
|
|
}
|
|
//no answer or over buffer exit
|
|
if ( (millis() - timeout > 2000) || (response.length() >200)){
|
|
LOG ("Time out\r\n")
|
|
done = true;
|
|
}
|
|
CONFIG::wait (5);
|
|
}
|
|
}
|
|
LOG ("Send line error\r\n")
|
|
return false;
|
|
}
|
|
|
|
//send M29 / M30 command to close file on SD////////////////////////////
|
|
void CloseSerialUpload (bool iserror, String & filename , int32_t linenb)
|
|
{
|
|
purge_serial();
|
|
String command = "M29";
|
|
purge_serial();
|
|
if (!sendLine2Serial (command,linenb, &linenb)) {
|
|
wait_for_data(2000);
|
|
if (!sendLine2Serial (command,linenb, &linenb)) {
|
|
if ((CONFIG::GetFirmwareTarget() == REPETIER4DV) || (CONFIG::GetFirmwareTarget() == REPETIER) ) {
|
|
sendLine2Serial (command,-1, NULL);
|
|
}
|
|
}
|
|
}
|
|
if (iserror) {
|
|
String cmdfilename = "M30 " + filename;
|
|
sendLine2Serial (cmdfilename,-1, NULL);
|
|
ESPCOM::println (F ("SD upload failed"), PRINTER_PIPE);
|
|
ESPCOM::flush (DEFAULT_PRINTER_PIPE);
|
|
web_interface->_upload_status = UPLOAD_STATUS_FAILED;
|
|
} else {
|
|
ESPCOM::println (F ("SD upload done"), PRINTER_PIPE);
|
|
ESPCOM::flush (DEFAULT_PRINTER_PIPE);
|
|
web_interface->_upload_status = UPLOAD_STATUS_SUCCESSFUL;
|
|
}
|
|
//lets give time to FW to proceed
|
|
wait_for_data(2000);
|
|
purge_serial();
|
|
web_interface->blockserial = false;
|
|
}
|
|
|
|
|
|
//constructor
|
|
WEBINTERFACE_CLASS::WEBINTERFACE_CLASS (int port) : web_server (port)
|
|
#if defined(ASYNCWEBSERVER)
|
|
, web_events("/events")
|
|
#ifdef WS_DATA_FEATURE
|
|
, web_socket("/ws")
|
|
#endif
|
|
#endif
|
|
{
|
|
//that handle "/" and default index.html.gz
|
|
#if defined(ASYNCWEBSERVER)
|
|
//trick to catch command line on "/" before file being processed
|
|
web_server.serveStatic ("/", SPIFFS, "/").setDefaultFile ("index.html").setFilter (filterOnRoot);
|
|
web_server.serveStatic ("/", SPIFFS, "/Nowhere");
|
|
//events functions
|
|
web_events.onConnect(handle_onevent_connect);
|
|
//events management
|
|
web_server.addHandler(&web_events);
|
|
#ifdef WS_DATA_FEATURE
|
|
//Websocket function
|
|
web_socket.onEvent(handle_Websocket_Event);
|
|
//Websocket management
|
|
web_server.addHandler(&web_socket);
|
|
#endif
|
|
#else
|
|
web_server.on("/",HTTP_ANY, handle_web_interface_root);
|
|
#endif
|
|
//need to be there even no authentication to say to UI no authentication
|
|
web_server.on("/login", HTTP_ANY, handle_login);
|
|
#ifdef SSDP_FEATURE
|
|
web_server.on ("/description.xml", HTTP_GET, handle_SSDP);
|
|
#endif
|
|
#ifdef CAPTIVE_PORTAL_FEATURE
|
|
#if defined(ASYNCWEBSERVER)
|
|
web_server.on ("/generate_204", HTTP_ANY, [] (AsyncWebServerRequest * request) {
|
|
request->redirect ("/");
|
|
});
|
|
web_server.on ("/gconnectivitycheck.gstatic.com", HTTP_ANY, [] (AsyncWebServerRequest * request) {
|
|
request->redirect ("/");
|
|
});
|
|
//do not forget the / at the end
|
|
web_server.on ("/fwlink/", HTTP_ANY, [] (AsyncWebServerRequest * request) {
|
|
request->redirect ("/");
|
|
});
|
|
#else
|
|
web_server.on("/generate_204",HTTP_ANY, handle_web_interface_root);
|
|
web_server.on("/gconnectivitycheck.gstatic.com",HTTP_ANY, handle_web_interface_root);
|
|
//do not forget the / at the end
|
|
web_server.on("/fwlink/",HTTP_ANY, handle_web_interface_root);
|
|
#endif
|
|
#endif
|
|
//SPIFFS
|
|
web_server.on ("/files", HTTP_ANY, handleFileList, SPIFFSFileupload);
|
|
#ifdef WEB_UPDATE_FEATURE
|
|
web_server.on ("/updatefw", HTTP_ANY, handleUpdate, WebUpdateUpload);
|
|
#endif
|
|
//Page not found handler
|
|
web_server.onNotFound ( handle_not_found);
|
|
//web commands
|
|
web_server.on ("/command", HTTP_ANY, handle_web_command);
|
|
web_server.on ("/command_silent", HTTP_ANY, handle_web_command_silent);
|
|
//Serial SD management
|
|
web_server.on ("/upload_serial", HTTP_ANY, handle_serial_SDFileList, SDFile_serial_upload);
|
|
|
|
blockserial = false;
|
|
restartmodule = false;
|
|
_head = NULL;
|
|
_nb_ip = 0;
|
|
_upload_status = UPLOAD_STATUS_NONE;
|
|
}
|
|
//Destructor
|
|
WEBINTERFACE_CLASS::~WEBINTERFACE_CLASS()
|
|
{
|
|
while (_head) {
|
|
auth_ip * current = _head;
|
|
_head = _head->_next;
|
|
delete current;
|
|
}
|
|
_nb_ip = 0;
|
|
}
|
|
//check authentification
|
|
level_authenticate_type WEBINTERFACE_CLASS::is_authenticated()
|
|
{
|
|
#ifdef AUTHENTICATION_FEATURE
|
|
if (web_server.hasHeader ("Cookie") ) {
|
|
String cookie = web_server.header ("Cookie");
|
|
int pos = cookie.indexOf ("ESPSESSIONID=");
|
|
if (pos != -1) {
|
|
int pos2 = cookie.indexOf (";", pos);
|
|
String sessionID = cookie.substring (pos + strlen ("ESPSESSIONID="), pos2);
|
|
IPAddress ip = web_server.client().remoteIP();
|
|
//check if cookie can be reset and clean table in same time
|
|
return ResetAuthIP (ip, sessionID.c_str() );
|
|
}
|
|
}
|
|
return LEVEL_GUEST;
|
|
#else
|
|
return LEVEL_ADMIN;
|
|
#endif
|
|
}
|
|
|
|
#ifdef AUTHENTICATION_FEATURE
|
|
//add the information in the linked list if possible
|
|
bool WEBINTERFACE_CLASS::AddAuthIP (auth_ip * item)
|
|
{
|
|
if (_nb_ip > MAX_AUTH_IP) {
|
|
return false;
|
|
}
|
|
item->_next = _head;
|
|
_head = item;
|
|
_nb_ip++;
|
|
return true;
|
|
}
|
|
|
|
//Session ID based on IP and time using 16 char
|
|
char * WEBINTERFACE_CLASS::create_session_ID()
|
|
{
|
|
static char sessionID[17];
|
|
//reset SESSIONID
|
|
for (int i = 0; i < 17; i++) {
|
|
sessionID[i] = '\0';
|
|
}
|
|
//get time
|
|
uint32_t now = millis();
|
|
//get remote IP
|
|
IPAddress remoteIP = web_server.client().remoteIP();
|
|
//generate SESSIONID
|
|
if (0 > sprintf (sessionID, "%02X%02X%02X%02X%02X%02X%02X%02X", remoteIP[0], remoteIP[1], remoteIP[2], remoteIP[3], (uint8_t) ( (now >> 0) & 0xff), (uint8_t) ( (now >> 8) & 0xff), (uint8_t) ( (now >> 16) & 0xff), (uint8_t) ( (now >> 24) & 0xff) ) ) {
|
|
strcpy (sessionID, "NONE");
|
|
}
|
|
return sessionID;
|
|
}
|
|
|
|
|
|
|
|
bool WEBINTERFACE_CLASS::ClearAuthIP (IPAddress ip, const char * sessionID)
|
|
{
|
|
auth_ip * current = _head;
|
|
auth_ip * previous = NULL;
|
|
bool done = false;
|
|
while (current) {
|
|
if ( (ip == current->ip) && (strcmp (sessionID, current->sessionID) == 0) ) {
|
|
//remove
|
|
done = true;
|
|
if (current == _head) {
|
|
_head = current->_next;
|
|
_nb_ip--;
|
|
delete current;
|
|
current = _head;
|
|
} else {
|
|
previous->_next = current->_next;
|
|
_nb_ip--;
|
|
delete current;
|
|
current = previous->_next;
|
|
}
|
|
} else {
|
|
previous = current;
|
|
current = current->_next;
|
|
}
|
|
}
|
|
return done;
|
|
}
|
|
|
|
//Get info
|
|
auth_ip * WEBINTERFACE_CLASS::GetAuth (IPAddress ip, const char * sessionID)
|
|
{
|
|
auth_ip * current = _head;
|
|
auth_ip * previous = NULL;
|
|
//get time
|
|
//uint32_t now = millis();
|
|
while (current) {
|
|
if (ip == current->ip) {
|
|
if (strcmp (sessionID, current->sessionID) == 0) {
|
|
//found
|
|
return current;
|
|
}
|
|
}
|
|
previous = current;
|
|
current = current->_next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//Review all IP to reset timers
|
|
level_authenticate_type WEBINTERFACE_CLASS::ResetAuthIP (IPAddress ip, const char * sessionID)
|
|
{
|
|
auth_ip * current = _head;
|
|
auth_ip * previous = NULL;
|
|
//get time
|
|
//uint32_t now = millis();
|
|
while (current) {
|
|
if ( (millis() - current->last_time) > 180000) {
|
|
//remove
|
|
if (current == _head) {
|
|
_head = current->_next;
|
|
_nb_ip--;
|
|
delete current;
|
|
current = _head;
|
|
} else {
|
|
previous->_next = current->_next;
|
|
_nb_ip--;
|
|
delete current;
|
|
current = previous->_next;
|
|
}
|
|
} else {
|
|
if (ip == current->ip) {
|
|
if (strcmp (sessionID, current->sessionID) == 0) {
|
|
//reset time
|
|
current->last_time = millis();
|
|
return (level_authenticate_type) current->level;
|
|
}
|
|
}
|
|
previous = current;
|
|
current = current->_next;
|
|
}
|
|
}
|
|
return LEVEL_GUEST;
|
|
}
|
|
#endif
|
|
|
|
//Check what is the content tye according extension file
|
|
String WEBINTERFACE_CLASS::getContentType (String filename)
|
|
{
|
|
if (filename.endsWith (".htm") ) {
|
|
return "text/html";
|
|
} else if (filename.endsWith (".html") ) {
|
|
return "text/html";
|
|
} else if (filename.endsWith (".css") ) {
|
|
return "text/css";
|
|
} else if (filename.endsWith (".js") ) {
|
|
return "application/javascript";
|
|
} else if (filename.endsWith (".png") ) {
|
|
return "image/png";
|
|
} else if (filename.endsWith (".gif") ) {
|
|
return "image/gif";
|
|
} else if (filename.endsWith (".jpeg") ) {
|
|
return "image/jpeg";
|
|
} else if (filename.endsWith (".jpg") ) {
|
|
return "image/jpeg";
|
|
} else if (filename.endsWith (".ico") ) {
|
|
return "image/x-icon";
|
|
} else if (filename.endsWith (".xml") ) {
|
|
return "text/xml";
|
|
} else if (filename.endsWith (".pdf") ) {
|
|
return "application/x-pdf";
|
|
} else if (filename.endsWith (".zip") ) {
|
|
return "application/x-zip";
|
|
} else if (filename.endsWith (".gz") ) {
|
|
return "application/x-gzip";
|
|
} else if (filename.endsWith (".txt") ) {
|
|
return "text/plain";
|
|
}
|
|
return "application/octet-stream";
|
|
}
|
|
|
|
|
|
WEBINTERFACE_CLASS * web_interface;
|