diff options
39 files changed, 1162 insertions, 1167 deletions
diff --git a/Developer-documentation/New ESP8266 WiFi module code for Duet WiFi.odt b/Developer-documentation/New ESP8266 WiFi module code for Duet WiFi.odt Binary files differindex 4285cce9..7b25d70e 100644 --- a/Developer-documentation/New ESP8266 WiFi module code for Duet WiFi.odt +++ b/Developer-documentation/New ESP8266 WiFi module code for Duet WiFi.odt diff --git a/Release/Duet-0.6-0.8.5/Edge/DuetWebControl-1.15-b2.zip b/Release/Duet-0.6-0.8.5/Edge/DuetWebControl-1.15.zip Binary files differindex b9ff2f4a..8f185985 100644 --- a/Release/Duet-0.6-0.8.5/Edge/DuetWebControl-1.15-b2.zip +++ b/Release/Duet-0.6-0.8.5/Edge/DuetWebControl-1.15.zip diff --git a/Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.18beta2.bin b/Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.18RC1.bin Binary files differindex 688c84ba..a7f19596 100644 --- a/Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.18beta2.bin +++ b/Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.18RC1.bin diff --git a/Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.18beta1.bin b/Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.18beta1.bin Binary files differdeleted file mode 100644 index 4a2b5d54..00000000 --- a/Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.18beta1.bin +++ /dev/null diff --git a/Release/Duet-Ethernet/Edge/DuetEthernetFirmware-1.18beta1.bin b/Release/Duet-Ethernet/Edge/DuetEthernetFirmware-1.18RC1.bin Binary files differindex 8ab92365..781c891b 100644 --- a/Release/Duet-Ethernet/Edge/DuetEthernetFirmware-1.18beta1.bin +++ b/Release/Duet-Ethernet/Edge/DuetEthernetFirmware-1.18RC1.bin diff --git a/Release/Duet-Ethernet/Edge/DuetWebControl-1.15-b2.zip b/Release/Duet-Ethernet/Edge/DuetWebControl-1.15.zip Binary files differindex b9ff2f4a..8f185985 100644 --- a/Release/Duet-Ethernet/Edge/DuetWebControl-1.15-b2.zip +++ b/Release/Duet-Ethernet/Edge/DuetWebControl-1.15.zip diff --git a/Release/Duet-WiFi/Edge/DuetWebControl-1.15-b2.bin b/Release/Duet-WiFi/Edge/DuetWebControl-1.15.bin Binary files differindex aa52cfcd..c559b6ff 100644 --- a/Release/Duet-WiFi/Edge/DuetWebControl-1.15-b2.bin +++ b/Release/Duet-WiFi/Edge/DuetWebControl-1.15.bin diff --git a/Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.18beta2.bin b/Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.18RC1.bin Binary files differindex 9c772b21..4f73426d 100644 --- a/Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.18beta2.bin +++ b/Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.18RC1.bin diff --git a/Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.18beta1.bin b/Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.18beta1.bin Binary files differdeleted file mode 100644 index 0093c86e..00000000 --- a/Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.18beta1.bin +++ /dev/null diff --git a/Release/RADDS/Edge/RepRapFirmware-RADDS-1.18RC1.bin b/Release/RADDS/Edge/RepRapFirmware-RADDS-1.18RC1.bin Binary files differnew file mode 100644 index 00000000..53b2ddf5 --- /dev/null +++ b/Release/RADDS/Edge/RepRapFirmware-RADDS-1.18RC1.bin diff --git a/Release/RADDS/Edge/RepRapFirmware-RADDS-1.18beta2.bin b/Release/RADDS/Edge/RepRapFirmware-RADDS-1.18beta2.bin Binary files differdeleted file mode 100644 index 2dd10462..00000000 --- a/Release/RADDS/Edge/RepRapFirmware-RADDS-1.18beta2.bin +++ /dev/null diff --git a/src/Duet/Webserver.cpp b/src/Duet/Webserver.cpp index d21fd99b..74c6f4b4 100644 --- a/src/Duet/Webserver.cpp +++ b/src/Duet/Webserver.cpp @@ -259,34 +259,6 @@ void Webserver::Diagnostics(MessageType mtype) telnetInterpreter->Diagnostics(mtype); } -bool Webserver::GCodeAvailable(const WebSource source) const -{ - switch (source) - { - case WebSource::HTTP: - return httpInterpreter->GCodeAvailable(); - - case WebSource::Telnet: - return telnetInterpreter->GCodeAvailable(); - } - - return false; -} - -char Webserver::ReadGCode(const WebSource source) -{ - switch (source) - { - case WebSource::HTTP: - return httpInterpreter->ReadGCode(); - - case WebSource::Telnet: - return telnetInterpreter->ReadGCode(); - } - - return 0; -} - void Webserver::HandleGCodeReply(const WebSource source, OutputBuffer *reply) { switch (source) @@ -315,20 +287,6 @@ void Webserver::HandleGCodeReply(const WebSource source, const char *reply) } } -uint16_t Webserver::GetGCodeBufferSpace(const WebSource source) const -{ - switch (source) - { - case WebSource::HTTP: - return httpInterpreter->GetGCodeBufferSpace(); - - case WebSource::Telnet: - return telnetInterpreter->GetGCodeBufferSpace(); - } - - return 0; -} - // Handle immediate disconnects here (cs will be freed after this call) // May be called by ISR, but not while LwIP is NOT locked void Webserver::ConnectionLost(Connection conn) @@ -521,7 +479,6 @@ bool ProtocolInterpreter::FinishUpload(uint32_t fileLength) Webserver::HttpInterpreter::HttpInterpreter(Platform *p, Webserver *ws, Network *n) : ProtocolInterpreter(p, ws, n), state(doingCommandWord), numSessions(0), clientsServed(0) { - gcodeReadIndex = gcodeWriteIndex = 0; gcodeReply = new OutputStack(); deferredRequestConnection = NoConnection; seq = 0; @@ -751,6 +708,7 @@ void Webserver::HttpInterpreter::SendFile(const char* nameOfFileToSend, bool isW transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n"); transaction->Write("Pragma: no-cache\n"); transaction->Write("Expires: 0\n"); + transaction->Write("Access-Control-Allow-Origin: *\n"); } const char* contentType; @@ -830,6 +788,7 @@ void Webserver::HttpInterpreter::SendGCodeReply() transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n"); transaction->Write("Pragma: no-cache\n"); transaction->Write("Expires: 0\n"); + transaction->Write("Access-Control-Allow-Origin: *\n"); transaction->Write("Content-Type: text/plain\n"); transaction->Printf("Content-Length: %u\n", gcodeReply->DataLength()); transaction->Write("Connection: close\n\n"); @@ -888,16 +847,8 @@ void Webserver::HttpInterpreter::SendJsonResponse(const char* command) return; } - bool keepOpen = false; bool mayKeepOpen; - if (numQualKeys == 0) - { - GetJsonResponse(command, jsonResponse, mayKeepOpen); - } - else - { - GetJsonResponse(command, jsonResponse, mayKeepOpen); - } + GetJsonResponse(command, jsonResponse, mayKeepOpen); // Check special cases of deferred requests (rr_fileinfo) and rejected messages NetworkTransaction *transaction = webserver->currentTransaction; @@ -908,7 +859,7 @@ void Webserver::HttpInterpreter::SendJsonResponse(const char* command) } // Send the JSON response - + bool keepOpen = false; if (mayKeepOpen) { // Check that the browser wants to persist the connection too @@ -927,6 +878,7 @@ void Webserver::HttpInterpreter::SendJsonResponse(const char* command) transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n"); transaction->Write("Pragma: no-cache\n"); transaction->Write("Expires: 0\n"); + transaction->Write("Access-Control-Allow-Origin: *\n"); transaction->Write("Content-Type: application/json\n"); transaction->Printf("Content-Length: %u\n", (jsonResponse != nullptr) ? jsonResponse->Length() : 0); transaction->Printf("Connection: %s\n\n", keepOpen ? "keep-alive" : "close"); @@ -1011,8 +963,9 @@ void Webserver::HttpInterpreter::GetJsonResponse(const char* request, OutputBuff } else if (StringEquals(request, "gcode") && GetKeyValue("gcode") != nullptr) { - LoadGcodeBuffer(GetKeyValue("gcode")); - response->printf("{\"buff\":%u}", GetGCodeBufferSpace()); + RegularGCodeInput * const httpInput = reprap.GetGCodes()->GetHTTPInput(); + httpInput->Put(HTTP_MESSAGE, GetKeyValue("gcode")); + response->printf("{\"buff\":%u}", httpInput->BufferSpaceLeft()); } else if (StringEquals(request, "upload")) { @@ -1548,7 +1501,27 @@ bool Webserver::HttpInterpreter::ProcessMessage() ResetState(); return true; } - else if (IsAuthenticated() && StringEquals(commandWords[0], "POST")) + + if (StringEquals(commandWords[0], "OPTIONS")) + { + NetworkTransaction *transaction = webserver->currentTransaction; + + transaction->Write("HTTP/1.1 200 OK\n"); + transaction->Write("Allow: OPTIONS, GET, POST\n"); + transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n"); + transaction->Write("Pragma: no-cache\n"); + transaction->Write("Expires: 0\n"); + transaction->Write("Access-Control-Allow-Origin: *\n"); + transaction->Write("Access-Control-Allow-Headers: Content-Type\n"); + transaction->Write("Content-Length: 0\n"); + transaction->Write("\n"); + transaction->Commit(false); + + ResetState(); + return true; + } + + if (IsAuthenticated() && StringEquals(commandWords[0], "POST")) { const bool isUploadRequest = (StringEquals(commandWords[1], KO_START "upload")) || (commandWords[1][0] == '/' && StringEquals(commandWords[1] + 1, KO_START "upload")); @@ -1715,114 +1688,6 @@ bool Webserver::HttpInterpreter::RemoveAuthentication() return false; } -// Process a received string of gcodes -void Webserver::HttpInterpreter::LoadGcodeBuffer(const char* gc) -{ - 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 == '\n') - { - gcodeTempBuf[gtp] = 0; - ProcessGcode(gcodeTempBuf); - gtp = 0; - inComment = false; - } - else - { - if (c == ';') - { - inComment = true; - } - - if (gtp == ARRAY_UPB(gcodeTempBuf)) - { - // gcode is too long, we haven't room for another character and a null - if (c != ' ' && !inComment) - { - platform->Message(HOST_MESSAGE, "Error: GCode local buffer overflow in HTTP webserver.\n"); - 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 one M Codes so we can deal with 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::HttpInterpreter::ProcessGcode(const char* gc) -{ - if (StringStartsWith(gc, "M112") && !isdigit(gc[4])) // emergency stop - { - reprap.EmergencyStop(); - gcodeReadIndex = gcodeWriteIndex; // clear the buffer - reprap.GetGCodes()->Reset(); - } - else - { - StoreGcodeData(gc, strlen(gc) + 1); - } -} - -// Process a received string of gcodes -void Webserver::HttpInterpreter::StoreGcodeData(const char* data, uint16_t len) -{ - if (len > GetGCodeBufferSpace()) - { - platform->Message(HOST_MESSAGE, "Error: GCode buffer overflow in HTTP Webserver!\n"); - } - else - { - uint16_t remaining = gcodeBufferLength - gcodeWriteIndex; - if (len <= remaining) - { - memcpy(gcodeBuffer + gcodeWriteIndex, data, len); - } - else - { - memcpy(gcodeBuffer + gcodeWriteIndex, data, remaining); - memcpy(gcodeBuffer, data + remaining, len - remaining); - } - gcodeWriteIndex = (gcodeWriteIndex + len) % gcodeBufferLength; - } -} - -// Feeding G Codes to the GCodes class -char Webserver::HttpInterpreter::ReadGCode() -{ - char c; - if (gcodeReadIndex == gcodeWriteIndex) - { - c = 0; - } - else - { - c = gcodeBuffer[gcodeReadIndex]; - gcodeReadIndex = (gcodeReadIndex + 1u) % gcodeBufferLength; - } - return c; -} - // Handle a G Code reply from the GCodes class void Webserver::HttpInterpreter::HandleGCodeReply(OutputBuffer *reply) { @@ -1891,6 +1756,7 @@ void Webserver::HttpInterpreter::ProcessDeferredRequest() transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n"); transaction->Write("Pragma: no-cache\n"); transaction->Write("Expires: 0\n"); + transaction->Write("Access-Control-Allow-Origin: *\n"); transaction->Write("Content-Type: application/json\n"); transaction->Printf("Content-Length: %u\n", (jsonResponse != nullptr) ? jsonResponse->Length() : 0); transaction->Printf("Connection: close\n\n"); @@ -2295,7 +2161,6 @@ void Webserver::FtpInterpreter::ProcessLine() { SendReply(500, "Unknown command."); } - break; case waitingForPasvPort: @@ -2310,7 +2175,6 @@ void Webserver::FtpInterpreter::ProcessLine() { webserver->currentTransaction->Defer(DeferralMode::ResetData); } - break; case pasvPortConnected: @@ -2566,7 +2430,7 @@ void Webserver::FtpInterpreter::ChangeDirectory(const char *newDirectory) //******************************************************************************************** Webserver::TelnetInterpreter::TelnetInterpreter(Platform *p, Webserver *ws, Network *n) - : ProtocolInterpreter(p, ws, n), connectedClients(0), processNextLine(false), gcodeReadIndex(0), gcodeWriteIndex(0), gcodeReply(nullptr) + : ProtocolInterpreter(p, ws, n), connectedClients(0), processNextLine(false), gcodeReply(nullptr) { ResetState(); } @@ -2638,7 +2502,8 @@ bool Webserver::TelnetInterpreter::CanParseData() } // In order to support TCP streaming mode, check if we can store any more data at this time - if (GetGCodeBufferSpace() < clientPointer + 1) + RegularGCodeInput * const telnetInput = reprap.GetGCodes()->GetTelnetInput(); + if (telnetInput->BufferSpaceLeft() < clientPointer + 1) { webserver->currentTransaction->Defer(DeferralMode::DeferOnly); return false; @@ -2697,7 +2562,8 @@ bool Webserver::TelnetInterpreter::CharFromClient(char c) { // This line is complete, do we have enough space left to store it? clientMessage[clientPointer] = 0; - if (GetGCodeBufferSpace() < clientPointer + 1) + RegularGCodeInput * const telnetInput = reprap.GetGCodes()->GetTelnetInput(); + if (telnetInput->BufferSpaceLeft() < clientPointer + 1) { // No - defer this transaction, so we can process more of it next time webserver->currentTransaction->Defer(DeferralMode::DeferOnly); @@ -2731,7 +2597,6 @@ void Webserver::TelnetInterpreter::ResetState() state = idle; connectTime = 0; clientPointer = 0; - gcodeReadIndex = gcodeWriteIndex; // clear the buffer } // Usually we should not try to send any data here, because that would purge the packet's @@ -2774,70 +2639,15 @@ bool Webserver::TelnetInterpreter::ProcessLine() transaction->Commit(false); return true; } + // All other codes are stored for the GCodes class - ProcessGcode(clientMessage); + RegularGCodeInput * const telnetInput = reprap.GetGCodes()->GetTelnetInput(); + telnetInput->Put(TELNET_MESSAGE, clientMessage); break; } return false; } -// Process a null-terminated gcode -// We intercept one M Codes so we can deal with 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::TelnetInterpreter::ProcessGcode(const char* gc) -{ - if (StringStartsWith(gc, "M112") && !isdigit(gc[4])) // emergency stop - { - reprap.EmergencyStop(); - gcodeReadIndex = gcodeWriteIndex; // clear the buffer - reprap.GetGCodes()->Reset(); - } - else - { - StoreGcodeData(gc, strlen(gc) + 1); - } -} - -// Process a received string of gcodes -void Webserver::TelnetInterpreter::StoreGcodeData(const char* data, uint16_t len) -{ - if (len > GetGCodeBufferSpace()) - { - platform->Message(HOST_MESSAGE, "Error: GCode buffer overflow in Telnet Webserver!\n"); - } - else - { - uint16_t remaining = gcodeBufferLength - gcodeWriteIndex; - if (len <= remaining) - { - memcpy(gcodeBuffer + gcodeWriteIndex, data, len); - } - else - { - memcpy(gcodeBuffer + gcodeWriteIndex, data, remaining); - memcpy(gcodeBuffer, data + remaining, len - remaining); - } - gcodeWriteIndex = (gcodeWriteIndex + len) % gcodeBufferLength; - } -} - -// Feeding G Codes to the GCodes class -char Webserver::TelnetInterpreter::ReadGCode() -{ - char c; - if (gcodeReadIndex == gcodeWriteIndex) - { - c = 0; - } - else - { - c = gcodeBuffer[gcodeReadIndex]; - gcodeReadIndex = (gcodeReadIndex + 1u) % gcodeBufferLength; - } - return c; -} - // Handle a G-Code reply from the GCodes class; replace \n with \r\n void Webserver::TelnetInterpreter::HandleGCodeReply(OutputBuffer *reply) { diff --git a/src/Duet/Webserver.h b/src/Duet/Webserver.h index 3e4f2c30..698f6ea4 100644 --- a/src/Duet/Webserver.h +++ b/src/Duet/Webserver.h @@ -130,15 +130,10 @@ public: void Exit(); void Diagnostics(MessageType mtype); - bool GCodeAvailable(const WebSource source) const; - char ReadGCode(const WebSource source); void HandleGCodeReply(const WebSource source, OutputBuffer *reply); void HandleGCodeReply(const WebSource source, const char *reply); uint32_t GetReplySeq() const; - // Returns the available G-Code buffer space of the HTTP interpreter (may be dropped in a future version) - uint16_t GetGCodeBufferSpace(const WebSource source) const; - void ConnectionLost(Connection conn /*const ConnectionState *cs*/); void ConnectionError(); @@ -161,11 +156,8 @@ protected: bool DoingFastUpload() const override; void DoFastUpload(); - bool GCodeAvailable() const; - char ReadGCode(); void HandleGCodeReply(OutputBuffer *reply); void HandleGCodeReply(const char *reply); - uint16_t GetGCodeBufferSpace() const; uint32_t GetReplySeq() const; private: @@ -234,17 +226,7 @@ protected: bool RemoveAuthentication(); const char* GetKeyValue(const char *key) const; // return the value of the specified key, or nullptr if not present - // Deal with incoming G-Codes - - char gcodeBuffer[gcodeBufferLength]; - uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer - - void LoadGcodeBuffer(const char* gc); - void ProcessGcode(const char* gc); - void StoreGcodeData(const char* data, uint16_t len); - // Responses from GCodes class - uint32_t seq; // Sequence number for G-Code replies OutputStack *gcodeReply; @@ -318,11 +300,8 @@ protected: bool CharFromClient(const char c) override; void ResetState(); - bool GCodeAvailable() const; - char ReadGCode(); void HandleGCodeReply(OutputBuffer *reply); void HandleGCodeReply(const char *reply); - uint16_t GetGCodeBufferSpace() const; void SendGCodeReply(); @@ -345,16 +324,7 @@ protected: bool ProcessLine(); - // Deal with incoming G-Codes - - char gcodeBuffer[gcodeBufferLength]; - uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer - - void ProcessGcode(const char* gc); - void StoreGcodeData(const char* data, uint16_t len); - // Converted response from GCodes class (NL -> CRNL) - OutputBuffer * volatile gcodeReply; }; TelnetInterpreter *telnetInterpreter; @@ -376,11 +346,6 @@ inline bool ProtocolInterpreter::IsUploading() const { return uploadState != not inline uint32_t Webserver::GetReplySeq() const { return httpInterpreter->GetReplySeq(); } -inline uint16_t Webserver::HttpInterpreter::GetGCodeBufferSpace() const { return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength; } -inline bool Webserver::HttpInterpreter::GCodeAvailable() const { return gcodeReadIndex != gcodeWriteIndex; } inline uint32_t Webserver::HttpInterpreter::GetReplySeq() const { return seq; } -inline uint16_t Webserver::TelnetInterpreter::GetGCodeBufferSpace() const { return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength; } -inline bool Webserver::TelnetInterpreter::GCodeAvailable() const { return gcodeReadIndex != gcodeWriteIndex; } - #endif diff --git a/src/DuetNG/DuetEthernet/Network.cpp b/src/DuetNG/DuetEthernet/Network.cpp index ea27bc86..fe844b13 100644 --- a/src/DuetNG/DuetEthernet/Network.cpp +++ b/src/DuetNG/DuetEthernet/Network.cpp @@ -433,7 +433,7 @@ NetworkTransaction *Network::GetTransaction(Connection conn) { if (conn != NoConnection) { - NetworkTransaction *tr = conn->GetTransaction(); + NetworkTransaction * const tr = conn->GetTransaction(); if (tr != nullptr && !tr->IsSending()) { currentTransactionSocketNumber = conn->GetNumber(); @@ -445,7 +445,7 @@ NetworkTransaction *Network::GetTransaction(Connection conn) size_t socketNum = currentTransactionSocketNumber; do { - NetworkTransaction *tr = sockets[socketNum].GetTransaction(); + NetworkTransaction * const tr = sockets[socketNum].GetTransaction(); if (tr != nullptr && !tr->IsSending()) { currentTransactionSocketNumber = socketNum; diff --git a/src/DuetNG/DuetEthernet/Webserver.cpp b/src/DuetNG/DuetEthernet/Webserver.cpp index 6a5b5c6d..114b291b 100644 --- a/src/DuetNG/DuetEthernet/Webserver.cpp +++ b/src/DuetNG/DuetEthernet/Webserver.cpp @@ -259,34 +259,6 @@ void Webserver::Diagnostics(MessageType mtype) telnetInterpreter->Diagnostics(mtype); } -bool Webserver::GCodeAvailable(const WebSource source) const -{ - switch (source) - { - case WebSource::HTTP: - return httpInterpreter->GCodeAvailable(); - - case WebSource::Telnet: - return telnetInterpreter->GCodeAvailable(); - } - - return false; -} - -char Webserver::ReadGCode(const WebSource source) -{ - switch (source) - { - case WebSource::HTTP: - return httpInterpreter->ReadGCode(); - - case WebSource::Telnet: - return telnetInterpreter->ReadGCode(); - } - - return 0; -} - void Webserver::HandleGCodeReply(const WebSource source, OutputBuffer *reply) { switch (source) @@ -315,20 +287,6 @@ void Webserver::HandleGCodeReply(const WebSource source, const char *reply) } } -uint16_t Webserver::GetGCodeBufferSpace(const WebSource source) const -{ - switch (source) - { - case WebSource::HTTP: - return httpInterpreter->GetGCodeBufferSpace(); - - case WebSource::Telnet: - return telnetInterpreter->GetGCodeBufferSpace(); - } - - return 0; -} - // Handle immediate disconnects here (cs will be freed after this call) // May be called by ISR, but not while LwIP is NOT locked void Webserver::ConnectionLost(Connection conn) @@ -521,7 +479,6 @@ bool ProtocolInterpreter::FinishUpload(uint32_t fileLength) Webserver::HttpInterpreter::HttpInterpreter(Platform *p, Webserver *ws, Network *n) : ProtocolInterpreter(p, ws, n), state(doingCommandWord), numSessions(0), clientsServed(0) { - gcodeReadIndex = gcodeWriteIndex = 0; gcodeReply = new OutputStack(); deferredRequestConnection = NoConnection; seq = 0; @@ -751,6 +708,7 @@ void Webserver::HttpInterpreter::SendFile(const char* nameOfFileToSend, bool isW transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n"); transaction->Write("Pragma: no-cache\n"); transaction->Write("Expires: 0\n"); + transaction->Write("Access-Control-Allow-Origin: *\n"); } const char* contentType; @@ -830,6 +788,7 @@ void Webserver::HttpInterpreter::SendGCodeReply() transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n"); transaction->Write("Pragma: no-cache\n"); transaction->Write("Expires: 0\n"); + transaction->Write("Access-Control-Allow-Origin: *\n"); transaction->Write("Content-Type: text/plain\n"); transaction->Printf("Content-Length: %u\n", gcodeReply->DataLength()); transaction->Write("Connection: close\n\n"); @@ -888,16 +847,8 @@ void Webserver::HttpInterpreter::SendJsonResponse(const char* command) return; } - bool keepOpen = false; bool mayKeepOpen; - if (numQualKeys == 0) - { - GetJsonResponse(command, jsonResponse, mayKeepOpen); - } - else - { - GetJsonResponse(command, jsonResponse, mayKeepOpen); - } + GetJsonResponse(command, jsonResponse, mayKeepOpen); // Check special cases of deferred requests (rr_fileinfo) and rejected messages NetworkTransaction *transaction = webserver->currentTransaction; @@ -908,7 +859,7 @@ void Webserver::HttpInterpreter::SendJsonResponse(const char* command) } // Send the JSON response - + bool keepOpen = false; if (mayKeepOpen) { // Check that the browser wants to persist the connection too @@ -927,6 +878,7 @@ void Webserver::HttpInterpreter::SendJsonResponse(const char* command) transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n"); transaction->Write("Pragma: no-cache\n"); transaction->Write("Expires: 0\n"); + transaction->Write("Access-Control-Allow-Origin: *\n"); transaction->Write("Content-Type: application/json\n"); transaction->Printf("Content-Length: %u\n", (jsonResponse != nullptr) ? jsonResponse->Length() : 0); transaction->Printf("Connection: %s\n\n", keepOpen ? "keep-alive" : "close"); @@ -1011,8 +963,9 @@ void Webserver::HttpInterpreter::GetJsonResponse(const char* request, OutputBuff } else if (StringEquals(request, "gcode") && GetKeyValue("gcode") != nullptr) { - LoadGcodeBuffer(GetKeyValue("gcode")); - response->printf("{\"buff\":%u}", GetGCodeBufferSpace()); + RegularGCodeInput * const httpInput = reprap.GetGCodes()->GetHTTPInput(); + httpInput->Put(HTTP_MESSAGE, GetKeyValue("gcode")); + response->printf("{\"buff\":%u}", httpInput->BufferSpaceLeft()); } else if (StringEquals(request, "upload")) { @@ -1548,7 +1501,27 @@ bool Webserver::HttpInterpreter::ProcessMessage() ResetState(); return true; } - else if (IsAuthenticated() && StringEquals(commandWords[0], "POST")) + + if (StringEquals(commandWords[0], "OPTIONS")) + { + NetworkTransaction *transaction = webserver->currentTransaction; + + transaction->Write("HTTP/1.1 200 OK\n"); + transaction->Write("Allow: OPTIONS, GET, POST\n"); + transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n"); + transaction->Write("Pragma: no-cache\n"); + transaction->Write("Expires: 0\n"); + transaction->Write("Access-Control-Allow-Origin: *\n"); + transaction->Write("Access-Control-Allow-Headers: Content-Type\n"); + transaction->Write("Content-Length: 0\n"); + transaction->Write("\n"); + transaction->Commit(false); + + ResetState(); + return true; + } + + if (IsAuthenticated() && StringEquals(commandWords[0], "POST")) { const bool isUploadRequest = (StringEquals(commandWords[1], KO_START "upload")) || (commandWords[1][0] == '/' && StringEquals(commandWords[1] + 1, KO_START "upload")); @@ -1715,114 +1688,6 @@ bool Webserver::HttpInterpreter::RemoveAuthentication() return false; } -// Process a received string of gcodes -void Webserver::HttpInterpreter::LoadGcodeBuffer(const char* gc) -{ - 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 == '\n') - { - gcodeTempBuf[gtp] = 0; - ProcessGcode(gcodeTempBuf); - gtp = 0; - inComment = false; - } - else - { - if (c == ';') - { - inComment = true; - } - - if (gtp == ARRAY_UPB(gcodeTempBuf)) - { - // gcode is too long, we haven't room for another character and a null - if (c != ' ' && !inComment) - { - platform->Message(HOST_MESSAGE, "Error: GCode local buffer overflow in HTTP webserver.\n"); - 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 one M Codes so we can deal with 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::HttpInterpreter::ProcessGcode(const char* gc) -{ - if (StringStartsWith(gc, "M112") && !isdigit(gc[4])) // emergency stop - { - reprap.EmergencyStop(); - gcodeReadIndex = gcodeWriteIndex; // clear the buffer - reprap.GetGCodes()->Reset(); - } - else - { - StoreGcodeData(gc, strlen(gc) + 1); - } -} - -// Process a received string of gcodes -void Webserver::HttpInterpreter::StoreGcodeData(const char* data, uint16_t len) -{ - if (len > GetGCodeBufferSpace()) - { - platform->Message(HOST_MESSAGE, "Error: GCode buffer overflow in HTTP Webserver!\n"); - } - else - { - uint16_t remaining = gcodeBufferLength - gcodeWriteIndex; - if (len <= remaining) - { - memcpy(gcodeBuffer + gcodeWriteIndex, data, len); - } - else - { - memcpy(gcodeBuffer + gcodeWriteIndex, data, remaining); - memcpy(gcodeBuffer, data + remaining, len - remaining); - } - gcodeWriteIndex = (gcodeWriteIndex + len) % gcodeBufferLength; - } -} - -// Feeding G Codes to the GCodes class -char Webserver::HttpInterpreter::ReadGCode() -{ - char c; - if (gcodeReadIndex == gcodeWriteIndex) - { - c = 0; - } - else - { - c = gcodeBuffer[gcodeReadIndex]; - gcodeReadIndex = (gcodeReadIndex + 1u) % gcodeBufferLength; - } - return c; -} - // Handle a G Code reply from the GCodes class void Webserver::HttpInterpreter::HandleGCodeReply(OutputBuffer *reply) { @@ -1891,6 +1756,7 @@ void Webserver::HttpInterpreter::ProcessDeferredRequest() transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n"); transaction->Write("Pragma: no-cache\n"); transaction->Write("Expires: 0\n"); + transaction->Write("Access-Control-Allow-Origin: *\n"); transaction->Write("Content-Type: application/json\n"); transaction->Printf("Content-Length: %u\n", (jsonResponse != nullptr) ? jsonResponse->Length() : 0); transaction->Printf("Connection: close\n\n"); @@ -2295,7 +2161,6 @@ void Webserver::FtpInterpreter::ProcessLine() { SendReply(500, "Unknown command."); } - break; case waitingForPasvPort: @@ -2565,7 +2430,7 @@ void Webserver::FtpInterpreter::ChangeDirectory(const char *newDirectory) //******************************************************************************************** Webserver::TelnetInterpreter::TelnetInterpreter(Platform *p, Webserver *ws, Network *n) - : ProtocolInterpreter(p, ws, n), connectedClients(0), processNextLine(false), gcodeReadIndex(0), gcodeWriteIndex(0), gcodeReply(nullptr) + : ProtocolInterpreter(p, ws, n), connectedClients(0), processNextLine(false), gcodeReply(nullptr) { ResetState(); } @@ -2637,7 +2502,8 @@ bool Webserver::TelnetInterpreter::CanParseData() } // In order to support TCP streaming mode, check if we can store any more data at this time - if (GetGCodeBufferSpace() < clientPointer + 1) + RegularGCodeInput * const telnetInput = reprap.GetGCodes()->GetTelnetInput(); + if (telnetInput->BufferSpaceLeft() < clientPointer + 1) { webserver->currentTransaction->Defer(DeferralMode::DeferOnly); return false; @@ -2696,7 +2562,8 @@ bool Webserver::TelnetInterpreter::CharFromClient(char c) { // This line is complete, do we have enough space left to store it? clientMessage[clientPointer] = 0; - if (GetGCodeBufferSpace() < clientPointer + 1) + RegularGCodeInput * const telnetInput = reprap.GetGCodes()->GetTelnetInput(); + if (telnetInput->BufferSpaceLeft() < clientPointer + 1) { // No - defer this transaction, so we can process more of it next time webserver->currentTransaction->Defer(DeferralMode::DeferOnly); @@ -2730,7 +2597,6 @@ void Webserver::TelnetInterpreter::ResetState() state = idle; connectTime = 0; clientPointer = 0; - gcodeReadIndex = gcodeWriteIndex; // clear the buffer } // Usually we should not try to send any data here, because that would purge the packet's @@ -2773,70 +2639,15 @@ bool Webserver::TelnetInterpreter::ProcessLine() transaction->Commit(false); return true; } + // All other codes are stored for the GCodes class - ProcessGcode(clientMessage); + RegularGCodeInput * const telnetInput = reprap.GetGCodes()->GetTelnetInput(); + telnetInput->Put(TELNET_MESSAGE, clientMessage); break; } return false; } -// Process a null-terminated gcode -// We intercept one M Codes so we can deal with 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::TelnetInterpreter::ProcessGcode(const char* gc) -{ - if (StringStartsWith(gc, "M112") && !isdigit(gc[4])) // emergency stop - { - reprap.EmergencyStop(); - gcodeReadIndex = gcodeWriteIndex; // clear the buffer - reprap.GetGCodes()->Reset(); - } - else - { - StoreGcodeData(gc, strlen(gc) + 1); - } -} - -// Process a received string of gcodes -void Webserver::TelnetInterpreter::StoreGcodeData(const char* data, uint16_t len) -{ - if (len > GetGCodeBufferSpace()) - { - platform->Message(HOST_MESSAGE, "Error: GCode buffer overflow in Telnet Webserver!\n"); - } - else - { - uint16_t remaining = gcodeBufferLength - gcodeWriteIndex; - if (len <= remaining) - { - memcpy(gcodeBuffer + gcodeWriteIndex, data, len); - } - else - { - memcpy(gcodeBuffer + gcodeWriteIndex, data, remaining); - memcpy(gcodeBuffer, data + remaining, len - remaining); - } - gcodeWriteIndex = (gcodeWriteIndex + len) % gcodeBufferLength; - } -} - -// Feeding G Codes to the GCodes class -char Webserver::TelnetInterpreter::ReadGCode() -{ - char c; - if (gcodeReadIndex == gcodeWriteIndex) - { - c = 0; - } - else - { - c = gcodeBuffer[gcodeReadIndex]; - gcodeReadIndex = (gcodeReadIndex + 1u) % gcodeBufferLength; - } - return c; -} - // Handle a G-Code reply from the GCodes class; replace \n with \r\n void Webserver::TelnetInterpreter::HandleGCodeReply(OutputBuffer *reply) { diff --git a/src/DuetNG/DuetEthernet/Webserver.h b/src/DuetNG/DuetEthernet/Webserver.h index 3e4f2c30..698f6ea4 100644 --- a/src/DuetNG/DuetEthernet/Webserver.h +++ b/src/DuetNG/DuetEthernet/Webserver.h @@ -130,15 +130,10 @@ public: void Exit(); void Diagnostics(MessageType mtype); - bool GCodeAvailable(const WebSource source) const; - char ReadGCode(const WebSource source); void HandleGCodeReply(const WebSource source, OutputBuffer *reply); void HandleGCodeReply(const WebSource source, const char *reply); uint32_t GetReplySeq() const; - // Returns the available G-Code buffer space of the HTTP interpreter (may be dropped in a future version) - uint16_t GetGCodeBufferSpace(const WebSource source) const; - void ConnectionLost(Connection conn /*const ConnectionState *cs*/); void ConnectionError(); @@ -161,11 +156,8 @@ protected: bool DoingFastUpload() const override; void DoFastUpload(); - bool GCodeAvailable() const; - char ReadGCode(); void HandleGCodeReply(OutputBuffer *reply); void HandleGCodeReply(const char *reply); - uint16_t GetGCodeBufferSpace() const; uint32_t GetReplySeq() const; private: @@ -234,17 +226,7 @@ protected: bool RemoveAuthentication(); const char* GetKeyValue(const char *key) const; // return the value of the specified key, or nullptr if not present - // Deal with incoming G-Codes - - char gcodeBuffer[gcodeBufferLength]; - uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer - - void LoadGcodeBuffer(const char* gc); - void ProcessGcode(const char* gc); - void StoreGcodeData(const char* data, uint16_t len); - // Responses from GCodes class - uint32_t seq; // Sequence number for G-Code replies OutputStack *gcodeReply; @@ -318,11 +300,8 @@ protected: bool CharFromClient(const char c) override; void ResetState(); - bool GCodeAvailable() const; - char ReadGCode(); void HandleGCodeReply(OutputBuffer *reply); void HandleGCodeReply(const char *reply); - uint16_t GetGCodeBufferSpace() const; void SendGCodeReply(); @@ -345,16 +324,7 @@ protected: bool ProcessLine(); - // Deal with incoming G-Codes - - char gcodeBuffer[gcodeBufferLength]; - uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer - - void ProcessGcode(const char* gc); - void StoreGcodeData(const char* data, uint16_t len); - // Converted response from GCodes class (NL -> CRNL) - OutputBuffer * volatile gcodeReply; }; TelnetInterpreter *telnetInterpreter; @@ -376,11 +346,6 @@ inline bool ProtocolInterpreter::IsUploading() const { return uploadState != not inline uint32_t Webserver::GetReplySeq() const { return httpInterpreter->GetReplySeq(); } -inline uint16_t Webserver::HttpInterpreter::GetGCodeBufferSpace() const { return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength; } -inline bool Webserver::HttpInterpreter::GCodeAvailable() const { return gcodeReadIndex != gcodeWriteIndex; } inline uint32_t Webserver::HttpInterpreter::GetReplySeq() const { return seq; } -inline uint16_t Webserver::TelnetInterpreter::GetGCodeBufferSpace() const { return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength; } -inline bool Webserver::TelnetInterpreter::GCodeAvailable() const { return gcodeReadIndex != gcodeWriteIndex; } - #endif diff --git a/src/DuetNG/DuetWiFi/Webserver.cpp b/src/DuetNG/DuetWiFi/Webserver.cpp index 4b290ed9..d7be7363 100644 --- a/src/DuetNG/DuetWiFi/Webserver.cpp +++ b/src/DuetNG/DuetWiFi/Webserver.cpp @@ -96,7 +96,6 @@ const char* badEscapeResponse = "bad escape"; // Constructor and initialisation Webserver::Webserver(Platform* p, Network *n) : state(doingFilename), platform(p), network(n), numSessions(0), clientsServed(0) { - gcodeReadIndex = gcodeWriteIndex = 0; gcodeReply = new OutputStack(); processingDeferredRequest = false; seq = 0; @@ -423,42 +422,6 @@ void Webserver::Diagnostics(MessageType mtype) platform->MessageF(mtype, "HTTP sessions: %d of %d\n", numSessions, maxHttpSessions); } -bool Webserver::GCodeAvailable(const WebSource source) const -{ - switch (source) - { - case WebSource::HTTP: - return gcodeReadIndex != gcodeWriteIndex; - - case WebSource::Telnet: - // Telnet not supported - return false; - } - - return false; -} - -char Webserver::ReadGCode(const WebSource source) -{ - switch (source) - { - case WebSource::HTTP: - if (gcodeReadIndex != gcodeWriteIndex) - { - char c = gcodeBuffer[gcodeReadIndex]; - gcodeReadIndex = (gcodeReadIndex + 1u) % gcodeBufferLength; - return c; - } - break; - - case WebSource::Telnet: - // Telnet not supported - return 0; - } - - return 0; -} - void Webserver::HandleGCodeReply(const WebSource source, OutputBuffer *reply) { switch (source) @@ -733,10 +696,11 @@ bool Webserver::ProcessFirstFragment(HttpSession& session, const char* command, const char* gcodeVal = GetKeyValue("gcode"); if (gcodeVal != nullptr) { - LoadGcodeBuffer(gcodeVal); + RegularGCodeInput * const httpInput = reprap.GetGCodes()->GetHTTPInput(); + httpInput->Put(HTTP_MESSAGE, gcodeVal); if (OutputBuffer::Allocate(response)) { - response->printf("{\"buff\":%u}", GetGCodeBufferSpace()); + response->printf("{\"buff\":%u}", httpInput->BufferSpaceLeft()); } } else @@ -1016,96 +980,4 @@ void Webserver::CheckSessions() } } -// Process a received string of gcodes -void Webserver::LoadGcodeBuffer(const char* gc) -{ - 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 == '\n') - { - gcodeTempBuf[gtp] = 0; - ProcessGcode(gcodeTempBuf); - gtp = 0; - inComment = false; - } - else - { - if (c == ';') - { - inComment = true; - } - - if (gtp == ARRAY_UPB(gcodeTempBuf)) - { - // gcode is too long, we haven't room for another character and a null - if (c != ' ' && !inComment) - { - platform->Message(HOST_MESSAGE, "Error: GCode local buffer overflow in HTTP webserver.\n"); - 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 one M Codes so we can deal with 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) -{ - if (StringStartsWith(gc, "M112") && !isdigit(gc[4])) // emergency stop - { - reprap.EmergencyStop(); - gcodeReadIndex = gcodeWriteIndex; // clear the buffer - reprap.GetGCodes()->Reset(); - } - else - { - StoreGcodeData(gc, strlen(gc) + 1); - } -} - -// Process a received string of gcodes -void Webserver::StoreGcodeData(const char* data, uint16_t len) -{ - if (len > GetGCodeBufferSpace()) - { - platform->Message(HOST_MESSAGE, "Error: GCode buffer overflow in HTTP Webserver!\n"); - } - else - { - uint16_t remaining = gcodeBufferLength - gcodeWriteIndex; - if (len <= remaining) - { - memcpy(gcodeBuffer + gcodeWriteIndex, data, len); - } - else - { - memcpy(gcodeBuffer + gcodeWriteIndex, data, remaining); - memcpy(gcodeBuffer, data + remaining, len - remaining); - } - gcodeWriteIndex = (gcodeWriteIndex + len) % gcodeBufferLength; - } -} - // End diff --git a/src/DuetNG/DuetWiFi/Webserver.h b/src/DuetNG/DuetWiFi/Webserver.h index d8459d8d..a1d5bbc6 100644 --- a/src/DuetNG/DuetWiFi/Webserver.h +++ b/src/DuetNG/DuetWiFi/Webserver.h @@ -59,13 +59,9 @@ public: void Exit(); void Diagnostics(MessageType mtype); - bool GCodeAvailable(const WebSource source) const; - char ReadGCode(const WebSource source); void HandleGCodeReply(const WebSource source, OutputBuffer *reply); void HandleGCodeReply(const WebSource source, const char *reply); uint32_t GetReplySeq() const { return seq; } - // Returns the available G-Code buffer space of the HTTP interpreter (may be dropped in a future version) - uint16_t GetGCodeBufferSpace(const WebSource source) const { return 0; } private: static const uint32_t lastFragmentFlag = 0x80000000; @@ -112,16 +108,9 @@ private: bool GetJsonResponse(uint32_t remoteIp, const char* request, OutputBuffer *&response, const char* key, const char* value, size_t valueLength, bool& keepOpen); void SendFile(const char* nameOfFileToSend, HttpSession& session); - // Deal with incoming G-Codes - char gcodeBuffer[gcodeBufferLength]; - uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer uint32_t seq; // sequence number for G-Code replies - void LoadGcodeBuffer(const char* gc); - void ProcessGcode(const char* gc); - void StoreGcodeData(const char* data, uint16_t len); void SendGCodeReply(HttpSession& session); - uint16_t GetGCodeBufferSpace() const; HttpSession *StartSession(uint32_t ip); // start a new session for this requester HttpSession *FindSession(uint32_t ip); // find an existing session for this requester @@ -170,9 +159,4 @@ private: size_t numSessions, clientsServed; }; -inline uint16_t Webserver::GetGCodeBufferSpace() const -{ - return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength; -} - #endif diff --git a/src/GCodes/GCodeBuffer.cpp b/src/GCodes/GCodeBuffer.cpp index 4b770829..d6659b7c 100644 --- a/src/GCodes/GCodeBuffer.cpp +++ b/src/GCodes/GCodeBuffer.cpp @@ -12,13 +12,19 @@ #include "RepRap.h" // Create a default GCodeBuffer -GCodeBuffer::GCodeBuffer(const char* id, MessageType mt) +GCodeBuffer::GCodeBuffer(const char* id, MessageType mt, bool usesCodeQueue) : machineState(new GCodeMachineState()), identity(id), checksumRequired(false), writingFileDirectory(nullptr), - toolNumberAdjust(0), responseMessageType(mt) + toolNumberAdjust(0), responseMessageType(mt), queueCodes(usesCodeQueue) { Init(); } +void GCodeBuffer::Reset() +{ + while (PopState()) { } + Init(); +} + void GCodeBuffer::Init() { gcodePointer = 0; @@ -31,17 +37,28 @@ void GCodeBuffer::Diagnostics(MessageType mtype) { switch (bufferState) { - case GCodeBufferState::idle: - reprap.GetPlatform()->MessageF(mtype, "%s is idle\n", identity); - break; + case GCodeBufferState::idle: + scratchString.printf("%s is idle", identity); + break; - case GCodeBufferState::ready: - reprap.GetPlatform()->MessageF(mtype, "%s is ready with \"%s\"\n", identity, Buffer()); - break; + case GCodeBufferState::ready: + scratchString.printf("%s is ready with \"%s\"", identity, Buffer()); + break; - case GCodeBufferState::executing: - reprap.GetPlatform()->MessageF(mtype, "%s is doing \"%s\"\n", identity, Buffer()); + case GCodeBufferState::executing: + scratchString.printf("%s is doing \"%s\"", identity, Buffer()); + } + + scratchString.cat(" in state(s)"); + const GCodeMachineState *ms = machineState; + do + { + scratchString.catf(" %d", ms->state); + ms = ms->previous; } + while (ms != nullptr); + scratchString.cat('\n'); + reprap.GetPlatform()->Message(mtype, scratchString.Pointer()); } int GCodeBuffer::CheckSum() const diff --git a/src/GCodes/GCodeBuffer.h b/src/GCodes/GCodeBuffer.h index 49141d22..ffca0c1f 100644 --- a/src/GCodes/GCodeBuffer.h +++ b/src/GCodes/GCodeBuffer.h @@ -16,8 +16,9 @@ class GCodeBuffer { public: - GCodeBuffer(const char* id, MessageType mt); - void Init(); // Set it up + GCodeBuffer(const char* id, MessageType mt, bool useCodeQueue); + void Reset(); // Reset it to its state after start-up + void Init(); // Set it up to parse another G-code void Diagnostics(MessageType mtype); // Write some debug info bool Put(char c); // Add a character to the end bool Put(const char *str, size_t len); // Add an entire string @@ -53,6 +54,7 @@ public: void SetState(GCodeState newState); void AdvanceState(); const char *GetIdentity() const { return identity; } + const bool CanQueueCodes() const { return queueCodes; } uint32_t whenTimerStarted; // when we started waiting bool timerRunning; // true if we are waiting @@ -75,10 +77,11 @@ private: int readPointer; // Where in the buffer to read next bool inComment; // Are we after a ';' character? bool checksumRequired; // True if we only accept commands with a valid checksum - GCodeBufferState bufferState; // Idle, executing or paused + GCodeBufferState bufferState; // Idle, executing or paused const char* writingFileDirectory; // If the G Code is going into a file, where that is int toolNumberAdjust; // The adjustment to tool numbers in commands we receive const MessageType responseMessageType; // The message type we use for responses to commands coming from this channel + bool queueCodes; // Can we queue certain G-codes from this source? }; inline const char* GCodeBuffer::Buffer() const diff --git a/src/GCodes/GCodeInput.cpp b/src/GCodes/GCodeInput.cpp new file mode 100644 index 00000000..ee97ec71 --- /dev/null +++ b/src/GCodes/GCodeInput.cpp @@ -0,0 +1,322 @@ +/* + * GCodeInput.cpp + * + * Created on: 16 Sep 2016 + * Author: Christian + */ + +#include "GCodeInput.h" + +#include "RepRap.h" +#include "GCodes.h" + + +// G-code input class for wrapping around Stream-based hardware ports + +void StreamGCodeInput::Reset() +{ + while (device.available() > 0) + { + device.read(); + } +} + +bool StreamGCodeInput::FillBuffer(GCodeBuffer *gb) +{ + size_t bytesToPass = min<size_t>(device.available(), GCODE_LENGTH); + for(size_t i = 0; i < bytesToPass; i++) + { + char c = static_cast<char>(device.read()); + + if (gb->WritingFileDirectory() == reprap.GetPlatform()->GetWebDir()) + { + // HTML uploads are handled by the GCodes class + reprap.GetGCodes()->WriteHTMLToFile(*gb, c); + } + else if (gb->Put(c)) + { + // Check if we can finish a file upload + if (gb->WritingFileDirectory() != nullptr) + { + reprap.GetGCodes()->WriteGCodeToFile(*gb); + gb->SetFinished(true); + } + + // Code is complete, stop here + return true; + } + } + + return false; +} + +size_t StreamGCodeInput::BytesCached() const +{ + return device.available(); +} + + +// Dynamic G-code input class for caching codes from software-defined sources + + +RegularGCodeInput::RegularGCodeInput(bool removeComments): stripComments(removeComments), + state(GCodeInputState::idle), buffer(reinterpret_cast<char * const>(buf32)), writingPointer(0), readingPointer(0) +{ +} + +void RegularGCodeInput::Reset() +{ + state = GCodeInputState::idle; + writingPointer = readingPointer = 0; +} + +bool RegularGCodeInput::FillBuffer(GCodeBuffer *gb) +{ + size_t bytesToPass = min<size_t>(BytesCached(), GCODE_LENGTH); + for(size_t i = 0; i < bytesToPass; i++) + { + // Get a char from the buffer + char c = buffer[readingPointer++]; + if (readingPointer == GCodeInputBufferSize) + { + readingPointer = 0; + } + + // Pass it on to the GCodeBuffer + if (gb->WritingFileDirectory() == reprap.GetPlatform()->GetWebDir()) + { + // HTML uploads are handled by the GCodes class + reprap.GetGCodes()->WriteHTMLToFile(*gb, c); + } + else if (gb->Put(c)) + { + // Check if we can finish a file upload + if (gb->WritingFileDirectory() != nullptr) + { + reprap.GetGCodes()->WriteGCodeToFile(*gb); + gb->SetFinished(true); + } + + // Code is complete, stop here + return true; + } + } + + return false; +} + +size_t RegularGCodeInput::BytesCached() const +{ + if (writingPointer >= readingPointer) + { + return writingPointer - readingPointer; + } + return GCodeInputBufferSize - readingPointer + writingPointer; +} + +void RegularGCodeInput::Put(MessageType mtype, const char c) +{ + if (BufferSpaceLeft() == 0) + { + // Don't let the buffer overflow if we run out of space + return; + } + + // Check for M112 (emergency stop) and for M122 (diagnostics) while receiving new characters + switch (state) + { + case GCodeInputState::idle: + if (c <= ' ') + { + // ignore whitespaces at the beginning + return; + } + + state = (c == 'M') ? GCodeInputState::doingMCode : GCodeInputState::doingCode; + break; + + + case GCodeInputState::doingCode: + if (stripComments && c == ';') + { + // ignore comments if possible + state = GCodeInputState::inComment; + break; + } + // no break + + case GCodeInputState::inComment: + if (c == 0 || c == '\r' || c == '\n') + { + state = GCodeInputState::idle; + } + break; + + case GCodeInputState::doingMCode: + if (c == '1') + { + state = GCodeInputState::doingMCode1; + } + break; + + case GCodeInputState::doingMCode1: + if (c == '1') + { + state = GCodeInputState::doingMCode11; + } + else if (c == '2') + { + state = GCodeInputState::doingMCode12; + } + else + { + state = GCodeInputState::doingCode; + } + break; + + case GCodeInputState::doingMCode11: + if (c == '2') + { + state = GCodeInputState::doingMCode112; + break; + } + state = GCodeInputState::doingCode; + break; + + case GCodeInputState::doingMCode12: + if (c == '2') + { + state = GCodeInputState::doingMCode122; + break; + } + state = GCodeInputState::doingCode; + break; + + case GCodeInputState::doingMCode112: + if (c <= ' ' || c == ';') + { + // Emergency stop requested - perform it now + reprap.EmergencyStop(); + reprap.GetGCodes()->Reset(); + + // But don't run it twice + Reset(); + return; + } + + state = GCodeInputState::doingCode; + break; + + case GCodeInputState::doingMCode122: + if (c <= ' ' || c == ';') + { + // Diagnostics requested - report them now + // Only send the report to the appropriate channel, because if we send it as a generic message instead then it gets truncated. + reprap.Diagnostics(mtype); + + // But don't report them twice + Reset(); + return; + } + break; + } + + // Feed another character into the buffer + if (state != GCodeInputState::inComment) + { + buffer[writingPointer++] = c; + if (writingPointer == GCodeInputBufferSize) + { + writingPointer = 0; + } + } +} + +void RegularGCodeInput::Put(MessageType mtype, const char *buf) +{ + Put(mtype, buf, strlen(buf) + 1); +} + +void RegularGCodeInput::Put(MessageType mtype, const char *buf, size_t len) +{ + if (len > BufferSpaceLeft()) + { + // Don't cache this if we don't have enough space left + return; + } + + for (size_t i = 0; i < len; i++) + { + Put(mtype, buf[i]); + } +} + +size_t RegularGCodeInput::BufferSpaceLeft() const +{ + return (readingPointer - writingPointer - 1u) % GCodeInputBufferSize; +} + + +// File-based G-code input source + +// Reset this input. Should be called when the associated file is being closed +void FileGCodeInput::Reset() +{ + lastFile = nullptr; + RegularGCodeInput::Reset(); +} + +// Read another chunk of G-codes from the file and return true if more data is available +bool FileGCodeInput::ReadFromFile(FileData &file) +{ + const size_t bytesCached = BytesCached(); + + // Keep track of the last file we read from + if (lastFile != nullptr && lastFile != file.f) + { + if (bytesCached > 0) + { + // Rewind back to the right position so we can resume at the right position later. + // This may be necessary when nested macros are executed. + lastFile->Seek(lastFile->Position() - bytesCached); + } + + RegularGCodeInput::Reset(); + } + lastFile = file.f; + + // Read more from the file + if (bytesCached < GCodeInputFileReadThreshold) + { + // Reset the read+write pointers for better performance if possible + if (readingPointer == writingPointer) + { + readingPointer = writingPointer = 0; + } + + // Read blocks with sizes multiple of 4 for HSMCI efficiency + uint32_t readBuffer32[(GCodeInputBufferSize + 3) / 4]; + char * const readBuffer = reinterpret_cast<char * const>(readBuffer32); + + int bytesRead = file.Read(readBuffer, BufferSpaceLeft() & (~3)); + if (bytesRead > 0) + { + int remaining = GCodeInputBufferSize - writingPointer; + if (bytesRead <= remaining) + { + memcpy(buffer + writingPointer, readBuffer, bytesRead); + } + else + { + memcpy(buffer + writingPointer, readBuffer, remaining); + memcpy(buffer, readBuffer + remaining, bytesRead - remaining); + } + writingPointer = (writingPointer + bytesRead) % GCodeInputBufferSize; + + return true; + } + } + + return bytesCached > 0; +} + diff --git a/src/GCodes/GCodeInput.h b/src/GCodes/GCodeInput.h new file mode 100644 index 00000000..1c0084ba --- /dev/null +++ b/src/GCodes/GCodeInput.h @@ -0,0 +1,107 @@ +/* + * GCodeInput.h + * + * Created on: 16 Sep 2016 + * Author: Christian + */ + +#ifndef GCODEINPUT_H +#define GCODEINPUT_H + +#include "RepRapFirmware.h" + +#include "GCodeBuffer.h" +#include "Storage/FileStore.h" + + +const size_t GCodeInputBufferSize = 256; // How many bytes can we cache per input source? +const size_t GCodeInputFileReadThreshold = 128; // How many free bytes must be available before data is read from the SD card? + + +// This base class is intended to provide incoming G-codes for the GCodeBuffer class +class GCodeInput +{ +public: + virtual void Reset() = 0; // Clean all the cached data from this input + virtual bool FillBuffer(GCodeBuffer *gb) = 0; // Fill a GCodeBuffer with the last available G-code + virtual size_t BytesCached() const = 0; // How many bytes have been cached? +}; + + +// This class wraps around an existing Stream device which lets us avoid double buffering. +// The only downside is that we cannot (yet) look through the hardware buffer and check for requested emergency stops. +// TODO: This will require some more work in the Arduino core. +class StreamGCodeInput : GCodeInput +{ +public: + StreamGCodeInput(Stream &dev) : device(dev) { } + + void Reset() override; + bool FillBuffer(GCodeBuffer *gb) override; // Fill a GCodeBuffer with the last available G-code + size_t BytesCached() const override; // How many bytes have been cached? + +private: + Stream &device; +}; + + +// When characters from input sources are received, they should be checked consequently for M112 (Emergency Stop). +// This allows us to react faster to an incoming emergency stop since other codes may be blocking the associated +// GCodeBuffer instance. +enum class GCodeInputState +{ + idle, + doingCode, + inComment, + doingMCode, + doingMCode1, + doingMCode11, + doingMCode12, + doingMCode112, + doingMCode122 +}; + +// This class allows caching of dynamic content (from web-based sources) and implements a simple ring buffer. +// In addition, incoming codes are checked for M112 (emergency stop) to execute perform emergency stops as quickly +// as possible. Comments can be optionally stripped from sources where comments are not needed (e.g. HTTP). +class RegularGCodeInput : GCodeInput +{ +public: + RegularGCodeInput(bool removeComments); + + void Reset() override; + bool FillBuffer(GCodeBuffer *gb) override; // Fill a GCodeBuffer with the last available G-code + size_t BytesCached() const override; // How many bytes have been cached? + + void Put(MessageType mtype, const char c); // Append a single character + void Put(MessageType mtype, const char *buf); // Append a null-terminated string to the buffer + void Put(MessageType mtype, const char *buf, size_t len); // Append a generic string to the buffer + + size_t BufferSpaceLeft() const; // How much space do we have left? + +private: + bool stripComments; + GCodeInputState state; + +protected: + uint32_t buf32[(GCodeInputBufferSize + 3) / 4]; + char * const buffer; + size_t writingPointer, readingPointer; +}; + +// This class is an expansion of the RegularGCodeInput class to buffer G-codes and to rewind file positions when +// nested G-code files are started. However buffered codes are not explicitly checked for M112. +class FileGCodeInput : public RegularGCodeInput +{ +public: + FileGCodeInput() : RegularGCodeInput(false), lastFile(nullptr) { } + + void Reset() override; // This should be called when the last file is being closed + + bool ReadFromFile(FileData &file); // Read another chunk of G-codes from the file and return true if more data is available + +private: + FileStore *lastFile; +}; + +#endif diff --git a/src/GCodes/GCodeQueue.cpp b/src/GCodes/GCodeQueue.cpp new file mode 100644 index 00000000..f881e4ea --- /dev/null +++ b/src/GCodes/GCodeQueue.cpp @@ -0,0 +1,224 @@ +/* + * GCodeQueue.cpp + * + * Created on: 22 Jun 2016 + * Author: Christian + */ + +#include "GCodeQueue.h" + +#include "RepRap.h" +#include "GCodes.h" +#include "Movement/Move.h" + +// GCodeQueue class + +GCodeQueue::GCodeQueue() : freeItems(nullptr), queuedItems(nullptr) +{ + for(size_t i = 0; i < maxQueuedCodes; i++) + { + freeItems = new QueuedCode(freeItems); + } +} + +bool GCodeQueue::QueueCode(GCodeBuffer &gb, uint32_t segmentsLeft) +{ + // Don't queue anything if no moves are being performed + uint32_t scheduledMoves = reprap.GetMove()->GetScheduledMoves() + segmentsLeft; + if (scheduledMoves == reprap.GetMove()->GetCompletedMoves()) + { + return false; + } + +#if SUPPORT_ROLAND + // Don't queue codes if the Roland module is active + if (reprap.GetRoland()->Active()) + { + return false; + } +#endif + + // Check for G-Codes that can be queued + bool queueCode = false; + switch (gb.GetCommandLetter()) + { + case 'G': + { + const int code = gb.GetIValue(); + + // Set active/standby temperatures + queueCode = (code == 10 && gb.Seen('P')); + break; + } + + case 'M': + { + const int code = gb.GetIValue(); + + // Fan control + queueCode |= (code == 106 || code == 107); + + // Set temperatures and return immediately + queueCode |= (code == 104 || code == 140 || code == 141 || code == 144); + + // Display Message (LCD), Beep, RGB colour, Set servo position + queueCode |= (code == 117 || code == 300 || code == 280 || code == 420); + + // Valve control + queueCode |= (code == 126 || code == 127); + break; + } + } + + // Does it make sense to queue this code? + if (queueCode) + { + char codeToRun[GCODE_LENGTH]; + size_t codeToRunLength; + + // Can we queue this code somewhere? + if (freeItems == nullptr) + { + // No - we've run out of free items. Run the first outstanding code + queueCode = false; + codeToRunLength = strlen(queuedItems->code); + strncpy(codeToRun, queuedItems->code, codeToRunLength); + codeToRun[ARRAY_UPB(codeToRun)] = 0; + + // Release the first queued item so that it can be reused later + QueuedCode *item = queuedItems; + queuedItems = item->next; + item->next = nullptr; + freeItems = item; + } + + // Unlink a free element and assign gb's code to it + QueuedCode *code = freeItems; + freeItems = code->next; + code->AssignFrom(gb); + code->executeAtMove = scheduledMoves; + + // Append it to the list of queued codes + if (queuedItems == nullptr) + { + queuedItems = code; + } + else + { + QueuedCode *last = queuedItems; + while (last->Next() != nullptr) + { + last = last->Next(); + } + last->next = code; + } + code->next = nullptr; + + // Overwrite the passed gb's content if we could not store its original code + if (!queueCode && !gb.Put(codeToRun, codeToRunLength)) + { + gb.Put('\n'); + } + } + + return queueCode; +} + +bool GCodeQueue::FillBuffer(GCodeBuffer *gb) +{ + // Can this buffer be filled? + if (queuedItems == nullptr || queuedItems->executeAtMove > reprap.GetMove()->GetCompletedMoves()) + { + // No - stop here + return false; + } + + // Yes - load it into the passed GCodeBuffer instance + QueuedCode *code = queuedItems; + code->AssignTo(gb); + + // Release this item again + queuedItems = queuedItems->next; + code->next = freeItems; + freeItems = code; + return true; +} + +// Because some moves may end before the print is actually paused, we need a method to +// remove all the entries that will not be executed after the print has finally paused +void GCodeQueue::PurgeEntries() +{ + QueuedCode *item = queuedItems, *lastItem = nullptr; + while (item != nullptr) + { + if (item->executeAtMove > reprap.GetMove()->GetScheduledMoves()) + { + // Release this item + QueuedCode *nextItem = item->Next(); + item->next = freeItems; + freeItems = item; + + // Unlink it from the list + if (lastItem == nullptr) + { + queuedItems = nextItem; + } + else + { + lastItem->next = nextItem; + } + item = nextItem; + } + else + { + lastItem = item; + item = item->Next(); + } + } +} + +void GCodeQueue::Clear() +{ + while (queuedItems != nullptr) + { + QueuedCode *item = queuedItems; + queuedItems = item->Next(); + item->next = freeItems; + freeItems = item; + } +} + +void GCodeQueue::Diagnostics(MessageType mtype) +{ + reprap.GetPlatform()->MessageF(mtype, "Code queue is %s\n", (queuedItems == nullptr) ? "empty." : "not empty:"); + if (queuedItems != nullptr) + { + QueuedCode *item = queuedItems; + size_t queueLength = 0; + do + { + queueLength++; + reprap.GetPlatform()->MessageF(mtype, "Queued '%s' for move %d\n", item->code, item->executeAtMove); + } while ((item = item->Next()) != nullptr); + reprap.GetPlatform()->MessageF(mtype, "%d of %d codes have been queued.\n", queueLength, maxQueuedCodes); + } +} + + +// QueuedCode class + +void QueuedCode::AssignFrom(GCodeBuffer &gb) +{ + toolNumberAdjust = gb.GetToolNumberAdjust(); + strncpy(code, gb.Buffer(), GCODE_LENGTH); + code[ARRAY_UPB(code)] = 0; +} + +void QueuedCode::AssignTo(GCodeBuffer *gb) +{ + gb->SetToolNumberAdjust(toolNumberAdjust); + if (!gb->Put(code, strlen(code))) + { + gb->Put('\n'); + } +} diff --git a/src/GCodes/GCodeQueue.h b/src/GCodes/GCodeQueue.h new file mode 100644 index 00000000..fb1d8772 --- /dev/null +++ b/src/GCodes/GCodeQueue.h @@ -0,0 +1,53 @@ +/* + * GCodeQueue.h + * + * Created on: 22 Jun 2016 + * Author: Christian + */ +#ifndef GCODEQUEUE_H +#define GCODEQUEUE_H + +#include "RepRapFirmware.h" +#include "GCodeBuffer.h" + +const size_t maxQueuedCodes = 8; // How many codes can be queued? + +class QueuedCode; + +class GCodeQueue +{ + public: + GCodeQueue(); + + bool QueueCode(GCodeBuffer &gb, uint32_t segmentsLeft); // Attempt to queue a G-code and return true on success + bool FillBuffer(GCodeBuffer *gb); // If there is another move to execute at this time, fill a buffer + void PurgeEntries(); // Remove stored codes when a print is being paused + void Clear(); // Clean up all the stored codes + + void Diagnostics(MessageType mtype); + + private: + QueuedCode *freeItems; + QueuedCode *queuedItems; +}; + +class QueuedCode +{ + public: + friend class GCodeQueue; + + QueuedCode(QueuedCode *n) : next(n) { } + QueuedCode *Next() const { return next; } + + private: + QueuedCode *next; + + char code[GCODE_LENGTH]; + uint32_t executeAtMove; + int toolNumberAdjust; + + void AssignFrom(GCodeBuffer &gb); + void AssignTo(GCodeBuffer *gb); +}; + +#endif diff --git a/src/GCodes/GCodes.cpp b/src/GCodes/GCodes.cpp index f537eb5c..3e12f83c 100644 --- a/src/GCodes/GCodes.cpp +++ b/src/GCodes/GCodes.cpp @@ -25,6 +25,7 @@ #include "GCodes.h" #include "GCodeBuffer.h" +#include "GCodeQueue.h" #include "Heating/Heat.h" #include "Platform.h" #include "Movement/Move.h" @@ -49,6 +50,7 @@ const char* DefaultHeightMapFile = "heightmap.csv"; const size_t gcodeReplyLength = 2048; // long enough to pass back a reasonable number of files in response to M20 + void GCodes::RestorePoint::Init() { for (size_t i = 0; i < DRIVES; ++i) @@ -62,12 +64,21 @@ GCodes::GCodes(Platform* p, Webserver* w) : platform(p), webserver(w), active(false), isFlashing(false), fileBeingHashed(nullptr), lastWarningMillis(0) { - httpGCode = new GCodeBuffer("http", HTTP_MESSAGE); - telnetGCode = new GCodeBuffer("telnet", TELNET_MESSAGE); - fileGCode = new GCodeBuffer("file", GENERIC_MESSAGE); - serialGCode = new GCodeBuffer("serial", HOST_MESSAGE); - auxGCode = new GCodeBuffer("aux", AUX_MESSAGE); - daemonGCode = new GCodeBuffer("daemon", GENERIC_MESSAGE); + httpInput = new RegularGCodeInput(true); + telnetInput = new RegularGCodeInput(true); + fileInput = new FileGCodeInput(); + serialInput = new StreamGCodeInput(SERIAL_MAIN_DEVICE); + auxInput = new StreamGCodeInput(SERIAL_AUX_DEVICE); + + httpGCode = new GCodeBuffer("http", HTTP_MESSAGE, false); + telnetGCode = new GCodeBuffer("telnet", TELNET_MESSAGE, true); + fileGCode = new GCodeBuffer("file", GENERIC_MESSAGE, true); + serialGCode = new GCodeBuffer("serial", HOST_MESSAGE, true); + auxGCode = new GCodeBuffer("aux", AUX_MESSAGE, false); + daemonGCode = new GCodeBuffer("daemon", GENERIC_MESSAGE, false); + queuedGCode = new GCodeBuffer("queue", GENERIC_MESSAGE, false); + + codeQueue = new GCodeQueue(); } void GCodes::Exit() @@ -94,7 +105,6 @@ void GCodes::Init() eofStringLength = strlen(eofString); offSetSet = false; runningConfigFile = false; - doingToolChange = false; active = true; longWait = platform->Time(); limitAxes = true; @@ -114,18 +124,22 @@ void GCodes::Init() retractHop = 0.0; retractSpeed = unRetractSpeed = DefaultRetractSpeed * SecondsToMinutes; isRetracted = false; + lastAuxStatusReportType = -1; // no status reports requested yet } // This is called from Init and when doing an emergency stop void GCodes::Reset() { - httpGCode->Init(); - telnetGCode->Init(); - fileGCode->Init(); - serialGCode->Init(); - auxGCode->Init(); + // Here we could reset the input sources as well, but this would mess up M122\nM999 + // because both codes are sent at once from the web interface. Hence we don't do this here + + httpGCode->Reset(); + telnetGCode->Reset(); + fileGCode->Reset(); + serialGCode->Reset(); + auxGCode->Reset(); auxGCode->SetCommsProperties(1); // by default, we require a checksum on the aux port - daemonGCode->Init(); + daemonGCode->Reset(); nextGcodeSource = 0; @@ -160,10 +174,12 @@ void GCodes::Reset() simulationMode = 0; simulationTime = 0.0; isPaused = false; + doingToolChange = false; moveBuffer.filePos = noFilePosition; lastEndstopStates = platform->GetAllEndstopStates(); firmwareUpdateModuleMap = 0; + codeQueue->Clear(); cancelWait = isWaiting = displayNoToolWarning = displayDeltaNotHomedWarning = false; for (size_t i = 0; i < NumResources; ++i) @@ -335,28 +351,8 @@ void GCodes::Spin() } else { + CheckReportDue(gb, reply); isWaiting = true; - - // In Marlin emulation mode we should return some sort of undocumented message here every second. Try a standard temperature report. - if (platform->Emulating() == marlin && gb.GetResponseMessageType() == MessageType::HOST_MESSAGE) - { - const uint32_t now = millis(); - if (gb.timerRunning) - { - if (now - gb.whenTimerStarted >= 1000) - { - gb.whenTimerStarted = now; - GenerateTemperatureReport(reply); - reply.cat('\n'); - platform->Message(HOST_MESSAGE, reply.Pointer()); - } - } - else - { - gb.whenTimerStarted = now; - gb.timerRunning = true; - } - } } break; @@ -696,80 +692,33 @@ void GCodes::StartNextGCode(GCodeBuffer& gb, StringRef& reply) { DoFilePrint(gb, reply); } + else if (&gb == queuedGCode) + { + // Code queue + codeQueue->FillBuffer(queuedGCode); + } else if (&gb == httpGCode) { // Webserver - for (unsigned int i = 0; i < 16 && webserver->GCodeAvailable(WebSource::HTTP); ++i) - { - const char b = webserver->ReadGCode(WebSource::HTTP); - if (gb.Put(b)) - { - // We have a complete gcode - if (gb.WritingFileDirectory() != nullptr) - { - WriteGCodeToFile(gb); - gb.SetFinished(true); - } - else - { - gb.SetFinished(ActOnCode(gb, reply)); - } - break; - } - } + httpInput->FillBuffer(httpGCode); } else if (&gb == telnetGCode) { // Telnet - for (unsigned int i = 0; i < GCODE_LENGTH && webserver->GCodeAvailable(WebSource::Telnet); ++i) - { - char b = webserver->ReadGCode(WebSource::Telnet); - if (gb.Put(b)) - { - gb.SetFinished(ActOnCode(gb, reply)); - break; - } - } + telnetInput->FillBuffer(telnetGCode); } else if (&gb == serialGCode) { // USB interface - for (unsigned int i = 0; i < 16 && platform->GCodeAvailable(SerialSource::USB); ++i) - { - const char b = platform->ReadFromSource(SerialSource::USB); - // Check the special case of uploading the reprap.htm file - if (gb.WritingFileDirectory() == platform->GetWebDir()) - { - WriteHTMLToFile(gb, b); - } - else if (gb.Put(b)) // add char to buffer and test whether the gcode is complete - { - // We have a complete gcode - if (gb.WritingFileDirectory() != nullptr) - { - WriteGCodeToFile(gb); - gb.SetFinished(true); - } - else - { - gb.SetFinished(ActOnCode(gb, reply)); - } - break; - } - } + serialInput->FillBuffer(serialGCode); } else if (&gb == auxGCode) { // Aux serial port (typically PanelDue) - for (unsigned int i = 0; i < 16 && platform->GCodeAvailable(SerialSource::AUX); ++i) + if (auxInput->FillBuffer(auxGCode)) { - char b = platform->ReadFromSource(SerialSource::AUX); - if (gb.Put(b)) // add char to buffer and test whether the gcode is complete - { - platform->SetAuxDetected(); - gb.SetFinished(ActOnCode(gb, reply)); - break; - } + // by default we assume no PanelDue is attached + platform->SetAuxDetected(); } } } @@ -777,66 +726,65 @@ void GCodes::StartNextGCode(GCodeBuffer& gb, StringRef& reply) void GCodes::DoFilePrint(GCodeBuffer& gb, StringRef& reply) { FileData& fd = gb.MachineState().fileState; - for (int i = 0; i < 50 && fd.IsLive(); ++i) + + // Do we have more data to process? + if (fileInput->ReadFromFile(fd)) { - char b; - if (fd.Read(b)) + // Yes - fill up the GCodeBuffer and run the next code + if (fileInput->FillBuffer(&gb)) { - if (gb.Put(b)) + gb.SetFinished(ActOnCode(gb, reply)); + } + } + else + { + // We have reached the end of the file. Check for the last line of gcode not ending in newline. + if (!gb.StartingNewCode()) // if there is something in the buffer + { + if (gb.Put('\n')) // in case there wasn't a newline ending the file { gb.SetFinished(ActOnCode(gb, reply)); return; } } - else + + gb.Init(); // mark buffer as empty + + if (gb.MachineState().previous == nullptr) { - // We have reached the end of the file. Check for the last line of gcode not ending in newline. - if (!gb.StartingNewCode()) // if there is something in the buffer + // Finished printing SD card file + // Don't close the file until all moves have been completed, in case the print gets paused. + // Also, this keeps the state as 'Printing' until the print really has finished. + if (LockMovementAndWaitForStandstill(gb)) { - if (gb.Put('\n')) // in case there wasn't a newline ending the file + fileInput->Reset(); + fd.Close(); + UnlockAll(gb); + reprap.GetPrintMonitor()->StoppedPrint(); + if (platform->Emulating() == marlin) { - gb.SetFinished(ActOnCode(gb, reply)); - return; + // Pronterface expects a "Done printing" message + HandleReply(gb, false, "Done printing file"); } } - - gb.Init(); // mark buffer as empty - - if (gb.MachineState().previous == nullptr) + } + else + { + // Finished a macro or finished processing config.g + fileInput->Reset(); + fd.Close(); + if (runningConfigFile) { - // Finished printing SD card file - // Don't close the file until all moves have been completed, in case the print gets paused. - // Also, this keeps the state as 'Printing' until the print really has finished. - if (LockMovementAndWaitForStandstill(gb)) - { - fd.Close(); - UnlockAll(gb); - reprap.GetPrintMonitor()->StoppedPrint(); - if (platform->Emulating() == marlin) - { - // Pronterface expects a "Done printing" message - HandleReply(gb, false, "Done printing file"); - } - } + CopyConfigFinalValues(gb); + runningConfigFile = false; } - else + Pop(gb); + gb.Init(); + if (gb.GetState() == GCodeState::normal) { - // Finished a macro or finished processing config.g - fd.Close(); - if (runningConfigFile) - { - CopyConfigFinalValues(gb); - runningConfigFile = false; - } - Pop(gb); - gb.Init(); - if (gb.GetState() == GCodeState::normal) - { - UnlockAll(gb); - HandleReply(gb, false, ""); - } + UnlockAll(gb); + HandleReply(gb, false, ""); } - return; } } } @@ -934,6 +882,9 @@ void GCodes::DoPause(GCodeBuffer& gb) { fdata.Seek(fPos); // replay the abandoned instructions if/when we resume } + fileInput->Reset(); + codeQueue->PurgeEntries(); + if (segmentsLeft != 0) { for (size_t drive = numAxes; drive < DRIVES; ++drive) @@ -969,13 +920,15 @@ void GCodes::Diagnostics(MessageType mtype) platform->Message(mtype, "=== GCodes ===\n"); platform->MessageF(mtype, "Segments left: %u\n", segmentsLeft); platform->MessageF(mtype, "Stack records: %u allocated, %u in use\n", GCodeMachineState::GetNumAllocated(), GCodeMachineState::GetNumInUse()); - const GCodeBuffer *movementOwner = resourceOwners[MoveResource]; + const GCodeBuffer * const movementOwner = resourceOwners[MoveResource]; platform->MessageF(mtype, "Movement lock held by %s\n", (movementOwner == nullptr) ? "null" : movementOwner->GetIdentity()); for (size_t i = 0; i < ARRAY_SIZE(gcodeSources); ++i) { gcodeSources[i]->Diagnostics(mtype); } + + codeQueue->Diagnostics(mtype); } // Lock movement and wait for pending moves to finish. @@ -1360,7 +1313,7 @@ int GCodes::SetUpMove(GCodeBuffer& gb, StringRef& reply) break; } } - moveBuffer.filePos = (&gb == fileGCode) ? gb.MachineState().fileState.GetPosition() : noFilePosition; + moveBuffer.filePos = (&gb == fileGCode) ? gb.MachineState().fileState.GetPosition() - fileInput->BytesCached() : noFilePosition; moveBuffer.canPauseAfter = (moveBuffer.endStopsToCheck == 0); //debugPrintf("Queue move pos %u\n", moveFilePos); } @@ -1425,16 +1378,19 @@ bool GCodes::DoArcMove(GCodeBuffer& gb, bool clockwise) // Deal with the X axes for (size_t axis = 0; axis < numAxes; ++axis) { - arcCentre[axis] = moveBuffer.initialCoords[axis] + iParam; - if ((arcAxesMoving & (1 << axis)) != 0) + if (axis != Y_AXIS) { - if (axesRelative) - { - moveBuffer.coords[axis] += xParam; - } - else + arcCentre[axis] = moveBuffer.initialCoords[axis] + iParam; + if ((arcAxesMoving & (1 << axis)) != 0) { - moveBuffer.coords[axis] = xParam - currentTool->GetOffset()[axis]; + if (axesRelative) + { + moveBuffer.coords[axis] += xParam; + } + else + { + moveBuffer.coords[axis] = xParam - currentTool->GetOffset()[axis]; + } } } } @@ -1548,9 +1504,10 @@ bool GCodes::ReadMove(RawMove& m) { // Calculate the move length, to see how much new babystepping is appropriate for this move float xMoveLength = 0.0; + const uint32_t xAxes = reprap.GetCurrentXAxes(); for (size_t drive = 0; drive < numAxes; ++drive) { - if ((arcAxesMoving & (1 << drive)) != 0) + if ((xAxes & (1 << drive)) != 0) { xMoveLength = max<float>(xMoveLength, fabs(m.coords[drive] - m.initialCoords[drive])); } @@ -2389,24 +2346,16 @@ bool GCodes::SaveHeightMap(GCodeBuffer& gb, StringRef& reply) const return err; } -// Clear the height map -void GCodes::ClearHeightMap() const -{ - HeightMap& heightMap = reprap.GetMove()->AccessBedProbeGrid(); - heightMap.ClearGridHeights(); - heightMap.UseHeightMap(false); -} - // Return the current coordinates as a printable string. // Coordinates are updated at the end of each movement, so this won't tell you where you are mid-movement. void GCodes::GetCurrentCoordinates(StringRef& s) const { float liveCoordinates[DRIVES]; reprap.GetMove()->LiveCoordinates(liveCoordinates, reprap.GetCurrentXAxes()); - const Tool *currentTool = reprap.GetCurrentTool(); + const Tool * const currentTool = reprap.GetCurrentTool(); if (currentTool != nullptr) { - const float *offset = currentTool->GetOffset(); + const float * const offset = currentTool->GetOffset(); for (size_t i = 0; i < numAxes; ++i) { liveCoordinates[i] += offset[i]; @@ -2416,7 +2365,7 @@ void GCodes::GetCurrentCoordinates(StringRef& s) const s.Clear(); for (size_t axis = 0; axis < numAxes; ++axis) { - s.catf("%c: %.2f ", axisLetters[axis], liveCoordinates[axis]); + s.catf("%c: %.3f ", axisLetters[axis], liveCoordinates[axis]); } for (size_t i = numAxes; i < DRIVES; i++) { @@ -2477,6 +2426,8 @@ void GCodes::WriteHTMLToFile(GCodeBuffer& gb, char b) } else { + // NB: This approach isn't very efficient, but I (chrishamm) think the whole uploading + // code should be rewritten anyway in the future and moved away from the GCodes class. fileBeingWritten->Write(b); } } @@ -2969,21 +2920,8 @@ void GCodes::HandleReply(GCodeBuffer& gb, bool error, const char* reply) } const Compatibility c = (&gb == serialGCode || &gb == telnetGCode) ? platform->Emulating() : me; - MessageType type = GENERIC_MESSAGE; - if (&gb == httpGCode) - { - type = HTTP_MESSAGE; - } - else if (&gb == telnetGCode) - { - type = TELNET_MESSAGE; - } - else if (&gb == serialGCode) - { - type = HOST_MESSAGE; - } - - const char* response = (gb.Seen('M') && gb.GetIValue() == 998) ? "rs " : "ok"; + const MessageType type = gb.GetResponseMessageType(); + const char* const response = (gb.Seen('M') && gb.GetIValue() == 998) ? "rs " : "ok"; const char* emulationType = 0; switch (c) @@ -3072,21 +3010,8 @@ void GCodes::HandleReply(GCodeBuffer& gb, bool error, OutputBuffer *reply) } const Compatibility c = (&gb == serialGCode || &gb == telnetGCode) ? platform->Emulating() : me; - MessageType type = GENERIC_MESSAGE; - if (&gb == httpGCode) - { - type = HTTP_MESSAGE; - } - else if (&gb == telnetGCode) - { - type = TELNET_MESSAGE; - } - else if (&gb == serialGCode) - { - type = HOST_MESSAGE; - } - - const char* response = (gb.Seen('M') && gb.GetIValue() == 998) ? "rs " : "ok"; + const MessageType type = gb.GetResponseMessageType(); + const char* const response = (gb.Seen('M') && gb.GetIValue() == 998) ? "rs " : "ok"; const char* emulationType = nullptr; switch (c) @@ -3325,7 +3250,7 @@ bool GCodes::RetractFilament(GCodeBuffer& gb, bool retract) moveBuffer.moveType = 0; moveBuffer.isFirmwareRetraction = true; moveBuffer.usePressureAdvance = false; - moveBuffer.filePos = (&gb == fileGCode) ? gb.MachineState().fileState.GetPosition() : noFilePosition; + moveBuffer.filePos = (&gb == fileGCode) ? gb.MachineState().fileState.GetPosition() - fileInput->BytesCached() : noFilePosition; moveBuffer.canPauseAfter = !retract; // don't pause after a retraction because that could cause too much retraction moveBuffer.xAxes = xAxes; segmentsLeft = 1; @@ -3354,7 +3279,9 @@ void GCodes::CancelPrint() segmentsLeft = 0; isPaused = false; + fileInput->Reset(); fileGCode->Init(); + FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState; if (fileBeingPrinted.IsLive()) { @@ -3362,6 +3289,9 @@ void GCodes::CancelPrint() } reprap.GetPrintMonitor()->StoppedPrint(); + + reprap.GetMove()->ResetMoveCounters(); + codeQueue->Clear(); } // Return true if all the heaters for the specified tool are at their set temperatures @@ -3559,7 +3489,7 @@ bool GCodes::WriteConfigOverrideFile(StringRef& reply, const char *fileName) con } // Store a standard-format temperature report in 'reply'. This doesn't put a newline character at the end. -void GCodes::GenerateTemperatureReport(StringRef& reply) +void GCodes::GenerateTemperatureReport(StringRef& reply) const { const int8_t bedHeater = reprap.GetHeat()->GetBedHeater(); const int8_t chamberHeater = reprap.GetHeat()->GetChamberHeater(); @@ -3590,6 +3520,70 @@ void GCodes::GenerateTemperatureReport(StringRef& reply) } } +// Check whether we need to report temperatures or status. +// 'reply' is a convenient buffer that is free for us to use. +void GCodes::CheckReportDue(GCodeBuffer& gb, StringRef& reply) const +{ + const uint32_t now = millis(); + if (gb.timerRunning) + { + if (now - gb.whenTimerStarted >= 1000) + { + if (platform->Emulating() == marlin && (&gb == serialGCode || &gb == telnetGCode)) + { + // In Marlin emulation mode we should return a standard temperature report every second + GenerateTemperatureReport(reply); + reply.cat('\n'); + platform->Message(HOST_MESSAGE, reply.Pointer()); + } + if (lastAuxStatusReportType >= 0) + { + // Send a standard status response for PanelDue + OutputBuffer * const statusBuf = GenerateJsonStatusResponse(0, -1, ResponseSource::AUX); + if (statusBuf != nullptr) + { + platform->AppendAuxReply(statusBuf); + } + } + gb.whenTimerStarted = now; + } + } + else + { + gb.whenTimerStarted = now; + gb.timerRunning = true; + } +} + +// Generate a M408 response +// Return the output buffer containing the response, or nullptr if we failed +OutputBuffer *GCodes::GenerateJsonStatusResponse(int type, int seq, ResponseSource source) const +{ + OutputBuffer *statusResponse = nullptr; + switch (type) + { + case 0: + case 1: + statusResponse = reprap.GetLegacyStatusResponse(type + 2, seq); + break; + + case 2: + case 3: + case 4: + statusResponse = reprap.GetStatusResponse(type - 1, source); + break; + + case 5: + statusResponse = reprap.GetConfigResponse(); + break; + } + if (statusResponse != nullptr) + { + statusResponse->cat('\n'); + } + return statusResponse; +} + // Resource locking/unlocking // Lock the resource, returning true if success. diff --git a/src/GCodes/GCodes.h b/src/GCodes/GCodes.h index ade82841..d3d5298e 100644 --- a/src/GCodes/GCodes.h +++ b/src/GCodes/GCodes.h @@ -23,10 +23,13 @@ Licence: GPL #define GCODES_H #include "RepRapFirmware.h" +#include "RepRap.h" // for type ResponseSource #include "Libraries/sha1/sha1.h" #include "Platform.h" // for type EndStopHit +#include "GCodeInput.h" class GCodeBuffer; +class GCodeQueue; const char feedrateLetter = 'F'; // GCode feedrate const char extrudeLetter = 'E'; // GCode extrude @@ -109,6 +112,12 @@ public: float GetTotalRawExtrusion() const { return rawExtruderTotal; } // Get the total extrusion since start of print, all drives float GetBabyStepOffset() const; // Get the current baby stepping Z offset + RegularGCodeInput *GetHTTPInput() const { return httpInput; } + RegularGCodeInput *GetTelnetInput() const { return telnetInput; } + + void WriteGCodeToFile(GCodeBuffer& gb); // Write this GCode into a file + void WriteHTMLToFile(GCodeBuffer& gb, char b); // Save an HTML file (usually to upload a new web interface) + bool IsFlashing() const { return isFlashing; } // Is a new firmware binary going to be flashed? bool IsPaused() const; @@ -194,16 +203,16 @@ private: void HandleReply(GCodeBuffer& gb, bool error, const char *reply); // Handle G-Code replies void HandleReply(GCodeBuffer& gb, bool error, OutputBuffer *reply); bool OpenFileToWrite(GCodeBuffer& gb, const char* directory, const char* fileName); // Start saving GCodes in a file - void WriteGCodeToFile(GCodeBuffer& gb); // Write this GCode into a file bool SendConfigToLine(); // Deal with M503 - void WriteHTMLToFile(GCodeBuffer& gb, char b); // Save an HTML file (usually to upload a new web interface) bool OffsetAxes(GCodeBuffer& gb); // Set offsets - deprecated, use G10 void SetPidParameters(GCodeBuffer& gb, int heater, StringRef& reply); // Set the P/I/D parameters for a heater void SetHeaterParameters(GCodeBuffer& gb, StringRef& reply); // Set the thermistor and ADC parameters for a heater void ManageTool(GCodeBuffer& gb, StringRef& reply); // Create a new tool definition void SetToolHeaters(Tool *tool, float temperature); // Set all a tool's heaters to the temperature. For M104... bool ToolHeatersAtSetTemperatures(const Tool *tool, bool waitWhenCooling) const; // Wait for the heaters associated with the specified tool to reach their set temperatures - void GenerateTemperatureReport(StringRef& reply); // Store a standard-format temperature report in reply + void GenerateTemperatureReport(StringRef& reply) const; // Store a standard-format temperature report in reply + OutputBuffer *GenerateJsonStatusResponse(int type, int seq, ResponseSource source) const; // Generate a M408 response + void CheckReportDue(GCodeBuffer& gb, StringRef& reply) const; // Check whether we need to report temperatures or status void SetAllAxesNotHomed(); // Flag all axes as not homed void SetPositions(const float positionNow[DRIVES], bool doBedCompensation = true); // Set the current position to be this @@ -223,7 +232,6 @@ private: bool ProbeGrid(GCodeBuffer& gb, StringRef& reply); // Start probing the grid, returning true if we didn't because of an error bool LoadHeightMap(GCodeBuffer& gb, StringRef& reply) const; // Load the height map from file bool SaveHeightMap(GCodeBuffer& gb, StringRef& reply) const; // Save the height map to file - void ClearHeightMap() const; // Clear the height map bool WriteConfigOverrideFile(StringRef& reply, const char *fileName) const; // Write the config-override file void CopyConfigFinalValues(GCodeBuffer& gb); // Copy the feed rate etc. from the daemon to the input channels @@ -235,7 +243,13 @@ private: Platform* const platform; // The RepRap machine Webserver* const webserver; // The web server class - GCodeBuffer* gcodeSources[6]; // The various sources of gcodes + RegularGCodeInput* httpInput; // These cache incoming G-codes... + RegularGCodeInput* telnetInput; // ... + FileGCodeInput* fileInput; // ... + StreamGCodeInput* serialInput; // ... + StreamGCodeInput* auxInput; // ...for the GCodeBuffers below + + GCodeBuffer* gcodeSources[7]; // The various sources of gcodes GCodeBuffer*& httpGCode = gcodeSources[0]; GCodeBuffer*& telnetGCode = gcodeSources[1]; @@ -243,6 +257,7 @@ private: GCodeBuffer*& serialGCode = gcodeSources[3]; GCodeBuffer*& auxGCode = gcodeSources[4]; // This one is for the LCD display on the async serial interface GCodeBuffer*& daemonGCode = gcodeSources[5]; // Used for executing config.g and trigger macro files + GCodeBuffer*& queuedGCode = gcodeSources[6]; size_t nextGcodeSource; // The one to check next const GCodeBuffer* resourceOwners[NumResources]; // Which gcode buffer owns each resource @@ -323,6 +338,9 @@ private: uint8_t firmwareUpdateModuleMap; // Bitmap of firmware modules to be updated bool isFlashing; // Is a new firmware binary going to be flashed? + // Code queue + GCodeQueue *codeQueue; // Stores certain codes for deferred execution + // SHA1 hashing FileStore *fileBeingHashed; SHA1Context hash; @@ -332,6 +350,7 @@ private: // Misc float longWait; // Timer for things that happen occasionally (seconds) uint32_t lastWarningMillis; // When we last sent a warning message for things that can happen very often + int8_t lastAuxStatusReportType; // The type of the last status report requested by PanelDue bool isWaiting; // True if waiting to reach temperature bool cancelWait; // Set true to cancel waiting bool displayNoToolWarning; // True if we need to display a 'no tool selected' warning diff --git a/src/GCodes/GCodes2.cpp b/src/GCodes/GCodes2.cpp index 023ccbb1..d801de2b 100644 --- a/src/GCodes/GCodes2.cpp +++ b/src/GCodes/GCodes2.cpp @@ -10,6 +10,7 @@ #include "GCodes.h" #include "GCodeBuffer.h" +#include "GCodeQueue.h" #include "Heating/Heat.h" #include "Movement/Move.h" #include "Network.h" @@ -45,6 +46,13 @@ bool GCodes::ActOnCode(GCodeBuffer& gb, StringRef& reply) return true; } + // Can we queue this code? + if (gb.CanQueueCodes() && codeQueue->QueueCode(gb, segmentsLeft)) + { + HandleReply(gb, false, ""); + return true; + } + // G29 string parameters may contain the letter M, and various M-code string parameter may contain the letter G. // So we now look for the first G, M or T in the command. switch (gb.GetCommandLetter()) @@ -203,7 +211,7 @@ bool GCodes::HandleGcode(GCodeBuffer& gb, StringRef& reply) break; default: // clear height map - reprap.GetMove()->AccessBedProbeGrid().ClearGridHeights(); + reprap.GetMove()->SetIdentityTransform(); break; } } @@ -1210,6 +1218,7 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) toolNumber += gb.GetToolNumberAdjust(); if (!cancelWait && !ToolHeatersAtSetTemperatures(reprap.GetTool(toolNumber), true)) { + CheckReportDue(gb, reply); // check whether we need to send a temperature or status report isWaiting = true; return false; } @@ -1228,6 +1237,7 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) { if (!reprap.GetHeat()->HeaterAtSetTemperature(heaters[i], true)) { + CheckReportDue(gb, reply); // check whether we need to send a temperature or status report isWaiting = true; return false; } @@ -1244,6 +1254,7 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) { if (!cancelWait && !reprap.GetHeat()->HeaterAtSetTemperature(chamberHeater, true)) { + CheckReportDue(gb, reply); // check whether we need to send a temperature or status report isWaiting = true; return false; } @@ -1254,6 +1265,7 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) // Wait for all heaters to be ready if (!seen && !cancelWait && !reprap.GetHeat()->AllHeatersAtSetTemperatures(true)) { + CheckReportDue(gb, reply); // check whether we need to send a temperature or status report isWaiting = true; return false; } @@ -1510,26 +1522,7 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) break; } - // In Marlin emulation mode we should return some sort of undocumented message here every second. Try a standard temperature report. - if (platform->Emulating() == marlin && gb.GetResponseMessageType() == MessageType::HOST_MESSAGE) - { - const uint32_t now = millis(); - if (gb.timerRunning) - { - if (now - gb.whenTimerStarted >= 1000) - { - gb.whenTimerStarted = now; - GenerateTemperatureReport(reply); - reply.cat('\n'); - platform->Message(HOST_MESSAGE, reply.Pointer()); - } - } - else - { - gb.whenTimerStarted = now; - gb.timerRunning = true; - } - } + CheckReportDue(gb, reply); // check whether we need to send a temperature or status report isWaiting = true; return false; } @@ -1620,7 +1613,27 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) } break; - case 206: // Offset axes - Deprecated + case 204: // Set max travel and printing accelerations + { + bool seen = false; + if (gb.Seen('P')) + { + platform->SetMaxPrintingAcceleration(gb.GetFValue()); + seen = true; + } + if (gb.Seen('T')) + { + platform->SetMaxTravelAcceleration(gb.GetFValue()); + seen = true; + } + if (!seen) + { + reply.printf("Maximum printing acceleration %.1f, maximum travel acceleration %.1f", platform->GetMaxPrintingAcceleration(), platform->GetMaxTravelAcceleration()); + } + } + break; + + case 206: // Offset axes - Deprecated result = OffsetAxes(gb); break; @@ -2081,32 +2094,18 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) case 408: // Get status in JSON format { - int type = gb.Seen('S') ? gb.GetIValue() : 0; - int seq = gb.Seen('R') ? gb.GetIValue() : -1; - - OutputBuffer *statusResponse = nullptr; - switch (type) + const int type = gb.Seen('S') ? gb.GetIValue() : 0; + const int seq = gb.Seen('R') ? gb.GetIValue() : -1; + if (&gb == auxGCode) { - case 0: - case 1: - statusResponse = reprap.GetLegacyStatusResponse(type + 2, seq); - break; - - case 2: - case 3: - case 4: - statusResponse = reprap.GetStatusResponse(type - 1, (&gb == auxGCode) ? ResponseSource::AUX : ResponseSource::Generic); - break; - - case 5: - statusResponse = reprap.GetConfigResponse(); - break; + lastAuxStatusReportType = type; } + OutputBuffer * const statusResponse = GenerateJsonStatusResponse(type, seq, (&gb == auxGCode) ? ResponseSource::AUX : ResponseSource::Generic); + if (statusResponse != nullptr) { UnlockAll(gb); - statusResponse->cat('\n'); HandleReply(gb, false, statusResponse); return true; } @@ -2419,35 +2418,35 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) break; case 559: // Upload config.g or another gcode file to put in the sys directory - { - const char* str = (gb.Seen('P') ? gb.GetString() : platform->GetConfigFile()); - const bool ok = OpenFileToWrite(gb, platform->GetSysDir(), str); - if (ok) - { - reply.printf("Writing to file: %s", str); - } - else { - reply.printf("Can't open file %s for writing.", str); - error = true; + const char* str = (gb.Seen('P') ? gb.GetString() : platform->GetConfigFile()); + const bool ok = OpenFileToWrite(gb, platform->GetSysDir(), str); + if (ok) + { + reply.printf("Writing to file: %s", str); + } + else + { + reply.printf("Can't open file %s for writing.", str); + error = true; + } } - } break; case 560: // Upload reprap.htm or another web interface file - { - const char* str = (gb.Seen('P') ? gb.GetString() : INDEX_PAGE_FILE); - const bool ok = OpenFileToWrite(gb, platform->GetWebDir(), str); - if (ok) - { - reply.printf("Writing to file: %s", str); - } - else { - reply.printf("Can't open file %s for writing.", str); - error = true; + const char* str = (gb.Seen('P') ? gb.GetString() : INDEX_PAGE_FILE); + const bool ok = OpenFileToWrite(gb, platform->GetWebDir(), str); + if (ok) + { + reply.printf("Writing to file: %s", str); + } + else + { + reply.printf("Can't open file %s for writing.", str); + error = true; + } } - } break; case 561: // Set identity transform (also clears bed probe grid) diff --git a/src/Heating/Pid.cpp b/src/Heating/Pid.cpp index 2f4661ec..80f41924 100644 --- a/src/Heating/Pid.cpp +++ b/src/Heating/Pid.cpp @@ -664,7 +664,7 @@ void PID::DoTuningStep() const int peakIndex = GetPeakTempIndex(); if (peakIndex < 0) { - if (millis() - tuningPhaseStartTime < 60 * 1000) + if (millis() - tuningPhaseStartTime < 120 * 1000) // allow 2 minutes for the bed temperature to start falling { return; // still waiting for peak temperature } diff --git a/src/Movement/DDA.cpp b/src/Movement/DDA.cpp index 45b007fa..c7273faa 100644 --- a/src/Movement/DDA.cpp +++ b/src/Movement/DDA.cpp @@ -327,6 +327,10 @@ bool DDA::Init(const GCodes::RawMove &nextMove, bool doMotorMapping) memcpy(normalisedDirectionVector, directionVector, sizeof(normalisedDirectionVector)); Absolute(normalisedDirectionVector, DRIVES); acceleration = VectorBoxIntersection(normalisedDirectionVector, accelerations, DRIVES); + if (xyMoving) + { + acceleration = min<float>(acceleration, (isPrintingMove) ? reprap.GetPlatform()->GetMaxPrintingAcceleration() : reprap.GetPlatform()->GetMaxTravelAcceleration()); + } // 6. Set the speed to the smaller of the requested and maximum speed. // Also enforce a minimum speed of 0.5mm/sec. We need a minimum speed to avoid overflow in the movement calculations. diff --git a/src/Movement/Move.cpp b/src/Movement/Move.cpp index f95a9d62..1f22546a 100644 --- a/src/Movement/Move.cpp +++ b/src/Movement/Move.cpp @@ -9,7 +9,7 @@ #include "Platform.h" #include "RepRap.h" -Move::Move(Platform* p, GCodes* g) : currentDda(NULL), grid(zBedProbePoints) +Move::Move(Platform* p, GCodes* g) : currentDda(NULL), grid(zBedProbePoints), scheduledMoves(0), completedMoves(0) { active = false; @@ -193,6 +193,7 @@ void Move::Spin() { ddaRingAddPointer = ddaRingAddPointer->GetNext(); idleCount = 0; + scheduledMoves++; } } } @@ -377,6 +378,7 @@ FilePosition Move::PausePrint(float positions[DRIVES], float& pausedFeedRate, ui } (void)dda->Free(); dda = dda->GetNext(); + scheduledMoves--; } while (dda != savedDdaRingAddPointer); } @@ -406,6 +408,8 @@ void Move::Diagnostics(MessageType mtype) numLookaheadUnderruns = numPrepareUnderruns = 0; longestGcodeWaitInterval = 0; + reprap.GetPlatform()->MessageF(mtype, "Scheduled moves: %u, completed moves: %u\n", scheduledMoves, completedMoves); + // Show the current probe position heights and type of bed compensation in use p->Message(mtype, "Bed compensation in use: "); if (numBedCompensationPoints == 0) @@ -417,7 +421,8 @@ void Move::Diagnostics(MessageType mtype) p->MessageF(mtype, "%d point\n", numBedCompensationPoints); } p->Message(mtype, "Bed probe heights:"); - for (size_t i = 0; i < MaxProbePoints; ++i) + // To keep the response short so that it doesn't get truncates when sending it via HTTP, we only show the first 5 bed probe points + for (size_t i = 0; i < 5; ++i) { p->MessageF(mtype, " %.3f", ZBedProbePoint(i)); } @@ -757,6 +762,7 @@ void Move::SetIdentityTransform() { numBedCompensationPoints = 0; grid.ClearGridHeights(); + grid.UseHeightMap(false); } void Move::SetTaperHeight(float h) @@ -1312,6 +1318,7 @@ void Move::CurrentMoveCompleted() currentDda->Complete(); currentDda = nullptr; ddaRingGetPointer = ddaRingGetPointer->GetNext(); + completedMoves++; } // Try to start another move. Must be called with interrupts disabled, to avoid a race condition. @@ -1430,6 +1437,7 @@ void Move::GetCurrentUserPosition(float m[DRIVES], uint8_t moveType, uint32_t xA void Move::LiveCoordinates(float m[DRIVES], uint32_t xAxes) { // The live coordinates and live endpoints are modified by the ISR, so be careful to get a self-consistent set of them + const size_t numAxes = reprap.GetGCodes()->GetNumAxes(); // do this before we disable interrupts cpu_irq_disable(); if (liveCoordinatesValid) { @@ -1440,7 +1448,6 @@ void Move::LiveCoordinates(float m[DRIVES], uint32_t xAxes) else { // Only the extruder coordinates are valid, so we need to convert the motor endpoints to coordinates - const size_t numAxes = reprap.GetGCodes()->GetNumAxes(); memcpy(m + numAxes, const_cast<const float *>(liveCoordinates + numAxes), sizeof(m[0]) * (DRIVES - numAxes)); int32_t tempEndPoints[MAX_AXES]; memcpy(tempEndPoints, const_cast<const int32_t*>(liveEndPoints), sizeof(tempEndPoints)); diff --git a/src/Movement/Move.h b/src/Movement/Move.h index 14b3b804..acc5e43f 100644 --- a/src/Movement/Move.h +++ b/src/Movement/Move.h @@ -118,6 +118,10 @@ public: bool IsExtruding() const; // Is filament being extruded? + uint32_t GetScheduledMoves() const { return scheduledMoves; } // How many moves have been scheduled? + uint32_t GetCompletedMoves() const { return completedMoves; } // How many moves have been completed? + void ResetMoveCounters() { scheduledMoves = completedMoves = 0; } + HeightMap& AccessBedProbeGrid() { return grid; } // Access the bed probing grid private: @@ -195,6 +199,9 @@ private: int coreXYMode; // 0 = Cartesian, 1 = CoreXY, 2 = CoreXZ, 3 = CoreYZ float axisFactors[MAX_AXES]; // How much further the motors need to move for each axis movement, on a CoreXY/CoreXZ/CoreYZ machine unsigned int stepErrors; // count of step errors, for diagnostics + + uint32_t scheduledMoves; // Move counters for the code queue + volatile uint32_t completedMoves; // This one is modified by an ISR, hence volatile }; //****************************************************************************************************** diff --git a/src/Platform.cpp b/src/Platform.cpp index 4658f777..da7afa52 100644 --- a/src/Platform.cpp +++ b/src/Platform.cpp @@ -301,6 +301,7 @@ void Platform::Init() ARRAY_INIT(accelerations, ACCELERATIONS); ARRAY_INIT(driveStepsPerUnit, DRIVE_STEPS_PER_UNIT); ARRAY_INIT(instantDvs, INSTANT_DVS); + maxPrintingAcceleration = maxTravelAcceleration = 10000.0; #if !defined(DUET_NG) && !defined(__RADDS__) // Motor current setting on Duet 0.6 and 0.8.5 @@ -1025,7 +1026,7 @@ void Platform::Beep(int freq, int ms) } // Send a short message to the aux channel. There is no flow control on this port, so it can't block for long. -void Platform::SendMessage(const char* msg) +void Platform::SendAuxMessage(const char* msg) { OutputBuffer *buf; if (OutputBuffer::Allocate(buf)) @@ -1457,8 +1458,7 @@ void Platform::DisableInterrupts() //extern "C" uint32_t longestWriteWaitTime, shortestWriteWaitTime, longestReadWaitTime, shortestReadWaitTime; //extern uint32_t maxRead, maxWrite; -// This diagnostics function is the first to be called, so it calls Message to start with. -// All other messages generated by this and other diagnostics functions must call AppendMessage. +// Return diagnostic information void Platform::Diagnostics(MessageType mtype) { Message(mtype, "=== Platform ===\n"); @@ -1471,14 +1471,12 @@ void Platform::Diagnostics(MessageType mtype) (char *) 0x20070000; #endif const struct mallinfo mi = mallinfo(); - Message(mtype, "Memory usage:\n"); - MessageF(mtype, "Program static ram used: %d\n", &_end - ramstart); + MessageF(mtype, "Static ram used: %d\n", &_end - ramstart); MessageF(mtype, "Dynamic ram used: %d\n", mi.uordblks); MessageF(mtype, "Recycled dynamic ram: %d\n", mi.fordblks); uint32_t currentStack, maxStack, neverUsed; GetStackUsage(¤tStack, &maxStack, &neverUsed); - MessageF(mtype, "Current stack ram used: %u\n", currentStack); - MessageF(mtype, "Maximum stack ram used: %u\n", maxStack); + MessageF(mtype, "Stack ram used: %u current, %u maximum\n", currentStack, maxStack); MessageF(mtype, "Never used ram: %u\n", neverUsed); // Show the up time and reason for the last reset @@ -1608,7 +1606,7 @@ void Platform::Diagnostics(MessageType mtype) #endif // Show current RTC time - Message(mtype, "Current date and time: "); + Message(mtype, "Date/time: "); struct tm timeInfo; if (gmtime_r(&realTime, &timeInfo) != nullptr) { @@ -1618,7 +1616,7 @@ void Platform::Diagnostics(MessageType mtype) } else { - Message(mtype, "clock not set\n"); + Message(mtype, "not set\n"); } // Debug @@ -2519,17 +2517,16 @@ void Platform::Message(MessageType type, const char *message) break; case HTTP_MESSAGE: + reprap.GetWebserver()->HandleGCodeReply(WebSource::HTTP, message); + break; + case TELNET_MESSAGE: - // Message that is to be sent to the web - { - const WebSource source = (type == HTTP_MESSAGE) ? WebSource::HTTP : WebSource::Telnet; - reprap.GetWebserver()->HandleGCodeReply(source, message); - } + reprap.GetWebserver()->HandleGCodeReply(WebSource::Telnet, message); break; case FIRMWARE_UPDATE_MESSAGE: Message(HOST_MESSAGE, message); // send message to USB - SendMessage(message); // send message to aux + SendAuxMessage(message); // send message to aux break; case GENERIC_MESSAGE: @@ -2581,12 +2578,11 @@ void Platform::Message(const MessageType type, OutputBuffer *buffer) break; case HTTP_MESSAGE: + reprap.GetWebserver()->HandleGCodeReply(WebSource::HTTP, buffer); + break; + case TELNET_MESSAGE: - // Message that is to be sent to the web - { - const WebSource source = (type == HTTP_MESSAGE) ? WebSource::HTTP : WebSource::Telnet; - reprap.GetWebserver()->HandleGCodeReply(source, buffer); - } + reprap.GetWebserver()->HandleGCodeReply(WebSource::Telnet, buffer); break; case GENERIC_MESSAGE: @@ -2932,48 +2928,6 @@ bool Platform::Inkjet(int bitPattern) } #endif -bool Platform::GCodeAvailable(const SerialSource source) const -{ - switch (source) - { - case SerialSource::USB: - return SERIAL_MAIN_DEVICE.available() > 0; - - case SerialSource::AUX: - return SERIAL_AUX_DEVICE.available() > 0; - - case SerialSource::AUX2: -#ifdef SERIAL_AUX2_DEVICE - return SERIAL_AUX2_DEVICE.available() > 0; -#else - return false; -#endif - } - - return false; -} - -char Platform::ReadFromSource(const SerialSource source) -{ - switch (source) - { - case SerialSource::USB: - return static_cast<char>(SERIAL_MAIN_DEVICE.read()); - - case SerialSource::AUX: - return static_cast<char>(SERIAL_AUX_DEVICE.read()); - - case SerialSource::AUX2: -#ifdef SERIAL_AUX2_DEVICE - return static_cast<char>(SERIAL_AUX2_DEVICE.read()); -#else - return 0; -#endif - } - - return 0; -} - #ifndef __RADDS__ // CPU temperature void Platform::GetMcuTemperatures(float& minT, float& currT, float& maxT) const @@ -3148,8 +3102,9 @@ void Platform::Tick() switch (tickState) { - case 1: // last conversion started was a thermistor + case 1: case 3: + // We process a thermistor reading on alternate ticks if (IsThermistorChannel(currentHeater)) { // Because we are in the tick ISR and no other ISR reads the averaging filter, we can cast away 'volatile' here @@ -3170,10 +3125,18 @@ void Platform::Tick() { currentHeater = 0; } + + // If we are not using a simple modulated IR sensor, process the Z probe reading on every tick for a faster response. + // If we are using a simple modulated IR sensor then we need to allow the reading to settle after turning the IR emitter on or off, + // so on alternate ticks we read it and switch the emitter + if (zProbeType != 2) + { + const_cast<ZProbeAveragingFilter&>((tickState == 1) ? zProbeOnFilter : zProbeOffFilter).ProcessReading(GetRawZProbeReading()); + } ++tickState; break; - case 2: // last conversion started was the Z probe, with IR LED on + case 2: const_cast<ZProbeAveragingFilter&>(zProbeOnFilter).ProcessReading(GetRawZProbeReading()); if (zProbeType == 2) // if using a modulated IR sensor { diff --git a/src/Platform.h b/src/Platform.h index a54c3b71..de7a4363 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -286,14 +286,6 @@ enum class ErrorCode : uint32_t OutputStackOverflow = 1 << 3 }; -// Different types of hardware-related input-output -enum class SerialSource -{ - USB, - AUX, - AUX2 -}; - struct AxisDriversConfig { size_t numDrivers; // Number of drivers assigned to each axis @@ -353,8 +345,6 @@ public: // Communications and data storage - bool GCodeAvailable(const SerialSource source) const; - char ReadFromSource(const SerialSource source); OutputBuffer *GetAuxGCodeReply(); // Returns cached G-Code reply for AUX devices and clears its reference void AppendAuxReply(OutputBuffer *buf); void AppendAuxReply(const char *msg); @@ -429,6 +419,14 @@ public: float Acceleration(size_t drive) const; const float* Accelerations() const; void SetAcceleration(size_t drive, float value); + float GetMaxPrintingAcceleration() const + { return maxPrintingAcceleration; } + void SetMaxPrintingAcceleration(float acc) + { maxPrintingAcceleration = acc; } + float GetMaxTravelAcceleration() const + { return maxTravelAcceleration; } + void SetMaxTravelAcceleration(float acc) + { maxTravelAcceleration = acc; } float MaxFeedrate(size_t drive) const; const float* MaxFeedrates() const; void SetMaxFeedrate(size_t drive, float value); @@ -555,7 +553,7 @@ public: // AUX device void Beep(int freq, int ms); - void SendMessage(const char* msg); + void SendAuxMessage(const char* msg); // Hotend configuration float GetFilamentWidth() const; @@ -691,6 +689,8 @@ private: Pin endStopPins[DRIVES]; float maxFeedrates[DRIVES]; float accelerations[DRIVES]; + float maxPrintingAcceleration; + float maxTravelAcceleration; float driveStepsPerUnit[DRIVES]; float instantDvs[DRIVES]; float pressureAdvance[MaxExtruders]; diff --git a/src/RADDS/Webserver.h b/src/RADDS/Webserver.h index de65d6cf..9f81c9f3 100644 --- a/src/RADDS/Webserver.h +++ b/src/RADDS/Webserver.h @@ -20,10 +20,7 @@ public: void Exit() const { }; void Diagnostics(MessageType mtype) const { }; - bool GCodeAvailable(const WebSource source) const { return false; } - char ReadGCode(const WebSource source) const { return '\0'; } uint32_t GetReplySeq() const { return (uint32_t)0; } - uint16_t GetGCodeBufferSpace(const WebSource source) const { return 0; } void HandleGCodeReply(const WebSource source, OutputBuffer *reply) const; void HandleGCodeReply(const WebSource source, const char *reply) const { }; diff --git a/src/RepRap.cpp b/src/RepRap.cpp index 004b620c..f613978c 100644 --- a/src/RepRap.cpp +++ b/src/RepRap.cpp @@ -579,7 +579,7 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) ch = '['; for (size_t axis = 0; axis < numAxes; axis++) { - response->catf("%c%.2f", ch, liveCoordinates[axis]); + response->catf("%c%.3f", ch, liveCoordinates[axis]); ch = ','; } } @@ -1155,7 +1155,7 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq) ch = '['; for (size_t drive = 0; drive < numAxes; drive++) { - response->catf("%c%.2f", ch, liveCoordinates[drive]); + response->catf("%c%.3f", ch, liveCoordinates[drive]); ch = ','; } @@ -1453,7 +1453,7 @@ void RepRap::SetMessage(const char *msg) if (platform->HaveAux()) { - platform->SendMessage(msg); + platform->SendAuxMessage(msg); } } diff --git a/src/Storage/FileStore.cpp b/src/Storage/FileStore.cpp index df435e9e..d650da2d 100644 --- a/src/Storage/FileStore.cpp +++ b/src/Storage/FileStore.cpp @@ -14,10 +14,8 @@ FileStore::FileStore(Platform* p) : platform(p) void FileStore::Init() { - bufferPointer = 0; inUse = false; writing = false; - lastBufferEntry = 0; openCount = 0; closeRequested = false; } @@ -46,7 +44,6 @@ bool FileStore::Open(const char* directory, const char* fileName, bool write) ? platform->GetMassStorage()->CombineName(directory, fileName) : fileName; writing = write; - lastBufferEntry = FileBufLen; // Try to create the path of this file if we want to write to it if (writing) @@ -90,7 +87,6 @@ bool FileStore::Open(const char* directory, const char* fileName, bool write) return false; } - bufferPointer = (writing) ? 0 : FileBufLen; inUse = true; openCount = 1; return true; @@ -156,7 +152,6 @@ bool FileStore::Close() FRESULT fr = f_close(&file); inUse = false; writing = false; - lastBufferEntry = 0; closeRequested = false; return ok && fr == FR_OK; } @@ -168,27 +163,13 @@ bool FileStore::Seek(FilePosition pos) platform->Message(GENERIC_MESSAGE, "Error: Attempt to seek on a non-open file.\n"); return false; } - if (writing) - { - WriteBuffer(); - } FRESULT fr = f_lseek(&file, pos); - bufferPointer = (writing) ? 0 : FileBufLen; return fr == FR_OK; } FilePosition FileStore::Position() const { - FilePosition pos = file.fptr; - if (writing) - { - pos += bufferPointer; - } - else if (bufferPointer < lastBufferEntry) - { - pos -= (lastBufferEntry - bufferPointer); - } - return pos; + return file.fptr; } #if 0 // not currently used @@ -219,63 +200,12 @@ float FileStore::FractionRead() const return (float)Position() / (float)len; } -uint8_t FileStore::Status() -{ - if (!inUse) - return (uint8_t)IOStatus::nothing; - - if (lastBufferEntry == FileBufLen) - return (uint8_t)IOStatus::byteAvailable; - - if (bufferPointer < lastBufferEntry) - return (uint8_t)IOStatus::byteAvailable; - - return (uint8_t)IOStatus::nothing; -} - -bool FileStore::ReadBuffer() -{ - FRESULT readStatus = f_read(&file, GetBuffer(), FileBufLen, &lastBufferEntry); // Read a chunk of file - if (readStatus != FR_OK) - { - platform->Message(GENERIC_MESSAGE, "Error: Cannot read file.\n"); - return false; - } - bufferPointer = 0; - return true; -} - -// Single character read via the buffer +// Single character read bool FileStore::Read(char& b) { - if (!inUse) - { - platform->Message(GENERIC_MESSAGE, "Error: Attempt to read from a non-open file.\n"); - return false; - } - - if (bufferPointer >= FileBufLen) - { - bool ok = ReadBuffer(); - if (!ok) - { - return false; - } - } - - if (bufferPointer >= lastBufferEntry) - { - b = 0; // Good idea? - return false; - } - - b = (char) GetBuffer()[bufferPointer]; - bufferPointer++; - - return true; + return Read(&b, sizeof(char)); } -// Block read, doesn't use the buffer // Returns the number of bytes read or -1 if the read process failed int FileStore::Read(char* extBuf, size_t nBytes) { @@ -285,7 +215,6 @@ int FileStore::Read(char* extBuf, size_t nBytes) return -1; } - bufferPointer = FileBufLen; // invalidate the buffer UINT bytes_read; FRESULT readStatus = f_read(&file, extBuf, nBytes, &bytes_read); if (readStatus != FR_OK) @@ -330,33 +259,9 @@ int FileStore::ReadLine(char* buf, size_t nBytes) return i; } -bool FileStore::WriteBuffer() -{ - if (bufferPointer != 0) - { - if (!InternalWriteBlock((const char*)GetBuffer(), bufferPointer)) - { - return false; - } - bufferPointer = 0; - } - return true; -} - bool FileStore::Write(char b) { - if (!inUse) - { - platform->Message(GENERIC_MESSAGE, "Error: Attempt to write byte to a non-open file.\n"); - return false; - } - GetBuffer()[bufferPointer] = b; - bufferPointer++; - if (bufferPointer >= FileBufLen) - { - return WriteBuffer(); - } - return true; + return Write(&b, sizeof(char)); } bool FileStore::Write(const char* b) @@ -364,7 +269,6 @@ bool FileStore::Write(const char* b) return Write(b, strlen(b)); } -// Direct block write that bypasses the buffer. Used when uploading files. bool FileStore::Write(const char *s, size_t len) { if (!inUse) @@ -373,15 +277,6 @@ bool FileStore::Write(const char *s, size_t len) return false; } - if (!WriteBuffer()) - { - return false; - } - return InternalWriteBlock(s, len); -} - -bool FileStore::InternalWriteBlock(const char *s, size_t len) -{ size_t bytesWritten; uint32_t time = micros(); @@ -393,7 +288,7 @@ bool FileStore::InternalWriteBlock(const char *s, size_t len) } if ((writeStatus != FR_OK) || (bytesWritten != len)) { - platform->Message(GENERIC_MESSAGE, "Error: Cannot write to file. Disc may be full.\n"); + platform->Message(GENERIC_MESSAGE, "Error: Cannot write to file. Drive may be full.\n"); return false; } return true; @@ -406,10 +301,6 @@ bool FileStore::Flush() platform->Message(GENERIC_MESSAGE, "Error: Attempt to flush a non-open file.\n"); return false; } - if (!WriteBuffer()) - { - return false; - } return f_sync(&file) == FR_OK; } diff --git a/src/Storage/FileStore.h b/src/Storage/FileStore.h index 29ace01c..364bdc8f 100644 --- a/src/Storage/FileStore.h +++ b/src/Storage/FileStore.h @@ -8,22 +8,11 @@ const size_t FileBufLen = 256; // 512 would be more efficient, but need to free up some RAM first -enum class IOStatus : uint8_t -{ - nothing = 0, - byteAvailable = 1, - atEoF = 2, - clientLive = 4, - clientConnected = 8 -}; - class Platform; class FileStore { public: - - uint8_t Status(); // Returns OR of IOStatus bool Read(char& b); // Read 1 byte int Read(char* buf, size_t nBytes); // Read a block of nBytes length int ReadLine(char* buf, size_t nBytes); // As Read but stop after '\n' or '\r\n' and null-terminate @@ -57,17 +46,9 @@ protected: bool Open(const char* directory, const char* fileName, bool write); private: - bool ReadBuffer(); - bool WriteBuffer(); - bool InternalWriteBlock(const char *s, size_t len); - uint8_t *GetBuffer() { return reinterpret_cast<uint8_t*>(buf32); } - - uint32_t buf32[FileBufLen/4]; Platform* platform; - unsigned int bufferPointer; FIL file; - unsigned int lastBufferEntry; volatile unsigned int openCount; volatile bool closeRequested; diff --git a/src/Storage/MassStorage.h b/src/Storage/MassStorage.h index 2371e671..25ed0aee 100644 --- a/src/Storage/MassStorage.h +++ b/src/Storage/MassStorage.h @@ -3,7 +3,7 @@ #include "RepRapFirmware.h" #include "Pins.h" -#include "Libraries/FatFs/ff.h" +#include "Libraries/Fatfs/ff.h" #include <ctime> // Info returned by FindFirst/FindNext calls diff --git a/src/Version.h b/src/Version.h index ce482ede..fb760e71 100644 --- a/src/Version.h +++ b/src/Version.h @@ -9,11 +9,11 @@ #define SRC_VERSION_H_ #ifndef VERSION -# define VERSION "1.18beta3" +# define VERSION "1.18RC1" #endif #ifndef DATE -# define DATE "2017-03-16" +# define DATE "2017-03-28" #endif #define AUTHORS "reprappro, dc42, chrishamm, t3p3, dnewman" |