
Tidied up axis homing tracking. When checking endstops, wait for move to complete before accepting further moves, otherwise subsequent moves use the wrong coordinates. Temperatures and Z probes are now monitored continuously using a tick interrupt to kick off ADC conversions. ADC is now run in 12-bit mode. Thermistor readings are passed through averaging filters. Thermistors are monitored for overheat conditions and bad readings in the tick ISR and the appropriate heater is turned off (useful because the main loop sometimes gets suspended while trying to do USB communication). Use watchdog timer to monitor the tick interrupt - needs patch to Arduino Due core library. Add facility to test watchdog timer (M111 S1001). Added an error status word to record that errors have occurred (e.g. over-temperature). M111 command changes so that S0 turns debug off, S1 turns debug on, S2 reports free memory - also now reports the type of the last restart and the error status word. Fixed problem whereby M111 debug reports were not sent to the web interface. Implemented M999 command, which resets the Duet. Removed an unused variable. Changed some more "char*" to "const char*". Changed extruder PID parameters and added more explanation for them.
983 lines
24 KiB
C++
983 lines
24 KiB
C++
/****************************************************************************************************
|
|
|
|
RepRapFirmware - Webserver
|
|
|
|
This class serves a single-page web applications to the attached network. This page forms the user's
|
|
interface with the RepRap machine. This software interprests returned values from the page and uses it
|
|
to Generate G Codes, which it sends to the RepRap. It also collects values from the RepRap like
|
|
temperature and uses those to construct the web page.
|
|
|
|
The page itself - reprap.htm - uses Knockout.js and Jquery.js. See:
|
|
|
|
http://knockoutjs.com/
|
|
|
|
http://jquery.com/
|
|
|
|
-----------------------------------------------------------------------------------------------------
|
|
|
|
Version 0.2
|
|
|
|
10 May 2013
|
|
|
|
Adrian Bowyer
|
|
RepRap Professional Ltd
|
|
http://reprappro.com
|
|
|
|
Licence: GPL
|
|
|
|
****************************************************************************************************/
|
|
|
|
#include "RepRapFirmware.h"
|
|
|
|
//***************************************************************************************************
|
|
|
|
bool Webserver::MatchBoundary(char c)
|
|
{
|
|
if(!postBoundary[0])
|
|
return false;
|
|
|
|
if(c == postBoundary[boundaryCount])
|
|
{
|
|
boundaryCount++;
|
|
if(!postBoundary[boundaryCount])
|
|
{
|
|
boundaryCount = 0;
|
|
return true;
|
|
}
|
|
} else
|
|
{
|
|
for(int i = 0; i < boundaryCount; i++)
|
|
postFile->Write(postBoundary[i]);
|
|
postFile->Write(c);
|
|
boundaryCount = 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
//****************************************************************************************************
|
|
|
|
// Feeding G Codes to the GCodes class
|
|
|
|
bool Webserver::GCodeAvailable()
|
|
{
|
|
return gcodeReadIndex != gcodeWriteIndex;
|
|
}
|
|
|
|
byte Webserver::ReadGCode()
|
|
{
|
|
byte c;
|
|
if (gcodeReadIndex == gcodeWriteIndex)
|
|
{
|
|
c = 0;
|
|
}
|
|
else
|
|
{
|
|
c = gcodeBuffer[gcodeReadIndex];
|
|
gcodeReadIndex = (gcodeReadIndex + 1u) % gcodeBufLength;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
// Process a received string of gcodes
|
|
void Webserver::LoadGcodeBuffer(const char* gc, bool convertWeb)
|
|
{
|
|
char gcodeTempBuf[GCODE_LENGTH];
|
|
uint16_t gtp = 0;
|
|
bool inComment = false;
|
|
for (;;)
|
|
{
|
|
char c = *gc++;
|
|
if(c == 0)
|
|
{
|
|
gcodeTempBuf[gtp] = 0;
|
|
ProcessGcode(gcodeTempBuf);
|
|
return;
|
|
}
|
|
|
|
if(c == '+' && convertWeb)
|
|
{
|
|
c = ' ';
|
|
}
|
|
else if(c == '%' && convertWeb)
|
|
{
|
|
c = *gc++;
|
|
if(c != 0)
|
|
{
|
|
unsigned char uc;
|
|
if(isalpha(c))
|
|
{
|
|
uc = 16*(c - 'A' + 10);
|
|
}
|
|
else
|
|
{
|
|
uc = 16*(c - '0');
|
|
}
|
|
c = *gc++;
|
|
if(c != 0)
|
|
{
|
|
if(isalpha(c))
|
|
{
|
|
uc += c - 'A' + 10;
|
|
}
|
|
else
|
|
{
|
|
uc += c - '0';
|
|
}
|
|
c = uc;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (c == '\n')
|
|
{
|
|
gcodeTempBuf[gtp] = 0;
|
|
ProcessGcode(gcodeTempBuf);
|
|
gtp = 0;
|
|
inComment = false;
|
|
}
|
|
else
|
|
{
|
|
if (c == ';')
|
|
{
|
|
inComment = true;
|
|
}
|
|
|
|
if(gtp == ARRAY_SIZE(gcodeTempBuf) - 1)
|
|
{
|
|
// gcode is too long, we haven't room for another character and a null
|
|
if (c != ' ' && !inComment)
|
|
{
|
|
platform->Message(HOST_MESSAGE, "Webserver: GCode local buffer overflow.\n");
|
|
HandleReply("Webserver: GCode local buffer overflow", true);
|
|
return;
|
|
}
|
|
// else we're either in a comment or the current character is a space.
|
|
// If we're in a comment, we'll silently truncate it.
|
|
// If the current character is a space, we'll wait until we see a non-comment character before reporting an error,
|
|
// in case the next character is end-of-line or the start of a comment.
|
|
}
|
|
else
|
|
{
|
|
gcodeTempBuf[gtp++] = c;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process a null-terminated gcode
|
|
// We intercept four G/M Codes so we can deal with file manipulation and emergencies. That
|
|
// way things don't get out of sync, and - as a file name can contain
|
|
// a valid G code (!) - confusion is avoided.
|
|
|
|
void Webserver::ProcessGcode(const char* gc)
|
|
{
|
|
int8_t specialAction = 0;
|
|
if(StringStartsWith(gc, "M30 "))
|
|
{
|
|
specialAction = 1;
|
|
}
|
|
else if(StringStartsWith(gc, "M23 "))
|
|
{
|
|
specialAction = 2;
|
|
}
|
|
else if(StringStartsWith(gc, "M112") && !isdigit(gc[4]))
|
|
{
|
|
specialAction = 3;
|
|
}
|
|
else if(StringStartsWith(gc, "M503") && !isdigit(gc[4]))
|
|
{
|
|
specialAction = 4;
|
|
}
|
|
|
|
if(specialAction != 0) // Delete or print a file?
|
|
{
|
|
switch (specialAction)
|
|
{
|
|
case 1: // Delete
|
|
reprap.GetGCodes()->DeleteFile(&gc[4]);
|
|
break;
|
|
|
|
case 2: // print
|
|
reprap.GetGCodes()->QueueFileToPrint(&gc[4]);
|
|
break;
|
|
|
|
case 3:
|
|
reprap.EmergencyStop();
|
|
break;
|
|
|
|
case 4:
|
|
{
|
|
FileStore *configFile = platform->GetFileStore(platform->GetSysDir(), platform->GetConfigFile(), false);
|
|
if(configFile == NULL)
|
|
{
|
|
HandleReply("Configuration file not found", true);
|
|
}
|
|
else
|
|
{
|
|
char c;
|
|
size_t i = 0;
|
|
while(i < STRING_LENGTH && configFile->Read(c))
|
|
{
|
|
gcodeReply[i++] = c;
|
|
}
|
|
configFile->Close();
|
|
gcodeReply[i] = 0;
|
|
++seq;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Copy the gcode to the buffer
|
|
size_t len = strlen(gc) + 1; // number of characters to copy
|
|
if (len > GetGcodeBufferSpace())
|
|
{
|
|
platform->Message(HOST_MESSAGE, "Webserver: GCode buffer overflow.\n");
|
|
HandleReply("Webserver: GCode buffer overflow", true);
|
|
}
|
|
else
|
|
{
|
|
size_t remaining = gcodeBufLength - gcodeWriteIndex;
|
|
if (len <= remaining)
|
|
{
|
|
memcpy(&gcodeBuffer[gcodeWriteIndex], gc, len);
|
|
}
|
|
else
|
|
{
|
|
memcpy(&gcodeBuffer[gcodeWriteIndex], gc, remaining);
|
|
memcpy(gcodeBuffer, gc + remaining, len - remaining);
|
|
}
|
|
gcodeWriteIndex = (gcodeWriteIndex + len) % gcodeBufLength;
|
|
}
|
|
}
|
|
}
|
|
|
|
//********************************************************************************************
|
|
|
|
// Communications with the client
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
|
|
// Output to the client
|
|
|
|
void Webserver::CloseClient()
|
|
{
|
|
writing = false;
|
|
//inPHPFile = false;
|
|
//InitialisePHP();
|
|
clientCloseTime = platform->Time();
|
|
needToCloseClient = true;
|
|
}
|
|
|
|
|
|
void Webserver::SendFile(const char* nameOfFileToSend)
|
|
{
|
|
char sLen[SHORT_STRING_LENGTH];
|
|
bool zip = false;
|
|
|
|
if(StringStartsWith(nameOfFileToSend, KO_START))
|
|
GetJsonResponse(&nameOfFileToSend[KO_FIRST]);
|
|
|
|
if(jsonPointer < 0)
|
|
{
|
|
fileBeingSent = platform->GetFileStore(platform->GetWebDir(), nameOfFileToSend, false);
|
|
if(fileBeingSent == NULL)
|
|
{
|
|
nameOfFileToSend = FOUR04_FILE;
|
|
fileBeingSent = platform->GetFileStore(platform->GetWebDir(), nameOfFileToSend, false);
|
|
}
|
|
writing = (fileBeingSent != NULL);
|
|
}
|
|
|
|
Network *net = platform->GetNetwork();
|
|
net->Write("HTTP/1.1 200 OK\n");
|
|
net->Write("Content-Type: ");
|
|
|
|
if(StringEndsWith(nameOfFileToSend, ".png"))
|
|
net->Write("image/png\n");
|
|
else if(StringEndsWith(nameOfFileToSend, ".ico"))
|
|
net->Write("image/x-icon\n");
|
|
else if (jsonPointer >= 0)
|
|
net->Write("application/json\n");
|
|
else if(StringEndsWith(nameOfFileToSend, ".js"))
|
|
net->Write("application/javascript\n");
|
|
else if(StringEndsWith(nameOfFileToSend, ".css"))
|
|
net->Write("text/css\n");
|
|
else if(StringEndsWith(nameOfFileToSend, ".htm") || StringEndsWith(nameOfFileToSend, ".html"))
|
|
net->Write("text/html\n");
|
|
else if(StringEndsWith(nameOfFileToSend, ".zip"))
|
|
{
|
|
net->Write("application/zip\n");
|
|
zip = true;
|
|
} else
|
|
net->Write("application/octet-stream\n");
|
|
|
|
if (jsonPointer >=0)
|
|
{
|
|
net->Write("Content-Length: ");
|
|
snprintf(sLen, SHORT_STRING_LENGTH, "%d", strlen(jsonResponse));
|
|
net->Write(sLen);
|
|
net->Write("\n");
|
|
}
|
|
|
|
if(zip)
|
|
{
|
|
net->Write("Content-Encoding: gzip\n");
|
|
net->Write("Content-Length: ");
|
|
snprintf(sLen, SHORT_STRING_LENGTH, "%llu", fileBeingSent->Length());
|
|
net->Write(sLen);
|
|
net->Write("\n");
|
|
}
|
|
|
|
net->Write("Connection: close\n");
|
|
net->Write('\n');
|
|
}
|
|
|
|
// Write a number of bytes if we can, returning true if we wrote anything
|
|
bool Webserver::WriteBytes()
|
|
{
|
|
Network *net = platform->GetNetwork();
|
|
uint8_t i;
|
|
for (i = 0; i < 50 && writing && net->CanWrite(); )
|
|
{
|
|
++i;
|
|
if(jsonPointer >= 0)
|
|
{
|
|
if(jsonResponse[jsonPointer])
|
|
{
|
|
net->Write(jsonResponse[jsonPointer++]);
|
|
}
|
|
else
|
|
{
|
|
jsonPointer = -1;
|
|
jsonResponse[0] = 0;
|
|
CloseClient();
|
|
break;
|
|
}
|
|
} else
|
|
{
|
|
char b;
|
|
if(fileBeingSent->Read(b))
|
|
{
|
|
net->Write(b);
|
|
}
|
|
else
|
|
{
|
|
fileBeingSent->Close();
|
|
CloseClient();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return i != 0;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------------------
|
|
|
|
// Input from the client
|
|
|
|
void Webserver::CheckPassword()
|
|
{
|
|
gotPassword = StringEndsWith(clientQualifier, password);
|
|
}
|
|
|
|
void Webserver::JsonReport(bool ok, const char* request)
|
|
{
|
|
if(ok)
|
|
{
|
|
if(reprap.Debug())
|
|
{
|
|
platform->Message(HOST_MESSAGE, "JSON response: ");
|
|
platform->Message(HOST_MESSAGE, jsonResponse);
|
|
platform->Message(HOST_MESSAGE, " queued\n");
|
|
}
|
|
} else
|
|
{
|
|
platform->Message(HOST_MESSAGE, "KnockOut request: ");
|
|
platform->Message(HOST_MESSAGE, request);
|
|
platform->Message(HOST_MESSAGE, " not recognised\n");
|
|
clientRequest[0] = 0;
|
|
}
|
|
}
|
|
|
|
void Webserver::GetJsonResponse(const char* request)
|
|
{
|
|
jsonPointer = 0;
|
|
writing = true;
|
|
|
|
if(StringStartsWith(request, "poll"))
|
|
{
|
|
strncpy(jsonResponse, "{\"poll\":[", STRING_LENGTH);
|
|
if(reprap.GetGCodes()->PrintingAFile())
|
|
strncat(jsonResponse, "\"P\",", STRING_LENGTH); // Printing
|
|
else
|
|
strncat(jsonResponse, "\"I\",", STRING_LENGTH); // Idle
|
|
for(int8_t heater = 0; heater < HEATERS; heater++)
|
|
{
|
|
strncat(jsonResponse, "\"", STRING_LENGTH);
|
|
strncat(jsonResponse, ftoa(0, reprap.GetHeat()->GetTemperature(heater), 1), STRING_LENGTH);
|
|
strncat(jsonResponse, "\",", STRING_LENGTH);
|
|
}
|
|
float liveCoordinates[DRIVES+1];
|
|
reprap.GetMove()->LiveCoordinates(liveCoordinates);
|
|
for(int8_t drive = 0; drive < AXES; drive++)
|
|
{
|
|
strncat(jsonResponse, "\"", STRING_LENGTH);
|
|
strncat(jsonResponse, ftoa(0, liveCoordinates[drive], 2), STRING_LENGTH);
|
|
strncat(jsonResponse, "\",", STRING_LENGTH);
|
|
}
|
|
|
|
// FIXME: should loop through all Es
|
|
|
|
strncat(jsonResponse, "\"", STRING_LENGTH);
|
|
strncat(jsonResponse, ftoa(0, liveCoordinates[AXES], 4), STRING_LENGTH);
|
|
strncat(jsonResponse, "\"", STRING_LENGTH);
|
|
strncat(jsonResponse, "]", STRING_LENGTH);
|
|
|
|
// Send the Z probe value
|
|
char scratch[SHORT_STRING_LENGTH+1];
|
|
scratch[SHORT_STRING_LENGTH] = 0;
|
|
int v0 = platform->ZProbe();
|
|
int v1, v2;
|
|
switch(platform->GetZProbeSecondaryValues(v1, v2))
|
|
{
|
|
case 1:
|
|
snprintf(scratch, SHORT_STRING_LENGTH, ",\"probe\":\"%d (%d)\"", v0, v1);
|
|
break;
|
|
case 2:
|
|
snprintf(scratch, SHORT_STRING_LENGTH, ",\"probe\":\"%d (%d, %d)\"", v0, v1, v2);
|
|
break;
|
|
default:
|
|
snprintf(scratch, SHORT_STRING_LENGTH, ",\"probe\":\"%d\"", v0);
|
|
break;
|
|
}
|
|
strncat(jsonResponse, scratch, STRING_LENGTH);
|
|
|
|
// Send the amount of buffer space available for gcodes
|
|
snprintf(scratch, SHORT_STRING_LENGTH, ",\"buff\":%u", GetReportedGcodeBufferSpace());
|
|
strncat(jsonResponse, scratch, STRING_LENGTH);
|
|
|
|
// Send the home state. To keep the messages short, we send 1 for homed and 0 for not homed, instead of true and false.
|
|
strncat(jsonResponse, ",\"hx\":", STRING_LENGTH);
|
|
strncat(jsonResponse, (reprap.GetGCodes()->GetAxisIsHomed(0)) ? "1" : "0", STRING_LENGTH);
|
|
strncat(jsonResponse, ",\"hy\":", STRING_LENGTH);
|
|
strncat(jsonResponse, (reprap.GetGCodes()->GetAxisIsHomed(1)) ? "1" : "0", STRING_LENGTH);
|
|
strncat(jsonResponse, ",\"hz\":", STRING_LENGTH);
|
|
strncat(jsonResponse, (reprap.GetGCodes()->GetAxisIsHomed(2)) ? "1" : "0", STRING_LENGTH);
|
|
|
|
// Send the response sequence number
|
|
strncat(jsonResponse, ",\"seq\":", STRING_LENGTH);
|
|
snprintf(scratch, SHORT_STRING_LENGTH, "%u", (unsigned int)seq);
|
|
strncat(jsonResponse, scratch, STRING_LENGTH);
|
|
|
|
// Send the response to the last command. Do this last because it is long and may need to be truncated.
|
|
strncat(jsonResponse, ",\"resp\":\"", STRING_LENGTH);
|
|
size_t jp = strnlen(jsonResponse, STRING_LENGTH);
|
|
const char *p = gcodeReply;
|
|
while (*p != 0 && jp < STRING_LENGTH - 2) // leave room for the final '"}'
|
|
{
|
|
char c = *p++;
|
|
char esc;
|
|
switch(c)
|
|
{
|
|
case '\r':
|
|
esc = 'r'; break;
|
|
case '\n':
|
|
esc = 'n'; break;
|
|
case '\t':
|
|
esc = 't'; break;
|
|
case '"':
|
|
esc = '"'; break;
|
|
case '\\':
|
|
esc = '\\'; break;
|
|
default:
|
|
esc = 0; break;
|
|
}
|
|
if (esc)
|
|
{
|
|
if (jp == STRING_LENGTH - 3)
|
|
break;
|
|
jsonResponse[jp++] = '\\';
|
|
jsonResponse[jp++] = esc;
|
|
}
|
|
else
|
|
{
|
|
jsonResponse[jp++] = c;
|
|
}
|
|
}
|
|
strncat(jsonResponse, "\"}", STRING_LENGTH);
|
|
|
|
jsonResponse[STRING_LENGTH] = 0;
|
|
JsonReport(true, request);
|
|
return;
|
|
}
|
|
|
|
if(StringStartsWith(request, "gcode"))
|
|
{
|
|
LoadGcodeBuffer(&clientQualifier[6], true);
|
|
char scratch[SHORT_STRING_LENGTH+1];
|
|
scratch[SHORT_STRING_LENGTH] = 0;
|
|
snprintf(scratch, SHORT_STRING_LENGTH, "{\"buff\":%u}", GetReportedGcodeBufferSpace());
|
|
strncat(jsonResponse, scratch, STRING_LENGTH);
|
|
JsonReport(true, request);
|
|
return;
|
|
}
|
|
|
|
if(StringStartsWith(request, "files"))
|
|
{
|
|
const char* fileList = platform->GetMassStorage()->FileList(platform->GetGCodeDir(), false);
|
|
strncpy(jsonResponse, "{\"files\":[", STRING_LENGTH);
|
|
strncat(jsonResponse, fileList, STRING_LENGTH);
|
|
strncat(jsonResponse, "]}", STRING_LENGTH);
|
|
JsonReport(true, request);
|
|
return;
|
|
}
|
|
|
|
if(StringStartsWith(request, "name"))
|
|
{
|
|
strncpy(jsonResponse, "{\"myName\":\"", STRING_LENGTH);
|
|
strncat(jsonResponse, myName, STRING_LENGTH);
|
|
strncat(jsonResponse, "\"}", STRING_LENGTH);
|
|
JsonReport(true, request);
|
|
return;
|
|
}
|
|
|
|
if(StringStartsWith(request, "password"))
|
|
{
|
|
CheckPassword();
|
|
strncpy(jsonResponse, "{\"password\":\"", STRING_LENGTH);
|
|
if(gotPassword)
|
|
strncat(jsonResponse, "right", STRING_LENGTH);
|
|
else
|
|
strncat(jsonResponse, "wrong", STRING_LENGTH);
|
|
strncat(jsonResponse, "\"}", STRING_LENGTH);
|
|
JsonReport(true, request);
|
|
return;
|
|
}
|
|
|
|
if(StringStartsWith(request, "axes"))
|
|
{
|
|
strncpy(jsonResponse, "{\"axes\":[", STRING_LENGTH);
|
|
for(int8_t drive = 0; drive < AXES; drive++)
|
|
{
|
|
strncat(jsonResponse, "\"", STRING_LENGTH);
|
|
strncat(jsonResponse, ftoa(0, platform->AxisLength(drive), 1), STRING_LENGTH);
|
|
if(drive < AXES-1)
|
|
strncat(jsonResponse, "\",", STRING_LENGTH);
|
|
else
|
|
strncat(jsonResponse, "\"", STRING_LENGTH);
|
|
}
|
|
strncat(jsonResponse, "]}", STRING_LENGTH);
|
|
JsonReport(true, request);
|
|
return;
|
|
}
|
|
|
|
JsonReport(false, request);
|
|
}
|
|
|
|
/*
|
|
|
|
Parse a string in clientLine[] from the user's web browser
|
|
|
|
Simple requests have the form:
|
|
|
|
GET /page2.htm HTTP/1.1
|
|
^ Start clientRequest[] at clientLine[5]; stop at the blank or...
|
|
|
|
...fancier ones with arguments after a '?' go:
|
|
|
|
GET /gather.asp?pwd=my_pwd HTTP/1.1
|
|
^ Start clientRequest[]
|
|
^ Start clientQualifier[]
|
|
*/
|
|
|
|
void Webserver::ParseGetPost()
|
|
{
|
|
if(reprap.Debug())
|
|
{
|
|
platform->Message(HOST_MESSAGE, "HTTP request: ");
|
|
platform->Message(HOST_MESSAGE, clientLine);
|
|
platform->Message(HOST_MESSAGE, "\n");
|
|
}
|
|
|
|
int i = 5;
|
|
int j = 0;
|
|
clientRequest[j] = 0;
|
|
clientQualifier[0] = 0;
|
|
while(clientLine[i] != ' ' && clientLine[i] != '?')
|
|
{
|
|
clientRequest[j] = clientLine[i];
|
|
j++;
|
|
i++;
|
|
}
|
|
clientRequest[j] = 0;
|
|
if(clientLine[i] == '?')
|
|
{
|
|
i++;
|
|
j = 0;
|
|
while(clientLine[i] != ' ')
|
|
{
|
|
clientQualifier[j] = clientLine[i];
|
|
j++;
|
|
i++;
|
|
}
|
|
clientQualifier[j] = 0;
|
|
}
|
|
}
|
|
|
|
void Webserver::InitialisePost()
|
|
{
|
|
postSeen = false;
|
|
receivingPost = false;
|
|
boundaryCount = 0;
|
|
postBoundary[0] = 0;
|
|
postFileName[0] = 0;
|
|
postFile = NULL;
|
|
}
|
|
|
|
void Webserver::ParseClientLine()
|
|
{
|
|
if(StringStartsWith(clientLine, "GET"))
|
|
{
|
|
ParseGetPost();
|
|
postSeen = false;
|
|
getSeen = true;
|
|
if(!clientRequest[0])
|
|
strncpy(clientRequest, INDEX_PAGE, STRING_LENGTH);
|
|
return;
|
|
}
|
|
|
|
if(StringStartsWith(clientLine, "POST"))
|
|
{
|
|
ParseGetPost();
|
|
InitialisePost();
|
|
postSeen = true;
|
|
getSeen = false;
|
|
if(!clientRequest[0])
|
|
strncpy(clientRequest, INDEX_PAGE, STRING_LENGTH);
|
|
return;
|
|
}
|
|
|
|
int bnd;
|
|
|
|
if(postSeen && ( (bnd = StringContains(clientLine, "boundary=")) >= 0) )
|
|
{
|
|
if(strlen(&clientLine[bnd]) >= POST_LENGTH - 4)
|
|
{
|
|
platform->Message(HOST_MESSAGE, "Post boundary buffer overflow.\n");
|
|
return;
|
|
}
|
|
postBoundary[0] = '-';
|
|
postBoundary[1] = '-';
|
|
strncpy(&postBoundary[2], &clientLine[bnd], POST_LENGTH - 3);
|
|
strncat(postBoundary, "--", POST_LENGTH);
|
|
//Serial.print("Got boundary: ");
|
|
//Serial.println(postBoundary);
|
|
return;
|
|
}
|
|
|
|
if(receivingPost && StringStartsWith(clientLine, "Content-Disposition:"))
|
|
{
|
|
bnd = StringContains(clientLine, "filename=\"");
|
|
if(bnd < 0)
|
|
{
|
|
platform->Message(HOST_MESSAGE, "Post disposition gives no filename.\n");
|
|
return;
|
|
}
|
|
int i = 0;
|
|
while(clientLine[bnd] && clientLine[bnd] != '"')
|
|
{
|
|
postFileName[i++] = clientLine[bnd++];
|
|
if(i >= POST_LENGTH)
|
|
{
|
|
i = 0;
|
|
platform->Message(HOST_MESSAGE, "Post filename buffer overflow.\n");
|
|
}
|
|
}
|
|
postFileName[i] = 0;
|
|
//Serial.print("Got file name: ");
|
|
//Serial.println(postFileName);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// if you've gotten to the end of the line (received a newline
|
|
// character) and the line is blank, the http request has ended,
|
|
// so you can send a reply
|
|
void Webserver::BlankLineFromClient()
|
|
{
|
|
clientLine[clientLinePointer] = 0;
|
|
clientLinePointer = 0;
|
|
//ParseQualifier();
|
|
|
|
//Serial.println("End of header.");
|
|
|
|
// Soak up any rubbish on the end.
|
|
|
|
char c;
|
|
while(platform->GetNetwork()->Read(c));
|
|
|
|
|
|
if(getSeen)
|
|
{
|
|
SendFile(clientRequest);
|
|
clientRequest[0] = 0;
|
|
return;
|
|
}
|
|
|
|
if(postSeen)
|
|
{
|
|
receivingPost = true;
|
|
postSeen = false;
|
|
return;
|
|
}
|
|
|
|
if(receivingPost)
|
|
{
|
|
postFile = platform->GetFileStore(platform->GetGCodeDir(), postFileName, true);
|
|
if(postFile == NULL || !postBoundary[0])
|
|
{
|
|
platform->Message(HOST_MESSAGE, "Can't open file for write or no post boundary: ");
|
|
platform->Message(HOST_MESSAGE, postFileName);
|
|
platform->Message(HOST_MESSAGE, "\n");
|
|
InitialisePost();
|
|
if(postFile != NULL)
|
|
postFile->Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process a character from the client, returning true if we did more than just store it
|
|
bool Webserver::CharFromClient(char c)
|
|
{
|
|
if(c == '\n' && clientLineIsBlank)
|
|
{
|
|
BlankLineFromClient();
|
|
return true;
|
|
}
|
|
|
|
if(c == '\n')
|
|
{
|
|
clientLine[clientLinePointer] = 0;
|
|
ParseClientLine();
|
|
// you're starting a new line
|
|
clientLineIsBlank = true;
|
|
clientLinePointer = 0;
|
|
return true;
|
|
} else if(c != '\r')
|
|
{
|
|
// you've gotten a character on the current line
|
|
clientLineIsBlank = false;
|
|
clientLine[clientLinePointer] = c;
|
|
clientLinePointer++;
|
|
if(clientLinePointer >= STRING_LENGTH)
|
|
{
|
|
platform->Message(HOST_MESSAGE, "Client read buffer overflow. Data:\n");
|
|
clientLine[STRING_LENGTH] = '\n'; // note that clientLine is now STRING_LENGTH+2 characters long to make room for these
|
|
clientLine[STRING_LENGTH+1] = 0;
|
|
platform->Message(HOST_MESSAGE, clientLine);
|
|
|
|
clientLinePointer = 0;
|
|
clientLine[clientLinePointer] = 0;
|
|
clientLineIsBlank = true;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Deal with input/output from/to the client (if any) one byte at a time.
|
|
|
|
void Webserver::Spin()
|
|
{
|
|
//char sw[2];
|
|
if(!active)
|
|
return;
|
|
|
|
if(writing)
|
|
{
|
|
// if(inPHPFile)
|
|
// WritePHPByte();
|
|
// else
|
|
if (WriteBytes()) // if we wrote something
|
|
{
|
|
platform->ClassReport("Webserver", longWait);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(platform->GetNetwork()->Active())
|
|
{
|
|
for(uint8_t i = 0;
|
|
i < 16 && (platform->GetNetwork()->Status() & (clientConnected | byteAvailable)) == (clientConnected | byteAvailable);
|
|
++i)
|
|
{
|
|
char c;
|
|
platform->GetNetwork()->Read(c);
|
|
//SerialUSB.print(c);
|
|
|
|
if(receivingPost && postFile != NULL)
|
|
{
|
|
if(MatchBoundary(c))
|
|
{
|
|
//Serial.println("Got to end of file.");
|
|
postFile->Close();
|
|
SendFile(clientRequest);
|
|
clientRequest[0] = 0;
|
|
InitialisePost();
|
|
}
|
|
platform->ClassReport("Webserver", longWait);
|
|
return;
|
|
}
|
|
|
|
if (CharFromClient(c))
|
|
break; // break if we did more than just store the character
|
|
}
|
|
}
|
|
|
|
if (platform->GetNetwork()->Status() & clientLive)
|
|
{
|
|
if(needToCloseClient)
|
|
{
|
|
if(platform->Time() - clientCloseTime < CLIENT_CLOSE_DELAY)
|
|
{
|
|
platform->ClassReport("Webserver", longWait);
|
|
return;
|
|
}
|
|
needToCloseClient = false;
|
|
platform->GetNetwork()->Close();
|
|
}
|
|
}
|
|
platform->ClassReport("Webserver", longWait);
|
|
}
|
|
|
|
//******************************************************************************************
|
|
|
|
// Constructor and initialisation
|
|
|
|
Webserver::Webserver(Platform* p)
|
|
{
|
|
platform = p;
|
|
active = false;
|
|
gotPassword = false;
|
|
}
|
|
|
|
void Webserver::Init()
|
|
{
|
|
writing = false;
|
|
receivingPost = false;
|
|
postSeen = false;
|
|
getSeen = false;
|
|
jsonPointer = -1;
|
|
clientLineIsBlank = true;
|
|
needToCloseClient = false;
|
|
clientLinePointer = 0;
|
|
clientLine[0] = 0;
|
|
clientRequest[0] = 0;
|
|
SetPassword(DEFAULT_PASSWORD);
|
|
SetName(DEFAULT_NAME);
|
|
//gotPassword = false;
|
|
gcodeReadIndex = gcodeWriteIndex = 0;
|
|
InitialisePost();
|
|
lastTime = platform->Time();
|
|
longWait = lastTime;
|
|
active = true;
|
|
gcodeReply[0] = 0;
|
|
seq = 0;
|
|
|
|
// Reinitialise the message file
|
|
|
|
//platform->GetMassStorage()->Delete(platform->GetWebDir(), MESSAGE_FILE);
|
|
}
|
|
|
|
// This is called when the connection has been lost.
|
|
// In particular, we must cancel any pending writes.
|
|
void Webserver::ConnectionError()
|
|
{
|
|
writing = false;
|
|
receivingPost = false;
|
|
postSeen = false;
|
|
getSeen = false;
|
|
jsonPointer = -1;
|
|
clientLineIsBlank = true;
|
|
needToCloseClient = false;
|
|
clientLinePointer = 0;
|
|
clientLine[0] = 0;
|
|
clientRequest[0] = 0;
|
|
gotPassword = false;
|
|
gcodeReadIndex = gcodeWriteIndex = 0;
|
|
InitialisePost();
|
|
lastTime = platform->Time();
|
|
longWait = lastTime;
|
|
active = true;
|
|
|
|
}
|
|
|
|
void Webserver::Exit()
|
|
{
|
|
platform->Message(HOST_MESSAGE, "Webserver class exited.\n");
|
|
active = false;
|
|
}
|
|
|
|
void Webserver::Diagnostics()
|
|
{
|
|
platform->Message(HOST_MESSAGE, "Webserver Diagnostics:\n");
|
|
}
|
|
|
|
void Webserver::SetPassword(const char* pw)
|
|
{
|
|
strncpy(password, pw, SHORT_STRING_LENGTH);
|
|
password[SHORT_STRING_LENGTH] = 0; // NB array is dimensioned to SHORT_STRING_LENGTH+1
|
|
}
|
|
|
|
void Webserver::SetName(const char* nm)
|
|
{
|
|
strncpy(myName, nm, SHORT_STRING_LENGTH);
|
|
myName[SHORT_STRING_LENGTH] = 0; // NB array is dimensioned to SHORT_STRING_LENGTH+1
|
|
}
|
|
|
|
void Webserver::HandleReply(const char *s, bool error)
|
|
{
|
|
if (strlen(s) == 0 && !error)
|
|
{
|
|
strcpy(gcodeReply, "ok");
|
|
}
|
|
else
|
|
{
|
|
if (error)
|
|
{
|
|
strcpy(gcodeReply, "Error: ");
|
|
strncat(gcodeReply, s, STRING_LENGTH);
|
|
}
|
|
else
|
|
{
|
|
strncpy(gcodeReply, s, STRING_LENGTH);
|
|
}
|
|
gcodeReply[STRING_LENGTH] = 0; // array is dimensioned to STRING_LENGTH+1
|
|
}
|
|
++seq;
|
|
}
|
|
|
|
void Webserver::AppendReply(const char *s)
|
|
{
|
|
strncat(gcodeReply, s, STRING_LENGTH);
|
|
}
|
|
|
|
// Get the actual amount of gcode buffer space we have
|
|
unsigned int Webserver::GetGcodeBufferSpace() const
|
|
{
|
|
return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufLength;
|
|
}
|
|
|
|
// Get the amount of gcode buffer space we are going to report
|
|
unsigned int Webserver::GetReportedGcodeBufferSpace() const
|
|
{
|
|
unsigned int temp = GetGcodeBufferSpace();
|
|
return (temp > maxReportedFreeBuf) ? maxReportedFreeBuf
|
|
: (temp < minReportedFreeBuf) ? 0
|
|
: temp;
|
|
}
|
|
|