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

github.com/Duet3D/RepRapFirmware.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/GCodes/GCodes1.cpp')
-rw-r--r--src/GCodes/GCodes1.cpp3286
1 files changed, 3286 insertions, 0 deletions
diff --git a/src/GCodes/GCodes1.cpp b/src/GCodes/GCodes1.cpp
new file mode 100644
index 00000000..e5622453
--- /dev/null
+++ b/src/GCodes/GCodes1.cpp
@@ -0,0 +1,3286 @@
+/****************************************************************************************************
+
+ RepRapFirmware - G Codes
+
+ This class interprets G Codes from one or more sources, and calls the functions in Move, Heat etc
+ that drive the machine to do what the G Codes command.
+
+ Most of the functions in here are designed not to wait, and they return a boolean. When you want them to do
+ something, you call them. If they return false, the machine can't do what you want yet. So you go away
+ and do something else. Then you try again. If they return true, the thing you wanted done has been done.
+
+ -----------------------------------------------------------------------------------------------------
+
+ Version 0.1
+
+ 13 February 2013
+
+ Adrian Bowyer
+ RepRap Professional Ltd
+ http://reprappro.com
+
+ Licence: GPL
+
+ ****************************************************************************************************/
+
+#include "RepRapFirmware.h"
+
+#ifdef DUET_NG
+#include "FirmwareUpdater.h"
+#endif
+
+#define DEGREE_SYMBOL "\xC2\xB0" // degree-symbol encoding in UTF8
+
+const char GCodes::axisLetters[MAX_AXES] = { 'X', 'Y', 'Z', 'U', 'V', 'W' };
+
+const char* PAUSE_G = "pause.g";
+const char* HomingFileNames[MAX_AXES] = { "homex.g", "homey.g", "homez.g", "homeu.g", "homev.g", "homew.g" };
+const char* HOME_ALL_G = "homeall.g";
+const char* HOME_DELTA_G = "homedelta.g";
+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)
+ {
+ moveCoords[i] = 0.0;
+ }
+ feedRate = DEFAULT_FEEDRATE/minutesToSeconds;
+}
+
+GCodes::GCodes(Platform* p, Webserver* w) :
+ platform(p), webserver(w), active(false), isFlashing(false),
+ fileBeingHashed(nullptr)
+{
+ 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);
+}
+
+void GCodes::Exit()
+{
+ platform->Message(HOST_MESSAGE, "GCodes class exited.\n");
+ active = false;
+}
+
+void GCodes::Init()
+{
+ Reset();
+ numAxes = MIN_AXES;
+ numExtruders = MaxExtruders;
+ distanceScale = 1.0;
+ rawExtruderTotal = 0.0;
+ for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
+ {
+ lastRawExtruderPosition[extruder] = 0.0;
+ rawExtruderTotalByDrive[extruder] = 0.0;
+ }
+ eofString = EOF_STRING;
+ eofStringCounter = 0;
+ eofStringLength = strlen(eofString);
+ offSetSet = false;
+ zProbesSet = false;
+ active = true;
+ longWait = platform->Time();
+ dwellTime = longWait;
+ limitAxes = true;
+ for(size_t axis = 0; axis < MAX_AXES; axis++)
+ {
+ axisScaleFactors[axis] = 1.0;
+ }
+ SetAllAxesNotHomed();
+ for (size_t i = 0; i < NUM_FANS; ++i)
+ {
+ pausedFanValues[i] = 0.0;
+ }
+ lastDefaultFanSpeed = 0.0;
+
+ retractLength = retractExtra = retractHop = 0.0;
+ retractSpeed = unRetractSpeed = 600.0;
+}
+
+// This is called from Init and when doing an emergency stop
+void GCodes::Reset()
+{
+ httpGCode->Init();
+ telnetGCode->Init();
+ fileGCode->Init();
+ serialGCode->Init();
+ auxGCode->Init();
+ auxGCode->SetCommsProperties(1); // by default, we require a checksum on the aux port
+ daemonGCode->Init();
+
+ nextGcodeSource = 0;
+
+ fileToPrint.Close();
+ fileBeingWritten = NULL;
+ dwellWaiting = false;
+ probeCount = 0;
+ cannedCycleMoveCount = 0;
+ cannedCycleMoveQueued = false;
+ speedFactor = 1.0 / minutesToSeconds; // default is just to convert from mm/minute to mm/second
+ for (size_t i = 0; i < MaxExtruders; ++i)
+ {
+ extrusionFactors[i] = 1.0;
+ }
+ for (size_t i = 0; i < DRIVES; ++i)
+ {
+ moveBuffer.coords[i] = 0.0;
+ }
+ moveBuffer.xAxes = DefaultXAxisMapping;
+
+ pauseRestorePoint.Init();
+ toolChangeRestorePoint.Init();
+
+ ClearMove();
+
+ for (size_t i = 0; i < MaxTriggers; ++i)
+ {
+ triggers[i].Init();
+ }
+ triggersPending = 0;
+
+ simulationMode = 0;
+ simulationTime = 0.0;
+ isPaused = false;
+ filePos = moveBuffer.filePos = noFilePosition;
+ lastEndstopStates = platform->GetAllEndstopStates();
+ firmwareUpdateModuleMap = 0;
+
+ cancelWait = isWaiting = false;
+
+ for (size_t i = 0; i < NumResources; ++i)
+ {
+ resourceOwners[i] = nullptr;
+ }
+
+ lastProbedTime = millis();
+}
+
+float GCodes::FractionOfFilePrinted() const
+{
+ const FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState;
+ return (fileBeingPrinted.IsLive()) ? fileBeingPrinted.FractionRead() : -1.0;
+}
+
+// Start running the config file
+// We use triggerCGode as the source to prevent any triggers being executed until we have finished
+bool GCodes::RunConfigFile(const char* fileName)
+{
+ return DoFileMacro(*daemonGCode, fileName, false);
+}
+
+// Are we still running the config file?
+bool GCodes::IsRunningConfigFile() const
+{
+ return daemonGCode->MachineState().fileState.IsLive();
+}
+
+void GCodes::Spin()
+{
+ if (!active)
+ {
+ return;
+ }
+
+ CheckTriggers();
+
+ // Get the GCodeBuffer that we want to work from
+ GCodeBuffer& gb = *(gcodeSources[nextGcodeSource]);
+
+ // Set up a buffer for the reply
+ char replyBuffer[gcodeReplyLength];
+ StringRef reply(replyBuffer, ARRAY_SIZE(replyBuffer));
+ reply.Clear();
+
+ if (gb.GetState() == GCodeState::normal)
+ {
+ StartNextGCode(gb, reply);
+ }
+ else
+ {
+ // Perform the next operation of the state machine for this gcode source
+ bool error = false;
+
+ switch (gb.GetState())
+ {
+ case GCodeState::waitingForMoveToComplete:
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ gb.SetState(GCodeState::normal);
+ }
+ break;
+
+ case GCodeState::homing:
+ if (toBeHomed == 0)
+ {
+ gb.SetState(GCodeState::normal);
+ }
+ else
+ {
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ // Leave the Z axis until all other axes are done
+ if ((toBeHomed & (1u << axis)) != 0 && (axis != Z_AXIS || toBeHomed == (1u << Z_AXIS)))
+ {
+ toBeHomed &= ~(1u << axis);
+ DoFileMacro(gb, HomingFileNames[axis]);
+ break;
+ }
+ }
+ }
+ break;
+
+ case GCodeState::setBed1:
+ reprap.GetMove()->SetIdentityTransform();
+ probeCount = 0;
+ gb.SetState(GCodeState::setBed2);
+ // no break
+
+ case GCodeState::setBed2:
+ {
+ int numProbePoints = reprap.GetMove()->NumberOfXYProbePoints();
+ if (DoSingleZProbeAtPoint(gb, probeCount, 0.0))
+ {
+ probeCount++;
+ if (probeCount >= numProbePoints)
+ {
+ zProbesSet = true;
+ reprap.GetMove()->FinishedBedProbing(0, reply);
+ gb.SetState(GCodeState::normal);
+ }
+ }
+ }
+ break;
+
+ case GCodeState::toolChange1: // Release the old tool (if any)
+ case GCodeState::m109ToolChange1: // Release the old tool (if any)
+ {
+ const Tool *oldTool = reprap.GetCurrentTool();
+ if (oldTool != NULL)
+ {
+ reprap.StandbyTool(oldTool->Number());
+ }
+ }
+ gb.AdvanceState();
+ if (reprap.GetTool(newToolNumber) != nullptr && AllAxesAreHomed())
+ {
+ scratchString.printf("tpre%d.g", newToolNumber);
+ DoFileMacro(gb, scratchString.Pointer(), false);
+ }
+ break;
+
+ case GCodeState::toolChange2: // Select the new tool (even if it doesn't exist - that just deselects all tools)
+ case GCodeState::m109ToolChange2: // Select the new tool (even if it doesn't exist - that just deselects all tools)
+ reprap.SelectTool(newToolNumber);
+ gb.AdvanceState();
+ if (reprap.GetTool(newToolNumber) != nullptr && AllAxesAreHomed())
+ {
+ scratchString.printf("tpost%d.g", newToolNumber);
+ DoFileMacro(gb, scratchString.Pointer(), false);
+ }
+ break;
+
+ case GCodeState::toolChangeComplete:
+ gb.SetState(GCodeState::normal);
+ break;
+
+ case GCodeState::m109ToolChangeComplete:
+ if (cancelWait || ToolHeatersAtSetTemperatures(reprap.GetCurrentTool(), gb.MachineState().waitWhileCooling))
+ {
+ cancelWait = isWaiting = false;
+ gb.SetState(GCodeState::normal);
+ }
+ // In Marlin emulation mode we should return some sort of (undocumented) message here every second...
+ isWaiting = true;
+ break;
+
+ case GCodeState::pausing1:
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ gb.SetState(GCodeState::pausing2);
+ DoFileMacro(gb, PAUSE_G);
+ }
+ break;
+
+ case GCodeState::pausing2:
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ reply.copy("Printing paused");
+ gb.SetState(GCodeState::normal);
+ }
+ break;
+
+ case GCodeState::resuming1:
+ case GCodeState::resuming2:
+ // Here when we have just finished running the resume macro file.
+ // Move the head back to the paused location
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ float currentZ = moveBuffer.coords[Z_AXIS];
+ for (size_t drive = 0; drive < numAxes; ++drive)
+ {
+ moveBuffer.coords[drive] = pauseRestorePoint.moveCoords[drive];
+ }
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ moveBuffer.coords[drive] = 0.0;
+ }
+ moveBuffer.feedRate = DEFAULT_FEEDRATE/minutesToSeconds; // ask for a good feed rate, we may have paused during a slow move
+ moveBuffer.moveType = 0;
+ moveBuffer.endStopsToCheck = 0;
+ moveBuffer.usePressureAdvance = false;
+ moveBuffer.filePos = noFilePosition;
+ if (gb.GetState() == GCodeState::resuming1 && currentZ > pauseRestorePoint.moveCoords[Z_AXIS])
+ {
+ // First move the head to the correct XY point, then move it down in a separate move
+ moveBuffer.coords[Z_AXIS] = currentZ;
+ gb.SetState(GCodeState::resuming2);
+ }
+ else
+ {
+ // Just move to the saved position in one go
+ gb.SetState(GCodeState::resuming3);
+ }
+ segmentsLeft = 1;
+ }
+ break;
+
+ case GCodeState::resuming3:
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ for (size_t i = 0; i < NUM_FANS; ++i)
+ {
+ platform->SetFanValue(i, pausedFanValues[i]);
+ }
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ lastRawExtruderPosition[drive - numAxes] = pauseRestorePoint.moveCoords[drive]; // reset the extruder position in case we are receiving absolute extruder moves
+ }
+ fileGCode->MachineState().feedrate = pauseRestorePoint.feedRate;
+ isPaused = false;
+ reply.copy("Printing resumed");
+ gb.SetState(GCodeState::normal);
+ }
+ break;
+
+ case GCodeState::flashing1:
+#ifdef DUET_NG
+ // Update additional modules before the main firmware
+ if (FirmwareUpdater::IsReady())
+ {
+ bool updating = false;
+ for (unsigned int module = 1; module < NumFirmwareUpdateModules; ++module)
+ {
+ if ((firmwareUpdateModuleMap & (1u << module)) != 0)
+ {
+ firmwareUpdateModuleMap &= ~(1u << module);
+ FirmwareUpdater::UpdateModule(module);
+ updating = true;
+ break;
+ }
+ }
+ if (!updating)
+ {
+ gb.SetState(GCodeState::flashing2);
+ }
+ }
+#else
+ gb.SetState(GCodeState::flashing2);
+#endif
+ break;
+
+ case GCodeState::flashing2:
+ if ((firmwareUpdateModuleMap & 1) != 0)
+ {
+ // Update main firmware
+ firmwareUpdateModuleMap = 0;
+ platform->UpdateFirmware();
+ // The above call does not return unless an error occurred
+ }
+ isFlashing = false;
+ gb.SetState(GCodeState::normal);
+ break;
+
+ case GCodeState::stopping: // MO after executing stop.g if present
+ case GCodeState::sleeping: // M1 after executing sleep.g if present
+ // Deselect the active tool and turn off all heaters, unless parameter Hn was used with n > 0
+ if (!gb.Seen('H') || gb.GetIValue() <= 0)
+ {
+ Tool* tool = reprap.GetCurrentTool();
+ if (tool != nullptr)
+ {
+ reprap.StandbyTool(tool->Number());
+ }
+ reprap.GetHeat()->SwitchOffAll();
+ }
+
+ // chrishamm 2014-18-10: Although RRP says M0 is supposed to turn off all drives and heaters,
+ // I think M1 is sufficient for this purpose. Leave M0 for a normal reset.
+ if (gb.GetState() == GCodeState::sleeping)
+ {
+ DisableDrives();
+ }
+ else
+ {
+ platform->SetDriversIdle();
+ }
+ gb.SetState(GCodeState::normal);
+ break;
+
+ case GCodeState::gridProbing1: // ready to move to next grid probe point
+ {
+ // Move to the current probe point
+ const GridDefinition& grid = reprap.GetMove()->AccessBedProbeGrid().GetGrid();
+ const float x = grid.GetXCoordinate(gridXindex);
+ const float y = grid.GetYCoordinate(gridYindex);
+ if (grid.IsInRadius(x, y) && platform->IsAccessibleProbePoint(x, y))
+ {
+ moveBuffer.moveType = 0;
+ moveBuffer.endStopsToCheck = 0;
+ moveBuffer.usePressureAdvance = false;
+ moveBuffer.filePos = noFilePosition;
+ moveBuffer.coords[X_AXIS] = x - platform->GetZProbeParameters().xOffset;
+ moveBuffer.coords[Y_AXIS] = y - platform->GetZProbeParameters().yOffset;
+ moveBuffer.coords[Z_AXIS] = platform->GetZProbeDiveHeight();
+ moveBuffer.feedRate = platform->GetZProbeTravelSpeed();
+ moveBuffer.xAxes = 0;
+ segmentsLeft = 1;
+ gb.SetState(GCodeState::gridProbing2);
+ }
+ else
+ {
+ gb.SetState(GCodeState::gridProbing4);
+ }
+ }
+ break;
+
+ case GCodeState::gridProbing2: // ready to probe the current grid probe point
+ if (LockMovementAndWaitForStandstill(gb)
+ && millis() - lastProbedTime >= (uint32_t)(reprap.GetPlatform()->GetZProbeParameters().recoveryTime * SecondsToMillis)
+ )
+ {
+ // Probe the bed at the current XY coordinates
+ // Check for probe already triggered at start
+ if (reprap.GetPlatform()->GetZProbeResult() == EndStopHit::lowHit)
+ {
+ reply.copy("Z probe already triggered before probing move started");
+ error = true;
+ gb.SetState(GCodeState::normal);
+ break;
+ }
+
+ zProbeTriggered = false;
+ moveBuffer.moveType = 0;
+ moveBuffer.endStopsToCheck = ZProbeActive;
+ moveBuffer.usePressureAdvance = false;
+ moveBuffer.filePos = noFilePosition;
+ moveBuffer.coords[Z_AXIS] = -platform->GetZProbeDiveHeight();
+ moveBuffer.feedRate = platform->GetZProbeParameters().probeSpeed;
+ moveBuffer.xAxes = 0;
+ segmentsLeft = 1;
+ gb.SetState(GCodeState::gridProbing3);
+ }
+ break;
+
+ case GCodeState::gridProbing3: // ready to lift the probe after probing the current grid probe point
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ if (!zProbeTriggered)
+ {
+ reply.copy("Z probe was not triggered during probing move");
+ error = true;
+ gb.SetState(GCodeState::normal);
+ break;
+ }
+
+ lastProbedTime = millis();
+ const float heightError = moveBuffer.coords[Z_AXIS] - platform->ZProbeStopHeight();
+ reprap.GetMove()->AccessBedProbeGrid().SetGridHeight(gridXindex, gridYindex, heightError);
+ ++numPointsProbed;
+ heightSum += (double)heightError;
+ heightSquaredSum += (double)heightError * (double)heightError;
+
+ // Move back up to the dive height
+ moveBuffer.moveType = 0;
+ moveBuffer.endStopsToCheck = 0;
+ moveBuffer.usePressureAdvance = false;
+ moveBuffer.filePos = noFilePosition;
+ moveBuffer.coords[Z_AXIS] = platform->GetZProbeDiveHeight();
+ moveBuffer.feedRate = platform->GetZProbeTravelSpeed();
+ moveBuffer.xAxes = 0;
+ segmentsLeft = 1;
+ gb.SetState(GCodeState::gridProbing4);
+ }
+ break;
+
+ case GCodeState::gridProbing4: // ready to compute the next probe point
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ const GridDefinition& grid = reprap.GetMove()->AccessBedProbeGrid().GetGrid();
+ if (gridYindex & 1)
+ {
+ // Odd row, so decreasing X
+ if (gridXindex == 0)
+ {
+ ++gridYindex;
+ }
+ else
+ {
+ --gridXindex;
+ }
+ }
+ else
+ {
+ // Even row, so increasing X
+ if (gridXindex + 1 == grid.NumXpoints())
+ {
+ ++gridYindex;
+ }
+ else
+ {
+ ++gridXindex;
+ }
+ }
+ if (gridYindex == grid.NumYpoints())
+ {
+ // Finished probing the grid
+ if (numPointsProbed >= 4)
+ {
+ error = SaveHeightMapToFile(reply);
+ const double mean = heightSum/numPointsProbed;
+ const double deviation = sqrt(((heightSquaredSum * numPointsProbed) - (heightSum * heightSum)))/numPointsProbed;
+ reply.catf(" - %u points probed, mean error %.2f, deviation %.2f", numPointsProbed, mean, deviation);
+ reprap.GetMove()->UseHeightMap(true);
+ }
+ else
+ {
+ reply.copy("Too few points probed");
+ error = true;
+ }
+ gb.SetState(GCodeState::normal);
+ }
+ else
+ {
+ gb.SetState(GCodeState::gridProbing1);
+ }
+ }
+ break;
+
+ default: // should not happen
+ platform->Message(GENERIC_MESSAGE, "Error: undefined GCodeState\n");
+ gb.SetState(GCodeState::normal);
+ break;
+ }
+
+ if (gb.GetState() == GCodeState::normal)
+ {
+ // We completed a command, so unlock resources and tell the host about it
+ UnlockAll(gb);
+ HandleReply(gb, error, reply.Pointer());
+ }
+ }
+
+ // Move on to the next gcode source ready for next time
+ ++nextGcodeSource;
+ if (nextGcodeSource == ARRAY_SIZE(gcodeSources))
+ {
+ nextGcodeSource = 0;
+ }
+
+ platform->ClassReport(longWait);
+}
+
+// Start a new gcode, or continue to execute one that has already been started:
+void GCodes::StartNextGCode(GCodeBuffer& gb, StringRef& reply)
+{
+ if (isPaused && &gb == fileGCode)
+ {
+ // We are paused, so don't process any more gcodes from the file being printed.
+ // There is a potential issue here if fileGCode holds any locks, so unlock everything.
+ UnlockAll(gb);
+ }
+ else if (gb.IsReady() || gb.IsExecuting())
+ {
+ gb.SetFinished(ActOnCode(gb, reply));
+ }
+ else if (gb.MachineState().fileState.IsLive())
+ {
+ DoFilePrint(gb, reply);
+ }
+ 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;
+ }
+ }
+ }
+ 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;
+ }
+ }
+ }
+ 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;
+ }
+ }
+ }
+ else if (&gb == auxGCode)
+ {
+ // Aux serial port (typically PanelDue)
+ for (unsigned int i = 0; i < 16 && platform->GCodeAvailable(SerialSource::AUX); ++i)
+ {
+ 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;
+ }
+ }
+ }
+}
+
+void GCodes::DoFilePrint(GCodeBuffer& gb, StringRef& reply)
+{
+ FileData& fd = gb.MachineState().fileState;
+ for (int i = 0; i < 50 && fd.IsLive(); ++i)
+ {
+ char b;
+ if (fd.Read(b))
+ {
+ if (gb.StartingNewCode() && &gb == fileGCode && gb.MachineState().previous == nullptr)
+ {
+ filePos = fd.GetPosition() - 1;
+ //debugPrintf("Set file pos %u\n", filePos);
+ }
+ if (gb.Put(b))
+ {
+ gb.SetFinished(ActOnCode(gb, reply));
+ return;
+ }
+ }
+ 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;
+ }
+ }
+
+ gb.Init(); // mark buffer as empty
+
+ // 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();
+ if (gb.MachineState().previous == nullptr)
+ {
+ // Finished printing SD card file
+ reprap.GetPrintMonitor()->StoppedPrint();
+ if (platform->Emulating() == marlin)
+ {
+ // Pronterface expects a "Done printing" message
+ HandleReply(gb, false, "Done printing file");
+ }
+ }
+ else
+ {
+ // Finished a macro
+ Pop(gb);
+ gb.Init();
+ if (gb.GetState() == GCodeState::normal)
+ {
+ UnlockAll(gb);
+ HandleReply(gb, false, "");
+ }
+ }
+ }
+ return;
+ }
+ }
+}
+
+// Check for and execute triggers
+void GCodes::CheckTriggers()
+{
+ // Check for endstop state changes that activate new triggers
+ const TriggerMask oldEndstopStates = lastEndstopStates;
+ lastEndstopStates = platform->GetAllEndstopStates();
+ const TriggerMask risen = lastEndstopStates & ~oldEndstopStates,
+ fallen = ~lastEndstopStates & oldEndstopStates;
+ unsigned int lowestTriggerPending = MaxTriggers;
+ for (unsigned int triggerNumber = 0; triggerNumber < MaxTriggers; ++triggerNumber)
+ {
+ const Trigger& ct = triggers[triggerNumber];
+ if ( ((ct.rising & risen) != 0 || (ct.falling & fallen) != 0)
+ && (ct.condition == 0 || (ct.condition == 1 && reprap.GetPrintMonitor()->IsPrinting()))
+ )
+ {
+ triggersPending |= (1u << triggerNumber);
+ }
+ if (triggerNumber < lowestTriggerPending && (triggersPending & (1u << triggerNumber)) != 0)
+ {
+ lowestTriggerPending = triggerNumber;
+ }
+ }
+
+ // If any triggers are pending, activate the one with the lowest number
+ if (lowestTriggerPending == 0)
+ {
+ triggersPending &= ~(1u << lowestTriggerPending); // clear the trigger
+ DoEmergencyStop();
+ }
+ else if (lowestTriggerPending < MaxTriggers // if a trigger is pending
+ && !daemonGCode->MachineState().fileState.IsLive()
+ && daemonGCode->GetState() == GCodeState::normal // and we are not already executing a trigger or config.g
+ )
+ {
+ if (lowestTriggerPending == 1)
+ {
+ if (isPaused || !reprap.GetPrintMonitor()->IsPrinting())
+ {
+ triggersPending &= ~(1u << lowestTriggerPending); // ignore a pause trigger if we are already paused
+ }
+ else if (LockMovement(*daemonGCode)) // need to lock movement before executing the pause macro
+ {
+ triggersPending &= ~(1u << lowestTriggerPending); // clear the trigger
+ DoPause(*daemonGCode);
+ }
+ }
+ else
+ {
+ triggersPending &= ~(1u << lowestTriggerPending); // clear the trigger
+ char buffer[25];
+ StringRef filename(buffer, ARRAY_SIZE(buffer));
+ filename.printf(SYS_DIR "trigger%u.g", lowestTriggerPending);
+ DoFileMacro(*daemonGCode, filename.Pointer(), true);
+ }
+ }
+}
+
+// Execute an emergency stop
+void GCodes::DoEmergencyStop()
+{
+ reprap.EmergencyStop();
+ Reset();
+ platform->Message(GENERIC_MESSAGE, "Emergency Stop! Reset the controller to continue.");
+}
+
+// Pause the print. Before calling this, check that we are doing a file print that isn't already paused and get the movement lock.
+void GCodes::DoPause(GCodeBuffer& gb)
+{
+ if (&gb == fileGCode)
+ {
+ // Pausing a file print because of a command in the file itself
+ for (size_t drive = 0; drive < numAxes; ++drive)
+ {
+ pauseRestorePoint.moveCoords[drive] = moveBuffer.coords[drive];
+ }
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ pauseRestorePoint.moveCoords[drive] = lastRawExtruderPosition[drive - numAxes]; // get current extruder positions into pausedMoveBuffer
+ }
+ pauseRestorePoint.feedRate = gb.MachineState().feedrate;
+ }
+ else
+ {
+ // Pausing a file print via another input source
+ pauseRestorePoint.feedRate = fileGCode->MachineState().feedrate; // the call to PausePrint may or may not change this
+ FilePosition fPos = reprap.GetMove()->PausePrint(pauseRestorePoint.moveCoords, pauseRestorePoint.feedRate, reprap.GetCurrentXAxes());
+ // tell Move we wish to pause the current print
+ FileData& fdata = fileGCode->MachineState().fileState;
+ if (fPos != noFilePosition && fdata.IsLive())
+ {
+ fdata.Seek(fPos); // replay the abandoned instructions if/when we resume
+ }
+ if (segmentsLeft != 0)
+ {
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ pauseRestorePoint.moveCoords[drive] += moveBuffer.coords[drive]; // add on the extrusion in the move not yet taken
+ }
+ ClearMove();
+ }
+
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ pauseRestorePoint.moveCoords[drive] = lastRawExtruderPosition[drive - numAxes] - pauseRestorePoint.moveCoords[drive];
+ }
+
+ //TODO record the virtual extruder positions of mixing tools too. But that's very hard to do unless we store it in the move.
+
+ if (reprap.Debug(moduleGcodes))
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Paused print, file offset=%u\n", fPos);
+ }
+ }
+
+ for (size_t i = 0; i < NUM_FANS; ++i)
+ {
+ pausedFanValues[i] = platform->GetFanValue(i);
+ }
+ gb.SetState(GCodeState::pausing1);
+ isPaused = true;
+}
+
+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];
+ 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);
+ }
+}
+
+// Lock movement and wait for pending moves to finish.
+// As a side-effect it loads moveBuffer with the last position and feedrate for you.
+bool GCodes::LockMovementAndWaitForStandstill(const GCodeBuffer& gb)
+{
+ // Lock movement to stop another source adding moves to the queue
+ if (!LockMovement(gb))
+ {
+ return false;
+ }
+
+ // Last one gone?
+ if (segmentsLeft != 0)
+ {
+ return false;
+ }
+
+ // Wait for all the queued moves to stop so we get the actual last position
+ if (!reprap.GetMove()->AllMovesAreFinished())
+ {
+ return false;
+ }
+
+ // Allow movement again
+ reprap.GetMove()->ResumeMoving();
+
+ // Get the current positions. These may not be the same as the ones we remembered from last time if we just did a special move.
+ reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes());
+ memcpy(moveBuffer.initialCoords, moveBuffer.coords, numAxes * sizeof(moveBuffer.initialCoords[0]));
+ return true;
+}
+
+// Save (some of) the state of the machine for recovery in the future.
+bool GCodes::Push(GCodeBuffer& gb)
+{
+ bool ok = gb.PushState();
+ if (!ok)
+ {
+ platform->Message(GENERIC_MESSAGE, "Push(): stack overflow!\n");
+ }
+ return ok;
+}
+
+// Recover a saved state
+void GCodes::Pop(GCodeBuffer& gb)
+{
+ if (!gb.PopState())
+ {
+ platform->Message(GENERIC_MESSAGE, "Pop(): stack underflow!\n");
+ }
+}
+
+// Move expects all axis movements to be absolute, and all extruder drive moves to be relative. This function serves that.
+// 'moveType' is the S parameter in the G0 or G1 command, or -1 if we are doing G92.
+// For regular (type 0) moves, we apply limits and do X axis mapping.
+// Returns the number of segments if we have a legal move (or 1 if we are doing G92), or zero if this gcode should be discarded
+unsigned int GCodes::LoadMoveBufferFromGCode(GCodeBuffer& gb, int moveType)
+{
+ // Zero every extruder drive as some drives may not be changed
+ for (size_t drive = numAxes; drive < DRIVES; drive++)
+ {
+ moveBuffer.coords[drive] = 0.0;
+ }
+
+ // Deal with feed rate
+ if (gb.Seen(feedrateLetter))
+ {
+ gb.MachineState().feedrate = gb.GetFValue() * distanceScale * speedFactor;
+ }
+ moveBuffer.feedRate = gb.MachineState().feedrate;
+
+ // First do extrusion, and check, if we are extruding, that we have a tool to extrude with
+ Tool* tool = reprap.GetCurrentTool();
+ if (gb.Seen(extrudeLetter))
+ {
+ if (tool == nullptr)
+ {
+ platform->Message(GENERIC_MESSAGE, "Attempting to extrude with no tool selected.\n");
+ return 0;
+ }
+ size_t eMoveCount = tool->DriveCount();
+ if (eMoveCount > 0)
+ {
+ // Set the drive values for this tool.
+ // chrishamm-2014-10-03: Do NOT check extruder temperatures here, because we may be executing queued codes like M116
+ if (tool->GetMixing())
+ {
+ const float moveArg = gb.GetFValue() * distanceScale;
+ if (moveType == -1) // if doing G92
+ {
+ tool->virtualExtruderPosition = moveArg;
+ }
+ else
+ {
+ const float requestedExtrusionAmount = (gb.MachineState().drivesRelative)
+ ? moveArg
+ : moveArg - tool->virtualExtruderPosition;
+ for (size_t eDrive = 0; eDrive < eMoveCount; eDrive++)
+ {
+ const int drive = tool->Drive(eDrive);
+ const float extrusionAmount = requestedExtrusionAmount * tool->GetMix()[eDrive];
+ lastRawExtruderPosition[drive] += extrusionAmount;
+ rawExtruderTotalByDrive[drive] += extrusionAmount;
+ rawExtruderTotal += extrusionAmount;
+ moveBuffer.coords[drive + numAxes] = extrusionAmount * extrusionFactors[drive];
+ }
+ }
+ }
+ else
+ {
+ float eMovement[MaxExtruders];
+ size_t mc = eMoveCount;
+ gb.GetFloatArray(eMovement, mc, false);
+ if (eMoveCount != mc)
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Wrong number of extruder drives for the selected tool: %s\n", gb.Buffer());
+ return 0;
+ }
+
+ for (size_t eDrive = 0; eDrive < eMoveCount; eDrive++)
+ {
+ const int drive = tool->Drive(eDrive);
+ const float moveArg = eMovement[eDrive] * distanceScale;
+ if (moveType == -1)
+ {
+ moveBuffer.coords[drive + numAxes] = moveArg;
+ lastRawExtruderPosition[drive] = moveArg;
+ }
+ else
+ {
+ const float extrusionAmount = (gb.MachineState().drivesRelative)
+ ? moveArg
+ : moveArg - lastRawExtruderPosition[drive];
+ lastRawExtruderPosition[drive] += extrusionAmount;
+ rawExtruderTotalByDrive[drive] += extrusionAmount;
+ rawExtruderTotal += extrusionAmount;
+ moveBuffer.coords[drive + numAxes] = extrusionAmount * extrusionFactors[drive];
+ }
+ }
+ }
+ }
+ }
+
+ // Now the movement axes
+ const Tool * const currentTool = reprap.GetCurrentTool();
+ unsigned int numSegments = 1;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ float moveArg = gb.GetFValue() * distanceScale * axisScaleFactors[axis];
+ if (moveType == -1) // if doing G92
+ {
+ SetAxisIsHomed(axis); // doing a G92 defines the absolute axis position
+ moveBuffer.coords[axis] = moveArg;
+ }
+ else if (axis == X_AXIS && moveType == 0 && currentTool != nullptr)
+ {
+ // Perform X axis mapping
+ const uint32_t xMap = currentTool->GetXAxisMap();
+ for (size_t mappedAxis = 0; mappedAxis < numAxes; ++mappedAxis)
+ {
+ if ((xMap & (1u << mappedAxis)) != 0)
+ {
+ float mappedMoveArg = moveArg;
+ if (gb.MachineState().axesRelative)
+ {
+ mappedMoveArg += moveBuffer.coords[mappedAxis];
+ }
+ else
+ {
+ mappedMoveArg -= currentTool->GetOffset()[mappedAxis]; // adjust requested position to compensate for tool offset
+ }
+ if (reprap.GetMove()->UsingHeightMap())
+ {
+ const unsigned int minSegments = reprap.GetMove()->AccessBedProbeGrid().GetMinimumSegments(fabs(mappedMoveArg - moveBuffer.coords[mappedAxis]));
+ if (minSegments > numSegments)
+ {
+ numSegments = minSegments;
+ }
+ }
+ moveBuffer.coords[mappedAxis] = mappedMoveArg;
+ }
+ }
+ }
+ else
+ {
+ if (gb.MachineState().axesRelative)
+ {
+ moveArg += moveBuffer.coords[axis];
+ }
+ else if (currentTool != nullptr && moveType == 0)
+ {
+ moveArg -= currentTool->GetOffset()[axis]; // adjust requested position to compensate for tool offset
+ }
+
+ if (axis < Z_AXIS && moveType == 0 && reprap.GetMove()->UsingHeightMap())
+ {
+ const unsigned int minSegments = reprap.GetMove()->AccessBedProbeGrid().GetMinimumSegments(fabs(moveArg - moveBuffer.coords[axis]));
+ if (minSegments > numSegments)
+ {
+ numSegments = minSegments;
+ }
+ }
+ moveBuffer.coords[axis] = moveArg;
+ }
+ }
+ }
+
+ // If doing a regular move and applying limits, limit all axes
+ if ( moveType == 0
+ && limitAxes
+#if SUPPORT_ROLAND
+ && !reprap.GetRoland()->Active()
+#endif
+ )
+ {
+ if (!reprap.GetMove()->IsDeltaMode())
+ {
+ // Cartesian or CoreXY printer, so limit those axes that have been homed
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (GetAxisIsHomed(axis))
+ {
+ float& f = moveBuffer.coords[axis];
+ if (f < platform->AxisMinimum(axis))
+ {
+ f = platform->AxisMinimum(axis);
+ }
+ else if (f > platform->AxisMaximum(axis))
+ {
+ f = platform->AxisMaximum(axis);
+ }
+ }
+ }
+ }
+ else if (AllAxesAreHomed()) // this is to allow extruder-only moves before homing
+ {
+ // If axes have been homed on a delta printer and this isn't a homing move, check for movements outside limits.
+ // Skip this check if axes have not been homed, so that extruder-only moved are allowed before homing
+ // Constrain the move to be within the build radius
+ const float diagonalSquared = fsquare(moveBuffer.coords[X_AXIS]) + fsquare(moveBuffer.coords[Y_AXIS]);
+ if (diagonalSquared > reprap.GetMove()->GetDeltaParams().GetPrintRadiusSquared())
+ {
+ const float factor = sqrtf(reprap.GetMove()->GetDeltaParams().GetPrintRadiusSquared() / diagonalSquared);
+ moveBuffer.coords[X_AXIS] *= factor;
+ moveBuffer.coords[Y_AXIS] *= factor;
+ }
+
+ // Constrain the end height of the move to be no greater than the homed height and no lower than -0.2mm
+ moveBuffer.coords[Z_AXIS] = max<float>(platform->AxisMinimum(Z_AXIS),
+ min<float>(moveBuffer.coords[Z_AXIS], reprap.GetMove()->GetDeltaParams().GetHomedHeight()));
+ }
+ }
+
+ return numSegments;
+}
+
+// This function is called for a G Code that makes a move.
+// If the Move class can't receive the move (i.e. things have to wait), return 0.
+// If we have queued the move and the caller doesn't need to wait for it to complete, return 1.
+// If we need to wait for the move to complete before doing another one (e.g. because endstops are checked in this move), return 2.
+int GCodes::SetUpMove(GCodeBuffer& gb, StringRef& reply)
+{
+ // Last one gone yet?
+ if (segmentsLeft != 0)
+ {
+ return 0;
+ }
+
+ // Check to see if the move is a 'homing' move that endstops are checked on.
+ moveBuffer.endStopsToCheck = 0;
+ moveBuffer.moveType = 0;
+ moveBuffer.xAxes = reprap.GetCurrentXAxes();
+ if (gb.Seen('S'))
+ {
+ int ival = gb.GetIValue();
+ if (ival == 1 || ival == 2)
+ {
+ moveBuffer.moveType = ival;
+ moveBuffer.xAxes = 0; // don't do bed compensation
+ }
+
+ if (ival == 1)
+ {
+ for (size_t i = 0; i < numAxes; ++i)
+ {
+ if (gb.Seen(axisLetters[i]))
+ {
+ moveBuffer.endStopsToCheck |= (1u << i);
+ }
+ }
+ }
+ else if (ival == 99) // temporary code to log Z probe change positions
+ {
+ moveBuffer.endStopsToCheck |= LogProbeChanges;
+ }
+ }
+
+ if (reprap.GetMove()->IsDeltaMode())
+ {
+ // Extra checks to avoid damaging delta printers
+ if (moveBuffer.moveType != 0 && !gb.MachineState().axesRelative)
+ {
+ // We have been asked to do a move without delta mapping on a delta machine, but the move is not relative.
+ // This may be damaging and is almost certainly a user mistake, so ignore the move.
+ reply.copy("Attempt to move the motors of a delta printer to absolute positions");
+ return 1;
+ }
+
+ if (moveBuffer.moveType == 0 && !AllAxesAreHomed())
+ {
+ // The user may be attempting to move a delta printer to an XYZ position before homing the axes
+ // This may be damaging and is almost certainly a user mistake, so ignore the move. But allow extruder-only moves.
+ if (gb.Seen(axisLetters[X_AXIS]) || gb.Seen(axisLetters[Y_AXIS]) || gb.Seen(axisLetters[Z_AXIS]))
+ {
+ reply.copy("Attempt to move the head of a delta printer before homing the towers");
+ return 1;
+ }
+ }
+ }
+
+ // Load the last position and feed rate into moveBuffer
+#if SUPPORT_ROLAND
+ if (reprap.GetRoland()->Active())
+ {
+ reprap.GetRoland()->GetCurrentRolandPosition(moveBuffer);
+ }
+ else
+#endif
+ {
+ reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, moveBuffer.moveType, reprap.GetCurrentXAxes());
+ }
+
+ // Load the move buffer with either the absolute movement required or the relative movement required
+ float oldCoords[MAX_AXES];
+ memcpy(oldCoords, moveBuffer.coords, sizeof(oldCoords));
+ segmentsLeft = LoadMoveBufferFromGCode(gb, moveBuffer.moveType);
+ if (segmentsLeft != 0)
+ {
+ // Flag whether we should use pressure advance, if there is any extrusion in this move.
+ // We assume it is a normal printing move needing pressure advance if there is forward extrusion and XY movement.
+ // The movement code will only apply pressure advance if there is forward extrusion, so we only need to check for XY movement here.
+ moveBuffer.usePressureAdvance = false;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ if (axis != Z_AXIS && moveBuffer.coords[axis] != oldCoords[axis])
+ {
+ moveBuffer.usePressureAdvance = true;
+ break;
+ }
+ }
+ moveBuffer.filePos = (&gb == fileGCode) ? filePos : noFilePosition;
+ //debugPrintf("Queue move pos %u\n", moveFilePos);
+ }
+ return (moveBuffer.moveType != 0 || moveBuffer.endStopsToCheck != 0) ? 2 : 1;
+}
+
+// The Move class calls this function to find what to do next.
+
+bool GCodes::ReadMove(RawMove& m)
+{
+ if (segmentsLeft == 0)
+ {
+ return false;
+ }
+
+ m = moveBuffer;
+ if (segmentsLeft == 1)
+ {
+ ClearMove();
+ }
+ else
+ {
+ // This move needs to be divided into 2 or more segments
+ // Do the axes
+ for (size_t drive = 0; drive < numAxes; ++drive)
+ {
+ const float movementToDo = (moveBuffer.coords[drive] - moveBuffer.initialCoords[drive])/segmentsLeft;
+ moveBuffer.initialCoords[drive] += movementToDo;
+ m.coords[drive] = moveBuffer.initialCoords[drive];
+ }
+
+ // Do the extruders
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ const float extrusionToDo = moveBuffer.coords[drive]/segmentsLeft;
+ m.coords[drive] = extrusionToDo;
+ moveBuffer.coords[drive] -= extrusionToDo;
+ }
+ --segmentsLeft;
+ }
+ return true;
+}
+
+void GCodes::ClearMove()
+{
+ segmentsLeft = 0;
+ moveBuffer.endStopsToCheck = 0;
+ moveBuffer.moveType = 0;
+ moveBuffer.isFirmwareRetraction = false;
+}
+
+// Run a file macro. Prior to calling this, 'state' must be set to the state we want to enter when the macro has been completed.
+// Return true if the file was found or it wasn't and we were asked to report that fact.
+bool GCodes::DoFileMacro(GCodeBuffer& gb, const char* fileName, bool reportMissing)
+{
+ FileStore * const f = platform->GetFileStore(platform->GetSysDir(), fileName, false);
+ if (f == nullptr)
+ {
+ if (reportMissing)
+ {
+ // Don't use snprintf into scratchString here, because fileName may be aliased to scratchString
+ platform->MessageF(GENERIC_MESSAGE, "Macro file %s not found.\n", fileName);
+ return true;
+ }
+ return false;
+ }
+
+ if (!Push(gb))
+ {
+ return true;
+ }
+ gb.MachineState().fileState.Set(f);
+ gb.MachineState().doingFileMacro = true;
+ gb.SetState(GCodeState::normal);
+ gb.Init();
+ return true;
+}
+
+void GCodes::FileMacroCyclesReturn(GCodeBuffer& gb)
+{
+ if (gb.MachineState().doingFileMacro)
+ {
+ gb.PopState();
+ gb.Init();
+ }
+}
+
+// To execute any move, call this until it returns true.
+// There is only one copy of the canned cycle variable so you must acquire the move lock before calling this.
+bool GCodes::DoCannedCycleMove(GCodeBuffer& gb, EndstopChecks ce)
+{
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ if (cannedCycleMoveQueued) // if the move has already been queued, it must have finished
+ {
+ Pop(gb);
+ cannedCycleMoveQueued = false;
+ return true;
+ }
+
+ // Otherwise, the move has not been queued yet
+ if (!Push(gb))
+ {
+ return true; // stack overflow
+ }
+
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ switch(cannedMoveType[drive])
+ {
+ case CannedMoveType::none:
+ break;
+ case CannedMoveType::relative:
+ moveBuffer.coords[drive] += cannedMoveCoords[drive];
+ break;
+ case CannedMoveType::absolute:
+ moveBuffer.coords[drive] = cannedMoveCoords[drive];
+ break;
+ }
+ }
+ moveBuffer.feedRate = cannedFeedRate;
+ moveBuffer.xAxes = 0;
+ moveBuffer.endStopsToCheck = ce;
+ moveBuffer.filePos = noFilePosition;
+ moveBuffer.usePressureAdvance = false;
+ segmentsLeft = 1;
+ cannedCycleMoveQueued = true;
+ }
+ return false;
+}
+
+// This handles G92
+bool GCodes::SetPositions(GCodeBuffer& gb)
+{
+ // Don't pause the machine if only extruder drives are being reset (DC, 2015-09-06).
+ // This avoids blobs and seams when the gcode uses absolute E coordinates and periodically includes G92 E0.
+ bool includingAxes = false;
+ for (size_t drive = 0; drive < numAxes; ++drive)
+ {
+ if (gb.Seen(axisLetters[drive]))
+ {
+ includingAxes = true;
+ break;
+ }
+ }
+
+ if (includingAxes)
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ }
+ else if (segmentsLeft != 0) // wait for previous move to be taken so that GetCurrentUserPosition returns the correct value
+ {
+ return false;
+ }
+
+ reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes()); // make sure move buffer is up to date
+ bool ok = LoadMoveBufferFromGCode(gb, -1);
+ if (ok && includingAxes)
+ {
+#if SUPPORT_ROLAND
+ if (reprap.GetRoland()->Active())
+ {
+ for(size_t axis = 0; axis < AXES; axis++)
+ {
+ if (!reprap.GetRoland()->ProcessG92(moveBuffer[axis], axis))
+ {
+ return false;
+ }
+ }
+ }
+#endif
+ SetPositions(moveBuffer.coords);
+ }
+
+ return true;
+}
+
+// Offset the axes by the X, Y, and Z amounts in the M code in gb. Say the machine is at [10, 20, 30] and
+// the offsets specified are [8, 2, -5]. The machine will move to [18, 22, 25] and henceforth consider that point
+// to be [10, 20, 30].
+bool GCodes::OffsetAxes(GCodeBuffer& gb)
+{
+ if (!offSetSet)
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ cannedMoveType[drive] = CannedMoveType::none;
+ if (drive < numAxes)
+ {
+ record[drive] = moveBuffer.coords[drive];
+ if (gb.Seen(axisLetters[drive]))
+ {
+ cannedMoveCoords[drive] = gb.GetFValue();
+ cannedMoveType[drive] = CannedMoveType::relative;
+ }
+ }
+ else
+ {
+ record[drive] = 0.0;
+ }
+ }
+
+ if (gb.Seen(feedrateLetter)) // Has the user specified a feedrate?
+ {
+ cannedFeedRate = gb.GetFValue() * distanceScale * SECONDS_TO_MINUTES;
+ }
+ else
+ {
+ cannedFeedRate = DEFAULT_FEEDRATE;
+ }
+
+ offSetSet = true;
+ }
+
+ if (DoCannedCycleMove(gb, 0))
+ {
+ // Restore positions
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ moveBuffer.coords[drive] = record[drive];
+ }
+ reprap.GetMove()->SetLiveCoordinates(record); // This doesn't transform record
+ reprap.GetMove()->SetPositions(record); // This does
+ offSetSet = false;
+ return true;
+ }
+
+ return false;
+}
+
+// Home one or more of the axes. Which ones are decided by the
+// booleans homeX, homeY and homeZ.
+// Returns true if completed, false if needs to be called again.
+// 'reply' is only written if there is an error.
+// 'error' is false on entry, gets changed to true if there is an error.
+bool GCodes::DoHome(GCodeBuffer& gb, StringRef& reply, bool& error)
+{
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+#if SUPPORT_ROLAND
+ // Deal with a Roland configuration
+ if (reprap.GetRoland()->Active())
+ {
+ bool rolHome = reprap.GetRoland()->ProcessHome();
+ if (rolHome)
+ {
+ for(size_t axis = 0; axis < AXES; axis++)
+ {
+ axisIsHomed[axis] = true;
+ }
+ }
+ return rolHome;
+ }
+#endif
+
+ if (reprap.GetMove()->IsDeltaMode())
+ {
+ SetAllAxesNotHomed();
+ DoFileMacro(gb, HOME_DELTA_G);
+ }
+ else
+ {
+ toBeHomed = 0;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ toBeHomed |= (1u << axis);
+ SetAxisNotHomed(axis);
+ }
+ }
+
+ if (toBeHomed == 0 || toBeHomed == ((1u << numAxes) - 1))
+ {
+ // Homing everything
+ SetAllAxesNotHomed();
+ DoFileMacro(gb, HOME_ALL_G);
+ }
+ else if ( platform->MustHomeXYBeforeZ()
+ && ((toBeHomed & (1u << Z_AXIS)) != 0)
+ && ((toBeHomed | axesHomed | (1u << Z_AXIS)) != ((1u << numAxes) - 1))
+ )
+ {
+ // We can only home Z if both X and Y have already been homed or are being homed
+ reply.copy("Must home all other axes before homing Z");
+ error = true;
+ }
+ else
+ {
+ gb.SetState(GCodeState::homing);
+ }
+ }
+ return true;
+}
+
+// This lifts Z a bit, moves to the probe XY coordinates (obtained by a call to GetProbeCoordinates() ),
+// probes the bed height, and records the Z coordinate probed. If you want to program any general
+// internal canned cycle, this shows how to do it.
+// On entry, probePointIndex specifies which of the points this is.
+bool GCodes::DoSingleZProbeAtPoint(GCodeBuffer& gb, int probePointIndex, float heightAdjust)
+{
+ reprap.GetMove()->SetIdentityTransform(); // It doesn't matter if these are called repeatedly
+
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ cannedMoveType[drive] = CannedMoveType::none;
+ }
+
+ switch (cannedCycleMoveCount)
+ {
+ case 0: // Move Z to the dive height. This only does anything on the first move; on all the others Z is already there
+ cannedMoveCoords[Z_AXIS] = platform->GetZProbeDiveHeight() + max<float>(platform->ZProbeStopHeight(), 0.0);
+ cannedMoveType[Z_AXIS] = CannedMoveType::absolute;
+ cannedFeedRate = platform->GetZProbeTravelSpeed();
+ if (DoCannedCycleMove(gb, 0))
+ {
+ cannedCycleMoveCount++;
+ }
+ return false;
+
+ case 1: // Move to the correct XY coordinates
+ GetProbeCoordinates(probePointIndex, cannedMoveCoords[X_AXIS], cannedMoveCoords[Y_AXIS], cannedMoveCoords[Z_AXIS]);
+ cannedMoveType[X_AXIS] = CannedMoveType::absolute;
+ cannedMoveType[Y_AXIS] = CannedMoveType::absolute;
+ // NB - we don't use the Z value
+ cannedFeedRate = platform->GetZProbeTravelSpeed();
+ if (DoCannedCycleMove(gb, 0))
+ {
+ cannedCycleMoveCount++;
+ }
+ return false;
+
+ case 2: // Probe the bed
+ if (millis() - lastProbedTime >= (uint32_t)(platform->GetZProbeParameters().recoveryTime * SecondsToMillis))
+ {
+ const float height = (GetAxisIsHomed(Z_AXIS))
+ ? 2 * platform->GetZProbeDiveHeight() // Z axis has been homed, so no point in going very far
+ : 1.1 * platform->AxisTotalLength(Z_AXIS); // Z axis not homed yet, so treat this as a homing move
+ switch(DoZProbe(gb, height))
+ {
+ case 0:
+ // Z probe is already triggered at the start of the move, so abandon the probe and record an error
+ platform->Message(GENERIC_MESSAGE, "Error: Z probe already triggered at start of probing move\n");
+ cannedCycleMoveCount++;
+ reprap.GetMove()->SetZBedProbePoint(probePointIndex, platform->GetZProbeDiveHeight(), true, true);
+ break;
+
+ case 1:
+ // Z probe did not trigger
+ platform->Message(GENERIC_MESSAGE, "Error: Z probe was not triggered during probing move\n");
+ cannedCycleMoveCount++;
+ reprap.GetMove()->SetZBedProbePoint(probePointIndex, -(platform->GetZProbeDiveHeight()), true, true);
+ break;
+
+ case 2:
+ // Successful probing
+ lastProbedTime = millis();
+ if (GetAxisIsHomed(Z_AXIS))
+ {
+ lastProbedZ = moveBuffer.coords[Z_AXIS] - (platform->ZProbeStopHeight() + heightAdjust);
+ }
+ else
+ {
+ // The Z axis has not yet been homed, so treat this probe as a homing move.
+ moveBuffer.coords[Z_AXIS] = platform->ZProbeStopHeight() + heightAdjust;
+ SetPositions(moveBuffer.coords);
+ SetAxisIsHomed(Z_AXIS);
+ lastProbedZ = 0.0;
+ }
+ reprap.GetMove()->SetZBedProbePoint(probePointIndex, lastProbedZ, true, false);
+ cannedCycleMoveCount++;
+ break;
+
+ default:
+ break;
+ }
+ }
+ return false;
+
+ case 3: // Raise the head back up to the dive height
+ cannedMoveCoords[Z_AXIS] = platform->GetZProbeDiveHeight() + max<float>(platform->ZProbeStopHeight(), 0.0);
+ cannedMoveType[Z_AXIS] = CannedMoveType::absolute;
+ cannedFeedRate = platform->GetZProbeTravelSpeed();
+ if (DoCannedCycleMove(gb, 0))
+ {
+ cannedCycleMoveCount = 0;
+ return true;
+ }
+ return false;
+
+ default: // should not happen
+ cannedCycleMoveCount = 0;
+ return true;
+ }
+}
+
+// This simply moves down till the Z probe/switch is triggered. Call it repeatedly until it returns true.
+// Called when we do a G30 with no P parameter.
+bool GCodes::DoSingleZProbe(GCodeBuffer& gb, bool reportOnly, float heightAdjust)
+{
+ switch (DoZProbe(gb, 1.1 * platform->AxisTotalLength(Z_AXIS)))
+ {
+ case 0: // failed
+ platform->Message(GENERIC_MESSAGE, "Error: Z probe already triggered at start of probing move\n");
+ return true;
+
+ case 1:
+ platform->Message(GENERIC_MESSAGE, "Error: Z probe was not triggered during probing move\n");
+ return true;
+
+ case 2: // success
+ if (!reportOnly)
+ {
+ moveBuffer.coords[Z_AXIS] = platform->ZProbeStopHeight() + heightAdjust;
+ SetPositions(moveBuffer.coords);
+ SetAxisIsHomed(Z_AXIS);
+ lastProbedZ = 0.0;
+ }
+ return true;
+
+ default: // not finished yet
+ return false;
+ }
+}
+
+// Do a Z probe cycle up to the maximum specified distance.
+// Returns -1 if not complete yet
+// Returns 0 if Z probe already triggered at start of probing
+// Returns 1 if Z probe didn't trigger
+// Returns 2 if success, with the current position in moveBuffer
+int GCodes::DoZProbe(GCodeBuffer& gb, float distance)
+{
+ if (platform->GetZProbeType() == ZProbeTypeDelta)
+ {
+ const ZProbeParameters& params = platform->GetZProbeParameters();
+ return reprap.GetMove()->DoDeltaProbe(params.extraParam, 1.0, params.probeSpeed, distance); //TODO second parameter
+ }
+ else
+ {
+ // Check for probe already triggered at start
+ if (!cannedCycleMoveQueued)
+ {
+ if (reprap.GetPlatform()->GetZProbeResult() == EndStopHit::lowHit)
+ {
+ return 0;
+ }
+ zProbeTriggered = false;
+ }
+
+ // Do a normal canned cycle Z movement with Z probe enabled
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ cannedMoveType[drive] = CannedMoveType::none;
+ }
+
+ cannedMoveCoords[Z_AXIS] = -distance;
+ cannedMoveType[Z_AXIS] = CannedMoveType::relative;
+ cannedFeedRate = platform->GetZProbeParameters().probeSpeed;
+
+ if (DoCannedCycleMove(gb, ZProbeActive))
+ {
+ return (zProbeTriggered) ? 2 : 1;
+ }
+ return -1;
+ }
+}
+
+// This is called to execute a G30.
+// It sets wherever we are as the probe point P (probePointIndex)
+// then probes the bed, or gets all its parameters from the arguments.
+// If X or Y are specified, use those; otherwise use the machine's
+// coordinates. If no Z is specified use the machine's coordinates. If it
+// is specified and is greater than SILLY_Z_VALUE (i.e. greater than -9999.0)
+// then that value is used. If it's less than SILLY_Z_VALUE the bed is
+// probed and that value is used.
+// Call this repeatedly until it returns true.
+bool GCodes::SetSingleZProbeAtAPosition(GCodeBuffer& gb, StringRef& reply)
+{
+ if (reprap.GetMove()->IsDeltaMode() && !AllAxesAreHomed())
+ {
+ reply.copy("Must home before bed probing");
+ return true;
+ }
+
+ float heightAdjust = 0.0;
+ bool dummy;
+ gb.TryGetFValue('H', heightAdjust, dummy);
+
+ if (!gb.Seen('P'))
+ {
+ bool reportOnly = false;
+ if (gb.Seen('S') && gb.GetIValue() < 0)
+ {
+ reportOnly = true;
+ }
+ return DoSingleZProbe(gb, reportOnly, heightAdjust);
+ }
+
+ int probePointIndex = gb.GetIValue();
+ if (probePointIndex < 0 || (unsigned int)probePointIndex >= MaxProbePoints)
+ {
+ reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Z probe point index out of range.\n");
+ return true;
+ }
+
+ float x = (gb.Seen(axisLetters[X_AXIS])) ? gb.GetFValue() : moveBuffer.coords[X_AXIS];
+ float y = (gb.Seen(axisLetters[Y_AXIS])) ? gb.GetFValue() : moveBuffer.coords[Y_AXIS];
+ float z = (gb.Seen(axisLetters[Z_AXIS])) ? gb.GetFValue() : moveBuffer.coords[Z_AXIS];
+
+ reprap.GetMove()->SetXBedProbePoint(probePointIndex, x);
+ reprap.GetMove()->SetYBedProbePoint(probePointIndex, y);
+
+ if (z > SILLY_Z_VALUE)
+ {
+ reprap.GetMove()->SetZBedProbePoint(probePointIndex, z, false, false);
+ if (gb.Seen('S'))
+ {
+ zProbesSet = true;
+ reprap.GetMove()->FinishedBedProbing(gb.GetIValue(), reply);
+ }
+ return true;
+ }
+ else
+ {
+ if (DoSingleZProbeAtPoint(gb, probePointIndex, heightAdjust))
+ {
+ if (gb.Seen('S'))
+ {
+ zProbesSet = true;
+ int sParam = gb.GetIValue();
+ if (sParam == 1)
+ {
+ // G30 with a silly Z value and S=1 is equivalent to G30 with no parameters in that it sets the current Z height
+ // This is useful because it adjusts the XY position to account for the probe offset.
+ moveBuffer.coords[Z_AXIS] += lastProbedZ;
+ SetPositions(moveBuffer.coords);
+ lastProbedZ = 0.0;
+ }
+ else
+ {
+ reprap.GetMove()->FinishedBedProbing(sParam, reply);
+ }
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// This returns the (X, Y) points to probe the bed at probe point count. When probing, it returns false.
+// If called after probing has ended it returns true, and the Z coordinate probed is also returned.
+bool GCodes::GetProbeCoordinates(int count, float& x, float& y, float& z) const
+{
+ const ZProbeParameters& rp = platform->GetZProbeParameters();
+ x = reprap.GetMove()->XBedProbePoint(count) - rp.xOffset;
+ y = reprap.GetMove()->YBedProbePoint(count) - rp.yOffset;
+ z = reprap.GetMove()->ZBedProbePoint(count);
+ return zProbesSet;
+}
+
+bool GCodes::SetPrintZProbe(GCodeBuffer& gb, StringRef& reply)
+{
+ ZProbeParameters params = platform->GetZProbeParameters();
+ bool seen = false;
+ gb.TryGetFValue(axisLetters[X_AXIS], params.xOffset, seen);
+ gb.TryGetFValue(axisLetters[Y_AXIS], params.yOffset, seen);
+ gb.TryGetFValue(axisLetters[Z_AXIS], params.height, seen);
+ gb.TryGetIValue('P', params.adcValue, seen);
+
+ if (gb.Seen('C'))
+ {
+ params.temperatureCoefficient = gb.GetFValue();
+ seen = true;
+ if (gb.Seen('S'))
+ {
+ params.calibTemperature = gb.GetFValue();
+ }
+ else
+ {
+ // Use the current bed temperature as the calibration temperature if no value was provided
+ params.calibTemperature = platform->GetZProbeTemperature();
+ }
+ }
+
+ if (seen)
+ {
+ platform->SetZProbeParameters(params);
+ }
+ else
+ {
+ const int v0 = platform->ZProbe();
+ int v1, v2;
+ switch (platform->GetZProbeSecondaryValues(v1, v2))
+ {
+ case 1:
+ reply.printf("%d (%d)", v0, v1);
+ break;
+ case 2:
+ reply.printf("%d (%d, %d)", v0, v1, v2);
+ break;
+ default:
+ reply.printf("%d", v0);
+ break;
+ }
+ }
+ return true;
+}
+
+// Define the probing grid, returning true if error
+// Called when we see an M557 command with no P parameter
+bool GCodes::DefineGrid(GCodeBuffer& gb, StringRef &reply)
+{
+ reprap.GetMove()->UseHeightMap(false);
+
+ bool seenX = false, seenY = false, seenR = false, seenS = false;
+ float xValues[2];
+ float yValues[2];
+
+ if (gb.Seen('X'))
+ {
+ size_t count = 2;
+ gb.GetFloatArray(xValues, count, false);
+ if (count == 2)
+ {
+ seenX = true;
+ }
+ else
+ {
+ reply.copy("ERROR: Wrong number of X values in M577, need 2");
+ return true;
+ }
+ }
+ if (gb.Seen('Y'))
+ {
+ size_t count = 2;
+ gb.GetFloatArray(yValues, count, false);
+ if (count == 2)
+ {
+ seenY = true;
+ }
+ else
+ {
+ reply.copy("ERROR: Wrong number of Y values in M577, need 2");
+ return true;
+ }
+ }
+
+ float radius = -1.0;
+ gb.TryGetFValue('R', radius, seenR);
+ float spacing = DefaultGridSpacing;
+ gb.TryGetFValue('S', spacing, seenS);
+
+ if (!seenX && !seenY && !seenR && !seenS)
+ {
+ // Just print the existing grid parameters
+ const GridDefinition& grid = reprap.GetMove()->AccessBedProbeGrid().GetGrid();
+ if (grid.IsValid())
+ {
+ reply.copy("Grid: ");
+ grid.PrintParameters(reply);
+ }
+ else
+ {
+ reply.copy("Grid is not defined");
+ }
+ return false;
+ }
+
+ if (seenX != seenY)
+ {
+ reply.copy("ERROR: specify both or neither of X and Y in M577");
+ return true;
+ }
+
+ if (!seenX && !seenR)
+ {
+ // Must have given just the S parameter
+ reply.copy("ERROR: specify at least radius or X,Y ranges in M577");
+ return true;
+
+ }
+
+ if (!seenX)
+ {
+ if (radius > 0)
+ {
+ const float effectiveRadius = floor((radius - 0.1)/spacing) * spacing;
+ xValues[0] = yValues[0] = -effectiveRadius;
+ xValues[1] = yValues[1] = effectiveRadius + 0.1;
+ }
+ else
+ {
+ reply.copy("ERROR: M577 radius must be positive unless X and Y are specified");
+ return true;
+ }
+ }
+ GridDefinition newGrid(xValues, yValues, radius, spacing); // create a new grid
+ if (newGrid.IsValid())
+ {
+ reprap.GetMove()->AccessBedProbeGrid().SetGrid(newGrid);
+ return false;
+ }
+ else
+ {
+ reply.copy("ERROR: bad grid definition: ");
+ newGrid.PrintError(reply);
+ return true;
+ }
+}
+
+// Start probing the grid, returning true if we didn't because of an error.
+// Prior to calling this the movement system must be locked.
+bool GCodes::ProbeGrid(GCodeBuffer& gb, StringRef& reply)
+{
+ int32_t sParam = 0;
+ bool dummy;
+ gb.TryGetIValue('S', sParam, dummy);
+
+ if (gb.Seen('P'))
+ {
+ heightMapFile = gb.GetString();
+ }
+ else
+ {
+ heightMapFile = DefaultHeightMapFile;
+ }
+
+ Move * const move = reprap.GetMove();
+ switch(sParam)
+ {
+ case 0: // Probe the bed and save to file
+ if (!move->AccessBedProbeGrid().GetGrid().IsValid())
+ {
+ reply.copy("No valid grid defined for G29 bed probing");
+ return true;
+ }
+
+ if (!AllAxesAreHomed())
+ {
+ reply.copy("Must home printer before G29 bed probing");
+ return true;
+ }
+
+ gridXindex = gridYindex = 0;
+ numPointsProbed = 0;
+ heightSum = heightSquaredSum = 0.0;
+
+ move->AccessBedProbeGrid().ClearGridHeights();
+ move->UseHeightMap(false);
+ move->SetIdentityTransform();
+ gb.SetState(GCodeState::gridProbing1);
+ return false;
+
+ case 1: // Load height map from file
+ {
+ const char* locHeightMapFileName;
+ if (gb.Seen('P'))
+ {
+ locHeightMapFileName = gb.GetString();
+ }
+ else
+ {
+ locHeightMapFileName = DefaultHeightMapFile;
+ }
+ FileStore * const f = platform->GetFileStore(platform->GetSysDir(), locHeightMapFileName, false);
+
+ if (f == nullptr)
+ {
+ reply.printf("Height map file %s not found", locHeightMapFileName);
+ return true;
+ }
+
+ reply.printf("Failed to load height map from file %s: ", heightMapFile); // set up error message to append to
+ const bool err = move->AccessBedProbeGrid().LoadFromFile(f, reply);
+ f->Close();
+ if (err)
+ {
+ move->AccessBedProbeGrid().ClearGridHeights(); // make sure we don't end up with a partial height map
+ }
+ else
+ {
+ reply.Clear(); // wipe the error message
+ }
+
+ move->UseHeightMap(!err);
+ return err;
+ }
+
+ case 2: // Clear height map
+ move->AccessBedProbeGrid().ClearGridHeights();
+ move->UseHeightMap(false);
+ return false;
+
+ default:
+ reply.copy("Invalid S parameter in G29 command");
+ return true;
+ }
+}
+
+// Save the height map and write the success or error message to 'reply', returning true if an error occurred
+bool GCodes::SaveHeightMapToFile(StringRef& reply) const
+{
+ Platform *platform = reprap.GetPlatform();
+ FileStore * const f = platform->GetFileStore(platform->GetSysDir(), heightMapFile, true);
+ bool err;
+ if (f == nullptr)
+ {
+ reply.printf("Failed to create height map file %s", heightMapFile);
+ err = true;
+ }
+ else
+ {
+ err = reprap.GetMove()->AccessBedProbeGrid().SaveToFile(f);
+ f->Close();
+ if (err)
+ {
+ platform->GetMassStorage()->Delete(platform->GetSysDir(), heightMapFile);
+ reply.printf("Failed to save height map to file %s", heightMapFile);
+ }
+ else
+ {
+ reply.printf("Height map saved to file %s", heightMapFile);
+ }
+ }
+ return err;
+}
+
+// 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();
+ if (currentTool != nullptr)
+ {
+ const float *offset = currentTool->GetOffset();
+ for (size_t i = 0; i < numAxes; ++i)
+ {
+ liveCoordinates[i] += offset[i];
+ }
+ }
+
+ s.Clear();
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ s.catf("%c: %.2f ", axisLetters[axis], liveCoordinates[axis]);
+ }
+ for (size_t i = numAxes; i < DRIVES; i++)
+ {
+ s.catf("E%u: %.1f ", i - numAxes, liveCoordinates[i]);
+ }
+
+ // Print the axis stepper motor positions as Marlin does, as an aid to debugging.
+ // Don't bother with the extruder endpoints, they are zero after any non-extruding move.
+ s.cat(" Count");
+ for (size_t i = 0; i < numAxes; ++i)
+ {
+ s.catf(" %d", reprap.GetMove()->GetEndPoint(i));
+ }
+}
+
+bool GCodes::OpenFileToWrite(GCodeBuffer& gb, const char* directory, const char* fileName)
+{
+ fileBeingWritten = platform->GetFileStore(directory, fileName, true);
+ eofStringCounter = 0;
+ if (fileBeingWritten == NULL)
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Can't open GCode file \"%s\" for writing.\n", fileName);
+ return false;
+ }
+ else
+ {
+ gb.SetWritingFileDirectory(directory);
+ return true;
+ }
+}
+
+void GCodes::WriteHTMLToFile(GCodeBuffer& gb, char b)
+{
+ if (fileBeingWritten == NULL)
+ {
+ platform->Message(GENERIC_MESSAGE, "Attempt to write to a null file.\n");
+ return;
+ }
+
+ if (eofStringCounter != 0 && b != eofString[eofStringCounter])
+ {
+ fileBeingWritten->Write(eofString);
+ eofStringCounter = 0;
+ }
+
+ if (b == eofString[eofStringCounter])
+ {
+ eofStringCounter++;
+ if (eofStringCounter >= eofStringLength)
+ {
+ fileBeingWritten->Close();
+ fileBeingWritten = NULL;
+ gb.SetWritingFileDirectory(NULL);
+ const char* r = (platform->Emulating() == marlin) ? "Done saving file." : "";
+ HandleReply(gb, false, r);
+ return;
+ }
+ }
+ else
+ {
+ fileBeingWritten->Write(b);
+ }
+}
+
+void GCodes::WriteGCodeToFile(GCodeBuffer& gb)
+{
+ if (fileBeingWritten == NULL)
+ {
+ platform->Message(GENERIC_MESSAGE, "Attempt to write to a null file.\n");
+ return;
+ }
+
+ // End of file?
+ if (gb.Seen('M'))
+ {
+ if (gb.GetIValue() == 29)
+ {
+ fileBeingWritten->Close();
+ fileBeingWritten = NULL;
+ gb.SetWritingFileDirectory(NULL);
+ const char* r = (platform->Emulating() == marlin) ? "Done saving file." : "";
+ HandleReply(gb, false, r);
+ return;
+ }
+ }
+
+ // Resend request?
+ if (gb.Seen('G'))
+ {
+ if (gb.GetIValue() == 998)
+ {
+ if (gb.Seen('P'))
+ {
+ scratchString.printf("%d\n", gb.GetIValue());
+ HandleReply(gb, false, scratchString.Pointer());
+ return;
+ }
+ }
+ }
+
+ fileBeingWritten->Write(gb.Buffer());
+ fileBeingWritten->Write('\n');
+ HandleReply(gb, false, "");
+}
+
+// Set up a file to print, but don't print it yet.
+void GCodes::QueueFileToPrint(const char* fileName)
+{
+ FileStore * const f = platform->GetFileStore(platform->GetGCodeDir(), fileName, false);
+ if (f != nullptr)
+ {
+ // Cancel current print if there is any
+ if (!reprap.GetPrintMonitor()->IsPrinting())
+ {
+ CancelPrint();
+ }
+
+ fileGCode->SetToolNumberAdjust(0); // clear tool number adjustment
+
+ // Reset all extruder positions when starting a new print
+ for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
+ {
+ lastRawExtruderPosition[extruder] = 0.0;
+ rawExtruderTotalByDrive[extruder] = 0.0;
+ }
+ rawExtruderTotal = 0.0;
+ reprap.GetMove()->ResetExtruderPositions();
+
+ fileToPrint.Set(f);
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "GCode file \"%s\" not found\n", fileName);
+ }
+}
+
+void GCodes::DeleteFile(const char* fileName)
+{
+ if (!platform->GetMassStorage()->Delete(platform->GetGCodeDir(), fileName))
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Could not delete file \"%s\"\n", fileName);
+ }
+}
+
+// Function to handle dwell delays. Return true for dwell finished, false otherwise.
+bool GCodes::DoDwell(GCodeBuffer& gb)
+{
+ float dwell;
+ if (gb.Seen('S'))
+ {
+ dwell = gb.GetFValue();
+ }
+ else if (gb.Seen('P'))
+ {
+ dwell = 0.001 * (float) gb.GetIValue(); // P values are in milliseconds; we need seconds
+ }
+ else
+ {
+ return true; // No time given - throw it away
+ }
+
+#if SUPPORT_ROLAND
+ // Deal with a Roland configuration
+ if (reprap.GetRoland()->Active())
+ {
+ return reprap.GetRoland()->ProcessDwell(gb.GetLValue());
+ }
+#endif
+
+ // Wait for all the queued moves to stop
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+ if (simulationMode != 0)
+ {
+ simulationTime += dwell;
+ return true;
+ }
+ else
+ {
+ return DoDwellTime(dwell);
+ }
+}
+
+bool GCodes::DoDwellTime(float dwell)
+{
+ // Are we already in the dwell?
+ if (dwellWaiting)
+ {
+ if (platform->Time() - dwellTime >= 0.0)
+ {
+ dwellWaiting = false;
+ reprap.GetMove()->ResumeMoving();
+ return true;
+ }
+ return false;
+ }
+
+ // New dwell - set it up
+ dwellWaiting = true;
+ dwellTime = platform->Time() + dwell;
+ return false;
+}
+
+// Set offset, working and standby temperatures for a tool. I.e. handle a G10.
+bool GCodes::SetOrReportOffsets(GCodeBuffer &gb, StringRef& reply)
+{
+ if (gb.Seen('P'))
+ {
+ int8_t toolNumber = gb.GetIValue();
+ toolNumber += gb.GetToolNumberAdjust();
+ Tool* tool = reprap.GetTool(toolNumber);
+ if (tool == NULL)
+ {
+ reply.printf("Attempt to set/report offsets and temperatures for non-existent tool: %d", toolNumber);
+ return true;
+ }
+
+ // Deal with setting offsets
+ float offset[MAX_AXES];
+ for (size_t i = 0; i < MAX_AXES; ++i)
+ {
+ offset[i] = tool->GetOffset()[i];
+ }
+
+ bool settingOffset = false;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ gb.TryGetFValue(axisLetters[axis], offset[axis], settingOffset);
+ }
+ if (settingOffset)
+ {
+ if (!LockMovement(gb))
+ {
+ return false;
+ }
+ tool->SetOffset(offset);
+ }
+
+ // Deal with setting temperatures
+ bool settingTemps = false;
+ size_t hCount = tool->HeaterCount();
+ float standby[HEATERS];
+ float active[HEATERS];
+ if (hCount > 0)
+ {
+ tool->GetVariables(standby, active);
+ if (gb.Seen('R'))
+ {
+ gb.GetFloatArray(standby, hCount, true);
+ settingTemps = true;
+ }
+ if (gb.Seen('S'))
+ {
+ gb.GetFloatArray(active, hCount, true);
+ settingTemps = true;
+ }
+
+ if (settingTemps && simulationMode == 0)
+ {
+ tool->SetVariables(standby, active);
+ }
+ }
+
+ if (!settingOffset && !settingTemps)
+ {
+ // Print offsets and temperatures
+ reply.printf("Tool %d offsets:", toolNumber);
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ reply.catf(" %c%.2f", axisLetters[axis], offset[axis]);
+ }
+ if (hCount != 0)
+ {
+ reply.cat(", active/standby temperature(s):");
+ for (size_t heater = 0; heater < hCount; heater++)
+ {
+ reply.catf(" %.1f/%.1f", active[heater], standby[heater]);
+ }
+ }
+ }
+ }
+ return true;
+}
+
+void GCodes::ManageTool(GCodeBuffer& gb, StringRef& reply)
+{
+ if (!gb.Seen('P'))
+ {
+ // DC temporary code to allow tool numbers to be adjusted so that we don't need to edit multi-media files generated by slic3r
+ if (gb.Seen('S'))
+ {
+ int adjust = gb.GetIValue();
+ gb.SetToolNumberAdjust(adjust);
+ }
+ return;
+ }
+
+ // Check tool number
+ bool seen = false;
+ const int toolNumber = gb.GetIValue();
+ if (toolNumber < 0)
+ {
+ platform->Message(GENERIC_MESSAGE, "Tool number must be positive!\n");
+ return;
+ }
+
+ // Check drives
+ long drives[MaxExtruders]; // There can never be more than we have...
+ size_t dCount = numExtruders; // Sets the limit and returns the count
+ if (gb.Seen('D'))
+ {
+ gb.GetLongArray(drives, dCount);
+ seen = true;
+ }
+ else
+ {
+ dCount = 0;
+ }
+
+ // Check heaters
+ long heaters[HEATERS];
+ size_t hCount = HEATERS;
+ if (gb.Seen('H'))
+ {
+ gb.GetLongArray(heaters, hCount);
+ seen = true;
+ }
+ else
+ {
+ hCount = 0;
+ }
+
+ // Check X axis mapping
+ uint32_t xMap;
+ if (gb.Seen('X'))
+ {
+ long xMapping[MAX_AXES];
+ size_t xCount = numAxes;
+ gb.GetLongArray(xMapping, xCount);
+ xMap = LongArrayToBitMap(xMapping, xCount) & ((1u << numAxes) - 1);
+ seen = true;
+ }
+ else
+ {
+ xMap = 1; // by default map X axis straight through
+ }
+
+ // Check for fan mapping
+ uint32_t fanMap;
+ if (gb.Seen('F'))
+ {
+ long fanMapping[NUM_FANS];
+ size_t fanCount = NUM_FANS;
+ gb.GetLongArray(fanMapping, fanCount);
+ fanMap = LongArrayToBitMap(fanMapping, fanCount) & ((1u << NUM_FANS) - 1);
+ seen = true;
+ }
+ else
+ {
+ fanMap = 1; // by default map fan 0 to fan 0
+ }
+
+ if (seen)
+ {
+ // Add or delete tool, so start by deleting the old one with this number, if any
+ reprap.DeleteTool(reprap.GetTool(toolNumber));
+
+ // M563 P# D-1 H-1 removes an existing tool
+ if (dCount == 1 && hCount == 1 && drives[0] == -1 && heaters[0] == -1)
+ {
+ // nothing more to do
+ }
+ else
+ {
+ Tool* tool = Tool::Create(toolNumber, drives, dCount, heaters, hCount, xMap, fanMap);
+ if (tool != nullptr)
+ {
+ reprap.AddTool(tool);
+ }
+ }
+ }
+ else
+ {
+ reprap.PrintTool(toolNumber, reply);
+ }
+}
+
+// Does what it says.
+void GCodes::DisableDrives()
+{
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ platform->DisableDrive(drive);
+ }
+ SetAllAxesNotHomed();
+}
+
+// Does what it says.
+void GCodes::SetEthernetAddress(GCodeBuffer& gb, int mCode)
+{
+ byte eth[4];
+ const char* ipString = gb.GetString();
+ uint8_t sp = 0;
+ uint8_t spp = 0;
+ uint8_t ipp = 0;
+ while (ipString[sp])
+ {
+ if (ipString[sp] == '.')
+ {
+ eth[ipp] = atoi(&ipString[spp]);
+ ipp++;
+ if (ipp > 3)
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Dud IP address: %s\n", gb.Buffer());
+ return;
+ }
+ sp++;
+ spp = sp;
+ }
+ else
+ {
+ sp++;
+ }
+ }
+ eth[ipp] = atoi(&ipString[spp]);
+ if (ipp == 3)
+ {
+ switch (mCode)
+ {
+ case 552:
+ platform->SetIPAddress(eth);
+ break;
+ case 553:
+ platform->SetNetMask(eth);
+ break;
+ case 554:
+ platform->SetGateWay(eth);
+ break;
+
+ default:
+ platform->Message(GENERIC_MESSAGE, "Setting ether parameter - dud code.\n");
+ }
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Dud IP address: %s\n", gb.Buffer());
+ }
+}
+
+void GCodes::SetMACAddress(GCodeBuffer& gb)
+{
+ uint8_t mac[6];
+ const char* ipString = gb.GetString();
+ uint8_t sp = 0;
+ uint8_t spp = 0;
+ uint8_t ipp = 0;
+ while (ipString[sp])
+ {
+ if (ipString[sp] == ':')
+ {
+ mac[ipp] = strtoul(&ipString[spp], NULL, 16);
+ ipp++;
+ if (ipp > 5)
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Dud MAC address: %s\n", gb.Buffer());
+ return;
+ }
+ sp++;
+ spp = sp;
+ }
+ else
+ {
+ sp++;
+ }
+ }
+ mac[ipp] = strtoul(&ipString[spp], NULL, 16);
+ if (ipp == 5)
+ {
+ platform->SetMACAddress(mac);
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Dud MAC address: %s\n", gb.Buffer());
+ }
+}
+
+bool GCodes::ChangeMicrostepping(size_t drive, int microsteps, int mode) const
+{
+ bool dummy;
+ unsigned int oldSteps = platform->GetMicrostepping(drive, dummy);
+ bool success = platform->SetMicrostepping(drive, microsteps, mode);
+ if (success && mode <= 1) // modes higher than 1 are used for special functions
+ {
+ // We changed the microstepping, so adjust the steps/mm to compensate
+ float stepsPerMm = platform->DriveStepsPerUnit(drive);
+ if (stepsPerMm > 0)
+ {
+ platform->SetDriveStepsPerUnit(drive, stepsPerMm * (float)microsteps / (float)oldSteps);
+ }
+ }
+ return success;
+}
+
+// Set the speeds of fans mapped for the current tool to lastDefaultFanSpeed
+void GCodes::SetMappedFanSpeed()
+{
+ if (reprap.GetCurrentTool() == nullptr)
+ {
+ platform->SetFanValue(0, lastDefaultFanSpeed);
+ }
+ else
+ {
+ const uint32_t fanMap = reprap.GetCurrentTool()->GetFanMapping();
+ for (size_t i = 0; i < NUM_FANS; ++i)
+ {
+ if ((fanMap & (1u << i)) != 0)
+ {
+ platform->SetFanValue(i, lastDefaultFanSpeed);
+ }
+ }
+ }
+}
+
+// Handle sending a reply back to the appropriate interface(s).
+// Note that 'reply' may be empty. If it isn't, then we need to append newline when sending it.
+// Also, gb may be null if we were executing a trigger macro.
+void GCodes::HandleReply(GCodeBuffer& gb, bool error, const char* reply)
+{
+ // Don't report "ok" responses if a (macro) file is being processed
+ // Also check that this response was triggered by a gcode
+ if ((gb.MachineState().doingFileMacro || &gb == fileGCode) && reply[0] == 0)
+ {
+ return;
+ }
+
+ // Second UART device, e.g. dc42's PanelDue. Do NOT use emulation for this one!
+ if (&gb == auxGCode)
+ {
+ platform->AppendAuxReply(reply);
+ return;
+ }
+
+ 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 char* emulationType = 0;
+
+ switch (c)
+ {
+ case me:
+ case reprapFirmware:
+ if (error)
+ {
+ platform->Message(type, "Error: ");
+ }
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ return;
+
+ case marlin:
+ // We don't need to handle M20 here because we always allocate an output buffer for that one
+ if (gb.Seen('M') && gb.GetIValue() == 28)
+ {
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ return;
+ }
+
+ if ((gb.Seen('M') && gb.GetIValue() == 105) || (gb.Seen('M') && gb.GetIValue() == 998))
+ {
+ platform->Message(type, response);
+ platform->Message(type, " ");
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ return;
+ }
+
+ if (reply[0] != 0 && !gb.IsDoingFileMacro())
+ {
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ }
+ else if (reply[0] != 0)
+ {
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ }
+ else
+ {
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ }
+ return;
+
+ case teacup:
+ emulationType = "teacup";
+ break;
+ case sprinter:
+ emulationType = "sprinter";
+ break;
+ case repetier:
+ emulationType = "repetier";
+ break;
+ default:
+ emulationType = "unknown";
+ }
+
+ if (emulationType != 0)
+ {
+ platform->MessageF(type, "Emulation of %s is not yet supported.\n", emulationType); // don't send this one to the web as well, it concerns only the USB interface
+ }
+}
+
+void GCodes::HandleReply(GCodeBuffer& gb, bool error, OutputBuffer *reply)
+{
+ // Although unlikely, it's possible that we get a nullptr reply. Don't proceed if this is the case
+ if (reply == nullptr)
+ {
+ return;
+ }
+
+ // Second UART device, e.g. dc42's PanelDue. Do NOT use emulation for this one!
+ if (&gb == auxGCode)
+ {
+ platform->AppendAuxReply(reply);
+ return;
+ }
+
+ 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 char* emulationType = nullptr;
+
+ switch (c)
+ {
+ case me:
+ case reprapFirmware:
+ if (error)
+ {
+ platform->Message(type, "Error: ");
+ }
+ platform->Message(type, reply);
+ return;
+
+ case marlin:
+ if (gb.Seen('M') && gb.GetIValue() == 20)
+ {
+ platform->Message(type, "Begin file list\n");
+ platform->Message(type, reply);
+ platform->Message(type, "End file list\n");
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ return;
+ }
+
+ if (gb.Seen('M') && gb.GetIValue() == 28)
+ {
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ platform->Message(type, reply);
+ return;
+ }
+
+ if ((gb.Seen('M') && gb.GetIValue() == 105) || (gb.Seen('M') && gb.GetIValue() == 998))
+ {
+ platform->Message(type, response);
+ platform->Message(type, " ");
+ platform->Message(type, reply);
+ return;
+ }
+
+ if (reply->Length() != 0 && !gb.IsDoingFileMacro())
+ {
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ }
+ else if (reply->Length() != 0)
+ {
+ platform->Message(type, reply);
+ }
+ else
+ {
+ OutputBuffer::ReleaseAll(reply);
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ }
+ return;
+
+ case teacup:
+ emulationType = "teacup";
+ break;
+ case sprinter:
+ emulationType = "sprinter";
+ break;
+ case repetier:
+ emulationType = "repetier";
+ break;
+ default:
+ emulationType = "unknown";
+ }
+
+ // If we get here then we didn't handle the message, so release the buffer(s)
+ OutputBuffer::ReleaseAll(reply);
+ if (emulationType != 0)
+ {
+ platform->MessageF(type, "Emulation of %s is not yet supported.\n", emulationType); // don't send this one to the web as well, it concerns only the USB interface
+ }
+}
+
+// Set PID parameters (M301 or M304 command). 'heater' is the default heater number to use.
+void GCodes::SetPidParameters(GCodeBuffer& gb, int heater, StringRef& reply)
+{
+ if (gb.Seen('H'))
+ {
+ heater = gb.GetIValue();
+ }
+
+ if (heater >= 0 && heater < HEATERS)
+ {
+ PidParameters pp = platform->GetPidParameters(heater);
+ bool seen = false;
+ gb.TryGetFValue('P', pp.kP, seen);
+ gb.TryGetFValue('I', pp.kI, seen);
+ gb.TryGetFValue('D', pp.kD, seen);
+ gb.TryGetFValue('T', pp.kT, seen);
+ gb.TryGetFValue('S', pp.kS, seen);
+
+ if (seen)
+ {
+ platform->SetPidParameters(heater, pp);
+ reprap.GetHeat()->UseModel(heater, false);
+ }
+ else
+ {
+ reply.printf("Heater %d P:%.2f I:%.3f D:%.2f T:%.2f S:%.2f", heater, pp.kP, pp.kI, pp.kD, pp.kT, pp.kS);
+ }
+ }
+}
+
+void GCodes::SetHeaterParameters(GCodeBuffer& gb, StringRef& reply)
+{
+ if (gb.Seen('P'))
+ {
+ int heater = gb.GetIValue();
+ if (heater >= 0 && heater < HEATERS)
+ {
+ Thermistor& th = platform->GetThermistor(heater);
+ bool seen = false;
+
+ // We must set the 25C resistance and beta together in order to calculate Rinf. Check for these first.
+ float r25 = th.GetR25();
+ float beta = th.GetBeta();
+ float shC = th.GetShc();
+ float seriesR = th.GetSeriesR();
+
+ gb.TryGetFValue('T', r25, seen);
+ gb.TryGetFValue('B', beta, seen);
+ gb.TryGetFValue('C', shC, seen);
+ gb.TryGetFValue('R', seriesR, seen);
+ if (seen)
+ {
+ th.SetParameters(r25, beta, shC, seriesR);
+ }
+
+ if (gb.Seen('L'))
+ {
+ th.SetLowOffset((int8_t)constrain<int>(gb.GetIValue(), -100, 100));
+ seen = true;
+ }
+ if (gb.Seen('H'))
+ {
+ th.SetHighOffset((int8_t)constrain<int>(gb.GetIValue(), -100, 100));
+ seen = true;
+ }
+
+ if (gb.Seen('X'))
+ {
+ int thermistor = gb.GetIValue();
+ if ( (0 <= thermistor && thermistor < HEATERS)
+ || ((int)FirstThermocoupleChannel <= thermistor && thermistor < (int)(FirstThermocoupleChannel + MaxSpiTempSensors))
+ || ((int)FirstRtdChannel <= thermistor && thermistor < (int)(FirstRtdChannel + MaxSpiTempSensors))
+ )
+ {
+ platform->SetThermistorNumber(heater, thermistor);
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Thermistor number %d is out of range\n", thermistor);
+ }
+ seen = true;
+ }
+
+ if (!seen)
+ {
+ reply.printf("T:%.1f B:%.1f C:%.2e R:%.1f L:%d H:%d X:%d",
+ th.GetR25(), th.GetBeta(), th.GetShc(), th.GetSeriesR(),
+ th.GetLowOffset(), th.GetHighOffset(), platform->GetThermistorNumber(heater));
+ }
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Heater number %d is out of range\n", heater);
+ }
+ }
+}
+
+void GCodes::SetToolHeaters(Tool *tool, float temperature)
+{
+ if (tool == NULL)
+ {
+ platform->Message(GENERIC_MESSAGE, "Setting temperature: no tool selected.\n");
+ return;
+ }
+
+ float standby[HEATERS];
+ float active[HEATERS];
+ tool->GetVariables(standby, active);
+ for (size_t h = 0; h < tool->HeaterCount(); h++)
+ {
+ active[h] = temperature;
+ }
+ tool->SetVariables(standby, active);
+}
+
+// Begin the tool change sequence
+void GCodes::StartToolChange(GCodeBuffer& gb, bool inM109)
+{
+ gb.SetState((inM109) ? GCodeState:: m109ToolChange1 : GCodeState::toolChange1);
+ const Tool * const oldTool = reprap.GetCurrentTool();
+ if (oldTool != nullptr && AllAxesAreHomed())
+ {
+ scratchString.printf("tfree%d.g", oldTool->Number());
+ DoFileMacro(gb, scratchString.Pointer(), false);
+ }
+}
+
+// Retract or un-retract filament, returning true if movement has been queued, false if this needs to be called again
+bool GCodes::RetractFilament(bool retract)
+{
+ if (retractLength != 0.0 || retractHop != 0.0 || (!retract && retractExtra != 0.0))
+ {
+ const Tool *tool = reprap.GetCurrentTool();
+ if (tool != nullptr)
+ {
+ size_t nDrives = tool->DriveCount();
+ if (nDrives != 0)
+ {
+ if (segmentsLeft != 0)
+ {
+ return false;
+ }
+
+ reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes());
+ for (size_t i = numAxes; i < DRIVES; ++i)
+ {
+ moveBuffer.coords[i] = 0.0;
+ }
+ // Set the feed rate. If there is any Z hop then we need to pass the Z speed, else we pass the extrusion speed.
+ const float speedToUse = (retract) ? retractSpeed : unRetractSpeed;
+ moveBuffer.feedRate = (retractHop == 0.0)
+ ? speedToUse * secondsToMinutes
+ : speedToUse * secondsToMinutes * retractHop/retractLength;
+ moveBuffer.coords[Z_AXIS] += (retract) ? retractHop : -retractHop;
+ const float lengthToUse = (retract) ? -retractLength : retractLength + retractExtra;
+ for (size_t i = 0; i < nDrives; ++i)
+ {
+ moveBuffer.coords[E0_AXIS + tool->Drive(i)] = lengthToUse;
+ }
+
+ moveBuffer.isFirmwareRetraction = true;
+ moveBuffer.usePressureAdvance = false;
+ moveBuffer.filePos = filePos;
+ moveBuffer.xAxes = reprap.GetCurrentXAxes();
+ segmentsLeft = 1;
+ }
+ }
+ }
+ return true;
+}
+
+// Return the amount of filament extruded
+float GCodes::GetRawExtruderPosition(size_t extruder) const
+{
+ return (extruder < numExtruders) ? lastRawExtruderPosition[extruder] : 0.0;
+}
+
+float GCodes::GetRawExtruderTotalByDrive(size_t extruder) const
+{
+ return (extruder < numExtruders) ? rawExtruderTotalByDrive[extruder] : 0.0;
+}
+
+// Cancel the current SD card print.
+// This is called from Pid.cpp when there is a heater fault, and from elsewhere in this module.
+void GCodes::CancelPrint()
+{
+ segmentsLeft = 0;
+ isPaused = false;
+
+ fileGCode->Init();
+ FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState;
+ if (fileBeingPrinted.IsLive())
+ {
+ fileBeingPrinted.Close();
+ }
+
+ reprap.GetPrintMonitor()->StoppedPrint();
+}
+
+// Return true if all the heaters for the specified tool are at their set temperatures
+bool GCodes::ToolHeatersAtSetTemperatures(const Tool *tool, bool waitWhenCooling) const
+{
+ if (tool != NULL)
+ {
+ for (size_t i = 0; i < tool->HeaterCount(); ++i)
+ {
+ if (!reprap.GetHeat()->HeaterAtSetTemperature(tool->Heater(i), waitWhenCooling))
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+// Set the current position
+void GCodes::SetPositions(float positionNow[DRIVES])
+{
+ // Transform the position so that e.g. if the user does G92 Z0,
+ // the position we report (which gets inverse-transformed) really is Z=0 afterwards
+ reprap.GetMove()->Transform(positionNow, reprap.GetCurrentXAxes());
+ reprap.GetMove()->SetLiveCoordinates(positionNow);
+ reprap.GetMove()->SetPositions(positionNow);
+}
+
+bool GCodes::IsPaused() const
+{
+ return isPaused && !IsPausing() && !IsResuming();
+}
+
+bool GCodes::IsPausing() const
+{
+ const GCodeState topState = fileGCode->OriginalMachineState().state;
+ return topState == GCodeState::pausing1 || topState == GCodeState::pausing2;
+}
+
+bool GCodes::IsResuming() const
+{
+ const GCodeState topState = fileGCode->OriginalMachineState().state;
+ return topState == GCodeState::resuming1 || topState == GCodeState::resuming2 || topState == GCodeState::resuming3;
+}
+
+bool GCodes::IsRunning() const
+{
+ return !IsPaused() && !IsPausing() && !IsResuming();
+}
+
+const char *GCodes::TranslateEndStopResult(EndStopHit es)
+{
+ switch (es)
+ {
+ case EndStopHit::lowHit:
+ return "at min stop";
+
+ case EndStopHit::highHit:
+ return "at max stop";
+
+ case EndStopHit::lowNear:
+ return "near min stop";
+
+ case EndStopHit::noStop:
+ default:
+ return "not stopped";
+ }
+}
+
+// Append a list of trigger endstops to a message
+void GCodes::ListTriggers(StringRef reply, TriggerMask mask)
+{
+ if (mask == 0)
+ {
+ reply.cat("(none)");
+ }
+ else
+ {
+ bool printed = false;
+ for (unsigned int i = 0; i < DRIVES; ++i)
+ {
+ if ((mask & (1u << i)) != 0)
+ {
+ if (printed)
+ {
+ reply.cat(' ');
+ }
+ if (i < numAxes)
+ {
+ reply.cat(axisLetters[i]);
+ }
+ else
+ {
+ reply.catf("E%d", i - numAxes);
+ }
+ printed = true;
+ }
+ }
+ }
+}
+
+// M38 (SHA1 hash of a file) implementation:
+bool GCodes::StartHash(const char* filename)
+{
+ // Get a FileStore object
+ fileBeingHashed = platform->GetFileStore(FS_PREFIX, filename, false);
+ if (fileBeingHashed == nullptr)
+ {
+ return false;
+ }
+
+ // Start hashing
+ SHA1Reset(&hash);
+ return true;
+}
+
+bool GCodes::AdvanceHash(StringRef &reply)
+{
+ // Read and process some more data from the file
+ uint32_t buf32[(FILE_BUFFER_SIZE + 3) / 4];
+ char *buffer = reinterpret_cast<char *>(buf32);
+
+ int bytesRead = fileBeingHashed->Read(buffer, FILE_BUFFER_SIZE);
+ if (bytesRead != -1)
+ {
+ SHA1Input(&hash, reinterpret_cast<const uint8_t *>(buffer), bytesRead);
+
+ if (bytesRead != FILE_BUFFER_SIZE)
+ {
+ // Calculate and report the final result
+ SHA1Result(&hash);
+ for(size_t i = 0; i < 5; i++)
+ {
+ reply.catf("%x", hash.Message_Digest[i]);
+ }
+
+ // Clean up again
+ fileBeingHashed->Close();
+ fileBeingHashed = nullptr;
+ return true;
+ }
+ return false;
+ }
+
+ // Something went wrong, we cannot read any more from the file
+ fileBeingHashed->Close();
+ fileBeingHashed = nullptr;
+ return true;
+}
+
+bool GCodes::AllAxesAreHomed() const
+{
+ const uint32_t allAxes = (1u << numAxes) - 1;
+ return (axesHomed & allAxes) == allAxes;
+}
+
+void GCodes::SetAllAxesNotHomed()
+{
+ axesHomed = 0;
+}
+
+// Resource locking/unlocking
+
+// Lock the resource, returning true if success.
+// Locking the same resource more than once only locks it once, there is no lock count held.
+bool GCodes::LockResource(const GCodeBuffer& gb, Resource r)
+{
+ if (resourceOwners[r] == &gb)
+ {
+ return true;
+ }
+ if (resourceOwners[r] == nullptr)
+ {
+ resourceOwners[r] = &gb;
+ gb.MachineState().lockedResources |= (1u << r);
+ return true;
+ }
+ return false;
+}
+
+bool GCodes::LockHeater(const GCodeBuffer& gb, int heater)
+{
+ if (heater >= 0 && heater < HEATERS)
+ {
+ return LockResource(gb, HeaterResourceBase + heater);
+ }
+ return true;
+}
+
+bool GCodes::LockFan(const GCodeBuffer& gb, int fan)
+{
+ if (fan >= 0 && fan < (int)NUM_FANS)
+ {
+ return LockResource(gb, FanResourceBase + fan);
+ }
+ return true;
+}
+
+// Lock the unshareable parts of the file system
+bool GCodes::LockFileSystem(const GCodeBuffer &gb)
+{
+ return LockResource(gb, FileSystemResource);
+}
+
+// Lock movement
+bool GCodes::LockMovement(const GCodeBuffer& gb)
+{
+ return LockResource(gb, MoveResource);
+}
+
+// Release all locks, except those that were owned when the current macro was started
+void GCodes::UnlockAll(const GCodeBuffer& gb)
+{
+ const GCodeMachineState * const mc = gb.MachineState().previous;
+ const uint32_t resourcesToKeep = (mc == nullptr) ? 0 : mc->lockedResources;
+ for (size_t i = 0; i < NumResources; ++i)
+ {
+ if (resourceOwners[i] == &gb && ((1u << i) & resourcesToKeep) == 0)
+ {
+ resourceOwners[i] = nullptr;
+ gb.MachineState().lockedResources &= ~(1u << i);
+ }
+ }
+}
+
+// Convert an array of longs to a bit map
+/*static*/ uint32_t GCodes::LongArrayToBitMap(const long *arr, size_t numEntries)
+{
+ uint32_t res = 0;
+ for (size_t i = 0; i < numEntries; ++i)
+ {
+ const long f = arr[i];
+ if (f >= 0 && f < 32)
+ {
+ res |= 1u << (unsigned int)f;
+ }
+ }
+ return res;
+}
+
+// End