Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Duet3D/RepRapFirmware.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Crocker <dcrocker@eschertech.com>2018-04-01 13:45:55 +0300
committerDavid Crocker <dcrocker@eschertech.com>2018-04-01 13:45:55 +0300
commit4d151a5d02a8bca3810e4a75bf225136548fa1ab (patch)
treec81234e4e1ad675310272c6ffaf74a94f9ac6438
parentde270d2a00fd83cc3961a749432bff940601f1dd (diff)
More RTOS work
Movd the file info parser out of PrintManager into a separate class within Storage and made it thread safe Added "Off" status for DWC and PanelDue Fixed bug with using G1 S1 Ennn on a delta printer
-rw-r--r--src/GCodes/GCodes.cpp12
-rw-r--r--src/GCodes/GCodes2.cpp6
-rw-r--r--src/Networking/HttpResponder.cpp7
-rw-r--r--src/Networking/HttpResponder.h4
-rw-r--r--src/Platform.cpp5
-rw-r--r--src/Platform.h1
-rw-r--r--src/PrintMonitor.cpp839
-rw-r--r--src/PrintMonitor.h61
-rw-r--r--src/RepRap.cpp78
-rw-r--r--src/RepRap.h1
-rw-r--r--src/Storage/FileInfoParser.cpp739
-rw-r--r--src/Storage/FileInfoParser.h86
-rw-r--r--src/Storage/MassStorage.h4
-rw-r--r--src/Version.h2
14 files changed, 970 insertions, 875 deletions
diff --git a/src/GCodes/GCodes.cpp b/src/GCodes/GCodes.cpp
index 3d82b3c7..00ada42f 100644
--- a/src/GCodes/GCodes.cpp
+++ b/src/GCodes/GCodes.cpp
@@ -2294,12 +2294,6 @@ const char* GCodes::DoStraightMove(GCodeBuffer& gb, bool isCoordinated)
if (moveBuffer.moveType != 0)
{
- // Special move. If on a delta, movement must be relative.
- if (!gb.MachineState().axesRelative && reprap.GetMove().GetKinematics().GetKinematicsType() == KinematicsType::linearDelta)
- {
- return "G0/G1: attempt to move delta motors to absolute positions";
- }
-
// This may be a raw motor move, in which case we need the current raw motor positions in moveBuffer.coords.
// If it isn't a raw motor move, it will still be applied without axis or bed transform applied,
// so make sure the initial coordinates don't have those either to avoid unwanted Z movement.
@@ -2317,6 +2311,12 @@ const char* GCodes::DoStraightMove(GCodeBuffer& gb, bool isCoordinated)
{
if (gb.Seen(axisLetters[axis]))
{
+ // If it is a special move on a delta, movement must be relative.
+ if (moveBuffer.moveType != 0 && !gb.MachineState().axesRelative && reprap.GetMove().GetKinematics().GetKinematicsType() == KinematicsType::linearDelta)
+ {
+ return "G0/G1: attempt to move individual motors of a delta machine to absolute positions";
+ }
+
SetBit(axesMentioned, axis);
const float moveArg = gb.GetFValue() * distanceScale;
if (moveBuffer.moveType != 0)
diff --git a/src/GCodes/GCodes2.cpp b/src/GCodes/GCodes2.cpp
index 19065eab..6d21eeba 100644
--- a/src/GCodes/GCodes2.cpp
+++ b/src/GCodes/GCodes2.cpp
@@ -845,7 +845,7 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, const StringRef& reply)
String<MaxFilenameLength> filename;
const bool gotFilename = gb.GetUnprecedentedString(filename.GetRef());
OutputBuffer *fileInfoResponse;
- const bool done = reprap.GetPrintMonitor().GetFileInfoResponse((gotFilename) ? filename.Pointer() : nullptr, fileInfoResponse);
+ const bool done = reprap.GetFileInfoResponse((gotFilename) ? filename.Pointer() : nullptr, fileInfoResponse, false);
if (done)
{
fileInfoResponse->cat('\n');
@@ -1892,9 +1892,9 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, const StringRef& reply)
case 204: // Set max travel and printing accelerations
{
bool seen = false;
- if (gb.Seen('S') && platform.Emulating() == Compatibility::marlin)
+ if (gb.Seen('S'))
{
- // For backwards compatibility e.g. with Cura, set both accelerations as Marlin does.
+ // For backwards compatibility with old versions of Marlin (e.g. for Cura and the Prusa fork of slic3r), set both accelerations
const float acc = gb.GetFValue();
platform.SetMaxPrintingAcceleration(acc);
platform.SetMaxTravelAcceleration(acc);
diff --git a/src/Networking/HttpResponder.cpp b/src/Networking/HttpResponder.cpp
index 88d5f6e1..58208494 100644
--- a/src/Networking/HttpResponder.cpp
+++ b/src/Networking/HttpResponder.cpp
@@ -116,7 +116,7 @@ bool HttpResponder::Spin()
// no break
case ResponderState::gettingFileInfo:
- if (SendFileInfo())
+ if (SendFileInfo(millis() - startedGettingFileInfoAt >= MaxFileInfoGetTime))
{
fileInfoLock.Release(this); // release the lock
}
@@ -562,6 +562,7 @@ bool HttpResponder::GetJsonResponse(const char* request, OutputBuffer *&response
// Simple rr_fileinfo call to get info about the file being printed
filenameBeingProcessed[0] = 0;
}
+ startedGettingFileInfoAt = millis();
responderState = ResponderState::gettingFileInfoLock;
return false;
}
@@ -618,10 +619,10 @@ const char* HttpResponder::GetKeyValue(const char *key) const
// Called to process a FileInfo request, which may take several calls
// When we have finished, set the state back to free.
-bool HttpResponder::SendFileInfo()
+bool HttpResponder::SendFileInfo(bool quitEarly)
{
OutputBuffer *jsonResponse = nullptr;
- const bool gotFileInfo = reprap.GetPrintMonitor().GetFileInfoResponse(filenameBeingProcessed, jsonResponse);
+ const bool gotFileInfo = reprap.GetFileInfoResponse(filenameBeingProcessed, jsonResponse, quitEarly);
if (gotFileInfo)
{
// Got it - send the response now
diff --git a/src/Networking/HttpResponder.h b/src/Networking/HttpResponder.h
index 4ba99d7c..04ba362b 100644
--- a/src/Networking/HttpResponder.h
+++ b/src/Networking/HttpResponder.h
@@ -37,6 +37,7 @@ private:
static const size_t MaxQualKeys = 5; // max number of key/value pairs in the qualifier
static const size_t MaxHeaders = 30; // max number of key/value pairs in the headers
static const uint32_t HttpSessionTimeout = 8000; // HTTP session timeout in milliseconds
+ static const uint32_t MaxFileInfoGetTime = 1800; // maximum length of time we spend getting file info, to avoid the client timing out (actual time will be a little longer than this)
enum class HttpParseState
{
@@ -80,7 +81,7 @@ private:
bool GetJsonResponse(const char* request, OutputBuffer *&response, bool& keepOpen);
void ProcessMessage();
void RejectMessage(const char* s, unsigned int code = 500);
- bool SendFileInfo();
+ bool SendFileInfo(bool quitEarly);
void DoUpload();
@@ -102,6 +103,7 @@ private:
size_t numHeaderKeys; // number of keys we have found, <= maxHeaders
// rr_fileinfo requests
+ uint32_t startedGettingFileInfoAt; // when we started trying to get file info
char filenameBeingProcessed[MaxFilenameLength]; // The filename being processed (for rr_fileinfo)
// Keeping track of HTTP sessions
diff --git a/src/Platform.cpp b/src/Platform.cpp
index 656018d6..17f25317 100644
--- a/src/Platform.cpp
+++ b/src/Platform.cpp
@@ -1640,6 +1640,11 @@ bool Platform::IsPowerOk() const
return !autoSaveEnabled || currentVin > autoPauseReading;
}
+bool Platform::HasVinPower() const
+{
+ return driversPowered; // not quite right because drivers are disabled if we get over-voltage too, but OK for the status report
+}
+
void Platform::EnableAutoSave(float saveVoltage, float resumeVoltage)
{
autoPauseReading = PowerVoltageToAdcReading(saveVoltage);
diff --git a/src/Platform.h b/src/Platform.h
index f1a16a35..32d3e270 100644
--- a/src/Platform.h
+++ b/src/Platform.h
@@ -551,6 +551,7 @@ public:
void GetPowerVoltages(float& minV, float& currV, float& maxV) const;
float GetCurrentPowerVoltage() const;
bool IsPowerOk() const;
+ bool HasVinPower() const;
void DisableAutoSave();
void EnableAutoSave(float saveVoltage, float resumeVoltage);
bool GetAutoSaveSettings(float& saveVoltage, float&resumeVoltage);
diff --git a/src/PrintMonitor.cpp b/src/PrintMonitor.cpp
index 139b6d05..43f6bb02 100644
--- a/src/PrintMonitor.cpp
+++ b/src/PrintMonitor.cpp
@@ -28,9 +28,7 @@ Licence: GPL
PrintMonitor::PrintMonitor(Platform& p, GCodes& gc) : platform(p), gCodes(gc), isPrinting(false),
printStartTime(0), pauseStartTime(0), totalPauseTime(0), heatingUp(false), currentLayer(0), warmUpDuration(0.0),
firstLayerDuration(0.0), firstLayerFilament(0.0), firstLayerProgress(0.0), lastLayerChangeTime(0.0),
- lastLayerFilament(0.0), lastLayerZ(0.0), numLayerSamples(0), layerEstimatedTimeLeft(0.0), parseState(notParsing),
- fileBeingParsed(nullptr), fileOverlapLength(0), printingFileParsed(false), accumulatedParseTime(0),
- accumulatedReadTime(0), accumulatedSeekTime(0)
+ lastLayerFilament(0.0), lastLayerZ(0.0), numLayerSamples(0), layerEstimatedTimeLeft(0.0), printingFileParsed(false)
{
filenameBeingPrinted[0] = 0;
}
@@ -40,12 +38,46 @@ void PrintMonitor::Init()
longWait = lastUpdateTime = millis();
}
+// Get information for the specified file, or the currently printing file, in JSON format
+bool PrintMonitor::GetPrintingFileInfoResponse(OutputBuffer *&response) const
+{
+ // If the file being printed hasn't been processed yet or if we cannot write the response, try again later
+ if (!printingFileParsed || !OutputBuffer::Allocate(response))
+ {
+ return false;
+ }
+
+ // Poll file info about a file currently being printed
+ response->printf("{\"err\":0,\"size\":%lu,\"height\":%.2f,\"firstLayerHeight\":%.2f,\"layerHeight\":%.2f,\"filament\":",
+ printingFileInfo.fileSize, (double)printingFileInfo.objectHeight, (double)printingFileInfo.firstLayerHeight, (double)printingFileInfo.layerHeight);
+ char ch = '[';
+ if (printingFileInfo.numFilaments == 0)
+ {
+ response->cat(ch);
+ }
+ else
+ {
+ for (size_t i = 0; i < printingFileInfo.numFilaments; ++i)
+ {
+ response->catf("%c%.1f", ch, (double)printingFileInfo.filamentNeeded[i]);
+ ch = ',';
+ }
+ }
+ response->cat("],\"generatedBy\":");
+ response->EncodeString(printingFileInfo.generatedBy.c_str(), printingFileInfo.generatedBy.Capacity(), false);
+ response->catf(",\"printDuration\":%d,\"fileName\":", (int)GetPrintDuration());
+ response->EncodeString(filenameBeingPrinted.c_str(), filenameBeingPrinted.Capacity(), false);
+ response->cat('}');
+
+ return true;
+}
+
void PrintMonitor::Spin()
{
// File information about the file being printed must be available before layer estimations can be made
if (filenameBeingPrinted[0] != 0 && !printingFileParsed)
{
- printingFileParsed = GetFileInfo(platform.GetGCodeDir(), filenameBeingPrinted.c_str(), printingFileInfo);
+ printingFileParsed = platform.GetMassStorage()->GetFileInfo(platform.GetGCodeDir(), filenameBeingPrinted.c_str(), printingFileInfo, false);
if (!printingFileParsed)
{
platform.ClassReport(longWait);
@@ -166,7 +198,7 @@ float PrintMonitor::GetWarmUpDuration() const
// Notifies this class that a file has been set for printing
void PrintMonitor::StartingPrint(const char* filename)
{
- printingFileParsed = GetFileInfo(platform.GetGCodeDir(), filename, printingFileInfo);
+ printingFileParsed = platform.GetMassStorage()->GetFileInfo(platform.GetGCodeDir(), filename, printingFileInfo, false);
filenameBeingPrinted.copy(filename);
}
@@ -273,407 +305,6 @@ void PrintMonitor::StoppedPrint()
lastLayerChangeTime = lastLayerFilament = lastLayerZ = 0.0;
}
-bool PrintMonitor::GetFileInfo(const char *directory, const char *fileName, GCodeFileInfo& info)
-{
- if (parseState != notParsing && !StringEquals(fileName, filenameBeingParsed.c_str()))
- {
- // We are already parsing a different file
- if (millis() - lastFileParseTime < MaxFileParseInterval)
- {
- return false; // try again later
- }
-
- // Time this client out because it has probably disconnected
- fileBeingParsed->Close();
- parseState = notParsing;
- }
-
- if (parseState == notParsing)
- {
- // See if we can access the file
- // Webserver may call rr_fileinfo for a directory, check this case here
- if (platform.GetMassStorage()->DirectoryExists(directory, fileName))
- {
- info.isValid = false;
- return true;
- }
-
- fileBeingParsed = platform.OpenFile(directory, fileName, OpenMode::read);
- if (fileBeingParsed == nullptr)
- {
- // Something went wrong - we cannot open it
- info.isValid = false;
- return true;
- }
-
- // File has been opened, let's start now
- filenameBeingParsed.copy(fileName);
- fileOverlapLength = 0;
-
- // Set up the info struct
- parsedFileInfo.isValid = true;
- parsedFileInfo.fileSize = fileBeingParsed->Length();
- parsedFileInfo.lastModifiedTime = platform.GetMassStorage()->GetLastModifiedTime(directory, fileName);
- parsedFileInfo.firstLayerHeight = 0.0;
- parsedFileInfo.objectHeight = 0.0;
- parsedFileInfo.layerHeight = 0.0;
- parsedFileInfo.numFilaments = 0;
- parsedFileInfo.generatedBy[0] = 0;
- for(size_t extr = 0; extr < MaxExtruders; extr++)
- {
- parsedFileInfo.filamentNeeded[extr] = 0.0;
- }
-
- // Record some debug values here
- if (reprap.Debug(modulePrintMonitor))
- {
- accumulatedReadTime = accumulatedParseTime = 0;
- platform.MessageF(UsbMessage, "-- Parsing file %s --\n", fileName);
- }
-
- // If the file is empty or not a G-Code file, we don't need to parse anything
- if (fileBeingParsed->Length() == 0 || (!StringEndsWith(fileName, ".gcode") && !StringEndsWith(fileName, ".g")
- && !StringEndsWith(fileName, ".gco") && !StringEndsWith(fileName, ".gc")))
- {
- fileBeingParsed->Close();
- info = parsedFileInfo;
- return true;
- }
- parseState = parsingHeader;
- }
-
- // Getting file information take a few runs. Speed it up when we are not printing by calling it several times.
- const uint32_t loopStartTime = millis();
- do
- {
- uint32_t buf32[(GCODE_READ_SIZE + GCODE_OVERLAP_SIZE + 3)/4 + 1]; // buffer should be 32-bit aligned for HSMCI (need the +1 so we can add a null terminator)
- char* const buf = reinterpret_cast<char*>(buf32);
- size_t sizeToRead, sizeToScan; // number of bytes we want to read and scan in this go
-
- switch (parseState)
- {
- case parsingHeader:
- {
- bool headerInfoComplete = true;
-
- // Read a chunk from the header. On the first run only process GCODE_READ_SIZE bytes, but use overlap next times.
- sizeToRead = (size_t)min<FilePosition>(fileBeingParsed->Length() - fileBeingParsed->Position(), GCODE_READ_SIZE);
- if (fileOverlapLength > 0)
- {
- memcpy(buf, fileOverlap, fileOverlapLength);
- sizeToScan = sizeToRead + fileOverlapLength;
- }
- else
- {
- sizeToScan = sizeToRead;
- }
-
- uint32_t startTime = millis();
- const int nbytes = fileBeingParsed->Read(&buf[fileOverlapLength], sizeToRead);
- if (nbytes != (int)sizeToRead)
- {
- platform.MessageF(ErrorMessage, "Failed to read header of G-Code file \"%s\"\n", fileName);
- parseState = notParsing;
- fileBeingParsed->Close();
- info = parsedFileInfo;
- return true;
- }
- buf[sizeToScan] = 0;
-
- // Record performance data
- uint32_t now = millis();
- accumulatedReadTime += now - startTime;
- startTime = now;
-
- // Search for filament usage (Cura puts it at the beginning of a G-code file)
- if (parsedFileInfo.numFilaments == 0)
- {
- parsedFileInfo.numFilaments = FindFilamentUsed(buf, sizeToScan, parsedFileInfo.filamentNeeded, DRIVES - reprap.GetGCodes().GetTotalAxes());
- headerInfoComplete &= (parsedFileInfo.numFilaments != 0);
- }
-
- // Look for first layer height
- if (parsedFileInfo.firstLayerHeight == 0.0)
- {
- headerInfoComplete &= FindFirstLayerHeight(buf, sizeToScan, parsedFileInfo.firstLayerHeight);
- }
-
- // Look for layer height
- if (parsedFileInfo.layerHeight == 0.0)
- {
- headerInfoComplete &= FindLayerHeight(buf, sizeToScan, parsedFileInfo.layerHeight);
- }
-
- // Look for slicer program
- if (parsedFileInfo.generatedBy.IsEmpty())
- {
- headerInfoComplete &= FindSlicerInfo(buf, sizeToScan, parsedFileInfo.generatedBy.GetRef());
- }
-
- // Keep track of the time stats
- accumulatedParseTime += millis() - startTime;
-
- // Can we proceed to the footer? Don't scan more than the first 4KB of the file
- FilePosition pos = fileBeingParsed->Position();
- if (headerInfoComplete || pos >= GCODE_HEADER_SIZE || pos == fileBeingParsed->Length())
- {
- // Yes - see if we need to output some debug info
- if (reprap.Debug(modulePrintMonitor))
- {
- platform.MessageF(UsbMessage, "Header complete, processed %lu bytes, read time %.3fs, parse time %.3fs\n",
- fileBeingParsed->Position(), (double)((float)accumulatedReadTime/1000.0), (double)((float)accumulatedParseTime/1000.0));
- }
-
- // Go to the last chunk and proceed from there on
- const FilePosition seekFromEnd = ((fileBeingParsed->Length() - 1) % GCODE_READ_SIZE) + 1;
- nextSeekPos = fileBeingParsed->Length() - seekFromEnd;
- accumulatedSeekTime = accumulatedReadTime = accumulatedParseTime = 0;
- fileOverlapLength = 0;
- parseState = seeking;
- }
- else
- {
- // No - copy the last chunk of the buffer for overlapping search
- fileOverlapLength = min<size_t>(sizeToRead, GCODE_OVERLAP_SIZE);
- memcpy(fileOverlap, &buf[sizeToRead - fileOverlapLength], fileOverlapLength);
- }
- }
- break;
-
- case seeking:
- // Seeking into a large file can take a long time using the FAT file system, so do it in stages
- {
- FilePosition currentPos = fileBeingParsed->Position();
- const uint32_t clsize = fileBeingParsed->ClusterSize();
- if (currentPos/clsize > nextSeekPos/clsize)
- {
- // Seeking backwards over a cluster boundary, so in practice the seek will start from the start of the file
- currentPos = 0;
- }
-
- // Seek at most 512 clusters at a time
- const FilePosition maxSeekDistance = 512 * (FilePosition)clsize;
- const bool doFullSeek = (nextSeekPos <= currentPos + maxSeekDistance);
- const FilePosition thisSeekPos = (doFullSeek) ? nextSeekPos : currentPos + maxSeekDistance;
-
- const uint32_t startTime = millis();
- if (!fileBeingParsed->Seek(thisSeekPos))
- {
- platform.Message(ErrorMessage, "Could not seek from end of file!\n");
- parseState = notParsing;
- fileBeingParsed->Close();
- info = parsedFileInfo;
- return true;
- }
- accumulatedSeekTime += millis() - startTime;
- if (doFullSeek)
- {
- parseState = parsingFooter;
- }
- }
- break;
-
- case parsingFooter:
- {
- // Processing the footer. See how many bytes we need to read and if we can reuse the overlap
- sizeToRead = (size_t)min<FilePosition>(fileBeingParsed->Length() - nextSeekPos, GCODE_READ_SIZE);
- if (fileOverlapLength > 0)
- {
- memcpy(&buf[sizeToRead], fileOverlap, fileOverlapLength);
- sizeToScan = sizeToRead + fileOverlapLength;
- }
- else
- {
- sizeToScan = sizeToRead;
- }
-
- // Read another chunk from the footer
- uint32_t startTime = millis();
- int nbytes = fileBeingParsed->Read(buf, sizeToRead);
- if (nbytes != (int)sizeToRead)
- {
- platform.MessageF(ErrorMessage, "Failed to read footer from G-Code file \"%s\"\n", fileName);
- parseState = notParsing;
- fileBeingParsed->Close();
- info = parsedFileInfo;
- return true;
- }
- buf[sizeToScan] = 0;
-
- // Record performance data
- uint32_t now = millis();
- accumulatedReadTime += now - startTime;
- startTime = now;
-
- bool footerInfoComplete = true;
-
- // Search for filament used
- if (parsedFileInfo.numFilaments == 0)
- {
- parsedFileInfo.numFilaments = FindFilamentUsed(buf, sizeToScan, parsedFileInfo.filamentNeeded, DRIVES - reprap.GetGCodes().GetTotalAxes());
- if (parsedFileInfo.numFilaments == 0)
- {
- footerInfoComplete = false;
- }
- }
-
- // Search for layer height
- if (parsedFileInfo.layerHeight == 0.0)
- {
- if (!FindLayerHeight(buf, sizeToScan, parsedFileInfo.layerHeight))
- {
- footerInfoComplete = false;
- }
- }
-
- // Search for object height
- if (parsedFileInfo.objectHeight == 0.0)
- {
- if (!FindHeight(buf, sizeToScan, parsedFileInfo.objectHeight))
- {
- footerInfoComplete = false;
- }
- }
-
- // Keep track of the time stats
- accumulatedParseTime += millis() - startTime;
-
- // If we've collected all details, scanned the last 192K of the file or if we cannot go any further, stop here.
- if (footerInfoComplete || nextSeekPos == 0 || fileBeingParsed->Length() - nextSeekPos >= GCODE_FOOTER_SIZE)
- {
- if (reprap.Debug(modulePrintMonitor))
- {
- platform.MessageF(UsbMessage, "Footer complete, processed %lu bytes, read time %.3fs, parse time %.3fs, seek time %.3fs\n",
- fileBeingParsed->Length() - fileBeingParsed->Position() + GCODE_READ_SIZE,
- (double)((float)accumulatedReadTime/1000.0), (double)((float)accumulatedParseTime/1000.0), (double)((float)accumulatedSeekTime/1000.0));
- }
- parseState = notParsing;
- fileBeingParsed->Close();
- info = parsedFileInfo;
- return true;
- }
-
- // Else go back further
- fileOverlapLength = (size_t)min<FilePosition>(sizeToScan, GCODE_OVERLAP_SIZE);
- memcpy(fileOverlap, buf, fileOverlapLength);
- nextSeekPos = (nextSeekPos <= GCODE_READ_SIZE) ? 0 : nextSeekPos - GCODE_READ_SIZE;
- parseState = seeking;
- }
- break;
-
- default: // should not get here
- info = parsedFileInfo;
- return true;
- }
- lastFileParseTime = millis();
- } while (!isPrinting && lastFileParseTime - loopStartTime < MAX_FILEINFO_PROCESS_TIME);
- return false;
-}
-
-// Get information for the specified file, or the currently printing file, in JSON format
-bool PrintMonitor::GetFileInfoResponse(const char *filename, OutputBuffer *&response)
-{
- // Poll file info for a specific file
- if (filename != nullptr && filename[0] != 0)
- {
- GCodeFileInfo info;
- if (!GetFileInfo(FS_PREFIX, filename, info))
- {
- // This may take a few runs...
- return false;
- }
-
- if (info.isValid)
- {
- if (!OutputBuffer::Allocate(response))
- {
- // Should never happen
- return false;
- }
-
- response->printf("{\"err\":0,\"size\":%lu,",info.fileSize);
- const struct tm * const timeInfo = gmtime(&info.lastModifiedTime);
- if (timeInfo->tm_year > /*19*/80)
- {
- response->catf("\"lastModified\":\"%04u-%02u-%02uT%02u:%02u:%02u\",",
- timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday,
- timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
- }
-
- response->catf("\"height\":%.2f,\"firstLayerHeight\":%.2f,\"layerHeight\":%.2f,\"filament\":",
- (double)info.objectHeight, (double)info.firstLayerHeight, (double)info.layerHeight);
- char ch = '[';
- if (info.numFilaments == 0)
- {
- response->cat(ch);
- }
- else
- {
- for(size_t i = 0; i < info.numFilaments; ++i)
- {
- response->catf("%c%.1f", ch, (double)info.filamentNeeded[i]);
- ch = ',';
- }
- }
- response->cat("],\"generatedBy\":");
- response->EncodeString(info.generatedBy.c_str(), info.generatedBy.Capacity(), false);
- response->cat("}");
- }
- else
- {
- if (!OutputBuffer::Allocate(response))
- {
- // Should never happen
- return false;
- }
-
- response->copy("{\"err\":1}");
- }
- }
- else if (IsPrinting())
- {
- // If the file being printed hasn't been processed yet or if we
- // cannot write the response, try again later
- if (!printingFileParsed || !OutputBuffer::Allocate(response))
- {
- return false;
- }
-
- // Poll file info about a file currently being printed
- response->printf("{\"err\":0,\"size\":%lu,\"height\":%.2f,\"firstLayerHeight\":%.2f,\"layerHeight\":%.2f,\"filament\":",
- printingFileInfo.fileSize, (double)printingFileInfo.objectHeight, (double)printingFileInfo.firstLayerHeight, (double)printingFileInfo.layerHeight);
- char ch = '[';
- if (printingFileInfo.numFilaments == 0)
- {
- response->cat(ch);
- }
- else
- {
- for (size_t i = 0; i < printingFileInfo.numFilaments; ++i)
- {
- response->catf("%c%.1f", ch, (double)printingFileInfo.filamentNeeded[i]);
- ch = ',';
- }
- }
- response->cat("],\"generatedBy\":");
- response->EncodeString(printingFileInfo.generatedBy.GetRef(), false);
- response->catf(",\"printDuration\":%d,\"fileName\":", (int)GetPrintDuration());
- response->EncodeString(filenameBeingPrinted.GetRef(), false);
- response->cat('}');
- }
- else
- {
- if (!OutputBuffer::Allocate(response))
- {
- // Should never happen
- return false;
- }
-
- response->copy("{\"err\":1}");
- }
- return true;
-}
-
// Estimate the print time left in seconds on a preset estimation method
float PrintMonitor::EstimateTimeLeft(PrintEstimationMethod method) const
{
@@ -806,402 +437,6 @@ float PrintMonitor::EstimateTimeLeft(PrintEstimationMethod method) const
return 0.0;
}
-// Scan the buffer for a G1 Zxxx command. The buffer is null-terminated.
-bool PrintMonitor::FindFirstLayerHeight(const char* buf, size_t len, float& height) const
-{
- if (len < 4)
- {
- // Don't start if the buffer is not big enough
- return false;
- }
- height = 0.0;
-
-//debugPrintf("Scanning %u bytes starting %.100s\n", len, buf);
- bool inComment = false, inRelativeMode = false, foundHeight = false;
- for(size_t i = 0; i < len - 4; i++)
- {
- if (buf[i] == ';')
- {
- inComment = true;
- }
- else if (inComment)
- {
- if (buf[i] == '\n')
- {
- inComment = false;
- }
- }
- else if (buf[i] == 'G')
- {
- // See if we can switch back to absolute mode
- if (inRelativeMode)
- {
- inRelativeMode = !(buf[i + 1] == '9' && buf[i + 2] == '0' && buf[i + 3] <= ' ');
- }
- // Ignore G0/G1 codes if in relative mode
- else if (buf[i + 1] == '9' && buf[i + 2] == '1' && buf[i + 3] <= ' ')
- {
- inRelativeMode = true;
- }
- // Look for "G0/G1 ... Z#HEIGHT#" command
- else if ((buf[i + 1] == '0' || buf[i + 1] == '1') && buf[i + 2] == ' ')
- {
- for(i += 3; i < len - 4; i++)
- {
- if (buf[i] == 'Z')
- {
- //debugPrintf("Found at offset %u text: %.100s\n", i, &buf[i + 1]);
- float flHeight = strtod(&buf[i + 1], nullptr);
- if ((height == 0.0 || flHeight < height) && (flHeight <= platform.GetNozzleDiameter() * 3.0))
- {
- height = flHeight; // Only report first Z height if it's somewhat reasonable
- foundHeight = true;
- // NB: Don't stop here, because some slicers generate two Z moves at the beginning
- }
- break;
- }
- else if (buf[i] == ';')
- {
- // Ignore comments
- break;
- }
- }
- }
- }
- }
- return foundHeight;
-}
-
-// Scan the buffer for a G1 Zxxx command. The buffer is null-terminated.
-// This parsing algorithm needs to be fast. The old one sometimes took 5 seconds or more to parse about 120K of data.
-// To speed up parsing, we now parse forwards from the start of the buffer. This means we can't stop when we have found a G1 Z command,
-// we have to look for a later G1 Z command in the buffer. But it is faster in the (common) case that we don't find a match in the buffer at all.
-bool PrintMonitor::FindHeight(const char* buf, size_t len, float& height) const
-{
- bool foundHeight = false;
- bool inRelativeMode = false;
- for(;;)
- {
- // Skip to next newline
- char c;
- while (len >= 6 && (c = *buf) != '\r' && c != '\n')
- {
- ++buf;
- --len;
- }
-
- // Skip the newline and any leading spaces
- do
- {
- ++buf; // skip the newline
- --len;
- c = *buf;
- } while (len >= 5 && (c == ' ' || c == '\t' || c == '\r' || c == '\n'));
-
- if (len < 5)
- {
- break; // not enough characters left for a G1 Zx.x command
- }
-
- ++buf; // move to 1 character beyond c
- --len;
-
- // In theory we should skip N and a line number here if they are present, but no slicers seem to generate line numbers
- if (c == 'G')
- {
- if (inRelativeMode)
- {
- // We have seen a G91 in this buffer already, so we are only interested in G90 commands that switch back to absolute mode
- if (buf[0] == '9' && buf[1] == '0' && (buf[2] < '0' || buf[2] > '9'))
- {
- // It's a G90 command so go back to absolute mode
- inRelativeMode = false;
- }
- }
- else if (*buf == '1' || *buf == '0')
- {
- // It could be a G0 or G1 command
- ++buf;
- --len;
- if (*buf < '0' || *buf > '9')
- {
- // It is a G0 or G1 command. See if it has a Z parameter.
- while (len >= 4)
- {
- c = *buf;
- if (c == 'Z')
- {
- const char* zpos = buf + 1;
- // Check special case of this code ending with ";E" or "; E" - ignore such codes
- while (len > 2 && *buf != '\n' && *buf != '\r' && *buf != ';')
- {
- ++buf;
- --len;
- }
- if ((len >= 2 && StringStartsWith(buf, ";E")) || (len >= 3 && StringStartsWith(buf, "; E")))
- {
- // Ignore this G1 Z command
- }
- else
- {
- height = strtod(zpos, nullptr);
- foundHeight = true;
- }
- break; // carry on looking for a later G1 Z command
- }
- if (c == ';' || c == '\n' || c == '\r')
- {
- break; // no Z parameter
- }
- ++buf;
- --len;
- }
- }
- }
- else if (buf[0] == '9' && buf[1] == '1' && (buf[2] < '0' || buf[2] > '9'))
- {
- // It's a G91 command
- inRelativeMode = true;
- }
- }
- else if (c == ';')
- {
- static const char kisslicerHeightString[] = " END_LAYER_OBJECT z=";
- if (len > 31 && StringStartsWith(buf, kisslicerHeightString))
- {
- height = strtod(buf + sizeof(kisslicerHeightString)/sizeof(char) - 1, nullptr);
- return true;
- }
- }
- }
- return foundHeight;
-}
-
-// Scan the buffer for the layer height. The buffer is null-terminated.
-bool PrintMonitor::FindLayerHeight(const char *buf, size_t len, float& layerHeight) const
-{
- static const char* const layerHeightStrings[] =
- {
- "layer_height", // slic3r
- "Layer height", // Cura
- "layerHeight", // S3D
- "layer_thickness_mm", // Kisslicer
- "layerThickness" // Matter Control
- };
-
- if (*buf != 0)
- {
- ++buf; // make sure we can look back 1 character after we find a match
- for (size_t i = 0; i < ARRAY_SIZE(layerHeightStrings); ++i) // search for each string in turn
- {
- const char *pos = buf;
- for(;;) // loop until success or strstr returns null
- {
- pos = strstr(pos, layerHeightStrings[i]);
- if (pos == nullptr)
- {
- break; // didn't find this string in the buffer, so try the next string
- }
-
- const char c = pos[-1]; // fetch the previous character
- pos += strlen(layerHeightStrings[i]); // skip the string we matched
- if (c == ' ' || c == ';' || c == '\t') // check we are not in the middle of a word
- {
- while (strchr(" \t=:,", *pos) != nullptr) // skip the possible separators
- {
- ++pos;
- }
- char *tailPtr;
- const float val = strtod(pos, &tailPtr);
- if (tailPtr != pos) // if we found and converted a number
- {
- layerHeight = val;
- return true;
- }
- }
- }
- }
- }
-
- return false;
-}
-
-bool PrintMonitor::FindSlicerInfo(const char* buf, size_t len, const StringRef& generatedBy) const
-{
- static const char * const GeneratedByStrings[] =
- {
- "generated by ", // slic3r and S3D
- ";Sliced by ", // ideaMaker
- "; KISSlicer", // KISSlicer
- ";Sliced at: ", // Cura (old)
- ";Generated with " // Cura (new)
- };
-
- size_t index = 0;
- const char* pos;
- do
- {
- pos = strstr(buf, GeneratedByStrings[index]);
- if (pos != nullptr)
- {
- break;
- }
- ++index;
- } while (index < ARRAY_SIZE(GeneratedByStrings));
-
- if (pos != nullptr)
- {
- const char* introString = "";
- switch (index)
- {
- default:
- pos += strlen(GeneratedByStrings[index]);
- break;
-
- case 2: // KISSlicer
- pos += 2;
- break;
-
- case 3: // Cura (old)
- introString = "Cura at ";
- pos += strlen(GeneratedByStrings[index]);
- break;
- }
-
- generatedBy.copy(introString);
- while (*pos >= ' ')
- {
- generatedBy.cat(*pos++);
- }
- return true;
- }
- return false;
-}
-
-// Scan the buffer for the filament used. The buffer is null-terminated.
-// Returns the number of filaments found.
-unsigned int PrintMonitor::FindFilamentUsed(const char* buf, size_t len, float *filamentUsed, size_t maxFilaments) const
-{
- unsigned int filamentsFound = 0;
-
- // Look for filament usage as generated by Slic3r and Cura
- const char* const filamentUsedStr1 = "ilament used"; // comment string used by slic3r and Cura, followed by filament used and "mm"
- const char* p = buf;
- while (filamentsFound < maxFilaments && (p = strstr(p, filamentUsedStr1)) != nullptr)
- {
- p += strlen(filamentUsedStr1);
- while(strchr(" :=\t", *p) != nullptr)
- {
- ++p; // this allows for " = " from default slic3r comment and ": " from default Cura comment
- }
- while (isDigit(*p))
- {
- char* q;
- filamentUsed[filamentsFound] = strtod(p, &q);
- p = q;
- if (*p == 'm')
- {
- ++p;
- if (*p == 'm')
- {
- ++p;
- }
- else
- {
- filamentUsed[filamentsFound] *= 1000.0; // Cura outputs filament used in metres not mm
- }
- }
- ++filamentsFound;
- while (strchr(", \t", *p) != nullptr)
- {
- ++p;
- }
- }
- }
-
- // Look for filament usage string generated by Ideamaker
- const char* const filamentUsedStr2 = ";Material#"; // comment string used by Ideamaker, e.g. ";Material#1 Used: 868.0"
- p = buf;
- while (filamentsFound < maxFilaments && (p = strstr(p, filamentUsedStr2)) != nullptr)
- {
- p += strlen(filamentUsedStr2);
- char *q;
- unsigned int num = strtoul(p, &q, 10);
- if (q != p && num < maxFilaments)
- {
- p = q;
- while(strchr(" Used:\t", *p) != nullptr)
- {
- ++p; // this allows for " Used: "
- }
- if (isDigit(*p))
- {
- filamentUsed[filamentsFound] = strtod(p, &q);
- ++filamentsFound;
- }
- }
- }
-
- // Look for filament usage as generated by S3D
- if (filamentsFound == 0)
- {
- const char *filamentLengthStr = "ilament length"; // comment string used by S3D
- p = buf;
- while (filamentsFound < maxFilaments && (p = strstr(p, filamentLengthStr)) != nullptr)
- {
- p += strlen(filamentLengthStr);
- while(strchr(" :=\t", *p) != nullptr)
- {
- ++p;
- }
- if (isDigit(*p))
- {
- filamentUsed[filamentsFound] = strtod(p, nullptr); // S3D reports filament usage in mm, no conversion needed
- ++filamentsFound;
- }
- }
- }
-
- // Look for filament usage as generated by recent KISSlicer versions
- if (filamentsFound == 0)
- {
- const char *filamentLengthStr = "; Ext ";
- p = buf;
- while (filamentsFound < maxFilaments && (p = strstr(p, filamentLengthStr)) != nullptr)
- {
- p += strlen(filamentLengthStr);
- while(isdigit(*p))
- {
- ++p;
- }
- while(strchr(" :=\t", *p) != nullptr)
- {
- ++p;
- }
-
- if (isDigit(*p))
- {
- filamentUsed[filamentsFound] = strtod(p, nullptr);
- ++filamentsFound;
- }
- }
- }
-
- // Special case: Old KISSlicer only generates the filament volume, so we need to calculate the length from it
- if (filamentsFound == 0)
- {
- const char *filamentVolumeStr = "; Estimated Build Volume: ";
- p = strstr(buf, filamentVolumeStr);
- if (p != nullptr)
- {
- const float filamentCMM = strtof(p + strlen(filamentVolumeStr), nullptr) * 1000.0;
- filamentUsed[filamentsFound++] = filamentCMM / (Pi * fsquare(platform.GetFilamentWidth() / 2.0));
- }
- }
-
- return filamentsFound;
-}
-
// This returns the amount of time the machine has printed without interruptions (i.e. pauses)
float PrintMonitor::GetPrintDuration() const
{
diff --git a/src/PrintMonitor.h b/src/PrintMonitor.h
index 78348514..e0544970 100644
--- a/src/PrintMonitor.h
+++ b/src/PrintMonitor.h
@@ -21,17 +21,7 @@ Licence: GPL
#define PRINTMONITOR_H
#include "RepRapFirmware.h"
-
-const FilePosition GCODE_HEADER_SIZE = 20000uL; // How many bytes to read from the header - I (DC) have a Kisslicer file with a layer height comment 14Kb from the start
-const FilePosition GCODE_FOOTER_SIZE = 400000uL; // How many bytes to read from the footer
-
-#if SAM4E || SAM4S || SAME70
-const size_t GCODE_READ_SIZE = 2048; // How many bytes to read in one go in GetFileInfo() (should be a multiple of 512 for read efficiency)
-#else
-const size_t GCODE_READ_SIZE = 1024; // How many bytes to read in one go in GetFileInfo() (should be a multiple of 512 for read efficiency)
-#endif
-
-const size_t GCODE_OVERLAP_SIZE = 100; // Size of the overlapping buffer for searching (should be a multiple of 4)
+#include "Storage/FileInfoParser.h" // for struct GCodeFileInfo
const float LAYER_HEIGHT_TOLERANCE = 0.015; // Tolerance for comparing two Z heights (in mm)
@@ -41,8 +31,6 @@ const float ESTIMATION_MIN_FILE_USAGE = 0.001; // Minimum per cent of the file
const float FIRST_LAYER_SPEED_FACTOR = 0.25; // First layer speed factor compared to other layers (only for layer-based estimation)
const uint32_t PRINTMONITOR_UPDATE_INTERVAL = 200; // Update interval in milliseconds
-const uint32_t MAX_FILEINFO_PROCESS_TIME = 200; // Maximum time to spend polling for file info in each call
-const uint32_t MaxFileParseInterval = 4000; // Maximum interval between repeat requests to parse a file
enum PrintEstimationMethod
{
@@ -51,28 +39,6 @@ enum PrintEstimationMethod
layerBased
};
-// Struct to hold Gcode file information
-struct GCodeFileInfo
-{
- bool isValid;
- FilePosition fileSize;
- time_t lastModifiedTime;
- float firstLayerHeight;
- float objectHeight;
- float filamentNeeded[MaxExtruders];
- unsigned int numFilaments;
- float layerHeight;
- String<50> generatedBy;
-};
-
-enum FileParseState
-{
- notParsing,
- parsingHeader,
- seeking,
- parsingFooter
-};
-
class PrintMonitor
{
public:
@@ -85,10 +51,6 @@ class PrintMonitor
void StartedPrint(); // Called whenever a new live print starts (see M24)
void StoppedPrint(); // Called whenever a file print has stopped
- // The following two methods need to be called until they return true - this may take a few runs
- bool GetFileInfo(const char *directory, const char *fileName, GCodeFileInfo& info);
- bool GetFileInfoResponse(const char *filename, OutputBuffer *&response);
-
// Return an estimate in seconds based on a specific estimation method
float EstimateTimeLeft(PrintEstimationMethod method) const;
@@ -101,13 +63,13 @@ class PrintMonitor
float GetFirstLayerHeight() const;
const char *GetPrintingFilename() const { return (isPrinting) ? filenameBeingPrinted.c_str() : nullptr; }
+ bool GetPrintingFileInfoResponse(OutputBuffer *&response) const;
private:
Platform& platform;
GCodes& gCodes;
uint32_t longWait;
uint32_t lastUpdateTime;
- uint32_t lastFileParseTime;
// Information/Events concerning the file being printed
void WarmUpComplete();
@@ -130,28 +92,9 @@ class PrintMonitor
float fileProgressPerLayer[MAX_LAYER_SAMPLES];
float layerEstimatedTimeLeft;
- // We parse G-Code files in multiple stages. These variables hold the required information
- FileParseState parseState;
- String<MaxFilenameLength> filenameBeingParsed;
- FileStore *fileBeingParsed;
- FilePosition nextSeekPos;
- GCodeFileInfo parsedFileInfo;
-
- char fileOverlap[GCODE_OVERLAP_SIZE];
- size_t fileOverlapLength;
-
bool printingFileParsed;
GCodeFileInfo printingFileInfo;
String<MaxFilenameLength> filenameBeingPrinted;
-
- // G-Code parser methods
- bool FindHeight(const char* buf, size_t len, float& height) const;
- bool FindFirstLayerHeight(const char* buf, size_t len, float& layerHeight) const;
- bool FindLayerHeight(const char* buf, size_t len, float& layerHeight) const;
- bool FindSlicerInfo(const char* buf, size_t len, const StringRef& generatedBy) const;
- unsigned int FindFilamentUsed(const char* buf, size_t len, float *filamentUsed, size_t maxFilaments) const;
-
- uint32_t accumulatedParseTime, accumulatedReadTime, accumulatedSeekTime;
};
inline bool PrintMonitor::IsPrinting() const { return isPrinting; }
diff --git a/src/RepRap.cpp b/src/RepRap.cpp
index f97932d5..a8108f72 100644
--- a/src/RepRap.cpp
+++ b/src/RepRap.cpp
@@ -1618,6 +1618,83 @@ OutputBuffer *RepRap::GetFilesResponse(const char *dir, bool flagsDirs)
return response;
}
+// Get information for the specified file, or the currently printing file, in JSON format
+bool RepRap::GetFileInfoResponse(const char *filename, OutputBuffer *&response, bool quitEarly)
+{
+ // Poll file info for a specific file
+ if (filename != nullptr && filename[0] != 0)
+ {
+ GCodeFileInfo info;
+ if (!platform->GetMassStorage()->GetFileInfo(FS_PREFIX, filename, info, quitEarly))
+ {
+ // This may take a few runs...
+ return false;
+ }
+
+ if (info.isValid)
+ {
+ if (!OutputBuffer::Allocate(response))
+ {
+ // Should never happen
+ return false;
+ }
+
+ response->printf("{\"err\":0,\"size\":%lu,",info.fileSize);
+ const struct tm * const timeInfo = gmtime(&info.lastModifiedTime);
+ if (timeInfo->tm_year > /*19*/80)
+ {
+ response->catf("\"lastModified\":\"%04u-%02u-%02uT%02u:%02u:%02u\",",
+ timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday,
+ timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
+ }
+
+ response->catf("\"height\":%.2f,\"firstLayerHeight\":%.2f,\"layerHeight\":%.2f,\"filament\":",
+ (double)info.objectHeight, (double)info.firstLayerHeight, (double)info.layerHeight);
+ char ch = '[';
+ if (info.numFilaments == 0)
+ {
+ response->cat(ch);
+ }
+ else
+ {
+ for (size_t i = 0; i < info.numFilaments; ++i)
+ {
+ response->catf("%c%.1f", ch, (double)info.filamentNeeded[i]);
+ ch = ',';
+ }
+ }
+ response->cat("],\"generatedBy\":");
+ response->EncodeString(info.generatedBy.c_str(), info.generatedBy.Capacity(), false);
+ response->cat("}");
+ }
+ else
+ {
+ if (!OutputBuffer::Allocate(response))
+ {
+ // Should never happen
+ return false;
+ }
+
+ response->copy("{\"err\":1}");
+ }
+ }
+ else if (GetPrintMonitor().IsPrinting())
+ {
+ return GetPrintMonitor().GetPrintingFileInfoResponse(response);
+ }
+ else
+ {
+ if (!OutputBuffer::Allocate(response))
+ {
+ // Should never happen
+ return false;
+ }
+
+ response->copy("{\"err\":1}");
+ }
+ return true;
+}
+
// Get a JSON-style filelist including file types and sizes
OutputBuffer *RepRap::GetFilelistResponse(const char *dir)
{
@@ -1757,6 +1834,7 @@ char RepRap::GetStatusCharacter() const
return (processingConfig) ? 'C' // Reading the configuration file
: (gCodes->IsFlashing()) ? 'F' // Flashing a new firmware binary
: (IsStopped()) ? 'H' // Halted
+ : (!platform->HasVinPower()) ? 'O' // Off i.e. powered down
: (gCodes->IsPausing()) ? 'D' // Pausing / Decelerating
: (gCodes->IsResuming()) ? 'R' // Resuming
: (gCodes->IsDoingToolChange()) ? 'T' // Changing tool
diff --git a/src/RepRap.h b/src/RepRap.h
index 145d23e5..dcdc62fe 100644
--- a/src/RepRap.h
+++ b/src/RepRap.h
@@ -102,6 +102,7 @@ public:
OutputBuffer *GetLegacyStatusResponse(uint8_t type, int seq);
OutputBuffer *GetFilesResponse(const char* dir, bool flagsDirs);
OutputBuffer *GetFilelistResponse(const char* dir);
+ bool GetFileInfoResponse(const char *filename, OutputBuffer *&response, bool quitEarly);
void Beep(unsigned int freq, unsigned int ms);
void SetMessage(const char *msg);
diff --git a/src/Storage/FileInfoParser.cpp b/src/Storage/FileInfoParser.cpp
new file mode 100644
index 00000000..fcd78500
--- /dev/null
+++ b/src/Storage/FileInfoParser.cpp
@@ -0,0 +1,739 @@
+/*
+ * FileInfoParser.cpp
+ *
+ * Created on: 31 Mar 2018
+ * Author: David
+ */
+
+#include "FileInfoParser.h"
+#include "OutputMemory.h"
+#include "RepRap.h"
+#include "Platform.h"
+#include "PrintMonitor.h"
+#include "GCodes/GCodes.h"
+
+void GCodeFileInfo::Init()
+{
+ isValid = false;
+ incomplete = true;
+ firstLayerHeight = 0.0;
+ objectHeight = 0.0;
+ layerHeight = 0.0;
+ numFilaments = 0;
+ generatedBy.Clear();
+ for (size_t extr = 0; extr < MaxExtruders; extr++)
+ {
+ filamentNeeded[extr] = 0.0;
+ }
+}
+
+FileInfoParser::FileInfoParser()
+ : parseState(notParsing), fileBeingParsed(nullptr), accumulatedParseTime(0), accumulatedReadTime(0), accumulatedSeekTime(0), fileOverlapLength(0)
+{
+ parsedFileInfo.Init();
+ parserMutexHandle = RTOSIface::CreateMutex(parserMutexStorage);
+}
+
+bool FileInfoParser::GetFileInfo(const char *directory, const char *fileName, GCodeFileInfo& info, bool quitEarly)
+{
+ Locker lock(parserMutexHandle, MAX_FILEINFO_PROCESS_TIME);
+ if (!lock)
+ {
+ return false;
+ }
+
+ if (parseState != notParsing && !StringEquals(fileName, filenameBeingParsed.c_str()))
+ {
+ // We are already parsing a different file
+ if (millis() - lastFileParseTime < MaxFileParseInterval)
+ {
+ return false; // try again later
+ }
+
+ // Time this client out because it has probably disconnected
+ fileBeingParsed->Close();
+ parseState = notParsing;
+ }
+
+ if (parseState == notParsing)
+ {
+ // See if we can access the file
+ // Webserver may call rr_fileinfo for a directory, check this case here
+ if (reprap.GetPlatform().GetMassStorage()->DirectoryExists(directory, fileName))
+ {
+ info.isValid = false;
+ return true;
+ }
+
+ fileBeingParsed = reprap.GetPlatform().OpenFile(directory, fileName, OpenMode::read);
+ if (fileBeingParsed == nullptr)
+ {
+ // Something went wrong - we cannot open it
+ info.isValid = false;
+ return true;
+ }
+
+ // File has been opened, let's start now
+ filenameBeingParsed.copy(fileName);
+ fileOverlapLength = 0;
+
+ // Set up the info struct
+ parsedFileInfo.Init();
+ parsedFileInfo.fileSize = fileBeingParsed->Length();
+ parsedFileInfo.lastModifiedTime = reprap.GetPlatform().GetMassStorage()->GetLastModifiedTime(directory, fileName);
+ parsedFileInfo.isValid = true;
+
+ // Record some debug values here
+ if (reprap.Debug(modulePrintMonitor))
+ {
+ accumulatedReadTime = accumulatedParseTime = 0;
+ reprap.GetPlatform().MessageF(UsbMessage, "-- Parsing file %s --\n", fileName);
+ }
+
+ // If the file is empty or not a G-Code file, we don't need to parse anything
+ if (fileBeingParsed->Length() == 0 || (!StringEndsWith(fileName, ".gcode") && !StringEndsWith(fileName, ".g")
+ && !StringEndsWith(fileName, ".gco") && !StringEndsWith(fileName, ".gc")))
+ {
+ fileBeingParsed->Close();
+ parsedFileInfo.incomplete = false;
+ info = parsedFileInfo;
+ return true;
+ }
+ parseState = parsingHeader;
+ }
+
+ // Getting file information take a few runs. Speed it up when we are not printing by calling it several times.
+ const uint32_t loopStartTime = millis();
+ do
+ {
+ uint32_t buf32[(GCODE_READ_SIZE + GCODE_OVERLAP_SIZE + 3)/4 + 1]; // buffer should be 32-bit aligned for HSMCI (need the +1 so we can add a null terminator)
+ char* const buf = reinterpret_cast<char*>(buf32);
+ size_t sizeToRead, sizeToScan; // number of bytes we want to read and scan in this go
+
+ switch (parseState)
+ {
+ case parsingHeader:
+ {
+ bool headerInfoComplete = true;
+
+ // Read a chunk from the header. On the first run only process GCODE_READ_SIZE bytes, but use overlap next times.
+ sizeToRead = (size_t)min<FilePosition>(fileBeingParsed->Length() - fileBeingParsed->Position(), GCODE_READ_SIZE);
+ if (fileOverlapLength > 0)
+ {
+ memcpy(buf, fileOverlap, fileOverlapLength);
+ sizeToScan = sizeToRead + fileOverlapLength;
+ }
+ else
+ {
+ sizeToScan = sizeToRead;
+ }
+
+ uint32_t startTime = millis();
+ const int nbytes = fileBeingParsed->Read(&buf[fileOverlapLength], sizeToRead);
+ if (nbytes != (int)sizeToRead)
+ {
+ reprap.GetPlatform().MessageF(ErrorMessage, "Failed to read header of G-Code file \"%s\"\n", fileName);
+ parseState = notParsing;
+ fileBeingParsed->Close();
+ info = parsedFileInfo;
+ return true;
+ }
+ buf[sizeToScan] = 0;
+
+ // Record performance data
+ uint32_t now = millis();
+ accumulatedReadTime += now - startTime;
+ startTime = now;
+
+ // Search for filament usage (Cura puts it at the beginning of a G-code file)
+ if (parsedFileInfo.numFilaments == 0)
+ {
+ parsedFileInfo.numFilaments = FindFilamentUsed(buf, sizeToScan, parsedFileInfo.filamentNeeded, DRIVES - reprap.GetGCodes().GetTotalAxes());
+ headerInfoComplete &= (parsedFileInfo.numFilaments != 0);
+ }
+
+ // Look for first layer height
+ if (parsedFileInfo.firstLayerHeight == 0.0)
+ {
+ headerInfoComplete &= FindFirstLayerHeight(buf, sizeToScan, parsedFileInfo.firstLayerHeight);
+ }
+
+ // Look for layer height
+ if (parsedFileInfo.layerHeight == 0.0)
+ {
+ headerInfoComplete &= FindLayerHeight(buf, sizeToScan, parsedFileInfo.layerHeight);
+ }
+
+ // Look for slicer program
+ if (parsedFileInfo.generatedBy.IsEmpty())
+ {
+ headerInfoComplete &= FindSlicerInfo(buf, sizeToScan, parsedFileInfo.generatedBy.GetRef());
+ }
+
+ // Keep track of the time stats
+ accumulatedParseTime += millis() - startTime;
+
+ // Can we proceed to the footer? Don't scan more than the first 4KB of the file
+ FilePosition pos = fileBeingParsed->Position();
+ if (headerInfoComplete || pos >= GCODE_HEADER_SIZE || pos == fileBeingParsed->Length())
+ {
+ // Yes - see if we need to output some debug info
+ if (reprap.Debug(modulePrintMonitor))
+ {
+ reprap.GetPlatform().MessageF(UsbMessage, "Header complete, processed %lu bytes, read time %.3fs, parse time %.3fs\n",
+ fileBeingParsed->Position(), (double)((float)accumulatedReadTime/1000.0), (double)((float)accumulatedParseTime/1000.0));
+ }
+
+ // Go to the last chunk and proceed from there on
+ const FilePosition seekFromEnd = ((fileBeingParsed->Length() - 1) % GCODE_READ_SIZE) + 1;
+ nextSeekPos = fileBeingParsed->Length() - seekFromEnd;
+ accumulatedSeekTime = accumulatedReadTime = accumulatedParseTime = 0;
+ fileOverlapLength = 0;
+ parseState = seeking;
+ }
+ else
+ {
+ // No - copy the last chunk of the buffer for overlapping search
+ fileOverlapLength = min<size_t>(sizeToRead, GCODE_OVERLAP_SIZE);
+ memcpy(fileOverlap, &buf[sizeToRead - fileOverlapLength], fileOverlapLength);
+ }
+ }
+ break;
+
+ case seeking:
+ // Seeking into a large file can take a long time using the FAT file system, so do it in stages
+ {
+ FilePosition currentPos = fileBeingParsed->Position();
+ const uint32_t clsize = fileBeingParsed->ClusterSize();
+ if (currentPos/clsize > nextSeekPos/clsize)
+ {
+ // Seeking backwards over a cluster boundary, so in practice the seek will start from the start of the file
+ currentPos = 0;
+ }
+
+ // Seek at most 512 clusters at a time
+ const FilePosition maxSeekDistance = 512 * (FilePosition)clsize;
+ const bool doFullSeek = (nextSeekPos <= currentPos + maxSeekDistance);
+ const FilePosition thisSeekPos = (doFullSeek) ? nextSeekPos : currentPos + maxSeekDistance;
+
+ const uint32_t startTime = millis();
+ if (!fileBeingParsed->Seek(thisSeekPos))
+ {
+ reprap.GetPlatform().Message(ErrorMessage, "Could not seek from end of file!\n");
+ parseState = notParsing;
+ fileBeingParsed->Close();
+ info = parsedFileInfo;
+ return true;
+ }
+ accumulatedSeekTime += millis() - startTime;
+ if (doFullSeek)
+ {
+ parseState = parsingFooter;
+ }
+ }
+ break;
+
+ case parsingFooter:
+ {
+ // Processing the footer. See how many bytes we need to read and if we can reuse the overlap
+ sizeToRead = (size_t)min<FilePosition>(fileBeingParsed->Length() - nextSeekPos, GCODE_READ_SIZE);
+ if (fileOverlapLength > 0)
+ {
+ memcpy(&buf[sizeToRead], fileOverlap, fileOverlapLength);
+ sizeToScan = sizeToRead + fileOverlapLength;
+ }
+ else
+ {
+ sizeToScan = sizeToRead;
+ }
+
+ // Read another chunk from the footer
+ uint32_t startTime = millis();
+ int nbytes = fileBeingParsed->Read(buf, sizeToRead);
+ if (nbytes != (int)sizeToRead)
+ {
+ reprap.GetPlatform().MessageF(ErrorMessage, "Failed to read footer from G-Code file \"%s\"\n", fileName);
+ parseState = notParsing;
+ fileBeingParsed->Close();
+ info = parsedFileInfo;
+ return true;
+ }
+ buf[sizeToScan] = 0;
+
+ // Record performance data
+ uint32_t now = millis();
+ accumulatedReadTime += now - startTime;
+ startTime = now;
+
+ bool footerInfoComplete = true;
+
+ // Search for filament used
+ if (parsedFileInfo.numFilaments == 0)
+ {
+ parsedFileInfo.numFilaments = FindFilamentUsed(buf, sizeToScan, parsedFileInfo.filamentNeeded, DRIVES - reprap.GetGCodes().GetTotalAxes());
+ if (parsedFileInfo.numFilaments == 0)
+ {
+ footerInfoComplete = false;
+ }
+ }
+
+ // Search for layer height
+ if (parsedFileInfo.layerHeight == 0.0)
+ {
+ if (!FindLayerHeight(buf, sizeToScan, parsedFileInfo.layerHeight))
+ {
+ footerInfoComplete = false;
+ }
+ }
+
+ // Search for object height
+ if (parsedFileInfo.objectHeight == 0.0)
+ {
+ if (!FindHeight(buf, sizeToScan, parsedFileInfo.objectHeight))
+ {
+ footerInfoComplete = false;
+ }
+ }
+
+ // Keep track of the time stats
+ accumulatedParseTime += millis() - startTime;
+
+ // If we've collected all details, scanned the last 192K of the file or if we cannot go any further, stop here.
+ if (footerInfoComplete || nextSeekPos == 0 || fileBeingParsed->Length() - nextSeekPos >= GCODE_FOOTER_SIZE)
+ {
+ if (reprap.Debug(modulePrintMonitor))
+ {
+ reprap.GetPlatform().MessageF(UsbMessage, "Footer complete, processed %lu bytes, read time %.3fs, parse time %.3fs, seek time %.3fs\n",
+ fileBeingParsed->Length() - fileBeingParsed->Position() + GCODE_READ_SIZE,
+ (double)((float)accumulatedReadTime/1000.0), (double)((float)accumulatedParseTime/1000.0), (double)((float)accumulatedSeekTime/1000.0));
+ }
+ parseState = notParsing;
+ fileBeingParsed->Close();
+ parsedFileInfo.incomplete = false;
+ info = parsedFileInfo;
+ return true;
+ }
+
+ // Else go back further
+ fileOverlapLength = (size_t)min<FilePosition>(sizeToScan, GCODE_OVERLAP_SIZE);
+ memcpy(fileOverlap, buf, fileOverlapLength);
+ nextSeekPos = (nextSeekPos <= GCODE_READ_SIZE) ? 0 : nextSeekPos - GCODE_READ_SIZE;
+ parseState = seeking;
+ }
+ break;
+
+ default: // should not get here
+ parsedFileInfo.incomplete = false;
+ info = parsedFileInfo;
+ parseState = notParsing;
+ return true;
+ }
+ lastFileParseTime = millis();
+ } while (!reprap.GetPrintMonitor().IsPrinting() && lastFileParseTime - loopStartTime < MAX_FILEINFO_PROCESS_TIME);
+
+ if (quitEarly)
+ {
+ info = parsedFileInfo; // note that the 'incomplete' flag is still set
+ parseState = notParsing;
+ return true;
+ }
+ return false;
+}
+
+// Scan the buffer for a G1 Zxxx command. The buffer is null-terminated.
+bool FileInfoParser::FindFirstLayerHeight(const char* buf, size_t len, float& height) const
+{
+ if (len < 4)
+ {
+ // Don't start if the buffer is not big enough
+ return false;
+ }
+ height = 0.0;
+
+//debugPrintf("Scanning %u bytes starting %.100s\n", len, buf);
+ bool inComment = false, inRelativeMode = false, foundHeight = false;
+ for(size_t i = 0; i < len - 4; i++)
+ {
+ if (buf[i] == ';')
+ {
+ inComment = true;
+ }
+ else if (inComment)
+ {
+ if (buf[i] == '\n')
+ {
+ inComment = false;
+ }
+ }
+ else if (buf[i] == 'G')
+ {
+ // See if we can switch back to absolute mode
+ if (inRelativeMode)
+ {
+ inRelativeMode = !(buf[i + 1] == '9' && buf[i + 2] == '0' && buf[i + 3] <= ' ');
+ }
+ // Ignore G0/G1 codes if in relative mode
+ else if (buf[i + 1] == '9' && buf[i + 2] == '1' && buf[i + 3] <= ' ')
+ {
+ inRelativeMode = true;
+ }
+ // Look for "G0/G1 ... Z#HEIGHT#" command
+ else if ((buf[i + 1] == '0' || buf[i + 1] == '1') && buf[i + 2] == ' ')
+ {
+ for(i += 3; i < len - 4; i++)
+ {
+ if (buf[i] == 'Z')
+ {
+ //debugPrintf("Found at offset %u text: %.100s\n", i, &buf[i + 1]);
+ float flHeight = strtod(&buf[i + 1], nullptr);
+ if ((height == 0.0 || flHeight < height) && (flHeight <= reprap.GetPlatform().GetNozzleDiameter() * 3.0))
+ {
+ height = flHeight; // Only report first Z height if it's somewhat reasonable
+ foundHeight = true;
+ // NB: Don't stop here, because some slicers generate two Z moves at the beginning
+ }
+ break;
+ }
+ else if (buf[i] == ';')
+ {
+ // Ignore comments
+ break;
+ }
+ }
+ }
+ }
+ }
+ return foundHeight;
+}
+
+// Scan the buffer for a G1 Zxxx command. The buffer is null-terminated.
+// This parsing algorithm needs to be fast. The old one sometimes took 5 seconds or more to parse about 120K of data.
+// To speed up parsing, we now parse forwards from the start of the buffer. This means we can't stop when we have found a G1 Z command,
+// we have to look for a later G1 Z command in the buffer. But it is faster in the (common) case that we don't find a match in the buffer at all.
+bool FileInfoParser::FindHeight(const char* buf, size_t len, float& height) const
+{
+ bool foundHeight = false;
+ bool inRelativeMode = false;
+ for(;;)
+ {
+ // Skip to next newline
+ char c;
+ while (len >= 6 && (c = *buf) != '\r' && c != '\n')
+ {
+ ++buf;
+ --len;
+ }
+
+ // Skip the newline and any leading spaces
+ do
+ {
+ ++buf; // skip the newline
+ --len;
+ c = *buf;
+ } while (len >= 5 && (c == ' ' || c == '\t' || c == '\r' || c == '\n'));
+
+ if (len < 5)
+ {
+ break; // not enough characters left for a G1 Zx.x command
+ }
+
+ ++buf; // move to 1 character beyond c
+ --len;
+
+ // In theory we should skip N and a line number here if they are present, but no slicers seem to generate line numbers
+ if (c == 'G')
+ {
+ if (inRelativeMode)
+ {
+ // We have seen a G91 in this buffer already, so we are only interested in G90 commands that switch back to absolute mode
+ if (buf[0] == '9' && buf[1] == '0' && (buf[2] < '0' || buf[2] > '9'))
+ {
+ // It's a G90 command so go back to absolute mode
+ inRelativeMode = false;
+ }
+ }
+ else if (*buf == '1' || *buf == '0')
+ {
+ // It could be a G0 or G1 command
+ ++buf;
+ --len;
+ if (*buf < '0' || *buf > '9')
+ {
+ // It is a G0 or G1 command. See if it has a Z parameter.
+ while (len >= 4)
+ {
+ c = *buf;
+ if (c == 'Z')
+ {
+ const char* zpos = buf + 1;
+ // Check special case of this code ending with ";E" or "; E" - ignore such codes
+ while (len > 2 && *buf != '\n' && *buf != '\r' && *buf != ';')
+ {
+ ++buf;
+ --len;
+ }
+ if ((len >= 2 && StringStartsWith(buf, ";E")) || (len >= 3 && StringStartsWith(buf, "; E")))
+ {
+ // Ignore this G1 Z command
+ }
+ else
+ {
+ height = strtod(zpos, nullptr);
+ foundHeight = true;
+ }
+ break; // carry on looking for a later G1 Z command
+ }
+ if (c == ';' || c == '\n' || c == '\r')
+ {
+ break; // no Z parameter
+ }
+ ++buf;
+ --len;
+ }
+ }
+ }
+ else if (buf[0] == '9' && buf[1] == '1' && (buf[2] < '0' || buf[2] > '9'))
+ {
+ // It's a G91 command
+ inRelativeMode = true;
+ }
+ }
+ else if (c == ';')
+ {
+ static const char kisslicerHeightString[] = " END_LAYER_OBJECT z=";
+ if (len > 31 && StringStartsWith(buf, kisslicerHeightString))
+ {
+ height = strtod(buf + sizeof(kisslicerHeightString)/sizeof(char) - 1, nullptr);
+ return true;
+ }
+ }
+ }
+ return foundHeight;
+}
+
+// Scan the buffer for the layer height. The buffer is null-terminated.
+bool FileInfoParser::FindLayerHeight(const char *buf, size_t len, float& layerHeight) const
+{
+ static const char* const layerHeightStrings[] =
+ {
+ "layer_height", // slic3r
+ "Layer height", // Cura
+ "layerHeight", // S3D
+ "layer_thickness_mm", // Kisslicer
+ "layerThickness" // Matter Control
+ };
+
+ if (*buf != 0)
+ {
+ ++buf; // make sure we can look back 1 character after we find a match
+ for (size_t i = 0; i < ARRAY_SIZE(layerHeightStrings); ++i) // search for each string in turn
+ {
+ const char *pos = buf;
+ for(;;) // loop until success or strstr returns null
+ {
+ pos = strstr(pos, layerHeightStrings[i]);
+ if (pos == nullptr)
+ {
+ break; // didn't find this string in the buffer, so try the next string
+ }
+
+ const char c = pos[-1]; // fetch the previous character
+ pos += strlen(layerHeightStrings[i]); // skip the string we matched
+ if (c == ' ' || c == ';' || c == '\t') // check we are not in the middle of a word
+ {
+ while (strchr(" \t=:,", *pos) != nullptr) // skip the possible separators
+ {
+ ++pos;
+ }
+ char *tailPtr;
+ const float val = strtod(pos, &tailPtr);
+ if (tailPtr != pos) // if we found and converted a number
+ {
+ layerHeight = val;
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+bool FileInfoParser::FindSlicerInfo(const char* buf, size_t len, const StringRef& generatedBy) const
+{
+ static const char * const GeneratedByStrings[] =
+ {
+ "generated by ", // slic3r and S3D
+ ";Sliced by ", // ideaMaker
+ "; KISSlicer", // KISSlicer
+ ";Sliced at: ", // Cura (old)
+ ";Generated with " // Cura (new)
+ };
+
+ size_t index = 0;
+ const char* pos;
+ do
+ {
+ pos = strstr(buf, GeneratedByStrings[index]);
+ if (pos != nullptr)
+ {
+ break;
+ }
+ ++index;
+ } while (index < ARRAY_SIZE(GeneratedByStrings));
+
+ if (pos != nullptr)
+ {
+ const char* introString = "";
+ switch (index)
+ {
+ default:
+ pos += strlen(GeneratedByStrings[index]);
+ break;
+
+ case 2: // KISSlicer
+ pos += 2;
+ break;
+
+ case 3: // Cura (old)
+ introString = "Cura at ";
+ pos += strlen(GeneratedByStrings[index]);
+ break;
+ }
+
+ generatedBy.copy(introString);
+ while (*pos >= ' ')
+ {
+ generatedBy.cat(*pos++);
+ }
+ return true;
+ }
+ return false;
+}
+
+// Scan the buffer for the filament used. The buffer is null-terminated.
+// Returns the number of filaments found.
+unsigned int FileInfoParser::FindFilamentUsed(const char* buf, size_t len, float *filamentUsed, size_t maxFilaments) const
+{
+ unsigned int filamentsFound = 0;
+
+ // Look for filament usage as generated by Slic3r and Cura
+ const char* const filamentUsedStr1 = "ilament used"; // comment string used by slic3r and Cura, followed by filament used and "mm"
+ const char* p = buf;
+ while (filamentsFound < maxFilaments && (p = strstr(p, filamentUsedStr1)) != nullptr)
+ {
+ p += strlen(filamentUsedStr1);
+ while(strchr(" :=\t", *p) != nullptr)
+ {
+ ++p; // this allows for " = " from default slic3r comment and ": " from default Cura comment
+ }
+ while (isDigit(*p))
+ {
+ char* q;
+ filamentUsed[filamentsFound] = strtod(p, &q);
+ p = q;
+ if (*p == 'm')
+ {
+ ++p;
+ if (*p == 'm')
+ {
+ ++p;
+ }
+ else
+ {
+ filamentUsed[filamentsFound] *= 1000.0; // Cura outputs filament used in metres not mm
+ }
+ }
+ ++filamentsFound;
+ while (strchr(", \t", *p) != nullptr)
+ {
+ ++p;
+ }
+ }
+ }
+
+ // Look for filament usage string generated by Ideamaker
+ const char* const filamentUsedStr2 = ";Material#"; // comment string used by Ideamaker, e.g. ";Material#1 Used: 868.0"
+ p = buf;
+ while (filamentsFound < maxFilaments && (p = strstr(p, filamentUsedStr2)) != nullptr)
+ {
+ p += strlen(filamentUsedStr2);
+ char *q;
+ unsigned int num = strtoul(p, &q, 10);
+ if (q != p && num < maxFilaments)
+ {
+ p = q;
+ while(strchr(" Used:\t", *p) != nullptr)
+ {
+ ++p; // this allows for " Used: "
+ }
+ if (isDigit(*p))
+ {
+ filamentUsed[filamentsFound] = strtod(p, &q);
+ ++filamentsFound;
+ }
+ }
+ }
+
+ // Look for filament usage as generated by S3D
+ if (filamentsFound == 0)
+ {
+ const char *filamentLengthStr = "ilament length"; // comment string used by S3D
+ p = buf;
+ while (filamentsFound < maxFilaments && (p = strstr(p, filamentLengthStr)) != nullptr)
+ {
+ p += strlen(filamentLengthStr);
+ while(strchr(" :=\t", *p) != nullptr)
+ {
+ ++p;
+ }
+ if (isDigit(*p))
+ {
+ filamentUsed[filamentsFound] = strtod(p, nullptr); // S3D reports filament usage in mm, no conversion needed
+ ++filamentsFound;
+ }
+ }
+ }
+
+ // Look for filament usage as generated by recent KISSlicer versions
+ if (filamentsFound == 0)
+ {
+ const char *filamentLengthStr = "; Ext ";
+ p = buf;
+ while (filamentsFound < maxFilaments && (p = strstr(p, filamentLengthStr)) != nullptr)
+ {
+ p += strlen(filamentLengthStr);
+ while(isdigit(*p))
+ {
+ ++p;
+ }
+ while(strchr(" :=\t", *p) != nullptr)
+ {
+ ++p;
+ }
+
+ if (isDigit(*p))
+ {
+ filamentUsed[filamentsFound] = strtod(p, nullptr);
+ ++filamentsFound;
+ }
+ }
+ }
+
+ // Special case: Old KISSlicer only generates the filament volume, so we need to calculate the length from it
+ if (filamentsFound == 0)
+ {
+ const char *filamentVolumeStr = "; Estimated Build Volume: ";
+ p = strstr(buf, filamentVolumeStr);
+ if (p != nullptr)
+ {
+ const float filamentCMM = strtof(p + strlen(filamentVolumeStr), nullptr) * 1000.0;
+ filamentUsed[filamentsFound++] = filamentCMM / (Pi * fsquare(reprap.GetPlatform().GetFilamentWidth() / 2.0));
+ }
+ }
+
+ return filamentsFound;
+}
+
+// End
diff --git a/src/Storage/FileInfoParser.h b/src/Storage/FileInfoParser.h
new file mode 100644
index 00000000..059d775c
--- /dev/null
+++ b/src/Storage/FileInfoParser.h
@@ -0,0 +1,86 @@
+/*
+ * FileInfoParser.h
+ *
+ * Created on: 31 Mar 2018
+ * Author: David
+ */
+
+#ifndef SRC_STORAGE_FILEINFOPARSER_H_
+#define SRC_STORAGE_FILEINFOPARSER_H_
+
+#include "RepRapFirmware.h"
+#include "RTOSIface.h"
+
+const FilePosition GCODE_HEADER_SIZE = 20000uL; // How many bytes to read from the header - I (DC) have a Kisslicer file with a layer height comment 14Kb from the start
+const FilePosition GCODE_FOOTER_SIZE = 400000uL; // How many bytes to read from the footer
+
+#if SAM4E || SAM4S || SAME70
+const size_t GCODE_READ_SIZE = 2048; // How many bytes to read in one go in GetFileInfo() (should be a multiple of 512 for read efficiency)
+#else
+const size_t GCODE_READ_SIZE = 1024; // How many bytes to read in one go in GetFileInfo() (should be a multiple of 512 for read efficiency)
+#endif
+
+const size_t GCODE_OVERLAP_SIZE = 100; // Size of the overlapping buffer for searching (should be a multiple of 4)
+
+const uint32_t MAX_FILEINFO_PROCESS_TIME = 200; // Maximum time to spend polling for file info in each call
+const uint32_t MaxFileParseInterval = 4000; // Maximum interval between repeat requests to parse a file
+
+// Struct to hold Gcode file information
+struct GCodeFileInfo
+{
+ FilePosition fileSize;
+ time_t lastModifiedTime;
+ float layerHeight;
+ float firstLayerHeight;
+ float objectHeight;
+ float filamentNeeded[MaxExtruders];
+ unsigned int numFilaments;
+ bool isValid;
+ bool incomplete;
+ String<50> generatedBy;
+
+ void Init();
+};
+
+enum FileParseState
+{
+ notParsing,
+ parsingHeader,
+ seeking,
+ parsingFooter
+};
+
+class FileInfoParser
+{
+public:
+ FileInfoParser();
+
+ // The following method needs to be called until it returns true - this may take a few runs
+ bool GetFileInfo(const char *directory, const char *fileName, GCodeFileInfo& info, bool quitEarly);
+
+private:
+
+ // G-Code parser methods
+ bool FindHeight(const char* buf, size_t len, float& height) const;
+ bool FindFirstLayerHeight(const char* buf, size_t len, float& layerHeight) const;
+ bool FindLayerHeight(const char* buf, size_t len, float& layerHeight) const;
+ bool FindSlicerInfo(const char* buf, size_t len, const StringRef& generatedBy) const;
+ unsigned int FindFilamentUsed(const char* buf, size_t len, float *filamentUsed, size_t maxFilaments) const;
+
+ // We parse G-Code files in multiple stages. These variables hold the required information
+ MutexHandle parserMutexHandle;
+ MutexStorage parserMutexStorage;
+
+ FileParseState parseState;
+ String<MaxFilenameLength> filenameBeingParsed;
+ FileStore *fileBeingParsed;
+ FilePosition nextSeekPos;
+ GCodeFileInfo parsedFileInfo;
+ uint32_t lastFileParseTime;
+ uint32_t accumulatedParseTime, accumulatedReadTime, accumulatedSeekTime;
+
+ size_t fileOverlapLength;
+ char fileOverlap[GCODE_OVERLAP_SIZE];
+};
+
+#endif /* SRC_STORAGE_FILEINFOPARSER_H_ */
diff --git a/src/Storage/MassStorage.h b/src/Storage/MassStorage.h
index b19c9c2a..e73716f3 100644
--- a/src/Storage/MassStorage.h
+++ b/src/Storage/MassStorage.h
@@ -7,6 +7,8 @@
#include "Libraries/Fatfs/ff.h"
#include "GCodes/GCodeResult.h"
#include "FileStore.h"
+#include "FileInfoParser.h"
+
#include <ctime>
#include "RTOSIface.h"
@@ -50,6 +52,7 @@ public:
unsigned int GetNumFreeFiles() const;
void Spin();
MutexHandle GetVolumeMutexHandle(size_t vol) const { return info[vol].volMutexHandle; }
+ bool GetFileInfo(const char *directory, const char *fileName, GCodeFileInfo& info, bool quitEarly) { return infoParser.GetFileInfo(directory, fileName, info, quitEarly); }
enum class InfoResult : uint8_t
{
@@ -102,6 +105,7 @@ private:
MutexStorage fsMutexStorage;
MutexStorage dirMutexStorage;
+ FileInfoParser infoParser;
DIR findDir;
FileWriteBuffer *freeWriteBuffers;
FileStore files[MAX_FILES];
diff --git a/src/Version.h b/src/Version.h
index 6bbe1f7a..9165a6e9 100644
--- a/src/Version.h
+++ b/src/Version.h
@@ -19,7 +19,7 @@
#endif
#ifndef DATE
-# define DATE "2018-03-30b1"
+# define DATE "2018-04-01b1"
#endif
#define AUTHORS "reprappro, dc42, chrishamm, t3p3, dnewman"