diff options
Diffstat (limited to 'src/GCodes/GCodeBuffer.cpp')
-rw-r--r-- | src/GCodes/GCodeBuffer.cpp | 440 |
1 files changed, 287 insertions, 153 deletions
diff --git a/src/GCodes/GCodeBuffer.cpp b/src/GCodes/GCodeBuffer.cpp index bea16d10..5c444a9d 100644 --- a/src/GCodes/GCodeBuffer.cpp +++ b/src/GCodes/GCodeBuffer.cpp @@ -27,18 +27,19 @@ void GCodeBuffer::Reset() void GCodeBuffer::Init() { - gcodePointer = 0; + gcodeLineEnd = 0; commandLength = 0; readPointer = -1; - inQuotes = inComment = timerRunning = false; - bufferState = GCodeBufferState::idle; + hadLineNumber = hadChecksum = timerRunning = false; + computedChecksum = 0; + bufferState = GCodeBufferState::parseNotStarted; } void GCodeBuffer::Diagnostics(MessageType mtype) { switch (bufferState) { - case GCodeBufferState::idle: + case GCodeBufferState::parseNotStarted: scratchString.printf("%s is idle", identity); break; @@ -48,6 +49,10 @@ void GCodeBuffer::Diagnostics(MessageType mtype) case GCodeBufferState::executing: scratchString.printf("%s is doing \"%s\"", identity, Buffer()); + break; + + default: + scratchString.printf("%s is assembling a command", identity); } scratchString.cat(" in state(s)"); @@ -62,14 +67,18 @@ void GCodeBuffer::Diagnostics(MessageType mtype) reprap.GetPlatform().Message(mtype, scratchString.Pointer()); } -int GCodeBuffer::CheckSum() const +inline void GCodeBuffer::AddToChecksum(char c) +{ + computedChecksum ^= (uint8_t)c; +} + +inline void GCodeBuffer::StoreAndAddToChecksum(char c) { - uint8_t cs = 0; - for (size_t i = 0; gcodeBuffer[i] != '*' && gcodeBuffer[i] != 0; i++) + computedChecksum ^= (uint8_t)c; + if (gcodeLineEnd < ARRAY_SIZE(gcodeBuffer)) { - cs = cs ^ (uint8_t)gcodeBuffer[i]; + gcodeBuffer[gcodeLineEnd++] = c; } - return (int)cs; } // Add a byte to the code being assembled. If false is returned, the code is @@ -81,88 +90,262 @@ bool GCodeBuffer::Put(char c) ++commandLength; } - if (!inQuotes && ((c == ';') || (gcodePointer == 0 && c == '('))) + if (c == 0 || c == '\n' || c == '\r') { - inComment = true; + return LineFinished(); } - else if (c == '\n' || c == '\r' || c == 0) + + // Process the incoming character in a state machine + bool again; + do { - gcodeBuffer[gcodePointer] = 0; - if (reprap.Debug(moduleGcodes) && gcodeBuffer[0] != 0 && !writingFileDirectory) // don't bother echoing blank/comment lines + again = false; + switch (bufferState) { - reprap.GetPlatform().MessageF(DebugMessage, "%s: %s\n", identity, gcodeBuffer); - } + case GCodeBufferState::parseNotStarted: // we haven't started parsing yet + switch (c) + { + case 'N': + case 'n': + hadLineNumber = true; + AddToChecksum(c); + bufferState = GCodeBufferState::parsingLineNumber; + lineNumber = 0; + break; - // Deal with line numbers and checksums - if (Seen('*')) - { - const int csSent = GetIValue(); - const int csHere = CheckSum(); - if (csSent != csHere) + case ' ': + case '\t': + AddToChecksum(c); + break; + + default: + bufferState = GCodeBufferState::parsingGCode; + commandStart = 0; + again = true; + break; + } + break; + + case GCodeBufferState::parsingLineNumber: // we saw N at the start and we are parsing the line number + if (isDigit(c)) { - if (Seen('N')) - { - snprintf(gcodeBuffer, GCODE_LENGTH, "M998 P%ld", GetIValue()); - } - Init(); - return true; + AddToChecksum(c); + lineNumber = (10 * lineNumber) + (c - '0'); + break; + } + else + { + bufferState = GCodeBufferState::parsingWhitespace; + again = true; + } + break; + + case GCodeBufferState::parsingWhitespace: + switch (c) + { + case ' ': + case '\t': + AddToChecksum(c); + break; + + default: + bufferState = GCodeBufferState::parsingGCode; + commandStart = 0; + again = true; + break; + } + break; + + case GCodeBufferState::parsingGCode: // parsing GCode words + switch (c) + { + case '*': + declaredChecksum = 0; + hadChecksum = true; + bufferState = GCodeBufferState::parsingChecksum; + break; + + case ';': + bufferState = GCodeBufferState::discarding; + break; + + case '(': + AddToChecksum(c); + bufferState = GCodeBufferState::parsingBracketedComment; + break; + + case '"': + StoreAndAddToChecksum(c); + bufferState = GCodeBufferState::parsingQuotedString; + break; + + default: + StoreAndAddToChecksum(c); } + break; - // Strip out the line number and checksum - gcodePointer = 0; - while (gcodeBuffer[gcodePointer] != ' ' && gcodeBuffer[gcodePointer] != 0) + case GCodeBufferState::parsingBracketedComment: // inside a (...) comment + AddToChecksum(c); + if (c == ')') { - gcodePointer++; + bufferState = GCodeBufferState::parsingGCode; } + break; - // Anything there? - if (gcodeBuffer[gcodePointer] == 0) + case GCodeBufferState::parsingQuotedString: // inside a double-quoted string + StoreAndAddToChecksum(c); + if (c == '"') { - // No... - gcodeBuffer[0] = 0; - Init(); - return false; + bufferState = GCodeBufferState::parsingGCode; } + break; - // Yes... - gcodePointer++; - int gp2 = 0; - while (gcodeBuffer[gcodePointer] != '*' && gcodeBuffer[gcodePointer] != 0) + case GCodeBufferState::parsingChecksum: // parsing the checksum after '*' + if (isDigit(c)) + { + declaredChecksum = (10 * declaredChecksum) + (c - '0'); + } + else { - gcodeBuffer[gp2] = gcodeBuffer[gcodePointer++]; - gp2++; + bufferState = GCodeBufferState::discarding; + again = true; } - gcodeBuffer[gp2] = 0; + break; + + case GCodeBufferState::discarding: // discarding characters after the checksum or an end-of-line comment + default: + // throw the character away + break; + } + } while (again); + + return false; +} + +// This is called when we are fed a null, CR or LF character. +// Return true if there is a completed command ready to be executed. +bool GCodeBuffer::LineFinished() +{ + if (gcodeLineEnd == 0) + { + // Empty line + Init(); + return false; + } + + if (gcodeLineEnd == ARRAY_SIZE(gcodeBuffer)) + { + reprap.GetPlatform().MessageF(ErrorMessage, "G-Code buffer '%s' length overflow\n", identity); + Init(); + return false; + } + + gcodeBuffer[gcodeLineEnd] = 0; + const bool badChecksum = (hadChecksum && computedChecksum != declaredChecksum); + const bool missingChecksum = (checksumRequired && !hadChecksum && machineState->previous == nullptr); + if (reprap.Debug(moduleGcodes) && !writingFileDirectory) + { + reprap.GetPlatform().MessageF(DebugMessage, "%s%s: %s\n", identity, ((badChecksum) ? "(bad-csum)" : (missingChecksum) ? "(no-csum)" : ""), gcodeBuffer); + } + + if (badChecksum) + { + if (hadLineNumber) + { + snprintf(gcodeBuffer, ARRAY_SIZE(gcodeBuffer), "M998 P%u", lineNumber); // request resend } - else if ((checksumRequired && machineState->previous == nullptr) || IsEmpty()) + else { - // Checksum not found or buffer empty - cannot do anything - gcodeBuffer[0] = 0; Init(); return false; } - readPointer = -1;; - bufferState = GCodeBufferState::ready; - return true; } - else if (!inComment || writingFileDirectory) + else if (missingChecksum) + { + // Checksum required but none was provided + Init(); + return false; + } + + commandStart = 0; + DecodeCommand(); + return true; +} + +// Decode this command command and find the start of the next one on the same line. +// On entry, 'commandStart' has already been set to the address the start of where the command should be. +// On return, the state must be set to 'ready' to indicate that a command is available and we should stop adding characters. +void GCodeBuffer::DecodeCommand() +{ + // Check for a valid command letter at the start + commandLetter = toupper(gcodeBuffer[commandStart]); + hasCommandNumber = false; + commandNumber = -1; + commandFraction = -1; + if (commandLetter == 'G' || commandLetter == 'M' || commandLetter == 'T') { - if (gcodePointer >= (int)GCODE_LENGTH) + parameterStart = commandStart + 1; + const bool negative = (gcodeBuffer[parameterStart] == '-'); + if (negative) { - reprap.GetPlatform().MessageF(ErrorMessage, "G-Code buffer '%s' length overflow\n", identity); - Init(); + ++parameterStart; } - else + if (isdigit(gcodeBuffer[parameterStart])) + { + hasCommandNumber = true; + // Read the number after the command letter + commandNumber = 0; + do + { + commandNumber = (10 * commandNumber) + (gcodeBuffer[parameterStart] - '0'); + ++parameterStart; + } + while (isdigit(gcodeBuffer[parameterStart])); + if (negative) + { + commandNumber = -commandNumber; + } + + // Read the fractional digit, if any + if (gcodeBuffer[parameterStart] == '.') + { + ++parameterStart; + if (isdigit(gcodeBuffer[parameterStart])) + { + commandFraction = gcodeBuffer[parameterStart] - '0'; + ++parameterStart; + } + } + } + + // Find where the end of the command is. We assume that a G or M preceded by a space and not inside quotes is the start of a new command. + bool inQuotes = false; + bool primed = false; + for (commandEnd = parameterStart; commandEnd < gcodeLineEnd; ++commandEnd) { - gcodeBuffer[gcodePointer++] = c; - if (c == '"' && !inComment) + const char c = gcodeBuffer[commandEnd]; + char c2; + if (c == '"') { inQuotes = !inQuotes; + primed = false; + } + else if (!inQuotes) + { + if (primed && ((c2 = toupper(c)) == 'G' || c2 == 'M')) + { + break; + } + primed = (c == ' ' || c == '\t'); } } } - - return false; + else + { + parameterStart = commandStart; + commandEnd = gcodeLineEnd; + } + bufferState = GCodeBufferState::ready; } // Add an entire string, overwriting any existing content @@ -184,8 +367,16 @@ void GCodeBuffer::SetFinished(bool f) { if (f) { - bufferState = GCodeBufferState::idle; - Init(); + if (commandEnd < gcodeLineEnd) + { + // There is another command in the same line of gcode + commandStart = commandEnd; + DecodeCommand(); + } + else + { + Init(); + } } else { @@ -198,88 +389,32 @@ FilePosition GCodeBuffer::GetFilePosition(size_t bytesCached) const { if (machineState->fileState.IsLive()) { - return machineState->fileState.GetPosition() - bytesCached - commandLength; + return machineState->fileState.GetPosition() - bytesCached - commandLength + commandStart; } return noFilePosition; } -// Does this buffer contain any code? -bool GCodeBuffer::IsEmpty() const -{ - const char *buf = gcodeBuffer; - while (*buf != 0 && strchr(" \t\n\r", *buf) != nullptr) - { - buf++; - } - return *buf == 0; -} - // Is 'c' in the G Code string? // Leave the pointer there for a subsequent read. bool GCodeBuffer::Seen(char c) { - readPointer = 0; bool inQuotes = false; - for (;;) + for (readPointer = parameterStart; (unsigned int)readPointer < commandEnd; ++readPointer) { const char b = gcodeBuffer[readPointer]; - if (b == 0) - { - break; - } if (b == '"') { inQuotes = !inQuotes; } - else if (!inQuotes) + else if (!inQuotes && toupper(b) == c) { - if (b == ';') - { - break; - } - if (toupper(b) == c) - { - return true; - } + return true; } - ++readPointer; } readPointer = -1; return false; } -// Return the first G, M or T command letter. Needed so that we don't pick up a spurious command letter from inside a string parameter. -char GCodeBuffer::GetCommandLetter() -{ - readPointer = 0; - for (;;) - { - char b = gcodeBuffer[readPointer]; - if (b == 0 || b == ';' || b == '"' || b == '(') - { - break; - } - b = (char)toupper(b); - if (b == 'T') - { - return b; - } - if (b == 'G' || b == 'M') - { - // Check that the command letter is followed by a digit. This is mostly to avoid a malformed string in which the first letter is M being interpreted as M0. - const char b2 = gcodeBuffer[readPointer + 1]; - if (b2 >= '0' && b2 <= '9') - { - return b; - } - break; - } - ++readPointer; - } - readPointer = -1; - return 0; -} - // Get a float after a G Code letter found by a call to Seen() float GCodeBuffer::GetFValue() { @@ -290,11 +425,11 @@ float GCodeBuffer::GetFValue() return result; } - reprap.GetPlatform().Message(ErrorMessage, "GCodes: Attempt to read a GCode float before a search.\n"); + INTERNAL_ERROR; return 0.0; } -// Get a :-separated list of floats after a key letter +// Get a colon-separated list of floats after a key letter const void GCodeBuffer::GetFloatArray(float a[], size_t& returnedLength, bool doPad) { if (readPointer >= 0) @@ -340,7 +475,7 @@ const void GCodeBuffer::GetFloatArray(float a[], size_t& returnedLength, bool do } else { - reprap.GetPlatform().Message(ErrorMessage, "GCodes: Attempt to read a GCode float array before a search.\n"); + INTERNAL_ERROR; returnedLength = 0; } } @@ -377,7 +512,8 @@ const void GCodeBuffer::GetLongArray(long l[], size_t& returnedLength) } else { - reprap.GetPlatform().Message(ErrorMessage, "GCodes: Attempt to read a GCode long array before a search.\n"); + INTERNAL_ERROR; + returnedLength = 0; } } @@ -388,12 +524,13 @@ const char* GCodeBuffer::GetString() { if (readPointer >= 0) { + commandEnd = gcodeLineEnd; // the string is the remainder of the line of gcode const char* const result = &gcodeBuffer[readPointer + 1]; readPointer = -1; return result; } - reprap.GetPlatform().Message(ErrorMessage, "GCodes: Attempt to read a GCode string before a search.\n"); + INTERNAL_ERROR; return ""; } @@ -440,7 +577,7 @@ bool GCodeBuffer::GetQuotedString(const StringRef& str) return false; } - reprap.GetPlatform().Message(ErrorMessage, "GCodes: Attempt to read a GCode string before a search.\n"); + INTERNAL_ERROR; return false; } @@ -454,6 +591,7 @@ bool GCodeBuffer::GetPossiblyQuotedString(const StringRef& str) return GetQuotedString(str); } + commandEnd = gcodeLineEnd; // the string is the remainder of the line of gcode str.Clear(); for (;;) { @@ -469,43 +607,33 @@ bool GCodeBuffer::GetPossiblyQuotedString(const StringRef& str) return !str.IsEmpty(); } - reprap.GetPlatform().Message(ErrorMessage, "GCodes: Attempt to read a possibly quoted GCode string before a search.\n"); + INTERNAL_ERROR; return false; } -// This returns a pointer to the end of the buffer where a -// string starts. It assumes that an M or G search has -// been done followed by a GetIValue(), so readPointer will -// be -1. It absorbs "M/Gnnn " (including the space) from the -// start and returns a pointer to the next location. - -// This is provided for legacy use, in particular in the M23 +// This returns a pointer to the end of the buffer where a string starts. +// It is provided for legacy use, in particular in the M23 // command that sets the name of a file to be printed. In // preference use GetString() which requires the string to have // been preceded by a tag letter. - // If no string was provided, it produces an error message if the string was not optional, and returns nullptr. const char* GCodeBuffer::GetUnprecedentedString(bool optional) { - readPointer = 0; - while (gcodeBuffer[readPointer] != 0 && gcodeBuffer[readPointer] != ' ') - { - readPointer++; - } + commandEnd = gcodeLineEnd; // the string is the remainder of the line + size_t i; + char c; + for (i = parameterStart; i < commandEnd && ((c = gcodeBuffer[i]) == ' ' || c == '\t'); ++i) { } - if (gcodeBuffer[readPointer] == 0) + if (i == commandEnd) { - readPointer = -1; if (!optional) { - reprap.GetPlatform().Message(ErrorMessage, "GCodes: String expected but not seen.\n"); + reprap.GetPlatform().MessageF(ErrorMessage, "%c%d: String expected but not seen.\n", commandLetter, commandNumber); } return nullptr; } - const char* const result = &gcodeBuffer[readPointer + 1]; - readPointer = -1; - return result; + return &gcodeBuffer[i]; } // Get an int32 after a G Code letter @@ -518,8 +646,7 @@ int32_t GCodeBuffer::GetIValue() return result; } - reprap.GetPlatform().Message(ErrorMessage, "GCodes: Attempt to read a GCode int before a search.\n"); - readPointer = -1; + INTERNAL_ERROR; return 0; } @@ -533,8 +660,7 @@ uint32_t GCodeBuffer::GetUIValue() return result; } - reprap.GetPlatform().Message(ErrorMessage, "GCodes: Attempt to read a GCode unsigned int before a search.\n"); - readPointer = -1; + INTERNAL_ERROR; return 0; } @@ -619,9 +745,10 @@ bool GCodeBuffer::GetIPAddress(uint8_t ip[4]) { if (readPointer < 0) { - reprap.GetPlatform().Message(ErrorMessage, "GCodes: Attempt to read a GCode string before a search.\n"); + INTERNAL_ERROR; return false; } + const char* p = &gcodeBuffer[readPointer + 1]; unsigned int n = 0; for (;;) @@ -654,6 +781,12 @@ bool GCodeBuffer::GetIPAddress(uint8_t ip[4]) // Get an IP address quad after a key letter bool GCodeBuffer::GetIPAddress(uint32_t& ip) { + if (readPointer < 0) + { + INTERNAL_ERROR; + return false; + } + uint8_t ipa[4]; const bool ok = GetIPAddress(ipa); if (ok) @@ -697,6 +830,7 @@ bool GCodeBuffer::PushState() ms->axesRelative = machineState->axesRelative; ms->doingFileMacro = machineState->doingFileMacro; ms->waitWhileCooling = machineState->waitWhileCooling; + ms->runningM501 = machineState->runningM501; ms->runningM502 = machineState->runningM502; ms->volumetricExtrusion = false; ms->messageAcknowledged = false; |