This repository has been archived on 2025-02-01. You can view files and clone it, but cannot push or open issues or pull requests.
reprapfirmware-dc42/Webserver.cpp

977 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;
if (platform->GetZProbeType() == 2)
{
snprintf(scratch, SHORT_STRING_LENGTH, ",\"probe\":\"%d (%d)\"", (int)platform->ZProbe(), platform->ZProbeOnVal());
}
else
{
snprintf(scratch, SHORT_STRING_LENGTH, ",\"probe\":\"%d\"", (int)platform->ZProbe());
}
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"))
{
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;
}