diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Configuration.h | 7 | ||||
-rw-r--r-- | src/GCodes/GCodeBuffer.h | 7 | ||||
-rw-r--r-- | src/GCodes/GCodeMachineState.cpp | 2 | ||||
-rw-r--r-- | src/GCodes/GCodeMachineState.h | 20 | ||||
-rw-r--r-- | src/GCodes/GCodes.cpp | 6605 | ||||
-rw-r--r-- | src/GCodes/GCodes.h | 28 | ||||
-rw-r--r-- | src/GCodes/GCodes1.cpp | 3286 | ||||
-rw-r--r-- | src/GCodes/GCodes2.cpp | 3481 | ||||
-rw-r--r-- | src/Heating/Heat.h | 12 | ||||
-rw-r--r-- | src/Movement/DDA.cpp | 4 | ||||
-rw-r--r-- | src/Movement/Grid.cpp | 222 | ||||
-rw-r--r-- | src/Movement/Grid.h | 63 | ||||
-rw-r--r-- | src/Movement/Move.cpp | 143 | ||||
-rw-r--r-- | src/Movement/Move.h | 15 | ||||
-rw-r--r-- | src/Pins.h | 6 | ||||
-rw-r--r-- | src/Platform.cpp | 154 | ||||
-rw-r--r-- | src/Platform.h | 92 | ||||
-rw-r--r-- | src/RADDS/Network.cpp | 8 | ||||
-rw-r--r-- | src/RADDS/Network.h | 34 | ||||
-rw-r--r-- | src/RADDS/Pins_radds.h | 248 | ||||
-rw-r--r-- | src/RADDS/Webserver.h | 38 | ||||
-rw-r--r-- | src/Reprap.cpp | 44 | ||||
-rw-r--r-- | src/Reprap.h | 4 | ||||
-rw-r--r-- | src/Storage/FileStore.cpp | 34 | ||||
-rw-r--r-- | src/Storage/FileStore.h | 1 |
25 files changed, 7618 insertions, 6940 deletions
diff --git a/src/Configuration.h b/src/Configuration.h index 62d7c58c..924e1634 100644 --- a/src/Configuration.h +++ b/src/Configuration.h @@ -28,11 +28,11 @@ Licence: GPL // Firmware name is now defined in the Pins file #ifndef VERSION -# define VERSION "1.17dev6" +# define VERSION "1.17dev7" #endif #ifndef DATE -# define DATE "2016-11-22" +# define DATE "2016-12-07" #endif #define AUTHORS "reprappro, dc42, zpl, t3p3, dnewman" @@ -105,7 +105,7 @@ const float DefaultMaxHeatingFaultTime = 5.0; // How many seconds we allow a he const float AllowedTemperatureDerivativeNoise = 0.25; // How much fluctuation in the averaged temperature derivative we allow const float MaxAmbientTemperature = 45.0; // We expect heaters to cool to this temperature or lower when switched off const float NormalAmbientTemperature = 25.0; // The ambient temperature we assume - allow for the printer heating its surroundings a little -const float DefaultMaxTempExcursion = 10.0; // How much error we tolerate when maintaining temperature before deciding that a heater fault has occurred +const float DefaultMaxTempExcursion = 15.0; // How much error we tolerate when maintaining temperature before deciding that a heater fault has occurred const float MinimumConnectedTemperature = -5.0; // Temperatures below this we treat as a disconnected thermistor static_assert(DefaultMaxTempExcursion > TEMPERATURE_CLOSE_ENOUGH, "DefaultMaxTempExcursion is too low"); @@ -173,7 +173,6 @@ const size_t OUTPUT_BUFFER_COUNT = 32; // How many OutputBuffer instances do const size_t RESERVED_OUTPUT_BUFFERS = 2; // Number of reserved output buffers after long responses. Must be enough for an HTTP header #endif - // Move system const float DEFAULT_FEEDRATE = 3000.0; // The initial requested feed rate after resetting the printer diff --git a/src/GCodes/GCodeBuffer.h b/src/GCodes/GCodeBuffer.h index aa8723c9..b1540871 100644 --- a/src/GCodes/GCodeBuffer.h +++ b/src/GCodes/GCodeBuffer.h @@ -48,6 +48,8 @@ public: bool IsDoingFileMacro() const; // Return true if this source is executing a file macro GCodeState GetState() const; void SetState(GCodeState newState); + void AdvanceState(); + const char *GetIdentity() const { return identity; } private: @@ -118,4 +120,9 @@ inline void GCodeBuffer::SetState(GCodeState newState) machineState->state = newState; } +inline void GCodeBuffer::AdvanceState() +{ + machineState->state = static_cast<GCodeState>(static_cast<uint8_t>(machineState->state) + 1); +} + #endif /* GCODEBUFFER_H_ */ diff --git a/src/GCodes/GCodeMachineState.cpp b/src/GCodes/GCodeMachineState.cpp index 3cfa1204..98dac7e6 100644 --- a/src/GCodes/GCodeMachineState.cpp +++ b/src/GCodes/GCodeMachineState.cpp @@ -12,7 +12,7 @@ unsigned int GCodeMachineState::numAllocated = 0; // Create a default initialised GCodeMachineState GCodeMachineState::GCodeMachineState() - : previous(nullptr), feedrate(DEFAULT_FEEDRATE), fileState(), lockedResources(0), state(GCodeState::normal), + : previous(nullptr), feedrate(DEFAULT_FEEDRATE/minutesToSeconds), fileState(), lockedResources(0), state(GCodeState::normal), drivesRelative(false), axesRelative(false), doingFileMacro(false) { } diff --git a/src/GCodes/GCodeMachineState.h b/src/GCodes/GCodeMachineState.h index 8424738a..1d05d2fa 100644 --- a/src/GCodes/GCodeMachineState.h +++ b/src/GCodes/GCodeMachineState.h @@ -12,6 +12,9 @@ #include "Configuration.h" #include "Storage/FileData.h" +const float minutesToSeconds = 60.0; +const float secondsToMinutes = 1.0/minutesToSeconds; + // Enumeration to list all the possible states that the Gcode processing machine may be in enum class GCodeState : uint8_t { @@ -21,9 +24,16 @@ enum class GCodeState : uint8_t setBed1, setBed2, setBed3, + + // These next 3 must be contiguous toolChange1, toolChange2, - toolChange3, + toolChangeComplete, + // These next 3 must be contiguous + m109ToolChange1, + m109ToolChange2, + m109ToolChangeComplete, + pausing1, pausing2, resuming1, @@ -50,9 +60,11 @@ public: FileData fileState; uint32_t lockedResources; GCodeState state; - bool drivesRelative; - bool axesRelative; - bool doingFileMacro; + unsigned int + drivesRelative : 1, + axesRelative : 1, + doingFileMacro : 1, + waitWhileCooling : 1; static GCodeMachineState *Allocate() post(!result.IsLive(); result.state == GCodeState::normal); diff --git a/src/GCodes/GCodes.cpp b/src/GCodes/GCodes.cpp deleted file mode 100644 index 37602f94..00000000 --- a/src/GCodes/GCodes.cpp +++ /dev/null @@ -1,6605 +0,0 @@ -/**************************************************************************************************** - - 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* BED_EQUATION_G = "bed.g"; -const char* PAUSE_G = "pause.g"; -const char* RESUME_G = "resume.g"; -const char* CANCEL_G = "cancel.g"; -const char* STOP_G = "stop.g"; -const char* SLEEP_G = "sleep.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 - -const float MinServoPulseWidth = 544.0, MaxServoPulseWidth = 2400.0; -const uint16_t ServoRefreshFrequency = 50; - -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; - - feedRate = DEFAULT_FEEDRATE/minutesToSeconds; - 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; - } -} - -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 (AllMovesAreFinishedAndMoveBufferIsLoaded()) - { - 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) - { - const Tool *oldTool = reprap.GetCurrentTool(); - if (oldTool != NULL) - { - reprap.StandbyTool(oldTool->Number()); - } - } - gb.SetState(GCodeState::toolChange2); - 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) - reprap.SelectTool(newToolNumber); - gb.SetState(GCodeState::toolChange3); - if (reprap.GetTool(newToolNumber) != nullptr && AllAxesAreHomed()) - { - scratchString.printf("tpost%d.g", newToolNumber); - DoFileMacro(gb, scratchString.Pointer(), false); - } - break; - - case GCodeState::toolChange3: - gb.SetState(GCodeState::normal); - break; - - case GCodeState::pausing1: - if (AllMovesAreFinishedAndMoveBufferIsLoaded()) - { - gb.SetState(GCodeState::pausing2); - DoFileMacro(gb, PAUSE_G); - } - break; - - case GCodeState::pausing2: - if (AllMovesAreFinishedAndMoveBufferIsLoaded()) - { - 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 (AllMovesAreFinishedAndMoveBufferIsLoaded()) - { - 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); - } - moveAvailable = true; - } - break; - - case GCodeState::resuming3: - if (AllMovesAreFinishedAndMoveBufferIsLoaded()) - { - 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 - } - 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()->GetBedProbeGrid(); - 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; - moveAvailable = true; - gb.SetState(GCodeState::gridProbing2); - } - else - { - gb.SetState(GCodeState::gridProbing4); - } - } - break; - - case GCodeState::gridProbing2: // ready to probe the current grid probe point - if (AllMovesAreFinishedAndMoveBufferIsLoaded()) - { - // 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; - moveAvailable = true; - gb.SetState(GCodeState::gridProbing3); - } - break; - - case GCodeState::gridProbing3: // ready to lift the probe after probing the current grid probe point - if (AllMovesAreFinishedAndMoveBufferIsLoaded()) - { - if (!zProbeTriggered) - { - reply.copy("Z probe was not triggered during probing move"); - error = true; - gb.SetState(GCodeState::normal); - break; - } - - const float heightError = moveBuffer.coords[Z_AXIS] - platform->ZProbeStopHeight(); - reprap.GetMove()->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; - moveAvailable = true; - gb.SetState(GCodeState::gridProbing4); - } - break; - - case GCodeState::gridProbing4: // ready to compute the next probe point - if (AllMovesAreFinishedAndMoveBufferIsLoaded()) - { - const GridDefinition grid = reprap.GetMove()->GetBedProbeGrid(); - 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 = reprap.GetMove()->SaveHeightMapToFile(heightMapFile, 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(); - } - 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 (AllMovesAreFinishedAndMoveBufferIsLoaded()) - { - 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 = feedRate; - } - else - { - // Pausing a file print via another input source - pauseRestorePoint.feedRate = 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 (moveAvailable) - { - 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, "Move available? %s\n", moveAvailable ? "yes" : "no"); - platform->MessageF(mtype, "Stack records: %u allocated, %u in use\n", GCodeMachineState::GetNumAllocated(), GCodeMachineState::GetNumInUse()); - - for (size_t i = 0; i < ARRAY_SIZE(gcodeSources); ++i) - { - gcodeSources[i]->Diagnostics(mtype); - } -} - -// The wait till everything's done function. If you need the machine to -// be idle before you do something (for example homing an axis, or shutting down) call this -// until it returns true. As a side-effect it loads moveBuffer with the last position and feedrate for you. -bool GCodes::AllMovesAreFinishedAndMoveBufferIsLoaded() -{ - // Last one gone? - if (moveAvailable) - { - return false; - } - - // Wait for all the queued moves to stop so we get the actual last position - if (!reprap.GetMove()->AllMovesAreFinished()) - { - return false; - } - - reprap.GetMove()->ResumeMoving(); - reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes()); - 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 true if we have a legal move (or G92 argument), false if this gcode should be discarded -bool 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)) - { - feedRate = gb.GetFValue() * distanceScale * speedFactor; - } - moveBuffer.feedRate = 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 false; - } - 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 false; - } - - 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 *currentTool = reprap.GetCurrentTool(); - 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 - } - 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 - } - 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 true; -} - -// 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 (moveAvailable) - { - 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)); - moveAvailable = LoadMoveBufferFromGCode(gb, moveBuffer.moveType); - if (moveAvailable) - { - // 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 (!moveAvailable) - { - return false; - } - - m = moveBuffer; - ClearMove(); - return true; -} - -void GCodes::ClearMove() -{ - moveAvailable = false; - 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; - moveAvailable = true; - 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 (moveAvailable) // 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++) - { - 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; - } - cannedMoveType[drive] = CannedMoveType::none; - } - - 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 - { - 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 - 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.param1, params.param2, params.probeSpeed, distance); - } - 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) -{ - 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()->GetBedProbeGrid(); - 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()->SetBedProbeGrid(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) -{ - long sParam = 0; - bool dummy; - gb.TryGetIValue('S', sParam, dummy); - - if (gb.Seen('P')) - { - heightMapFile = gb.GetString(); - } - else - { - heightMapFile = DefaultHeightMapFile; - } - - switch(sParam) - { - case 0: // Probe the bed and save to file - if (!reprap.GetMove()->GetBedProbeGrid().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; - - reprap.GetMove()->ClearGridHeights(); - reprap.GetMove()->SetIdentityTransform(); - gb.SetState(GCodeState::gridProbing1); - return false; - - case 1: // Load height map from file - return reprap.GetMove()->LoadHeightMapFromFile(heightMapFile, reply); - - case 2: // Clear height map - reprap.GetMove()->ClearGridHeights(); - return false; - - default: - reply.copy("Invalid S parameter in G29 command"); - return true; - } - - -} - -// 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); -} - -// 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 (moveAvailable) - { - 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(); - moveAvailable = true; - } - } - } - return true; -} - -// If the code to act on is completed, this returns true, -// otherwise false. It is called repeatedly for a given -// code until it returns true for that code. -bool GCodes::ActOnCode(GCodeBuffer& gb, StringRef& reply) -{ - // Discard empty buffers right away - if (gb.IsEmpty()) - { - return true; - } - - // M-code parameters might contain letters T and G, e.g. in filenames. - // dc42 assumes that G-and T-code parameters never contain the letter M. - // Therefore we must check for an M-code first. - if (gb.Seen('M')) - { - return HandleMcode(gb, reply); - } - // dc42 doesn't think a G-code parameter ever contains letter T, or a T-code ever contains letter G. - // So it doesn't matter in which order we look for them. - if (gb.Seen('G')) - { - return HandleGcode(gb, reply); - } - if (gb.Seen('T')) - { - return HandleTcode(gb, reply); - } - - // An invalid or queued buffer gets discarded - HandleReply(gb, false, ""); - return true; -} - -bool GCodes::HandleGcode(GCodeBuffer& gb, StringRef& reply) -{ - bool result = true; - bool error = false; - - int code = gb.GetIValue(); - if (simulationMode != 0 && code != 0 && code != 1 && code != 4 && code != 10 && code != 20 && code != 21 && code != 90 && code != 91 && code != 92) - { - return true; // we only simulate some gcodes - } - - switch (code) - { - case 0: // There are no rapid moves... - case 1: // Ordinary move - if (!LockMovement(gb)) - { - return false; - } - { - // Check for 'R' parameter here to go back to the coordinates at which the print was paused - // NOTE: restore point 2 (tool change) won't work when changing tools on dual axis machines because of X axis mapping. - // We could possibly fix this by saving the virtual X axis position instead of the physical axis positions. - // However, slicers normally command the tool to the correct place after a tool change, so we don't need this feature anyway. - int rParam = (gb.Seen('R')) ? gb.GetIValue() : 0; - RestorePoint *rp = (rParam == 1) ? &pauseRestorePoint : (rParam == 2) ? &toolChangeRestorePoint : nullptr; - if (rp != nullptr) - { - if (moveAvailable) - { - return false; - } - for (size_t axis = 0; axis < numAxes; ++axis) - { - float offset = gb.Seen(axisLetters[axis]) ? gb.GetFValue() * distanceScale : 0.0; - moveBuffer.coords[axis] = rp->moveCoords[axis] + offset; - } - // For now we don't handle extrusion at the same time - for (size_t drive = numAxes; drive < DRIVES; ++drive) - { - moveBuffer.coords[drive] = 0.0; - } - moveBuffer.feedRate = (gb.Seen(feedrateLetter)) ? gb.GetFValue() : feedRate; - moveBuffer.filePos = noFilePosition; - moveBuffer.usePressureAdvance = false; - moveAvailable = true; - } - else - { - int res = SetUpMove(gb, reply); - if (res == 2) - { - gb.SetState(GCodeState::waitingForMoveToComplete); - } - result = (res != 0); - } - } - break; - - case 4: // Dwell - result = DoDwell(gb); - break; - - case 10: // Set/report offsets and temperatures, or retract - if (gb.Seen('P')) - { - if (!SetOrReportOffsets(gb, reply)) - { - return false; - } - } - else - { - if (!LockMovement(gb)) - { - return false; - } - result = RetractFilament(true); - } - break; - - case 11: // Un-retract - if (!LockMovement(gb)) - { - return false; - } - result = RetractFilament(false); - break; - - case 20: // Inches (which century are we living in, here?) - distanceScale = INCH_TO_MM; - break; - - case 21: // mm - distanceScale = 1.0; - break; - - case 28: // Home - result = DoHome(gb, reply, error); - break; - - case 29: // Grid-based bed probing - if (!LockMovementAndWaitForStandstill(gb)) // do this first to make sure that a new grid isn't being defined - { - return false; - } - error = ProbeGrid(gb, reply); - break; - - case 30: // Z probe/manually set at a position and set that as point P - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - if (reprap.GetMove()->IsDeltaMode() && !AllAxesAreHomed()) - { - reply.copy("Must home a delta printer before bed probing"); - error = true; - } - else - { - result = SetSingleZProbeAtAPosition(gb, reply); - } - break; - - case 31: // Return the probe value, or set probe variables - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - result = SetPrintZProbe(gb, reply); - break; - - case 32: // Probe Z at multiple positions and generate the bed transform - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - - // Try to execute bed.g - if (!DoFileMacro(gb, BED_EQUATION_G, reprap.GetMove()->IsDeltaMode())) - { - // If we get here then we are not on a delta printer and there is no bed.g file - if (GetAxisIsHomed(X_AXIS) && GetAxisIsHomed(Y_AXIS)) - { - gb.SetState(GCodeState::setBed1); // no bed.g file, so use the coordinates specified by M557 - } - else - { - // We can only do bed levelling if X and Y have already been homed - reply.copy("Must home X and Y before bed probing"); - error = true; - } - } - break; - - case 90: // Absolute coordinates - gb.MachineState().axesRelative = false; - break; - - case 91: // Relative coordinates - gb.MachineState().axesRelative = true; // Axis movements (i.e. X, Y and Z) - break; - - case 92: // Set position - result = SetPositions(gb); - break; - - default: - error = true; - reply.printf("invalid G Code: %s", gb.Buffer()); - } - - if (result && gb.GetState() == GCodeState::normal) - { - UnlockAll(gb); - HandleReply(gb, error, reply.Pointer()); - } - return result; -} - -bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) -{ - bool result = true; - bool error = false; - - const int code = gb.GetIValue(); - if (simulationMode != 0 && (code < 20 || code > 37) && code != 0 && code != 1 && code != 82 && code != 83 && code != 105 && code != 111 && code != 112 && code != 122 && code != 408 && code != 999) - { - return true; // we don't yet simulate most M codes - } - - switch (code) - { - case 0: // Stop - case 1: // Sleep - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - { - bool wasPaused = isPaused; // isPaused gets cleared by CancelPrint - CancelPrint(); - if (wasPaused) - { - reply.copy("Print cancelled"); - // If we are cancelling a paused print with M0 and cancel.g exists then run it and do nothing else - if (code == 0) - { - if (DoFileMacro(gb, CANCEL_G, false)) - { - break; - } - } - } - } - - gb.SetState((code == 0) ? GCodeState::stopping : GCodeState::sleeping); - DoFileMacro(gb, (code == 0) ? STOP_G : SLEEP_G, false); - break; - -#if SUPPORT_ROLAND - case 3: // Spin spindle - if (reprap.GetRoland()->Active()) - { - if (gb.Seen('S')) - { - result = reprap.GetRoland()->ProcessSpindle(gb.GetFValue()); - } - } - break; -#endif - - case 18: // Motors off - case 84: - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - { - bool seen = false; - for (size_t axis = 0; axis < numAxes; axis++) - { - if (gb.Seen(axisLetters[axis])) - { - SetAxisNotHomed(axis); - platform->DisableDrive(axis); - seen = true; - } - } - - if (gb.Seen(extrudeLetter)) - { - long int eDrive[MaxExtruders]; - size_t eCount = numExtruders; - gb.GetLongArray(eDrive, eCount); - for (size_t i = 0; i < eCount; i++) - { - seen = true; - if (eDrive[i] < 0 || (size_t)eDrive[i] >= numExtruders) - { - reply.printf("Invalid extruder number specified: %ld", eDrive[i]); - error = true; - break; - } - platform->DisableDrive(numAxes + eDrive[i]); - } - } - - if (gb.Seen('S')) - { - seen = true; - - float idleTimeout = gb.GetFValue(); - if (idleTimeout < 0.0) - { - reply.copy("Idle timeouts cannot be negative!"); - error = true; - } - else - { - reprap.GetMove()->SetIdleTimeout(idleTimeout); - } - } - - if (!seen) - { - DisableDrives(); - } - } - break; - - case 20: // List files on SD card - if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers - { - return false; - } - { - OutputBuffer *fileResponse; - const int sparam = (gb.Seen('S')) ? gb.GetIValue() : 0; - const char* dir = (gb.Seen('P')) ? gb.GetString() : platform->GetGCodeDir(); - - if (sparam == 2) - { - fileResponse = reprap.GetFilesResponse(dir, true); // Send the file list in JSON format - fileResponse->cat('\n'); - } - else - { - if (!OutputBuffer::Allocate(fileResponse)) - { - // Cannot allocate an output buffer, try again later - return false; - } - - // To mimic the behaviour of the official RepRapPro firmware: - // If we are emulating RepRap then we print "GCode files:\n" at the start, otherwise we don't. - // If we are emulating Marlin and the code came via the serial/USB interface, then we don't put quotes around the names and we separate them with newline; - // otherwise we put quotes around them and separate them with comma. - if (platform->Emulating() == me || platform->Emulating() == reprapFirmware) - { - fileResponse->copy("GCode files:\n"); - } - - bool encapsulateList = ((&gb != serialGCode && &gb != telnetGCode) || platform->Emulating() != marlin); - FileInfo fileInfo; - if (platform->GetMassStorage()->FindFirst(dir, fileInfo)) - { - // iterate through all entries and append each file name - do { - if (encapsulateList) - { - fileResponse->catf("%c%s%c%c", FILE_LIST_BRACKET, fileInfo.fileName, FILE_LIST_BRACKET, FILE_LIST_SEPARATOR); - } - else - { - fileResponse->catf("%s\n", fileInfo.fileName); - } - } while (platform->GetMassStorage()->FindNext(fileInfo)); - - if (encapsulateList) - { - // remove the last separator - (*fileResponse)[fileResponse->Length() - 1] = 0; - } - } - else - { - fileResponse->cat("NONE\n"); - } - } - - UnlockAll(gb); - HandleReply(gb, false, fileResponse); - return true; - } - - case 21: // Initialise SD card - if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers - { - return false; - } - { - size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0; - result = platform->GetMassStorage()->Mount(card, reply, true); - } - break; - - case 22: // Release SD card - if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers - { - return false; - } - { - size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0; - result = platform->GetMassStorage()->Unmount(card, reply); - } - break; - - case 23: // Set file to print - case 32: // Select file and start SD print - if (fileGCode->OriginalMachineState().fileState.IsLive()) - { - reply.copy("Cannot set file to print, because a file is already being printed"); - error = true; - break; - } - - if (code == 32 && !LockMovementAndWaitForStandstill(gb)) - { - return false; - } - { - const char* filename = gb.GetUnprecedentedString(); - if (filename != nullptr) - { - QueueFileToPrint(filename); - if (fileToPrint.IsLive()) - { - reprap.GetPrintMonitor()->StartingPrint(filename); - if (platform->Emulating() == marlin && (&gb == serialGCode || &gb == telnetGCode)) - { - reply.copy("File opened\nFile selected"); - } - else - { - // Command came from web interface or PanelDue, or not emulating Marlin, so send a nicer response - reply.printf("File %s selected for printing", filename); - } - - if (code == 32) - { - fileGCode->OriginalMachineState().fileState.MoveFrom(fileToPrint); - reprap.GetPrintMonitor()->StartedPrint(); - } - } - else - { - reply.printf("Failed to open file %s", filename); - } - } - } - break; - - case 24: // Print/resume-printing the selected file - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - - if (isPaused) - { - gb.SetState(GCodeState::resuming1); - DoFileMacro(gb, RESUME_G); - } - else if (!fileToPrint.IsLive()) - { - reply.copy("Cannot print, because no file is selected!"); - error = true; - } - else - { - fileGCode->OriginalMachineState().fileState.MoveFrom(fileToPrint); - reprap.GetPrintMonitor()->StartedPrint(); - } - break; - - case 226: // Gcode Initiated Pause - if (&gb == fileGCode) // ignore M226 if it did't come from within a file being printed - { - DoPause(gb); - } - break; - - case 25: // Pause the print - if (isPaused) - { - reply.copy("Printing is already paused!!"); - error = true; - } - else if (!reprap.GetPrintMonitor()->IsPrinting()) - { - reply.copy("Cannot pause print, because no file is being printed!"); - error = true; - } - else - { - if (!LockMovement(gb)) // lock movement before calling DoPause - { - return false; - } - DoPause(gb); - } - break; - - case 26: // Set SD position - if (gb.Seen('S')) - { - const FilePosition value = gb.GetIValue(); - if (value < 0) - { - reply.copy("SD positions can't be negative!"); - error = true; - } - else if (fileGCode->OriginalMachineState().fileState.IsLive()) - { - if (!fileGCode->OriginalMachineState().fileState.Seek(value)) - { - reply.copy("The specified SD position is invalid!"); - error = true; - } - } - else if (fileToPrint.IsLive()) - { - if (!fileToPrint.Seek(value)) - { - reply.copy("The specified SD position is invalid!"); - error = true; - } - } - else - { - reply.copy("Cannot set SD file position, because no print is in progress!"); - error = true; - } - } - else - { - reply.copy("You must specify the SD position in bytes using the S parameter."); - error = true; - } - break; - - case 27: // Report print status - Deprecated - if (reprap.GetPrintMonitor()->IsPrinting()) - { - // Pronterface keeps sending M27 commands if "Monitor status" is checked, and it specifically expects the following response syntax - FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState; - reply.printf("SD printing byte %lu/%lu", fileBeingPrinted.GetPosition(), fileBeingPrinted.Length()); - } - else - { - reply.copy("Not SD printing."); - } - break; - - case 28: // Write to file - { - const char* str = gb.GetUnprecedentedString(); - if (str != nullptr) - { - bool ok = OpenFileToWrite(gb, platform->GetGCodeDir(), 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 29: // End of file being written; should be intercepted before getting here - reply.copy("GCode end-of-file being interpreted."); - break; - - case 30: // Delete file - { - const char *filename = gb.GetUnprecedentedString(); - if (filename != nullptr) - { - DeleteFile(filename); - } - } - break; - - // For case 32, see case 24 - - case 36: // Return file information - if (!LockFileSystem(gb)) // getting file info takes several calls and isn't reentrant - { - return false; - } - { - const char* filename = gb.GetUnprecedentedString(true); // get filename, or nullptr if none provided - OutputBuffer *fileInfoResponse; - result = reprap.GetPrintMonitor()->GetFileInfoResponse(filename, fileInfoResponse); - if (result) - { - fileInfoResponse->cat('\n'); - UnlockAll(gb); - HandleReply(gb, false, fileInfoResponse); - return true; - } - } - break; - - case 37: // Simulation mode on/off - if (gb.Seen('S')) - { - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - - bool wasSimulating = (simulationMode != 0); - simulationMode = (uint8_t)gb.GetIValue(); - reprap.GetMove()->Simulate(simulationMode); - - if (simulationMode != 0) - { - simulationTime = 0.0; - if (!wasSimulating) - { - // Starting a new simulation, so save the current position - reprap.GetMove()->GetCurrentUserPosition(simulationRestorePoint.moveCoords, 0, reprap.GetCurrentXAxes()); - simulationRestorePoint.feedRate = feedRate; - } - } - else if (wasSimulating) - { - // Ending a simulation, so restore the position - SetPositions(simulationRestorePoint.moveCoords); - for (size_t i = 0; i < DRIVES; ++i) - { - moveBuffer.coords[i] = simulationRestorePoint.moveCoords[i]; - } - feedRate = simulationRestorePoint.feedRate; - } - } - else - { - reply.printf("Simulation mode: %s, move time: %.1f sec, other time: %.1f sec", - (simulationMode != 0) ? "on" : "off", reprap.GetMove()->GetSimulationTime(), simulationTime); - } - break; - - case 38: // Report SHA1 of file - if (!LockFileSystem(gb)) // getting file hash takes several calls and isn't reentrant - { - return false; - } - if (fileBeingHashed == nullptr) - { - // See if we can open the file and start hashing - const char* filename = gb.GetUnprecedentedString(true); - if (StartHash(filename)) - { - // Hashing is now in progress... - result = false; - } - else - { - error = true; - reply.printf("Cannot open file: %s", filename); - } - } - else - { - // This can take some time. All the actual heavy lifting is in dedicated methods - result = AdvanceHash(reply); - } - break; - - case 42: // Turn an output pin on or off - if (gb.Seen('P')) - { - const int logicalPin = gb.GetIValue(); - Pin pin; - bool invert; - if (platform->GetFirmwarePin(logicalPin, PinAccess::pwm, pin, invert)) - { - if (gb.Seen('S')) - { - float val = gb.GetFValue(); - if (val > 1.0) - { - val /= 255.0; - } - val = constrain<float>(val, 0.0, 1.0); - if (invert) - { - val = 1.0 - val; - } - Platform::WriteAnalog(pin, val, DefaultPinWritePwmFreq); - } - // Ignore the command if no S parameter provided - } - else - { - reply.printf("Logical pin %d is not available for writing", logicalPin); - error = true; - } - } - break; - - case 80: // ATX power on - platform->SetAtxPower(true); - break; - - case 81: // ATX power off - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - platform->SetAtxPower(false); - break; - - case 82: // Use absolute extruder positioning - if (gb.MachineState().drivesRelative) // don't reset the absolute extruder position if it was already absolute - { - for (size_t extruder = 0; extruder < MaxExtruders; extruder++) - { - lastRawExtruderPosition[extruder] = 0.0; - } - gb.MachineState().drivesRelative = false; - } - break; - - case 83: // Use relative extruder positioning - if (!gb.MachineState().drivesRelative) // don't reset the absolute extruder position if it was already relative - { - for (size_t extruder = 0; extruder < MaxExtruders; extruder++) - { - lastRawExtruderPosition[extruder] = 0.0; - } - gb.MachineState().drivesRelative = true; - } - break; - - // For case 84, see case 18 - - case 85: // Set inactive time - break; - - case 92: // Set/report steps/mm for some axes - { - // Save the current positions as we may need them later - float positionNow[DRIVES]; - Move *move = reprap.GetMove(); - move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes()); - - bool seen = false; - for (size_t axis = 0; axis < numAxes; axis++) - { - if (gb.Seen(axisLetters[axis])) - { - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - platform->SetDriveStepsPerUnit(axis, gb.GetFValue()); - seen = true; - } - } - - if (gb.Seen(extrudeLetter)) - { - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - seen = true; - float eVals[MaxExtruders]; - size_t eCount = numExtruders; - gb.GetFloatArray(eVals, eCount, true); - - // The user may not have as many extruders as we allow for, so just set the ones for which a value is provided - for (size_t e = 0; e < eCount; e++) - { - platform->SetDriveStepsPerUnit(numAxes + e, eVals[e]); - } - } - - if (seen) - { - // On a delta, if we change the drive steps/mm then we need to recalculate the motor positions - SetPositions(positionNow); - } - else - { - reply.copy("Steps/mm: "); - for (size_t axis = 0; axis < numAxes; ++axis) - { - reply.catf("%c: %.3f, ", axisLetters[axis], platform->DriveStepsPerUnit(axis)); - } - reply.catf("E:"); - char sep = ' '; - for (size_t extruder = 0; extruder < numExtruders; extruder++) - { - reply.catf("%c%.3f", sep, platform->DriveStepsPerUnit(extruder + numAxes)); - sep = ':'; - } - } - } - break; - - case 98: // Call Macro/Subprogram - if (gb.Seen('P')) - { - DoFileMacro(gb, gb.GetString()); - } - break; - - case 99: // Return from Macro/Subprogram - FileMacroCyclesReturn(gb); - break; - - case 104: // Deprecated. This sets the active temperature of every heater of the active tool - if (gb.Seen('S')) - { - float temperature = gb.GetFValue(); - Tool* tool; - if (gb.Seen('T')) - { - int toolNumber = gb.GetIValue(); - toolNumber += gb.GetToolNumberAdjust(); - tool = reprap.GetTool(toolNumber); - } - else - { - tool = reprap.GetCurrentTool(); - // If no tool is selected, and there is only one tool, set the active temperature for that one - if (tool == nullptr) - { - tool = reprap.GetOnlyTool(); - } - } - SetToolHeaters(tool, temperature); - } - break; - - case 105: // Get temperatures - { - const int8_t bedHeater = reprap.GetHeat()->GetBedHeater(); - const int8_t chamberHeater = reprap.GetHeat()->GetChamberHeater(); - reply.copy("T:"); - for (int8_t heater = 0; heater < HEATERS; heater++) - { - if (heater != bedHeater && heater != chamberHeater) - { - Heat::HeaterStatus hs = reprap.GetHeat()->GetStatus(heater); - if (hs != Heat::HS_off && hs != Heat::HS_fault) - { - reply.catf("%.1f ", reprap.GetHeat()->GetTemperature(heater)); - } - } - } - if (bedHeater >= 0) - { - reply.catf("B:%.1f", reprap.GetHeat()->GetTemperature(bedHeater)); - } - else - { - // I'm not sure whether Pronterface etc. can handle a missing bed temperature, so return zero - reply.cat("B:0.0"); - } - if (chamberHeater >= 0.0) - { - reply.catf(" C:%.1f", reprap.GetHeat()->GetTemperature(chamberHeater)); - } - } - break; - - case 106: // Set/report fan values - { - bool seenFanNum = false; - int32_t fanNum = 0; // Default to the first fan - gb.TryGetIValue('P', fanNum, seenFanNum); - if (fanNum < 0 || fanNum > (int)NUM_FANS) - { - reply.printf("Fan number %d is invalid, must be between 0 and %u", fanNum, NUM_FANS); - } - else - { - bool seen = false; - Fan& fan = platform->GetFan(fanNum); - - if (gb.Seen('I')) // Invert cooling - { - const int invert = gb.GetIValue(); - if (invert < 0) - { - fan.Disable(); - } - else - { - fan.SetInverted(invert > 0); - } - seen = true; - } - - if (gb.Seen('F')) // Set PWM frequency - { - fan.SetPwmFrequency(gb.GetFValue()); - seen = true; - } - - if (gb.Seen('T')) // Set thermostatic trigger temperature - { - seen = true; - fan.SetTriggerTemperature(gb.GetFValue()); - } - - if (gb.Seen('B')) // Set blip time - { - seen = true; - fan.SetBlipTime(gb.GetFValue()); - } - - if (gb.Seen('L')) // Set minimum speed - { - seen = true; - fan.SetMinValue(gb.GetFValue()); - } - - if (gb.Seen('H')) // Set thermostatically-controller heaters - { - seen = true; - long heaters[HEATERS]; - size_t numH = HEATERS; - gb.GetLongArray(heaters, numH); - // Note that M106 H-1 disables thermostatic mode. The following code implements that automatically. - uint16_t hh = 0; - for (size_t h = 0; h < numH; ++h) - { - const int hnum = heaters[h]; - if (hnum >= 0 && hnum < HEATERS) - { - hh |= (1u << (unsigned int)hnum); - } - } - if (hh != 0) - { - platform->SetFanValue(fanNum, 1.0); // default the fan speed to full for safety - } - fan.SetHeatersMonitored(hh); - } - - if (gb.Seen('S')) // Set new fan value - process this after processing 'H' or it may not be acted on - { - const float f = constrain<float>(gb.GetFValue(), 0.0, 255.0); - if (seen || seenFanNum) - { - platform->SetFanValue(fanNum, f); - } - else - { - // We are processing an M106 S### command with no other recognised parameters and we have a tool selected. - // Apply the fan speed setting to the fans in the fan mapping for the current tool. - lastDefaultFanSpeed = f; - SetMappedFanSpeed(); - } - } - else if (gb.Seen('R')) - { - const int i = gb.GetIValue(); - switch(i) - { - case 0: - case 1: - // Restore fan speed to value when print was paused - platform->SetFanValue(fanNum, pausedFanValues[fanNum]); - break; - case 2: - // Set the speeds of mapped fans to the last known value. Fan number is ignored. - SetMappedFanSpeed(); - break; - default: - break; - } - } - else if (!seen) - { - reply.printf("Fan%i frequency: %dHz, speed: %d%%, min: %d%%, blip: %.2f, inverted: %s", - fanNum, - (int)(fan.GetPwmFrequency()), - (int)(fan.GetValue() * 100.0), - (int)(fan.GetMinValue() * 100.0), - fan.GetBlipTime(), - (fan.GetInverted()) ? "yes" : "no"); - uint16_t hh = fan.GetHeatersMonitored(); - if (hh != 0) - { - reply.catf(", trigger: %dC, heaters:", (int)fan.GetTriggerTemperature()); - for (unsigned int i = 0; i < HEATERS; ++i) - { - if ((hh & (1u << i)) != 0) - { - reply.catf(" %u", i); - } - } - } - } - } - } - break; - - case 107: // Fan off - deprecated - platform->SetFanValue(0, 0.0); //T3P3 as deprecated only applies to fan0 - break; - - case 108: // Cancel waiting for temperature - if (isWaiting) - { - cancelWait = true; - } - break; - - case 109: // Deprecated in RRF, but widely generated by slicers - { - float temperature; - bool waitWhenCooling; - if (gb.Seen('R')) - { - waitWhenCooling = true; - temperature = gb.GetFValue(); - } - else if (gb.Seen('S')) - { - waitWhenCooling = false; - temperature = gb.GetFValue(); - } - else - { - break; // no target temperature given - } - - Tool *tool; - if (gb.Seen('T')) - { - int toolNumber = gb.GetIValue(); - toolNumber += gb.GetToolNumberAdjust(); - tool = reprap.GetTool(toolNumber); - } - else - { - tool = reprap.GetCurrentTool(); - // If no tool is selected, and there is only one tool, set the active temperature for that one - if (tool == nullptr) - { - tool = reprap.GetOnlyTool(); - } - } - SetToolHeaters(tool, temperature); - if (cancelWait || ToolHeatersAtSetTemperatures(tool, waitWhenCooling)) - { - cancelWait = isWaiting = false; - break; - } - // In Marlin emulation mode we should return some sort of (undocumented) message here every second... - isWaiting = true; - return false; - } - break; - - case 110: // Set line numbers - line numbers are dealt with in the GCodeBuffer class - break; - - case 111: // Debug level - if (gb.Seen('S')) - { - bool dbv = (gb.GetIValue() != 0); - if (gb.Seen('P')) - { - reprap.SetDebug(static_cast<Module>(gb.GetIValue()), dbv); - } - else - { - reprap.SetDebug(dbv); - } - } - else - { - reprap.PrintDebug(); - } - break; - - case 112: // Emergency stop - acted upon in Webserver, but also here in case it comes from USB etc. - DoEmergencyStop(); - break; - - case 114: - GetCurrentCoordinates(reply); - break; - - case 115: // Print firmware version or set hardware type - if (gb.Seen('P')) - { - platform->SetBoardType((BoardType)gb.GetIValue()); - } - else - { - reply.printf("FIRMWARE_NAME: %s FIRMWARE_VERSION: %s ELECTRONICS: %s", NAME, VERSION, platform->GetElectronicsString()); -#ifdef DUET_NG - const char* expansionName = DuetExpansion::GetExpansionBoardName(); - if (expansionName != nullptr) - { - reply.catf(" + %s", expansionName); - } -#endif - reply.catf(" FIRMWARE_DATE: %s", DATE); - } - break; - - case 116: // Wait for set temperatures - { - bool seen = false; - if (gb.Seen('P')) - { - // Wait for the heaters associated with the specified tool to be ready - int toolNumber = gb.GetIValue(); - toolNumber += gb.GetToolNumberAdjust(); - if (!cancelWait && !ToolHeatersAtSetTemperatures(reprap.GetTool(toolNumber), true)) - { - isWaiting = true; - return false; - } - seen = true; - } - - if (gb.Seen('H')) - { - // Wait for specified heaters to be ready - long heaters[HEATERS]; - size_t heaterCount = HEATERS; - gb.GetLongArray(heaters, heaterCount); - if (!cancelWait) - { - for (size_t i=0; i<heaterCount; i++) - { - if (!reprap.GetHeat()->HeaterAtSetTemperature(heaters[i], true)) - { - isWaiting = true; - return false; - } - } - } - seen = true; - } - - if (gb.Seen('C')) - { - // Wait for chamber heater to be ready - const int8_t chamberHeater = reprap.GetHeat()->GetChamberHeater(); - if (chamberHeater != -1) - { - if (!cancelWait && !reprap.GetHeat()->HeaterAtSetTemperature(chamberHeater, true)) - { - isWaiting = true; - return false; - } - } - seen = true; - } - - // Wait for all heaters to be ready - if (!seen && !cancelWait && !reprap.GetHeat()->AllHeatersAtSetTemperatures(true)) - { - isWaiting = true; - return false; - } - - // If we get here, there is nothing more to wait for - cancelWait = isWaiting = false; - } - break; - - case 117: // Display message - { - const char *msg = gb.GetUnprecedentedString(true); - reprap.SetMessage((msg == nullptr) ? "" : msg); - } - break; - - case 119: - reply.copy("Endstops - "); - for (size_t axis = 0; axis < numAxes; axis++) - { - reply.catf("%c: %s, ", axisLetters[axis], TranslateEndStopResult(platform->Stopped(axis))); - } - reply.catf("Z probe: %s", TranslateEndStopResult(platform->GetZProbeResult())); - break; - - case 120: - Push(gb); - break; - - case 121: - Pop(gb); - break; - - case 122: - { - int val = (gb.Seen('P')) ? gb.GetIValue() : 0; - if (val == 0) - { - reprap.Diagnostics(gb.GetResponseMessageType()); - } - else - { - platform->DiagnosticTest(val); - } - } - break; - - case 135: // Set PID sample interval - if (gb.Seen('S')) - { - platform->SetHeatSampleTime(gb.GetFValue() * 0.001); // Value is in milliseconds; we want seconds - } - else - { - reply.printf("Heat sample time is %.3f seconds", platform->GetHeatSampleTime()); - } - break; - - case 140: // Set bed temperature - { - int8_t bedHeater; - if (gb.Seen('H')) - { - bedHeater = gb.GetIValue(); - if (bedHeater < 0) - { - // Make sure we stay within reasonable boundaries... - bedHeater = -1; - - // If we're disabling the hot bed, make sure the old heater is turned off - reprap.GetHeat()->SwitchOff(reprap.GetHeat()->GetBedHeater()); - } - else if (bedHeater >= HEATERS) - { - reply.copy("Invalid heater number!"); - error = true; - break; - } - reprap.GetHeat()->SetBedHeater(bedHeater); - - if (bedHeater < 0) - { - // Stop here if the hot bed has been disabled - break; - } - } - else - { - bedHeater = reprap.GetHeat()->GetBedHeater(); - if (bedHeater < 0) - { - reply.copy("Hot bed is not present!"); - error = true; - break; - } - } - - if(gb.Seen('S')) - { - float temperature = gb.GetFValue(); - if (temperature < NEARLY_ABS_ZERO) - { - reprap.GetHeat()->SwitchOff(bedHeater); - } - else - { - reprap.GetHeat()->SetActiveTemperature(bedHeater, temperature); - reprap.GetHeat()->Activate(bedHeater); - } - } - if(gb.Seen('R')) - { - reprap.GetHeat()->SetStandbyTemperature(bedHeater, gb.GetFValue()); - } - } - break; - - case 141: // Chamber temperature - { - bool seen = false; - if (gb.Seen('H')) - { - seen = true; - - int heater = gb.GetIValue(); - if (heater < 0) - { - const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater(); - if (currentHeater != -1) - { - reprap.GetHeat()->SwitchOff(currentHeater); - } - - reprap.GetHeat()->SetChamberHeater(-1); - } - else if (heater < HEATERS) - { - reprap.GetHeat()->SetChamberHeater(heater); - } - else - { - reply.copy("Bad heater number specified!"); - error = true; - } - } - - if (gb.Seen('S')) - { - seen = true; - - const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater(); - if (currentHeater != -1) - { - float temperature = gb.GetFValue(); - - if (temperature < NEARLY_ABS_ZERO) - { - reprap.GetHeat()->SwitchOff(currentHeater); - } - else - { - reprap.GetHeat()->SetActiveTemperature(currentHeater, temperature); - reprap.GetHeat()->Activate(currentHeater); - } - } - else - { - reply.copy("No chamber heater has been set up yet!"); - error = true; - } - } - - if (!seen) - { - const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater(); - if (currentHeater != -1) - { - reply.printf("Chamber heater %d is currently at %.1fC", currentHeater, reprap.GetHeat()->GetTemperature(currentHeater)); - } - else - { - reply.copy("No chamber heater has been configured yet."); - } - } - } - break; - - case 143: // Set temperature limit - { - const int heater = (gb.Seen('H')) ? gb.GetIValue() : 1; // default to extruder 1 if no heater number provided - if (heater < 0 || heater >= HEATERS) - { - reply.copy("Invalid heater number"); - error = true; - } - else if (gb.Seen('S')) - { - const float limit = gb.GetFValue(); - if (limit > BAD_LOW_TEMPERATURE && limit < BAD_ERROR_TEMPERATURE) - { - reprap.GetHeat()->SetTemperatureLimit(heater, limit); - } - else - { - reply.copy("Invalid temperature limit"); - error = true; - } - } - else - { - reply.printf("Temperature limit for heater %d is %.1fC", heater, reprap.GetHeat()->GetTemperatureLimit(heater)); - } - } - break; - - case 144: // Set bed to standby - { - const int8_t bedHeater = reprap.GetHeat()->GetBedHeater(); - if (bedHeater >= 0) - { - reprap.GetHeat()->Standby(bedHeater); - } - } - break; - - case 190: // Set bed temperature and wait - case 191: // Set chamber temperature and wait - { - const int8_t heater = (code == 191) ? reprap.GetHeat()->GetChamberHeater() : reprap.GetHeat()->GetBedHeater(); - if (heater >= 0) - { - float temperature; - bool waitWhenCooling; - if (gb.Seen('R')) - { - waitWhenCooling = true; - temperature = gb.GetFValue(); - } - else if (gb.Seen('S')) - { - waitWhenCooling = false; - temperature = gb.GetFValue(); - } - else - { - break; // no target temperature given - } - - reprap.GetHeat()->SetActiveTemperature(heater, temperature); - reprap.GetHeat()->Activate(heater); - if (cancelWait || reprap.GetHeat()->HeaterAtSetTemperature(heater, waitWhenCooling)) - { - cancelWait = isWaiting = false; - break; - } - // In Marlin emulation mode we should return some sort of (undocumented) message here every second... - isWaiting = true; - return false; - } - } - break; - - case 201: // Set/print axis accelerations - { - bool seen = false; - for (size_t axis = 0; axis < numAxes; axis++) - { - if (gb.Seen(axisLetters[axis])) - { - platform->SetAcceleration(axis, gb.GetFValue() * distanceScale); - seen = true; - } - } - - if (gb.Seen(extrudeLetter)) - { - seen = true; - float eVals[MaxExtruders]; - size_t eCount = numExtruders; - gb.GetFloatArray(eVals, eCount, true); - for (size_t e = 0; e < eCount; e++) - { - platform->SetAcceleration(numAxes + e, eVals[e] * distanceScale); - } - } - - if (gb.Seen('P')) - { - // Set max average printing acceleration - platform->SetMaxAverageAcceleration(gb.GetFValue() * distanceScale); - seen = true; - } - - if (!seen) - { - reply.printf("Accelerations: "); - for (size_t axis = 0; axis < numAxes; ++axis) - { - reply.catf("%c: %.1f, ", axisLetters[axis], platform->Acceleration(axis) / distanceScale); - } - reply.cat("E:"); - char sep = ' '; - for (size_t extruder = 0; extruder < numExtruders; extruder++) - { - reply.catf("%c%.1f", sep, platform->Acceleration(extruder + numAxes) / distanceScale); - sep = ':'; - } - reply.catf(", avg. printing: %.1f", platform->GetMaxAverageAcceleration()); - } - } - break; - - case 203: // Set/print maximum feedrates - { - bool seen = false; - for (size_t axis = 0; axis < numAxes; ++axis) - { - if (gb.Seen(axisLetters[axis])) - { - platform->SetMaxFeedrate(axis, gb.GetFValue() * distanceScale * secondsToMinutes); // G Code feedrates are in mm/minute; we need mm/sec - seen = true; - } - } - - if (gb.Seen(extrudeLetter)) - { - seen = true; - float eVals[MaxExtruders]; - size_t eCount = numExtruders; - gb.GetFloatArray(eVals, eCount, true); - for (size_t e = 0; e < eCount; e++) - { - platform->SetMaxFeedrate(numAxes + e, eVals[e] * distanceScale * secondsToMinutes); - } - } - - if (!seen) - { - reply.copy("Maximum feedrates: "); - for (size_t axis = 0; axis < numAxes; ++axis) - { - reply.catf("%c: %.1f, ", axisLetters[axis], platform->MaxFeedrate(axis) / (distanceScale * secondsToMinutes)); - } - reply.cat("E:"); - char sep = ' '; - for (size_t extruder = 0; extruder < numExtruders; extruder++) - { - reply.catf("%c%.1f", sep, platform->MaxFeedrate(extruder + numAxes) / (distanceScale * secondsToMinutes)); - sep = ':'; - } - } - } - break; - - case 205: //M205 advanced settings: minimum travel speed S=while printing T=travel only, B=minimum segment time X= maximum xy jerk, Z=maximum Z jerk - // This is superseded in this firmware by M codes for the separate types (e.g. M566). - break; - - case 206: // Offset axes - Deprecated - result = OffsetAxes(gb); - break; - - case 207: // Set firmware retraction details - { - bool seen = false; - if (gb.Seen('S')) - { - retractLength = max<float>(gb.GetFValue(), 0.0); - seen = true; - } - if (gb.Seen('R')) - { - retractExtra = max<float>(gb.GetFValue(), -retractLength); - seen = true; - } - if (gb.Seen('F')) - { - unRetractSpeed = retractSpeed = max<float>(gb.GetFValue(), 60.0); - seen = true; - } - if (gb.Seen('T')) // must do this one after 'F' - { - unRetractSpeed = max<float>(gb.GetFValue(), 60.0); - seen = true; - } - if (gb.Seen('Z')) - { - retractHop = max<float>(gb.GetFValue(), 0.0); - seen = true; - } - if (!seen) - { - reply.printf("Retraction settings: length %.2f/%.2fmm, speed %d/%dmm/min, Z hop %.2fmm", - retractLength, retractLength + retractExtra, (int)retractSpeed, (int)unRetractSpeed, retractHop); - } - } - break; - - case 208: // Set/print maximum axis lengths. If there is an S parameter with value 1 then we set the min value, else we set the max value. - { - bool setMin = (gb.Seen('S') ? (gb.GetIValue() == 1) : false); - bool seen = false; - for (size_t axis = 0; axis < numAxes; axis++) - { - if (gb.Seen(axisLetters[axis])) - { - float value = gb.GetFValue() * distanceScale; - if (setMin) - { - platform->SetAxisMinimum(axis, value); - } - else - { - platform->SetAxisMaximum(axis, value); - } - seen = true; - } - } - - if (!seen) - { - reply.copy("Axis limits "); - char sep = '-'; - for (size_t axis = 0; axis < numAxes; axis++) - { - reply.catf("%c %c: %.1f min, %.1f max", sep, axisLetters[axis], platform->AxisMinimum(axis), - platform->AxisMaximum(axis)); - sep = ','; - } - } - } - break; - - case 210: // Set/print homing feed rates - // This is no longer used, but for backwards compatibility we don't report an error - break; - - case 220: // Set/report speed factor override percentage - if (gb.Seen('S')) - { - float newSpeedFactor = (gb.GetFValue() / 100.0) * secondsToMinutes; // include the conversion from mm/minute to mm/second - if (newSpeedFactor > 0.0) - { - feedRate *= newSpeedFactor / speedFactor; - if (moveAvailable && !moveBuffer.isFirmwareRetraction) - { - // The last move has not gone yet, so we can update it - moveBuffer.feedRate *= newSpeedFactor / speedFactor; - } - speedFactor = newSpeedFactor; - } - else - { - reply.printf("Invalid speed factor specified."); - error = true; - } - } - else - { - reply.printf("Speed factor override: %.1f%%", speedFactor * minutesToSeconds * 100.0); - } - break; - - case 221: // Set/report extrusion factor override percentage - { - int extruder = 0; - if (gb.Seen('D')) // D parameter (if present) selects the extruder number - { - extruder = gb.GetIValue(); - } - - if (gb.Seen('S')) // S parameter sets the override percentage - { - float extrusionFactor = gb.GetFValue() / 100.0; - if (extruder >= 0 && (size_t)extruder < numExtruders && extrusionFactor >= 0.0) - { - if (moveAvailable && !moveBuffer.isFirmwareRetraction) - { - moveBuffer.coords[extruder + numAxes] *= extrusionFactor/extrusionFactors[extruder]; // last move not gone, so update it - } - extrusionFactors[extruder] = extrusionFactor; - } - } - else - { - reply.printf("Extrusion factor override for extruder %d: %.1f%%", extruder, - extrusionFactors[extruder] * 100.0); - } - } - break; - - // For case 226, see case 25 - - case 280: // Servos - if (gb.Seen('P')) - { - const int servoIndex = gb.GetIValue(); - Pin servoPin; - bool invert; - if (platform->GetFirmwarePin(servoIndex, PinAccess::servo, servoPin, invert)) - { - if (gb.Seen('I')) - { - if (gb.GetIValue() > 0) - { - invert = !invert; - } - } - if (gb.Seen('S')) - { - float angleOrWidth = gb.GetFValue(); - if (angleOrWidth < 0.0) - { - // Disable the servo by setting the pulse width to zero - Platform::WriteAnalog(servoPin, (invert) ? 1.0 : 0.0, ServoRefreshFrequency); - } - else - { - if (angleOrWidth < MinServoPulseWidth) - { - // User gave an angle so convert it to a pulse width in microseconds - angleOrWidth = (min<float>(angleOrWidth, 180.0) * ((MaxServoPulseWidth - MinServoPulseWidth) / 180.0)) + MinServoPulseWidth; - } - else if (angleOrWidth > MaxServoPulseWidth) - { - angleOrWidth = MaxServoPulseWidth; - } - float pwm = angleOrWidth * (ServoRefreshFrequency/1e6); - if (invert) - { - pwm = 1.0 - pwm; - } - Platform::WriteAnalog(servoPin, pwm, ServoRefreshFrequency); - } - } - // We don't currently allow the servo position to be read back - } - else - { - platform->MessageF(GENERIC_MESSAGE, "Error: Invalid servo index %d in M280 command\n", servoIndex); - } - } - break; - - case 300: // Beep - { - int ms = (gb.Seen('P')) ? gb.GetIValue() : 1000; // time in milliseconds - int freq = (gb.Seen('S')) ? gb.GetIValue() : 4600; // 4600Hz produces the loudest sound on a PanelDue - reprap.Beep(freq, ms); - } - break; - - case 301: // Set/report hot end PID values - SetPidParameters(gb, 1, reply); - break; - - case 302: // Allow, deny or report cold extrudes - if (gb.Seen('P')) - { - if (gb.GetIValue() > 0) - { - reprap.GetHeat()->AllowColdExtrude(); - } - else - { - reprap.GetHeat()->DenyColdExtrude(); - } - } - else - { - reply.printf("Cold extrusion is %s, use M302 P[1/0] to allow/deny it", - reprap.GetHeat()->ColdExtrude() ? "allowed" : "denied"); - } - break; - - case 303: // Run PID tuning - if (gb.Seen('H')) - { - const size_t heater = gb.GetIValue(); - const float temperature = (gb.Seen('S')) ? gb.GetFValue() : 225.0; - const float maxPwm = (gb.Seen('P')) ? gb.GetFValue() : 0.5; - if (heater < HEATERS && maxPwm >= 0.1 && maxPwm <= 1.0 && temperature >= 55.0 && temperature <= reprap.GetHeat()->GetTemperatureLimit(heater)) - { - reprap.GetHeat()->StartAutoTune(heater, temperature, maxPwm, reply); - } - else - { - reply.printf("Bad parameter in M303 command"); - } - } - else - { - reprap.GetHeat()->GetAutoTuneStatus(reply); - } - break; - - case 304: // Set/report heated bed PID values - { - const int8_t bedHeater = reprap.GetHeat()->GetBedHeater(); - if (bedHeater >= 0) - { - SetPidParameters(gb, bedHeater, reply); - } - } - break; - - case 305: // Set/report specific heater parameters - SetHeaterParameters(gb, reply); - break; - - case 307: // Set heater process model parameters - if (gb.Seen('H')) - { - size_t heater = gb.GetIValue(); - if (heater < HEATERS) - { - const FopDt& model = reprap.GetHeat()->GetHeaterModel(heater); - bool seen = false; - float gain = model.GetGain(), tc = model.GetTimeConstant(), td = model.GetDeadTime(), maxPwm = model.GetMaxPwm(); - int32_t dontUsePid = model.UsePid() ? 0 : 1; - - gb.TryGetFValue('A', gain, seen); - gb.TryGetFValue('C', tc, seen); - gb.TryGetFValue('D', td, seen); - gb.TryGetIValue('B', dontUsePid, seen); - gb.TryGetFValue('S', maxPwm, seen); - - if (seen) - { - if (reprap.GetHeat()->SetHeaterModel(heater, gain, tc, td, maxPwm, dontUsePid == 0)) - { - reprap.GetHeat()->UseModel(heater, true); - } - else - { - reply.copy("Error: bad model parameters"); - } - } - else if (!model.IsEnabled()) - { - reply.printf("Heater %u is disabled", heater); - } - else - { - reply.printf("Heater %u model: gain %.1f, time constant %.1f, dead time %.1f, max PWM %.2f, in use: %s, mode: %s", - heater, model.GetGain(), model.GetTimeConstant(), model.GetDeadTime(), model.GetMaxPwm(), - (reprap.GetHeat()->IsModelUsed(heater)) ? "yes" : "no", - (model.UsePid()) ? "PID" : "bang-bang"); - if (model.UsePid()) - { - // When reporting the PID parameters, we scale them by 255 for compatibility with older firmware and other firmware - const PidParams& spParams = model.GetPidParameters(false); - const float scaledSpKp = 255.0 * spParams.kP; - reply.catf("\nSetpoint change: P%.1f, I%.2f, D%.1f", - scaledSpKp, scaledSpKp * spParams.recipTi, scaledSpKp * spParams.tD); - const PidParams& ldParams = model.GetPidParameters(true); - const float scaledLoadKp = 255.0 * ldParams.kP; - reply.catf("\nLoad change: P%.1f, I%.2f, D%.1f", - scaledLoadKp, scaledLoadKp * ldParams.recipTi, scaledLoadKp * ldParams.tD); - } - } - } - } - break; - - case 350: // Set/report microstepping - { - // interp is current an int not a bool, because we use special values of interp to set the chopper control register - int interp = 0; - if (gb.Seen('I')) - { - interp = gb.GetIValue(); - } - - bool seen = false; - for (size_t axis = 0; axis < numAxes; axis++) - { - if (gb.Seen(axisLetters[axis])) - { - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - seen = true; - int microsteps = gb.GetIValue(); - if (ChangeMicrostepping(axis, microsteps, interp)) - { - SetAxisNotHomed(axis); - } - else - { - platform->MessageF(GENERIC_MESSAGE, "Drive %c does not support %dx microstepping%s\n", - axisLetters[axis], microsteps, (interp) ? " with interpolation" : ""); - } - } - } - - if (gb.Seen(extrudeLetter)) - { - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - seen = true; - long eVals[MaxExtruders]; - size_t eCount = numExtruders; - gb.GetLongArray(eVals, eCount); - for (size_t e = 0; e < eCount; e++) - { - if (!ChangeMicrostepping(numAxes + e, (int)eVals[e], interp)) - { - platform->MessageF(GENERIC_MESSAGE, "Drive E%u does not support %dx microstepping%s\n", - e, (int)eVals[e], (interp) ? " with interpolation" : ""); - } - } - } - - if (!seen) - { - reply.copy("Microstepping - "); - for (size_t axis = 0; axis < numAxes; ++axis) - { - bool interp; - int microsteps = platform->GetMicrostepping(axis, interp); - reply.catf("%c:%d%s, ", axisLetters[axis], microsteps, (interp) ? "(on)" : ""); - } - reply.cat("E"); - for (size_t extruder = 0; extruder < numExtruders; extruder++) - { - bool interp; - int microsteps = platform->GetMicrostepping(extruder + numAxes, interp); - reply.catf(":%d%s", microsteps, (interp) ? "(on)" : ""); - } - } - } - break; - - case 400: // Wait for current moves to finish - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - break; - - case 404: // Filament width and nozzle diameter - { - bool seen = false; - - if (gb.Seen('N')) - { - platform->SetFilamentWidth(gb.GetFValue()); - seen = true; - } - if (gb.Seen('D')) - { - platform->SetNozzleDiameter(gb.GetFValue()); - seen = true; - } - - if (!seen) - { - reply.printf("Filament width: %.2fmm, nozzle diameter: %.2fmm", platform->GetFilamentWidth(), platform->GetNozzleDiameter()); - } - } - break; - - 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) - { - 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; - } - - if (statusResponse != nullptr) - { - UnlockAll(gb); - statusResponse->cat('\n'); - HandleReply(gb, false, statusResponse); - return true; - } - } - break; - - case 500: // Store parameters in EEPROM - reprap.GetPlatform()->WriteNvData(); - break; - - case 501: // Load parameters from EEPROM - reprap.GetPlatform()->ReadNvData(); - if (gb.Seen('S')) - { - reprap.GetPlatform()->SetAutoSave(gb.GetIValue() > 0); - } - break; - - case 502: // Revert to default "factory settings" - reprap.GetPlatform()->ResetNvData(); - break; - - case 503: // List variable settings - { - if (!LockFileSystem(gb)) - { - return false; - } - - // Need a valid output buffer to continue... - OutputBuffer *configResponse; - if (!OutputBuffer::Allocate(configResponse)) - { - // No buffer available, try again later - return false; - } - - // Read the entire file - FileStore * const f = platform->GetFileStore(platform->GetSysDir(), platform->GetConfigFile(), false); - if (f == nullptr) - { - error = true; - reply.copy("Configuration file not found!"); - } - else - { - char fileBuffer[FILE_BUFFER_SIZE]; - size_t bytesRead, bytesLeftForWriting = OutputBuffer::GetBytesLeft(configResponse); - while ((bytesRead = f->Read(fileBuffer, FILE_BUFFER_SIZE)) > 0 && bytesLeftForWriting > 0) - { - // Don't write more data than we can process - if (bytesRead < bytesLeftForWriting) - { - bytesLeftForWriting -= bytesRead; - } - else - { - bytesRead = bytesLeftForWriting; - bytesLeftForWriting = 0; - } - - // Write it - configResponse->cat(fileBuffer, bytesRead); - } - f->Close(); - - UnlockAll(gb); - HandleReply(gb, false, configResponse); - return true; - } - } - break; - - case 540: // Set/report MAC address - if (gb.Seen('P')) - { - SetMACAddress(gb); - } - else - { - const byte* mac = platform->MACAddress(); - reply.printf("MAC: %x:%x:%x:%x:%x:%x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - } - break; - - case 550: // Set/report machine name - if (gb.Seen('P')) - { - reprap.SetName(gb.GetString()); - } - else - { - reply.printf("RepRap name: %s", reprap.GetName()); - } - break; - - case 551: // Set password (no option to report it) - if (gb.Seen('P')) - { - reprap.SetPassword(gb.GetString()); - } - break; - - case 552: // Enable/Disable network and/or Set/Get IP address - { - bool seen = false; - if (gb.Seen('P')) - { - seen = true; - SetEthernetAddress(gb, code); - } - - if (gb.Seen('R')) - { - reprap.GetNetwork()->SetHttpPort(gb.GetIValue()); - seen = true; - } - - // Process this one last in case the IP address is changed and the network enabled in the same command - if (gb.Seen('S')) // Has the user turned the network on or off? - { - seen = true; - if (gb.GetIValue() != 0) - { - reprap.GetNetwork()->Enable(); - } - else - { - reprap.GetNetwork()->Disable(); - } - } - - if (!seen) - { - const byte *config_ip = platform->IPAddress(); - const byte *actual_ip = reprap.GetNetwork()->IPAddress(); - reply.printf("Network is %s, configured IP address: %d.%d.%d.%d, actual IP address: %d.%d.%d.%d, HTTP port: %d", - reprap.GetNetwork()->IsEnabled() ? "enabled" : "disabled", - config_ip[0], config_ip[1], config_ip[2], config_ip[3], actual_ip[0], actual_ip[1], actual_ip[2], actual_ip[3], - reprap.GetNetwork()->GetHttpPort()); - } - } - break; - - case 553: // Set/Get netmask - if (gb.Seen('P')) - { - SetEthernetAddress(gb, code); - } - else - { - const byte *nm = platform->NetMask(); - reply.printf("Net mask: %d.%d.%d.%d ", nm[0], nm[1], nm[2], nm[3]); - } - break; - - case 554: // Set/Get gateway - if (gb.Seen('P')) - { - SetEthernetAddress(gb, code); - } - else - { - const byte *gw = platform->GateWay(); - reply.printf("Gateway: %d.%d.%d.%d ", gw[0], gw[1], gw[2], gw[3]); - } - break; - - case 555: // Set/report firmware type to emulate - if (gb.Seen('P')) - { - platform->SetEmulating((Compatibility) gb.GetIValue()); - } - else - { - reply.copy("Emulating "); - switch (platform->Emulating()) - { - case me: - case reprapFirmware: - reply.cat("RepRap Firmware (i.e. in native mode)"); - break; - - case marlin: - reply.cat("Marlin"); - break; - - case teacup: - reply.cat("Teacup"); - break; - - case sprinter: - reply.cat("Sprinter"); - break; - - case repetier: - reply.cat("Repetier"); - break; - - default: - reply.catf("Unknown: (%d)", platform->Emulating()); - } - } - break; - - case 556: // Axis compensation (we support only X, Y, Z) - if (gb.Seen('S')) - { - float value = gb.GetFValue(); - for (size_t axis = 0; axis <= Z_AXIS; axis++) - { - if (gb.Seen(axisLetters[axis])) - { - reprap.GetMove()->SetAxisCompensation(axis, gb.GetFValue() / value); - } - } - } - else - { - reply.printf("Axis compensations - XY: %.5f, YZ: %.5f, ZX: %.5f", - reprap.GetMove()->AxisCompensation(X_AXIS), reprap.GetMove()->AxisCompensation(Y_AXIS), - reprap.GetMove()->AxisCompensation(Z_AXIS)); - } - break; - - case 557: // Set/report Z probe point coordinates - if (gb.Seen('P')) - { - int point = gb.GetIValue(); - if (point < 0 || (unsigned int)point >= MaxProbePoints) - { - reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Z probe point index out of range.\n"); - } - else - { - bool seen = false; - if (gb.Seen(axisLetters[X_AXIS])) - { - reprap.GetMove()->SetXBedProbePoint(point, gb.GetFValue()); - seen = true; - } - if (gb.Seen(axisLetters[Y_AXIS])) - { - reprap.GetMove()->SetYBedProbePoint(point, gb.GetFValue()); - seen = true; - } - - if (!seen) - { - reply.printf("Probe point %d - [%.1f, %.1f]", point, reprap.GetMove()->XBedProbePoint(point), reprap.GetMove()->YBedProbePoint(point)); - } - } - } - else - { - LockMovement(gb); // to ensure that probing is not already in progress - error = DefineGrid(gb, reply); - } - break; - - case 558: // Set or report Z probe type and for which axes it is used - { - bool seenAxes = false, seenType = false, seenParam = false; - uint32_t zProbeAxes = platform->GetZProbeAxes(); - for (size_t axis = 0; axis < numAxes; axis++) - { - if (gb.Seen(axisLetters[axis])) - { - if (gb.GetIValue() > 0) - { - zProbeAxes |= (1u << axis); - } - else - { - zProbeAxes &= ~(1u << axis); - } - seenAxes = true; - } - } - if (seenAxes) - { - platform->SetZProbeAxes(zProbeAxes); - } - - // We must get and set the Z probe type first before setting the dive height etc., because different probe types may have different parameters - if (gb.Seen('P')) // probe type - { - platform->SetZProbeType(gb.GetIValue()); - seenType = true; - } - - ZProbeParameters params = platform->GetZProbeParameters(); - gb.TryGetFValue('H', params.diveHeight, seenParam); // dive height - - if (gb.Seen('F')) // feed rate i.e. probing speed - { - params.probeSpeed = gb.GetFValue() * secondsToMinutes; - seenParam = true; - } - - if (gb.Seen('T')) // travel speed to probe point - { - params.travelSpeed = gb.GetFValue() * secondsToMinutes; - seenParam = true; - } - - if (gb.Seen('I')) - { - params.invertReading = (gb.GetIValue() != 0); - seenParam = true; - } - - gb.TryGetFValue('S', params.param1, seenParam); // extra parameter for experimentation - gb.TryGetFValue('R', params.param2, seenParam); // extra parameter for experimentation - - if (seenParam) - { - platform->SetZProbeParameters(params); - } - - if (!(seenAxes || seenType || seenParam)) - { - reply.printf("Z Probe type %d, invert %s, dive height %.1fmm, probe speed %dmm/min, travel speed %dmm/min", - platform->GetZProbeType(), (params.invertReading) ? "yes" : "no", params.diveHeight, - (int)(params.probeSpeed * minutesToSeconds), (int)(params.travelSpeed * minutesToSeconds)); - if (platform->GetZProbeType() == ZProbeTypeDelta) - { - reply.catf(", parameters %.2f %.2f", params.param1, params.param2); - } - reply.cat(", used for axes:"); - for (size_t axis = 0; axis < numAxes; axis++) - { - if ((zProbeAxes & (1u << axis)) != 0) - { - reply.catf(" %c", axisLetters[axis]); - } - } - } - } - 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()); - 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); - 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: - reprap.GetMove()->SetIdentityTransform(); - break; - - case 562: // Reset temperature fault - use with great caution - if (gb.Seen('P')) - { - int heater = gb.GetIValue(); - if (heater >= 0 && heater < HEATERS) - { - reprap.ClearTemperatureFault(heater); - } - else - { - reply.copy("Invalid heater number.\n"); - error = true; - } - } - break; - - case 563: // Define tool - ManageTool(gb, reply); - break; - - case 564: // Think outside the box? - if (gb.Seen('S')) - { - limitAxes = (gb.GetIValue() != 0); - } - else - { - reply.printf("Movement outside the bed is %spermitted", (limitAxes) ? "not " : ""); - } - break; - - case 566: // Set/print maximum jerk speeds - { - bool seen = false; - for (size_t axis = 0; axis < numAxes; axis++) - { - if (gb.Seen(axisLetters[axis])) - { - platform->SetInstantDv(axis, gb.GetFValue() * distanceScale * secondsToMinutes); // G Code feedrates are in mm/minute; we need mm/sec - seen = true; - } - } - - if (gb.Seen(extrudeLetter)) - { - seen = true; - float eVals[MaxExtruders]; - size_t eCount = numExtruders; - gb.GetFloatArray(eVals, eCount, true); - for (size_t e = 0; e < eCount; e++) - { - platform->SetInstantDv(numAxes + e, eVals[e] * distanceScale * secondsToMinutes); - } - } - else if (!seen) - { - reply.copy("Maximum jerk rates: "); - for (size_t axis = 0; axis < numAxes; ++axis) - { - reply.catf("%c: %.1f, ", axisLetters[axis], platform->ConfiguredInstantDv(axis) / (distanceScale * secondsToMinutes)); - } - reply.cat("E:"); - char sep = ' '; - for (size_t extruder = 0; extruder < numExtruders; extruder++) - { - reply.catf("%c%.1f", sep, platform->ConfiguredInstantDv(extruder + numAxes) / (distanceScale * secondsToMinutes)); - sep = ':'; - } - } - } - break; - - case 567: // Set/report tool mix ratios - if (gb.Seen('P')) - { - int8_t tNumber = gb.GetIValue(); - Tool* tool = reprap.GetTool(tNumber); - if (tool != NULL) - { - if (gb.Seen(extrudeLetter)) - { - float eVals[MaxExtruders]; - size_t eCount = tool->DriveCount(); - gb.GetFloatArray(eVals, eCount, false); - if (eCount != tool->DriveCount()) - { - reply.printf("Setting mix ratios - wrong number of E drives: %s", gb.Buffer()); - } - else - { - tool->DefineMix(eVals); - } - } - else - { - reply.printf("Tool %d mix ratios:", tNumber); - char sep = ' '; - for (size_t drive = 0; drive < tool->DriveCount(); drive++) - { - reply.catf("%c%.3f", sep, tool->GetMix()[drive]); - sep = ':'; - } - } - } - } - break; - - case 568: // Turn on/off automatic tool mixing - if (gb.Seen('P')) - { - Tool* tool = reprap.GetTool(gb.GetIValue()); - if (tool != NULL) - { - if (gb.Seen('S')) - { - tool->SetMixing(gb.GetIValue() != 0); - } - else - { - reply.printf("Tool %d mixing is %s", tool->Number(), (tool->GetMixing()) ? "enabled" : "disabled"); - } - } - } - break; - - case 569: // Set/report axis direction - if (gb.Seen('P')) - { - size_t drive = gb.GetIValue(); - if (drive < DRIVES) - { - bool seen = false; - if (gb.Seen('S')) - { - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - platform->SetDirectionValue(drive, gb.GetIValue() != 0); - seen = true; - } - if (gb.Seen('R')) - { - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - platform->SetEnableValue(drive, gb.GetIValue() != 0); - seen = true; - } - if (gb.Seen('T')) - { - platform->SetDriverStepTiming(drive, gb.GetFValue()); - seen = true; - } - bool badParameter = false; - for (size_t axis = 0; axis < numAxes; ++axis) - { - if (gb.Seen(axisLetters[axis])) - { - badParameter = true; - } - } - if (gb.Seen(extrudeLetter)) - { - badParameter = true; - } - if (badParameter) - { - platform->Message(GENERIC_MESSAGE, "Error: M569 no longer accepts XYZE parameters; use M584 instead\n"); - } - else if (!seen) - { - reply.printf("A %d sends drive %u forwards, a %d enables it, and the minimum pulse width is %.1f microseconds", - (int)platform->GetDirectionValue(drive), drive, - (int)platform->GetEnableValue(drive), - platform->GetDriverStepTiming(drive)); - } - } - } - break; - - case 570: // Set/report heater timeout - if (gb.Seen('H')) - { - const size_t heater = gb.GetIValue(); - bool seen = false; - if (heater < HEATERS) - { - float maxTempExcursion, maxFaultTime; - reprap.GetHeat()->GetHeaterProtection(heater, maxTempExcursion, maxFaultTime); - gb.TryGetFValue('P', maxFaultTime, seen); - gb.TryGetFValue('T', maxTempExcursion, seen); - if (seen) - { - reprap.GetHeat()->SetHeaterProtection(heater, maxTempExcursion, maxFaultTime); - } - else - { - reply.printf("Heater %u allowed excursion %.1fC, fault trigger time %.1f seconds", heater, maxTempExcursion, maxFaultTime); - } - } - } - else if (gb.Seen('S')) - { - reply.copy("M570 S parameter is no longer required or supported"); - } - break; - - case 571: // Set output on extrude - if (gb.Seen('S')) - { - platform->SetExtrusionAncilliaryPWM(gb.GetFValue()); - } - else - { - reply.printf("Extrusion ancillary PWM: %.3f.", platform->GetExtrusionAncilliaryPWM()); - } - break; - - case 572: // Set/report elastic compensation - if (gb.Seen('D')) - { - // New usage: specify the extruder drive using the D parameter - size_t extruder = gb.GetIValue(); - if (gb.Seen('S')) - { - platform->SetPressureAdvance(extruder, gb.GetFValue()); - } - else - { - reply.printf("Pressure advance for extruder %u is %.3f seconds", extruder, platform->GetPressureAdvance(extruder)); - } - } - break; - - case 573: // Report heater average PWM - if (gb.Seen('P')) - { - int heater = gb.GetIValue(); - if (heater >= 0 && heater < HEATERS) - { - reply.printf("Average heater %d PWM: %.3f", heater, reprap.GetHeat()->GetAveragePWM(heater)); - } - } - break; - - case 574: // Set endstop configuration - { - bool seen = false; - bool logicLevel = (gb.Seen('S')) ? (gb.GetIValue() != 0) : true; - for (size_t axis = 0; axis < numAxes; ++axis) - { - if (gb.Seen(axisLetters[axis])) - { - int ival = gb.GetIValue(); - if (ival >= 0 && ival <= 3) - { - platform->SetEndStopConfiguration(axis, (EndStopType) ival, logicLevel); - seen = true; - } - } - } - if (!seen) - { - reply.copy("Endstop configuration:"); - EndStopType config; - bool logic; - for (size_t axis = 0; axis < numAxes; ++axis) - { - platform->GetEndStopConfiguration(axis, config, logic); - reply.catf(" %c %s (active %s),", axisLetters[axis], - (config == EndStopType::highEndStop) ? "high end" : (config == EndStopType::lowEndStop) ? "low end" : "none", - (config == EndStopType::noEndStop) ? "" : (logic) ? "high" : "low"); - } - } - } - break; - - case 575: // Set communications parameters - if (gb.Seen('P')) - { - size_t chan = gb.GetIValue(); - if (chan < NUM_SERIAL_CHANNELS) - { - bool seen = false; - if (gb.Seen('B')) - { - platform->SetBaudRate(chan, gb.GetIValue()); - seen = true; - } - if (gb.Seen('S')) - { - uint32_t val = gb.GetIValue(); - platform->SetCommsProperties(chan, val); - switch (chan) - { - case 0: - serialGCode->SetCommsProperties(val); - break; - case 1: - auxGCode->SetCommsProperties(val); - break; - default: - break; - } - seen = true; - } - if (!seen) - { - uint32_t cp = platform->GetCommsProperties(chan); - reply.printf("Channel %d: baud rate %d, %s checksum", chan, platform->GetBaudRate(chan), - (cp & 1) ? "requires" : "does not require"); - } - } - } - break; - - case 577: // Wait until endstop is triggered - if (gb.Seen('S')) - { - // Determine trigger type - EndStopHit triggerCondition; - switch (gb.GetIValue()) - { - case 1: - triggerCondition = EndStopHit::lowHit; - break; - case 2: - triggerCondition = EndStopHit::highHit; - break; - case 3: - triggerCondition = EndStopHit::lowNear; - break; - default: - triggerCondition = EndStopHit::noStop; - break; - } - - // Axis endstops - for (size_t axis=0; axis < numAxes; axis++) - { - if (gb.Seen(axisLetters[axis])) - { - if (platform->Stopped(axis) != triggerCondition) - { - result = false; - break; - } - } - } - - // Extruder drives - size_t eDriveCount = MaxExtruders; - long eDrives[MaxExtruders]; - if (gb.Seen(extrudeLetter)) - { - gb.GetLongArray(eDrives, eDriveCount); - for(size_t extruder = 0; extruder < eDriveCount; extruder++) - { - const size_t eDrive = eDrives[extruder]; - if (eDrive >= MaxExtruders) - { - reply.copy("Invalid extruder drive specified!"); - error = result = true; - break; - } - - if (platform->Stopped(eDrive + E0_AXIS) != triggerCondition) - { - result = false; - break; - } - } - } - } - break; - -#if SUPPORT_INKJET - case 578: // Fire Inkjet bits - if (!AllMovesAreFinishedAndMoveBufferIsLoaded()) - { - return false; - } - - if (gb.Seen('S')) // Need to handle the 'P' parameter too; see http://reprap.org/wiki/G-code#M578:_Fire_inkjet_bits - { - platform->Inkjet(gb.GetIValue()); - } - break; -#endif - - case 579: // Scale Cartesian axes (mostly for Delta configurations) - { - bool seen = false; - for (size_t axis = 0; axis < numAxes; axis++) - { - gb.TryGetFValue(axisLetters[axis], axisScaleFactors[axis], seen); - } - - if (!seen) - { - char sep = ':'; - reply.copy("Axis scale factors"); - for(size_t axis = 0; axis < numAxes; axis++) - { - reply.catf("%c %c: %.3f", sep, axisLetters[axis], axisScaleFactors[axis]); - sep = ','; - } - } - } - break; - -#if SUPPORT_ROLAND - case 580: // (De)Select Roland mill - if (gb.Seen('R')) - { - if (gb.GetIValue()) - { - reprap.GetRoland()->Activate(); - if (gb.Seen('P')) - { - result = reprap.GetRoland()->RawWrite(gb.GetString()); - } - } - else - { - result = reprap.GetRoland()->Deactivate(); - } - } - else - { - reply.printf("Roland is %s.", reprap.GetRoland()->Active() ? "active" : "inactive"); - } - break; -#endif - - case 581: // Configure external trigger - case 582: // Check external trigger - if (gb.Seen('T')) - { - unsigned int triggerNumber = gb.GetIValue(); - if (triggerNumber < MaxTriggers) - { - if (code == 582) - { - uint32_t states = platform->GetAllEndstopStates(); - if ((triggers[triggerNumber].rising & states) != 0 || (triggers[triggerNumber].falling & ~states) != 0) - { - triggersPending |= (1u << triggerNumber); - } - } - else - { - bool seen = false; - if (gb.Seen('C')) - { - seen = true; - triggers[triggerNumber].condition = gb.GetIValue(); - } - else if (triggers[triggerNumber].IsUnused()) - { - triggers[triggerNumber].condition = 0; // this is a new trigger, so set no condition - } - if (gb.Seen('S')) - { - seen = true; - int sval = gb.GetIValue(); - TriggerMask triggerMask = 0; - for (size_t axis = 0; axis < numAxes; ++axis) - { - if (gb.Seen(axisLetters[axis])) - { - triggerMask |= (1u << axis); - } - } - if (gb.Seen(extrudeLetter)) - { - long eStops[MaxExtruders]; - size_t numEntries = MaxExtruders; - gb.GetLongArray(eStops, numEntries); - for (size_t i = 0; i < numEntries; ++i) - { - if (eStops[i] >= 0 && (unsigned long)eStops[i] < MaxExtruders) - { - triggerMask |= (1u << (eStops[i] + E0_AXIS)); - } - } - } - switch(sval) - { - case -1: - if (triggerMask == 0) - { - triggers[triggerNumber].rising = triggers[triggerNumber].falling = 0; - } - else - { - triggers[triggerNumber].rising &= (~triggerMask); - triggers[triggerNumber].falling &= (~triggerMask); - } - break; - - case 0: - triggers[triggerNumber].falling |= triggerMask; - break; - - case 1: - triggers[triggerNumber].rising |= triggerMask; - break; - - default: - platform->Message(GENERIC_MESSAGE, "Bad S parameter in M581 command\n"); - } - } - if (!seen) - { - reply.printf("Trigger %u fires on a rising edge on ", triggerNumber); - ListTriggers(reply, triggers[triggerNumber].rising); - reply.cat(" or a falling edge on "); - ListTriggers(reply, triggers[triggerNumber].falling); - reply.cat(" endstop inputs"); - if (triggers[triggerNumber].condition == 1) - { - reply.cat(" when printing from SD card"); - } - } - } - } - else - { - platform->Message(GENERIC_MESSAGE, "Trigger number out of range\n"); - } - } - break; - - case 584: // Set axis/extruder to stepper driver(s) mapping - if (!LockMovementAndWaitForStandstill(gb)) // we also rely on this to retrieve the current motor positions to moveBuffer - { - return false; - } - { - bool seen = false, badDrive = false; - for (size_t drive = 0; drive < MAX_AXES; ++drive) - { - if (gb.Seen(axisLetters[drive])) - { - seen = true; - size_t numValues = MaxDriversPerAxis; - long drivers[MaxDriversPerAxis]; - gb.GetLongArray(drivers, numValues); - - // Check all the driver numbers are in range - bool badAxis = false; - AxisDriversConfig config; - config.numDrivers = numValues; - for (size_t i = 0; i < numValues; ++i) - { - if ((unsigned long)drivers[i] >= DRIVES) - { - badAxis = true; - } - else - { - config.driverNumbers[i] = (uint8_t)drivers[i]; - } - } - if (badAxis) - { - badDrive = true; - } - else - { - while (numAxes <= drive) - { - moveBuffer.coords[numAxes] = 0.0; // user has defined a new axis, so set its position - ++numAxes; - } - SetPositions(moveBuffer.coords); // tell the Move system where any new axes are - platform->SetAxisDriversConfig(drive, config); - if (numAxes + numExtruders > DRIVES) - { - numExtruders = DRIVES - numAxes; // if we added axes, we may have fewer extruders now - } - } - } - } - - if (gb.Seen(extrudeLetter)) - { - seen = true; - size_t numValues = DRIVES - numAxes; - long drivers[MaxExtruders]; - gb.GetLongArray(drivers, numValues); - numExtruders = numValues; - for (size_t i = 0; i < numValues; ++i) - { - if ((unsigned long)drivers[i] >= DRIVES) - { - badDrive = true; - } - else - { - platform->SetExtruderDriver(i, (uint8_t)drivers[i]); - } - } - } - - if (badDrive) - { - platform->Message(GENERIC_MESSAGE, "Error: invalid drive number in M584 command\n"); - } - else if (!seen) - { - reply.copy("Driver assignments:"); - for (size_t drive = 0; drive < numAxes; ++ drive) - { - reply.cat(' '); - const AxisDriversConfig& axisConfig = platform->GetAxisDriversConfig(drive); - char c = axisLetters[drive]; - for (size_t i = 0; i < axisConfig.numDrivers; ++i) - { - reply.catf("%c%u", c, axisConfig.driverNumbers[i]); - c = ':'; - } - } - reply.cat(' '); - char c = extrudeLetter; - for (size_t extruder = 0; extruder < numExtruders; ++extruder) - { - reply.catf("%c%u", c, platform->GetExtruderDriver(extruder)); - c = ':'; - } - } - } - break; - - case 665: // Set delta configuration - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - { - float positionNow[DRIVES]; - Move *move = reprap.GetMove(); - move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes()); // get the current position, we may need it later - DeltaParameters& params = move->AccessDeltaParams(); - bool wasInDeltaMode = params.IsDeltaMode(); // remember whether we were in delta mode - bool seen = false; - - if (gb.Seen('L')) - { - params.SetDiagonal(gb.GetFValue() * distanceScale); - seen = true; - } - if (gb.Seen('R')) - { - params.SetRadius(gb.GetFValue() * distanceScale); - seen = true; - } - if (gb.Seen('B')) - { - params.SetPrintRadius(gb.GetFValue() * distanceScale); - seen = true; - } - if (gb.Seen('X')) - { - // X tower position correction - params.SetXCorrection(gb.GetFValue()); - seen = true; - } - if (gb.Seen('Y')) - { - // Y tower position correction - params.SetYCorrection(gb.GetFValue()); - seen = true; - } - if (gb.Seen('Z')) - { - // Y tower position correction - params.SetZCorrection(gb.GetFValue()); - seen = true; - } - - // The homed height must be done last, because it gets recalculated when some of the other factors are changed - if (gb.Seen('H')) - { - params.SetHomedHeight(gb.GetFValue() * distanceScale); - seen = true; - } - - if (seen) - { - move->SetCoreXYMode(0); // CoreXYMode needs to be zero when executing special moves on a delta - - // If we have changed between Cartesian and Delta mode, we need to reset the motor coordinates to agree with the XYZ coordinates. - // This normally happens only when we process the M665 command in config.g. Also flag that the machine is not homed. - if (params.IsDeltaMode() != wasInDeltaMode) - { - SetPositions(positionNow); - } - SetAllAxesNotHomed(); - } - else - { - if (params.IsDeltaMode()) - { - reply.printf("Diagonal %.3f, delta radius %.3f, homed height %.3f, bed radius %.1f" - ", X %.3f" DEGREE_SYMBOL ", Y %.3f" DEGREE_SYMBOL ", Z %.3f" DEGREE_SYMBOL, - params.GetDiagonal() / distanceScale, params.GetRadius() / distanceScale, - params.GetHomedHeight() / distanceScale, params.GetPrintRadius() / distanceScale, - params.GetXCorrection(), params.GetYCorrection(), params.GetZCorrection()); - } - else - { - reply.printf("Printer is not in delta mode"); - } - } - } - break; - - case 666: // Set delta endstop adjustments - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - { - DeltaParameters& params = reprap.GetMove()->AccessDeltaParams(); - bool seen = false; - if (gb.Seen('X')) - { - params.SetEndstopAdjustment(X_AXIS, gb.GetFValue()); - seen = true; - } - if (gb.Seen('Y')) - { - params.SetEndstopAdjustment(Y_AXIS, gb.GetFValue()); - seen = true; - } - if (gb.Seen('Z')) - { - params.SetEndstopAdjustment(Z_AXIS, gb.GetFValue()); - seen = true; - } - if (gb.Seen('A')) - { - params.SetXTilt(gb.GetFValue() * 0.01); - seen = true; - } - if (gb.Seen('B')) - { - params.SetYTilt(gb.GetFValue() * 0.01); - seen = true; - } - - if (seen) - { - SetAllAxesNotHomed(); - } - else - { - reply.printf("Endstop adjustments X%.2f Y%.2f Z%.2f, tilt X%.2f%% Y%.2f%%", - params.GetEndstopAdjustment(X_AXIS), params.GetEndstopAdjustment(Y_AXIS), params.GetEndstopAdjustment(Z_AXIS), - params.GetXTilt() * 100.0, params.GetYTilt() * 100.0); - } - } - break; - - case 667: // Set CoreXY mode - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - { - Move* move = reprap.GetMove(); - bool seen = false; - float positionNow[DRIVES]; - move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes()); // get the current position, we may need it later - if (gb.Seen('S')) - { - move->SetCoreXYMode(gb.GetIValue()); - seen = true; - } - for (size_t axis = 0; axis < numAxes; ++axis) - { - if (gb.Seen(axisLetters[axis])) - { - move->SetCoreAxisFactor(axis, gb.GetFValue()); - seen = true; - } - } - - if (seen) - { - SetPositions(positionNow); - SetAllAxesNotHomed(); - } - else - { - reply.printf("Printer mode is %s with axis factors", move->GetGeometryString()); - for (size_t axis = 0; axis < numAxes; ++axis) - { - reply.catf(" %c:%f", axisLetters[axis], move->GetCoreAxisFactor(axis)); - } - } - } - break; - - case 905: // Set current RTC date and time - { - const time_t now = platform->GetDateTime(); - struct tm * const timeInfo = gmtime(&now); - bool seen = false; - - if (gb.Seen('P')) - { - // Set date - const char * const dateString = gb.GetString(); - if (strptime(dateString, "%Y-%m-%d", timeInfo) != nullptr) - { - if (!platform->SetDate(mktime(timeInfo))) - { - reply.copy("Could not set date"); - error = true; - break; - } - } - else - { - reply.copy("Invalid date format"); - error = true; - break; - } - - seen = true; - } - - if (gb.Seen('S')) - { - // Set time - const char * const timeString = gb.GetString(); - if (strptime(timeString, "%H:%M:%S", timeInfo) != nullptr) - { - if (!platform->SetTime(mktime(timeInfo))) - { - reply.copy("Could not set time"); - error = true; - break; - } - } - else - { - reply.copy("Invalid time format"); - error = true; - break; - } - - seen = true; - } - - // TODO: Add correction parameters for SAM4E - - if (!seen) - { - // Report current date and time - reply.printf("Current date and time: %04u-%02u-%02u %02u:%02u:%02u", - timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday, - timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec); - - if (!platform->IsDateTimeSet()) - { - reply.cat("\nWarning: RTC has not been configured yet!"); - } - } - } - break; - - case 906: // Set/report Motor currents - case 913: // Set/report motor current percent - { - bool seen = false; - for (size_t axis = 0; axis < numAxes; axis++) - { - if (gb.Seen(axisLetters[axis])) - { - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - platform->SetMotorCurrent(axis, gb.GetFValue(), code == 913); - seen = true; - } - } - - if (gb.Seen(extrudeLetter)) - { - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - - float eVals[MaxExtruders]; - size_t eCount = numExtruders; - gb.GetFloatArray(eVals, eCount, true); - // 2014-09-29 DC42: we no longer insist that the user supplies values for all possible extruder drives - for (size_t e = 0; e < eCount; e++) - { - platform->SetMotorCurrent(numAxes + e, eVals[e], code == 913); - } - seen = true; - } - - if (code == 906 && gb.Seen('I')) - { - float idleFactor = gb.GetFValue(); - if (idleFactor >= 0 && idleFactor <= 100.0) - { - platform->SetIdleCurrentFactor(idleFactor/100.0); - seen = true; - } - } - - if (!seen) - { - reply.copy((code == 913) ? "Motor current % of normal - " : "Motor current (mA) - "); - for (size_t axis = 0; axis < numAxes; ++axis) - { - reply.catf("%c:%d, ", axisLetters[axis], (int)platform->GetMotorCurrent(axis, code == 913)); - } - reply.cat("E"); - for (size_t extruder = 0; extruder < numExtruders; extruder++) - { - reply.catf(":%d", (int)platform->GetMotorCurrent(extruder + numAxes, code == 913)); - } - if (code == 906) - { - reply.catf(", idle factor %d%%", (int)(platform->GetIdleCurrentFactor() * 100.0)); - } - } - } - break; - - case 911: // Set power monitor threshold voltages - reply.printf("M911 not implemented yet"); - break; - - case 912: // Set electronics temperature monitor adjustment - // Currently we ignore the P parameter (i.e. temperature measurement channel) - if (gb.Seen('S')) - { - platform->SetMcuTemperatureAdjust(gb.GetFValue()); - } - else - { - reply.printf("MCU temperature calibration adjustment is %.1f" DEGREE_SYMBOL "C", platform->GetMcuTemperatureAdjust()); - } - break; - - // For case 913, see 906 - - case 997: // Perform firmware update - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - reprap.GetHeat()->SwitchOffAll(); // turn all heaters off because the main loop may get suspended - DisableDrives(); // all motors off - - if (firmwareUpdateModuleMap == 0) // have we worked out which modules to update? - { - // Find out which modules we have been asked to update - if (gb.Seen('S')) - { - long modulesToUpdate[3]; - size_t numUpdateModules = ARRAY_SIZE(modulesToUpdate); - gb.GetLongArray(modulesToUpdate, numUpdateModules); - for (size_t i = 0; i < numUpdateModules; ++i) - { - long t = modulesToUpdate[i]; - if (t < 0 || (unsigned long)t >= NumFirmwareUpdateModules) - { - platform->MessageF(GENERIC_MESSAGE, "Invalid module number '%ld'\n", t); - firmwareUpdateModuleMap = 0; - break; - } - firmwareUpdateModuleMap |= (1u << (unsigned int)t); - } - } - else - { - firmwareUpdateModuleMap = (1u << 0); // no modules specified, so update module 0 to match old behaviour - } - - if (firmwareUpdateModuleMap == 0) - { - break; // nothing to update - } - - // Check prerequisites of all modules to be updated, if any are not met then don't update any of them -#ifdef DUET_NG - if (!FirmwareUpdater::CheckFirmwareUpdatePrerequisites(firmwareUpdateModuleMap)) - { - firmwareUpdateModuleMap = 0; - break; - } -#endif - if ((firmwareUpdateModuleMap & 1) != 0 && !platform->CheckFirmwareUpdatePrerequisites()) - { - firmwareUpdateModuleMap = 0; - break; - } - } - - // If we get here then we have the module map, and all prerequisites are satisfied - isFlashing = true; // this tells the web interface and PanelDue that we are about to flash firmware - if (!DoDwellTime(1.0)) // wait a second so all HTTP clients and PanelDue are notified - { - return false; - } - - gb.SetState(GCodeState::flashing1); - break; - - case 998: - // The input handling code replaces the gcode by this when it detects a checksum error. - // Since we have no way of asking for the line to be re-sent, just report an error. - if (gb.Seen('P')) - { - int val = gb.GetIValue(); - if (val != 0) - { - reply.printf("Checksum error on line %d", val); - } - } - break; - - case 999: - result = DoDwellTime(0.5); // wait half a second to allow the response to be sent back to the web server, otherwise it may retry - if (result) - { - reprap.EmergencyStop(); // this disables heaters and drives - Duet WiFi pre-production boards need drives disabled here - uint16_t reason = (gb.Seen('P') && StringStartsWith(gb.GetString(), "ERASE")) - ? (uint16_t)SoftwareResetReason::erase - : (uint16_t)SoftwareResetReason::user; - platform->SoftwareReset(reason); // doesn't return - } - break; - - default: - error = true; - reply.printf("unsupported command: %s", gb.Buffer()); - } - - if (result && gb.GetState() == GCodeState::normal) - { - UnlockAll(gb); - HandleReply(gb, error, reply.Pointer()); - } - return result; -} - -bool GCodes::HandleTcode(GCodeBuffer& gb, StringRef& reply) -{ - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - - newToolNumber = gb.GetIValue(); - newToolNumber += gb.GetToolNumberAdjust(); - - // TODO for the tool change restore point to be useful, we should undo any X axis mapping and remove any tool offsets - for (size_t drive = 0; drive < DRIVES; ++drive) - { - toolChangeRestorePoint.moveCoords[drive] = moveBuffer.coords[drive]; - } - toolChangeRestorePoint.feedRate = feedRate; - - if (simulationMode == 0) // we don't yet simulate any T codes - { - const Tool * const oldTool = reprap.GetCurrentTool(); - // If old and new are the same we no longer follow the sequence. User can deselect and then reselect the tool if he wants the macros run. - if (oldTool->Number() != newToolNumber) - { - gb.SetState(GCodeState::toolChange1); - if (oldTool != nullptr && AllAxesAreHomed()) - { - scratchString.printf("tfree%d.g", oldTool->Number()); - DoFileMacro(gb, scratchString.Pointer(), false); - } - return true; // proceeding with state machine, so don't unlock or send a reply - } - } - - // If we get here, we have finished - UnlockAll(gb); - HandleReply(gb, false, ""); - 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() -{ - moveAvailable = false; - 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 -bool GCodes::LockResource(const GCodeBuffer& gb, Resource r) -{ - if (resourceOwners[r] == &gb) - { - return true; - } - if (resourceOwners[r] == nullptr) - { - resourceOwners[r] = &gb; - gb.MachineState().lockedResources |= (1 << 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); -} - -// Lock movement and wait for pending moves to finish -bool GCodes::LockMovementAndWaitForStandstill(const GCodeBuffer& gb) -{ - bool b = LockMovement(gb); - if (b) - { - b = AllMovesAreFinishedAndMoveBufferIsLoaded(); - } - return b; -} - -// 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 && ((1 << i) & resourcesToKeep) == 0) - { - resourceOwners[i] = nullptr; - gb.MachineState().lockedResources &= ~(1 << 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 diff --git a/src/GCodes/GCodes.h b/src/GCodes/GCodes.h index c6b6b476..eee3cd72 100644 --- a/src/GCodes/GCodes.h +++ b/src/GCodes/GCodes.h @@ -33,9 +33,6 @@ typedef uint16_t EndstopChecks; // must be large enough to hold a bitmap o const EndstopChecks ZProbeActive = 1 << 15; // must be distinct from 1 << (any drive number) const EndstopChecks LogProbeChanges = 1 << 14; // must be distinct from 1 << (any drive number) -const float minutesToSeconds = 60.0; -const float secondsToMinutes = 1.0/minutesToSeconds; - typedef uint16_t TriggerMask; struct Trigger @@ -66,6 +63,7 @@ public: struct RawMove { float coords[DRIVES]; // new positions for the axes, amount of movement for the extruders + float initialCoords[MAX_AXES]; // the initial positions of the axes float feedRate; // feed rate of this move FilePosition filePos; // offset in the file being printed that this move was read from uint32_t xAxes; // axes that X is mapped to @@ -156,7 +154,6 @@ private: void StartNextGCode(GCodeBuffer& gb, StringRef& reply); // Fetch a new or old GCode and process it void DoFilePrint(GCodeBuffer& gb, StringRef& reply); // Get G Codes from a file and print them - bool AllMovesAreFinishedAndMoveBufferIsLoaded(); // Wait for move queue to exhaust and the current position is loaded bool DoFileMacro(GCodeBuffer& gb, const char* fileName, bool reportMissing = true); // Run a GCode macro in a file, optionally report error if not found bool DoCannedCycleMove(GCodeBuffer& gb, EndstopChecks ce); // Do a move from an internally programmed canned cycle void FileMacroCyclesReturn(GCodeBuffer& gb); // End a macro @@ -176,7 +173,7 @@ private: bool SetPrintZProbe(GCodeBuffer& gb, StringRef& reply); // Either return the probe value, or set its threshold bool SetOrReportOffsets(GCodeBuffer& gb, StringRef& reply); // Deal with a G10 bool SetPositions(GCodeBuffer& gb); // Deal with a G92 - bool LoadMoveBufferFromGCode(GCodeBuffer& gb, int moveType); // Set up a move for the Move class + unsigned int LoadMoveBufferFromGCode(GCodeBuffer& gb, int moveType); // Set up a move for the Move class bool NoHome() const; // Are we homing and not finished? bool Push(GCodeBuffer& gb); // Push feedrate etc on the stack void Pop(GCodeBuffer& gb); // Pop feedrate etc @@ -185,17 +182,18 @@ private: void SetMACAddress(GCodeBuffer& gb); // Deals with an M540 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, // Start saving GCodes in a file - const char* fileName); + 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 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 + bool ToolHeatersAtSetTemperatures(const Tool *tool, bool waitWhenCooling) const; // Wait for the heaters associated with the specified tool to reach their set temperatures + void StartToolChange(GCodeBuffer& gb, bool inM109); // Begin the tool change sequence + void SetAllAxesNotHomed(); // Flag all axes as not homed void SetPositions(float positionNow[DRIVES]); // Set the current position to be this const char *TranslateEndStopResult(EndStopHit es); // Translate end stop result to text @@ -204,11 +202,15 @@ private: void ListTriggers(StringRef reply, TriggerMask mask); // Append a list of trigger endstops to a message void CheckTriggers(); // Check for and execute triggers void DoEmergencyStop(); // Execute an emergency stop - void DoPause(GCodeBuffer& gb); // Pause the print + + void DoPause(GCodeBuffer& gb) // Pause the print + pre(resourceOwners[movementResource] = &gb); + void SetMappedFanSpeed(); // Set the speeds of fans mapped for the current tool bool DefineGrid(GCodeBuffer& gb, StringRef &reply); // Define the probing grid, returning true if error bool ProbeGrid(GCodeBuffer& gb, StringRef& reply); // Start probing the grid, returning true if we didn't because of an error + bool SaveHeightMapToFile(StringRef& reply) const; // Save the height map to file static uint32_t LongArrayToBitMap(const long *arr, size_t numEntries); // Convert an array of longs to a bit map @@ -230,9 +232,8 @@ private: bool active; // Live and running? bool isPaused; // true if the print has been paused bool dwellWaiting; // We are in a dwell - bool moveAvailable; // Have we seen a move G Code and set it up? + unsigned int segmentsLeft; // The number of segments left to do in the current move, or 0 if no move available float dwellTime; // How long a pause for a dwell (seconds)? - float feedRate; // The feed rate of the last G0/G1 command that had an F parameter RawMove moveBuffer; // Move details to pass to Move class RestorePoint simulationRestorePoint; // The position and feed rate when we started a simulation RestorePoint pauseRestorePoint; // The position and feed rate when we paused the print @@ -269,6 +270,7 @@ private: // Z probe float lastProbedZ; // the last height at which the Z probe stopped + uint32_t lastProbedTime; // time in milliseconds that the probe was last triggered bool zProbesSet; // True if all Z probing is done and we can set the bed equation volatile bool zProbeTriggered; // Set by the step ISR when a move is aborted because the Z probe is triggered size_t gridXindex, gridYindex; // Which grid probe point is next 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 diff --git a/src/GCodes/GCodes2.cpp b/src/GCodes/GCodes2.cpp new file mode 100644 index 00000000..5c7ef148 --- /dev/null +++ b/src/GCodes/GCodes2.cpp @@ -0,0 +1,3481 @@ +/* + * GCodes2.cpp + * + * Created on: 3 Dec 2016 + * Author: David + * + * This file contains the code to see what G, M or T command we have and start processing it. + */ + +#include "RepRapFirmware.h" + +#ifdef DUET_NG +#include "FirmwareUpdater.h" +#endif + +const char* BED_EQUATION_G = "bed.g"; +const char* RESUME_G = "resume.g"; +const char* CANCEL_G = "cancel.g"; +const char* STOP_G = "stop.g"; +const char* SLEEP_G = "sleep.g"; + +const float MinServoPulseWidth = 544.0, MaxServoPulseWidth = 2400.0; +const uint16_t ServoRefreshFrequency = 50; + +// If the code to act on is completed, this returns true, +// otherwise false. It is called repeatedly for a given +// code until it returns true for that code. +bool GCodes::ActOnCode(GCodeBuffer& gb, StringRef& reply) +{ + // Discard empty buffers right away + if (gb.IsEmpty()) + { + return true; + } + + // M-code parameters might contain letters T and G, e.g. in filenames. + // dc42 assumes that G-and T-code parameters never contain the letter M. + // Therefore we must check for an M-code first. + if (gb.Seen('M')) + { + return HandleMcode(gb, reply); + } + // dc42 doesn't think a G-code parameter ever contains letter T, or a T-code ever contains letter G. + // So it doesn't matter in which order we look for them. + if (gb.Seen('G')) + { + return HandleGcode(gb, reply); + } + if (gb.Seen('T')) + { + return HandleTcode(gb, reply); + } + + // An invalid or queued buffer gets discarded + HandleReply(gb, false, ""); + return true; +} + +bool GCodes::HandleGcode(GCodeBuffer& gb, StringRef& reply) +{ + bool result = true; + bool error = false; + + int code = gb.GetIValue(); + if (simulationMode != 0 && code != 0 && code != 1 && code != 4 && code != 10 && code != 20 && code != 21 && code != 90 && code != 91 && code != 92) + { + return true; // we only simulate some gcodes + } + + switch (code) + { + case 0: // There are no rapid moves... + case 1: // Ordinary move + if (!LockMovement(gb)) + { + return false; + } + { + // Check for 'R' parameter here to go back to the coordinates at which the print was paused + // NOTE: restore point 2 (tool change) won't work when changing tools on dual axis machines because of X axis mapping. + // We could possibly fix this by saving the virtual X axis position instead of the physical axis positions. + // However, slicers normally command the tool to the correct place after a tool change, so we don't need this feature anyway. + int rParam = (gb.Seen('R')) ? gb.GetIValue() : 0; + RestorePoint *rp = (rParam == 1) ? &pauseRestorePoint : (rParam == 2) ? &toolChangeRestorePoint : nullptr; + if (rp != nullptr) + { + if (segmentsLeft != 0) + { + return false; + } + for (size_t axis = 0; axis < numAxes; ++axis) + { + float offset = gb.Seen(axisLetters[axis]) ? gb.GetFValue() * distanceScale : 0.0; + moveBuffer.coords[axis] = rp->moveCoords[axis] + offset; + } + // For now we don't handle extrusion at the same time + for (size_t drive = numAxes; drive < DRIVES; ++drive) + { + moveBuffer.coords[drive] = 0.0; + } + moveBuffer.feedRate = (gb.Seen(feedrateLetter)) ? gb.GetFValue() : gb.MachineState().feedrate; + moveBuffer.filePos = noFilePosition; + moveBuffer.usePressureAdvance = false; + segmentsLeft = 1; + } + else + { + int res = SetUpMove(gb, reply); + if (res == 2) + { + gb.SetState(GCodeState::waitingForMoveToComplete); + } + result = (res != 0); + } + } + break; + + case 4: // Dwell + result = DoDwell(gb); + break; + + case 10: // Set/report offsets and temperatures, or retract + if (gb.Seen('P')) + { + if (!SetOrReportOffsets(gb, reply)) + { + return false; + } + } + else + { + if (!LockMovement(gb)) + { + return false; + } + result = RetractFilament(true); + } + break; + + case 11: // Un-retract + if (!LockMovement(gb)) + { + return false; + } + result = RetractFilament(false); + break; + + case 20: // Inches (which century are we living in, here?) + distanceScale = INCH_TO_MM; + break; + + case 21: // mm + distanceScale = 1.0; + break; + + case 28: // Home + result = DoHome(gb, reply, error); + break; + + case 29: // Grid-based bed probing + if (!LockMovementAndWaitForStandstill(gb)) // do this first to make sure that a new grid isn't being defined + { + return false; + } + error = ProbeGrid(gb, reply); + break; + + case 30: // Z probe/manually set at a position and set that as point P + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + if (reprap.GetMove()->IsDeltaMode() && !AllAxesAreHomed()) + { + reply.copy("Must home a delta printer before bed probing"); + error = true; + } + else + { + result = SetSingleZProbeAtAPosition(gb, reply); + } + break; + + case 31: // Return the probe value, or set probe variables + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + result = SetPrintZProbe(gb, reply); + break; + + case 32: // Probe Z at multiple positions and generate the bed transform + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + + // Try to execute bed.g + if (!DoFileMacro(gb, BED_EQUATION_G, reprap.GetMove()->IsDeltaMode())) + { + // If we get here then we are not on a delta printer and there is no bed.g file + if (GetAxisIsHomed(X_AXIS) && GetAxisIsHomed(Y_AXIS)) + { + gb.SetState(GCodeState::setBed1); // no bed.g file, so use the coordinates specified by M557 + } + else + { + // We can only do bed levelling if X and Y have already been homed + reply.copy("Must home X and Y before bed probing"); + error = true; + } + } + break; + + case 90: // Absolute coordinates + gb.MachineState().axesRelative = false; + break; + + case 91: // Relative coordinates + gb.MachineState().axesRelative = true; // Axis movements (i.e. X, Y and Z) + break; + + case 92: // Set position + result = SetPositions(gb); + break; + + default: + error = true; + reply.printf("invalid G Code: %s", gb.Buffer()); + } + + if (result && gb.GetState() == GCodeState::normal) + { + UnlockAll(gb); + HandleReply(gb, error, reply.Pointer()); + } + return result; +} + +bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) +{ + bool result = true; + bool error = false; + + const int code = gb.GetIValue(); + if (simulationMode != 0 && (code < 20 || code > 37) && code != 0 && code != 1 && code != 82 && code != 83 && code != 105 && code != 111 && code != 112 && code != 122 && code != 408 && code != 999) + { + return true; // we don't yet simulate most M codes + } + + switch (code) + { + case 0: // Stop + case 1: // Sleep + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + { + bool wasPaused = isPaused; // isPaused gets cleared by CancelPrint + CancelPrint(); + if (wasPaused) + { + reply.copy("Print cancelled"); + // If we are cancelling a paused print with M0 and cancel.g exists then run it and do nothing else + if (code == 0) + { + if (DoFileMacro(gb, CANCEL_G, false)) + { + break; + } + } + } + } + + gb.SetState((code == 0) ? GCodeState::stopping : GCodeState::sleeping); + DoFileMacro(gb, (code == 0) ? STOP_G : SLEEP_G, false); + break; + +#if SUPPORT_ROLAND + case 3: // Spin spindle + if (reprap.GetRoland()->Active()) + { + if (gb.Seen('S')) + { + result = reprap.GetRoland()->ProcessSpindle(gb.GetFValue()); + } + } + break; +#endif + + case 18: // Motors off + case 84: + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + { + bool seen = false; + for (size_t axis = 0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + SetAxisNotHomed(axis); + platform->DisableDrive(axis); + seen = true; + } + } + + if (gb.Seen(extrudeLetter)) + { + long int eDrive[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetLongArray(eDrive, eCount); + for (size_t i = 0; i < eCount; i++) + { + seen = true; + if (eDrive[i] < 0 || (size_t)eDrive[i] >= numExtruders) + { + reply.printf("Invalid extruder number specified: %ld", eDrive[i]); + error = true; + break; + } + platform->DisableDrive(numAxes + eDrive[i]); + } + } + + if (gb.Seen('S')) + { + seen = true; + + float idleTimeout = gb.GetFValue(); + if (idleTimeout < 0.0) + { + reply.copy("Idle timeouts cannot be negative!"); + error = true; + } + else + { + reprap.GetMove()->SetIdleTimeout(idleTimeout); + } + } + + if (!seen) + { + DisableDrives(); + } + } + break; + + case 20: // List files on SD card + if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers + { + return false; + } + { + OutputBuffer *fileResponse; + const int sparam = (gb.Seen('S')) ? gb.GetIValue() : 0; + const char* dir = (gb.Seen('P')) ? gb.GetString() : platform->GetGCodeDir(); + + if (sparam == 2) + { + fileResponse = reprap.GetFilesResponse(dir, true); // Send the file list in JSON format + fileResponse->cat('\n'); + } + else + { + if (!OutputBuffer::Allocate(fileResponse)) + { + // Cannot allocate an output buffer, try again later + return false; + } + + // To mimic the behaviour of the official RepRapPro firmware: + // If we are emulating RepRap then we print "GCode files:\n" at the start, otherwise we don't. + // If we are emulating Marlin and the code came via the serial/USB interface, then we don't put quotes around the names and we separate them with newline; + // otherwise we put quotes around them and separate them with comma. + if (platform->Emulating() == me || platform->Emulating() == reprapFirmware) + { + fileResponse->copy("GCode files:\n"); + } + + bool encapsulateList = ((&gb != serialGCode && &gb != telnetGCode) || platform->Emulating() != marlin); + FileInfo fileInfo; + if (platform->GetMassStorage()->FindFirst(dir, fileInfo)) + { + // iterate through all entries and append each file name + do { + if (encapsulateList) + { + fileResponse->catf("%c%s%c%c", FILE_LIST_BRACKET, fileInfo.fileName, FILE_LIST_BRACKET, FILE_LIST_SEPARATOR); + } + else + { + fileResponse->catf("%s\n", fileInfo.fileName); + } + } while (platform->GetMassStorage()->FindNext(fileInfo)); + + if (encapsulateList) + { + // remove the last separator + (*fileResponse)[fileResponse->Length() - 1] = 0; + } + } + else + { + fileResponse->cat("NONE\n"); + } + } + + UnlockAll(gb); + HandleReply(gb, false, fileResponse); + return true; + } + + case 21: // Initialise SD card + if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers + { + return false; + } + { + size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0; + result = platform->GetMassStorage()->Mount(card, reply, true); + } + break; + + case 22: // Release SD card + if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers + { + return false; + } + { + size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0; + result = platform->GetMassStorage()->Unmount(card, reply); + } + break; + + case 23: // Set file to print + case 32: // Select file and start SD print + if (fileGCode->OriginalMachineState().fileState.IsLive()) + { + reply.copy("Cannot set file to print, because a file is already being printed"); + error = true; + break; + } + + if (code == 32 && !LockMovementAndWaitForStandstill(gb)) + { + return false; + } + { + const char* filename = gb.GetUnprecedentedString(); + if (filename != nullptr) + { + QueueFileToPrint(filename); + if (fileToPrint.IsLive()) + { + reprap.GetPrintMonitor()->StartingPrint(filename); + if (platform->Emulating() == marlin && (&gb == serialGCode || &gb == telnetGCode)) + { + reply.copy("File opened\nFile selected"); + } + else + { + // Command came from web interface or PanelDue, or not emulating Marlin, so send a nicer response + reply.printf("File %s selected for printing", filename); + } + + if (code == 32) + { + fileGCode->OriginalMachineState().fileState.MoveFrom(fileToPrint); + reprap.GetPrintMonitor()->StartedPrint(); + } + } + else + { + reply.printf("Failed to open file %s", filename); + } + } + } + break; + + case 24: // Print/resume-printing the selected file + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + + if (isPaused) + { + gb.SetState(GCodeState::resuming1); + DoFileMacro(gb, RESUME_G); + } + else if (!fileToPrint.IsLive()) + { + reply.copy("Cannot print, because no file is selected!"); + error = true; + } + else + { + fileGCode->OriginalMachineState().fileState.MoveFrom(fileToPrint); + reprap.GetPrintMonitor()->StartedPrint(); + } + break; + + case 226: // Gcode Initiated Pause + if (&gb == fileGCode) // ignore M226 if it did't come from within a file being printed + { + if (!LockMovement(gb)) // lock movement before calling DoPause + { + return false; + } + DoPause(gb); + } + break; + + case 25: // Pause the print + if (isPaused) + { + reply.copy("Printing is already paused!!"); + error = true; + } + else if (!reprap.GetPrintMonitor()->IsPrinting()) + { + reply.copy("Cannot pause print, because no file is being printed!"); + error = true; + } + else + { + if (!LockMovement(gb)) // lock movement before calling DoPause + { + return false; + } + DoPause(gb); + } + break; + + case 26: // Set SD position + if (gb.Seen('S')) + { + const FilePosition value = gb.GetIValue(); + if (value < 0) + { + reply.copy("SD positions can't be negative!"); + error = true; + } + else if (fileGCode->OriginalMachineState().fileState.IsLive()) + { + if (!fileGCode->OriginalMachineState().fileState.Seek(value)) + { + reply.copy("The specified SD position is invalid!"); + error = true; + } + } + else if (fileToPrint.IsLive()) + { + if (!fileToPrint.Seek(value)) + { + reply.copy("The specified SD position is invalid!"); + error = true; + } + } + else + { + reply.copy("Cannot set SD file position, because no print is in progress!"); + error = true; + } + } + else + { + reply.copy("You must specify the SD position in bytes using the S parameter."); + error = true; + } + break; + + case 27: // Report print status - Deprecated + if (reprap.GetPrintMonitor()->IsPrinting()) + { + // Pronterface keeps sending M27 commands if "Monitor status" is checked, and it specifically expects the following response syntax + FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState; + reply.printf("SD printing byte %lu/%lu", fileBeingPrinted.GetPosition(), fileBeingPrinted.Length()); + } + else + { + reply.copy("Not SD printing."); + } + break; + + case 28: // Write to file + { + const char* str = gb.GetUnprecedentedString(); + if (str != nullptr) + { + bool ok = OpenFileToWrite(gb, platform->GetGCodeDir(), 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 29: // End of file being written; should be intercepted before getting here + reply.copy("GCode end-of-file being interpreted."); + break; + + case 30: // Delete file + { + const char *filename = gb.GetUnprecedentedString(); + if (filename != nullptr) + { + DeleteFile(filename); + } + } + break; + + // For case 32, see case 24 + + case 36: // Return file information + if (!LockFileSystem(gb)) // getting file info takes several calls and isn't reentrant + { + return false; + } + { + const char* filename = gb.GetUnprecedentedString(true); // get filename, or nullptr if none provided + OutputBuffer *fileInfoResponse; + result = reprap.GetPrintMonitor()->GetFileInfoResponse(filename, fileInfoResponse); + if (result) + { + fileInfoResponse->cat('\n'); + UnlockAll(gb); + HandleReply(gb, false, fileInfoResponse); + return true; + } + } + break; + + case 37: // Simulation mode on/off + if (gb.Seen('S')) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + + bool wasSimulating = (simulationMode != 0); + simulationMode = (uint8_t)gb.GetIValue(); + reprap.GetMove()->Simulate(simulationMode); + + if (simulationMode != 0) + { + simulationTime = 0.0; + if (!wasSimulating) + { + // Starting a new simulation, so save the current position + reprap.GetMove()->GetCurrentUserPosition(simulationRestorePoint.moveCoords, 0, reprap.GetCurrentXAxes()); + simulationRestorePoint.feedRate = gb.MachineState().feedrate; + } + } + else if (wasSimulating) + { + // Ending a simulation, so restore the position + SetPositions(simulationRestorePoint.moveCoords); + for (size_t i = 0; i < DRIVES; ++i) + { + moveBuffer.coords[i] = simulationRestorePoint.moveCoords[i]; + } + gb.MachineState().feedrate = simulationRestorePoint.feedRate; + } + } + else + { + reply.printf("Simulation mode: %s, move time: %.1f sec, other time: %.1f sec", + (simulationMode != 0) ? "on" : "off", reprap.GetMove()->GetSimulationTime(), simulationTime); + } + break; + + case 38: // Report SHA1 of file + if (!LockFileSystem(gb)) // getting file hash takes several calls and isn't reentrant + { + return false; + } + if (fileBeingHashed == nullptr) + { + // See if we can open the file and start hashing + const char* filename = gb.GetUnprecedentedString(true); + if (StartHash(filename)) + { + // Hashing is now in progress... + result = false; + } + else + { + error = true; + reply.printf("Cannot open file: %s", filename); + } + } + else + { + // This can take some time. All the actual heavy lifting is in dedicated methods + result = AdvanceHash(reply); + } + break; + + case 42: // Turn an output pin on or off + if (gb.Seen('P')) + { + const int logicalPin = gb.GetIValue(); + Pin pin; + bool invert; + if (platform->GetFirmwarePin(logicalPin, PinAccess::pwm, pin, invert)) + { + if (gb.Seen('S')) + { + float val = gb.GetFValue(); + if (val > 1.0) + { + val /= 255.0; + } + val = constrain<float>(val, 0.0, 1.0); + if (invert) + { + val = 1.0 - val; + } + Platform::WriteAnalog(pin, val, DefaultPinWritePwmFreq); + } + // Ignore the command if no S parameter provided + } + else + { + reply.printf("Logical pin %d is not available for writing", logicalPin); + error = true; + } + } + break; + + case 80: // ATX power on + platform->SetAtxPower(true); + break; + + case 81: // ATX power off + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + platform->SetAtxPower(false); + break; + + case 82: // Use absolute extruder positioning + if (gb.MachineState().drivesRelative) // don't reset the absolute extruder position if it was already absolute + { + for (size_t extruder = 0; extruder < MaxExtruders; extruder++) + { + lastRawExtruderPosition[extruder] = 0.0; + } + gb.MachineState().drivesRelative = false; + } + break; + + case 83: // Use relative extruder positioning + if (!gb.MachineState().drivesRelative) // don't reset the absolute extruder position if it was already relative + { + for (size_t extruder = 0; extruder < MaxExtruders; extruder++) + { + lastRawExtruderPosition[extruder] = 0.0; + } + gb.MachineState().drivesRelative = true; + } + break; + + // For case 84, see case 18 + + case 85: // Set inactive time + break; + + case 92: // Set/report steps/mm for some axes + { + // Save the current positions as we may need them later + float positionNow[DRIVES]; + Move *move = reprap.GetMove(); + move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes()); + + bool seen = false; + for (size_t axis = 0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + platform->SetDriveStepsPerUnit(axis, gb.GetFValue()); + seen = true; + } + } + + if (gb.Seen(extrudeLetter)) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + seen = true; + float eVals[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetFloatArray(eVals, eCount, true); + + // The user may not have as many extruders as we allow for, so just set the ones for which a value is provided + for (size_t e = 0; e < eCount; e++) + { + platform->SetDriveStepsPerUnit(numAxes + e, eVals[e]); + } + } + + if (seen) + { + // On a delta, if we change the drive steps/mm then we need to recalculate the motor positions + SetPositions(positionNow); + } + else + { + reply.copy("Steps/mm: "); + for (size_t axis = 0; axis < numAxes; ++axis) + { + reply.catf("%c: %.3f, ", axisLetters[axis], platform->DriveStepsPerUnit(axis)); + } + reply.catf("E:"); + char sep = ' '; + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + reply.catf("%c%.3f", sep, platform->DriveStepsPerUnit(extruder + numAxes)); + sep = ':'; + } + } + } + break; + + case 98: // Call Macro/Subprogram + if (gb.Seen('P')) + { + DoFileMacro(gb, gb.GetString()); + } + break; + + case 99: // Return from Macro/Subprogram + FileMacroCyclesReturn(gb); + break; + + case 104: // Deprecated. This sets the active temperature of every heater of the active tool + if (gb.Seen('S')) + { + float temperature = gb.GetFValue(); + Tool* tool; + if (gb.Seen('T')) + { + int toolNumber = gb.GetIValue(); + toolNumber += gb.GetToolNumberAdjust(); + tool = reprap.GetTool(toolNumber); + } + else + { + tool = reprap.GetCurrentOrDefaultTool(); + } + SetToolHeaters(tool, temperature); + } + break; + + case 105: // Get temperatures + { + const int8_t bedHeater = reprap.GetHeat()->GetBedHeater(); + const int8_t chamberHeater = reprap.GetHeat()->GetChamberHeater(); + reply.copy("T:"); + for (int8_t heater = 0; heater < HEATERS; heater++) + { + if (heater != bedHeater && heater != chamberHeater) + { + Heat::HeaterStatus hs = reprap.GetHeat()->GetStatus(heater); + if (hs != Heat::HS_off && hs != Heat::HS_fault) + { + reply.catf("%.1f ", reprap.GetHeat()->GetTemperature(heater)); + } + } + } + if (bedHeater >= 0) + { + reply.catf("B:%.1f", reprap.GetHeat()->GetTemperature(bedHeater)); + } + else + { + // I'm not sure whether Pronterface etc. can handle a missing bed temperature, so return zero + reply.cat("B:0.0"); + } + if (chamberHeater >= 0.0) + { + reply.catf(" C:%.1f", reprap.GetHeat()->GetTemperature(chamberHeater)); + } + } + break; + + case 106: // Set/report fan values + { + bool seenFanNum = false; + int32_t fanNum = 0; // Default to the first fan + gb.TryGetIValue('P', fanNum, seenFanNum); + if (fanNum < 0 || fanNum > (int)NUM_FANS) + { + reply.printf("Fan number %d is invalid, must be between 0 and %u", fanNum, NUM_FANS); + } + else + { + bool seen = false; + Fan& fan = platform->GetFan(fanNum); + + if (gb.Seen('I')) // Invert cooling + { + const int invert = gb.GetIValue(); + if (invert < 0) + { + fan.Disable(); + } + else + { + fan.SetInverted(invert > 0); + } + seen = true; + } + + if (gb.Seen('F')) // Set PWM frequency + { + fan.SetPwmFrequency(gb.GetFValue()); + seen = true; + } + + if (gb.Seen('T')) // Set thermostatic trigger temperature + { + seen = true; + fan.SetTriggerTemperature(gb.GetFValue()); + } + + if (gb.Seen('B')) // Set blip time + { + seen = true; + fan.SetBlipTime(gb.GetFValue()); + } + + if (gb.Seen('L')) // Set minimum speed + { + seen = true; + fan.SetMinValue(gb.GetFValue()); + } + + if (gb.Seen('H')) // Set thermostatically-controller heaters + { + seen = true; + long heaters[HEATERS]; + size_t numH = HEATERS; + gb.GetLongArray(heaters, numH); + // Note that M106 H-1 disables thermostatic mode. The following code implements that automatically. + uint16_t hh = 0; + for (size_t h = 0; h < numH; ++h) + { + const int hnum = heaters[h]; + if (hnum >= 0 && hnum < HEATERS) + { + hh |= (1u << (unsigned int)hnum); + } + } + if (hh != 0) + { + platform->SetFanValue(fanNum, 1.0); // default the fan speed to full for safety + } + fan.SetHeatersMonitored(hh); + } + + if (gb.Seen('S')) // Set new fan value - process this after processing 'H' or it may not be acted on + { + const float f = constrain<float>(gb.GetFValue(), 0.0, 255.0); + if (seen || seenFanNum) + { + platform->SetFanValue(fanNum, f); + } + else + { + // We are processing an M106 S### command with no other recognised parameters and we have a tool selected. + // Apply the fan speed setting to the fans in the fan mapping for the current tool. + lastDefaultFanSpeed = f; + SetMappedFanSpeed(); + } + } + else if (gb.Seen('R')) + { + const int i = gb.GetIValue(); + switch(i) + { + case 0: + case 1: + // Restore fan speed to value when print was paused + platform->SetFanValue(fanNum, pausedFanValues[fanNum]); + break; + case 2: + // Set the speeds of mapped fans to the last known value. Fan number is ignored. + SetMappedFanSpeed(); + break; + default: + break; + } + } + else if (!seen) + { + reply.printf("Fan%i frequency: %dHz, speed: %d%%, min: %d%%, blip: %.2f, inverted: %s", + fanNum, + (int)(fan.GetPwmFrequency()), + (int)(fan.GetValue() * 100.0), + (int)(fan.GetMinValue() * 100.0), + fan.GetBlipTime(), + (fan.GetInverted()) ? "yes" : "no"); + uint16_t hh = fan.GetHeatersMonitored(); + if (hh != 0) + { + reply.catf(", trigger: %dC, heaters:", (int)fan.GetTriggerTemperature()); + for (unsigned int i = 0; i < HEATERS; ++i) + { + if ((hh & (1u << i)) != 0) + { + reply.catf(" %u", i); + } + } + } + } + } + } + break; + + case 107: // Fan off - deprecated + platform->SetFanValue(0, 0.0); //T3P3 as deprecated only applies to fan0 + break; + + case 108: // Cancel waiting for temperature + if (isWaiting) + { + cancelWait = true; + } + break; + + case 109: // Deprecated in RRF, but widely generated by slicers + { + float temperature; + if (gb.Seen('R')) + { + gb.MachineState().waitWhileCooling = true; + temperature = gb.GetFValue(); + } + else if (gb.Seen('S')) + { + gb.MachineState().waitWhileCooling = false; + temperature = gb.GetFValue(); + } + else + { + break; // no target temperature given + } + + Tool *tool; + if (gb.Seen('T')) + { + int toolNumber = gb.GetIValue(); + toolNumber += gb.GetToolNumberAdjust(); + tool = reprap.GetTool(toolNumber); + } + else + { + tool = reprap.GetCurrentOrDefaultTool(); + } + + SetToolHeaters(tool, temperature); + if (tool == nullptr) + { + break; // SetToolHeaters will already have generated an error message + } + + // M109 implies waiting for temperature to be reached, so it doesn't make sense unless the tool has been selected. + // Sadly, many slicers use M109 without selecting the tool first. So we select it here if necessary. + if (tool != reprap.GetCurrentTool()) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + + newToolNumber = tool->Number(); + StartToolChange(gb, true); + } + else + { + gb.SetState(GCodeState::m109ToolChangeComplete); + } + } + break; + + case 110: // Set line numbers - line numbers are dealt with in the GCodeBuffer class + break; + + case 111: // Debug level + if (gb.Seen('S')) + { + bool dbv = (gb.GetIValue() != 0); + if (gb.Seen('P')) + { + reprap.SetDebug(static_cast<Module>(gb.GetIValue()), dbv); + } + else + { + reprap.SetDebug(dbv); + } + } + else + { + reprap.PrintDebug(); + } + break; + + case 112: // Emergency stop - acted upon in Webserver, but also here in case it comes from USB etc. + DoEmergencyStop(); + break; + + case 114: + GetCurrentCoordinates(reply); + break; + + case 115: // Print firmware version or set hardware type + if (gb.Seen('P')) + { + platform->SetBoardType((BoardType)gb.GetIValue()); + } + else + { + reply.printf("FIRMWARE_NAME: %s FIRMWARE_VERSION: %s ELECTRONICS: %s", NAME, VERSION, platform->GetElectronicsString()); +#ifdef DUET_NG + const char* expansionName = DuetExpansion::GetExpansionBoardName(); + if (expansionName != nullptr) + { + reply.catf(" + %s", expansionName); + } +#endif + reply.catf(" FIRMWARE_DATE: %s", DATE); + } + break; + + case 116: // Wait for set temperatures + { + bool seen = false; + if (gb.Seen('P')) + { + // Wait for the heaters associated with the specified tool to be ready + int toolNumber = gb.GetIValue(); + toolNumber += gb.GetToolNumberAdjust(); + if (!cancelWait && !ToolHeatersAtSetTemperatures(reprap.GetTool(toolNumber), true)) + { + isWaiting = true; + return false; + } + seen = true; + } + + if (gb.Seen('H')) + { + // Wait for specified heaters to be ready + long heaters[HEATERS]; + size_t heaterCount = HEATERS; + gb.GetLongArray(heaters, heaterCount); + if (!cancelWait) + { + for (size_t i=0; i<heaterCount; i++) + { + if (!reprap.GetHeat()->HeaterAtSetTemperature(heaters[i], true)) + { + isWaiting = true; + return false; + } + } + } + seen = true; + } + + if (gb.Seen('C')) + { + // Wait for chamber heater to be ready + const int8_t chamberHeater = reprap.GetHeat()->GetChamberHeater(); + if (chamberHeater != -1) + { + if (!cancelWait && !reprap.GetHeat()->HeaterAtSetTemperature(chamberHeater, true)) + { + isWaiting = true; + return false; + } + } + seen = true; + } + + // Wait for all heaters to be ready + if (!seen && !cancelWait && !reprap.GetHeat()->AllHeatersAtSetTemperatures(true)) + { + isWaiting = true; + return false; + } + + // If we get here, there is nothing more to wait for + cancelWait = isWaiting = false; + } + break; + + case 117: // Display message + { + const char *msg = gb.GetUnprecedentedString(true); + reprap.SetMessage((msg == nullptr) ? "" : msg); + } + break; + + case 119: + reply.copy("Endstops - "); + for (size_t axis = 0; axis < numAxes; axis++) + { + reply.catf("%c: %s, ", axisLetters[axis], TranslateEndStopResult(platform->Stopped(axis))); + } + reply.catf("Z probe: %s", TranslateEndStopResult(platform->GetZProbeResult())); + break; + + case 120: + Push(gb); + break; + + case 121: + Pop(gb); + break; + + case 122: + { + int val = (gb.Seen('P')) ? gb.GetIValue() : 0; + if (val == 0) + { + reprap.Diagnostics(gb.GetResponseMessageType()); + } + else + { + platform->DiagnosticTest(val); + } + } + break; + + case 135: // Set PID sample interval + if (gb.Seen('S')) + { + platform->SetHeatSampleTime(gb.GetFValue() * 0.001); // Value is in milliseconds; we want seconds + } + else + { + reply.printf("Heat sample time is %.3f seconds", platform->GetHeatSampleTime()); + } + break; + + case 140: // Set bed temperature + { + int8_t bedHeater; + if (gb.Seen('H')) + { + bedHeater = gb.GetIValue(); + if (bedHeater < 0) + { + // Make sure we stay within reasonable boundaries... + bedHeater = -1; + + // If we're disabling the hot bed, make sure the old heater is turned off + reprap.GetHeat()->SwitchOff(reprap.GetHeat()->GetBedHeater()); + } + else if (bedHeater >= HEATERS) + { + reply.copy("Invalid heater number!"); + error = true; + break; + } + reprap.GetHeat()->SetBedHeater(bedHeater); + + if (bedHeater < 0) + { + // Stop here if the hot bed has been disabled + break; + } + } + else + { + bedHeater = reprap.GetHeat()->GetBedHeater(); + if (bedHeater < 0) + { + reply.copy("Hot bed is not present!"); + error = true; + break; + } + } + + if(gb.Seen('S')) + { + float temperature = gb.GetFValue(); + if (temperature < NEARLY_ABS_ZERO) + { + reprap.GetHeat()->SwitchOff(bedHeater); + } + else + { + reprap.GetHeat()->SetActiveTemperature(bedHeater, temperature); + reprap.GetHeat()->Activate(bedHeater); + } + } + if(gb.Seen('R')) + { + reprap.GetHeat()->SetStandbyTemperature(bedHeater, gb.GetFValue()); + } + } + break; + + case 141: // Chamber temperature + { + bool seen = false; + if (gb.Seen('H')) + { + seen = true; + + int heater = gb.GetIValue(); + if (heater < 0) + { + const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater(); + if (currentHeater != -1) + { + reprap.GetHeat()->SwitchOff(currentHeater); + } + + reprap.GetHeat()->SetChamberHeater(-1); + } + else if (heater < HEATERS) + { + reprap.GetHeat()->SetChamberHeater(heater); + } + else + { + reply.copy("Bad heater number specified!"); + error = true; + } + } + + if (gb.Seen('S')) + { + seen = true; + + const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater(); + if (currentHeater != -1) + { + float temperature = gb.GetFValue(); + + if (temperature < NEARLY_ABS_ZERO) + { + reprap.GetHeat()->SwitchOff(currentHeater); + } + else + { + reprap.GetHeat()->SetActiveTemperature(currentHeater, temperature); + reprap.GetHeat()->Activate(currentHeater); + } + } + else + { + reply.copy("No chamber heater has been set up yet!"); + error = true; + } + } + + if (!seen) + { + const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater(); + if (currentHeater != -1) + { + reply.printf("Chamber heater %d is currently at %.1fC", currentHeater, reprap.GetHeat()->GetTemperature(currentHeater)); + } + else + { + reply.copy("No chamber heater has been configured yet."); + } + } + } + break; + + case 143: // Set temperature limit + { + const int heater = (gb.Seen('H')) ? gb.GetIValue() : 1; // default to extruder 1 if no heater number provided + if (heater < 0 || heater >= HEATERS) + { + reply.copy("Invalid heater number"); + error = true; + } + else if (gb.Seen('S')) + { + const float limit = gb.GetFValue(); + if (limit > BAD_LOW_TEMPERATURE && limit < BAD_ERROR_TEMPERATURE) + { + reprap.GetHeat()->SetTemperatureLimit(heater, limit); + } + else + { + reply.copy("Invalid temperature limit"); + error = true; + } + } + else + { + reply.printf("Temperature limit for heater %d is %.1fC", heater, reprap.GetHeat()->GetTemperatureLimit(heater)); + } + } + break; + + case 144: // Set bed to standby + { + const int8_t bedHeater = reprap.GetHeat()->GetBedHeater(); + if (bedHeater >= 0) + { + reprap.GetHeat()->Standby(bedHeater); + } + } + break; + + case 190: // Set bed temperature and wait + case 191: // Set chamber temperature and wait + { + const int8_t heater = (code == 191) ? reprap.GetHeat()->GetChamberHeater() : reprap.GetHeat()->GetBedHeater(); + if (heater >= 0) + { + float temperature; + bool waitWhenCooling; + if (gb.Seen('R')) + { + waitWhenCooling = true; + temperature = gb.GetFValue(); + } + else if (gb.Seen('S')) + { + waitWhenCooling = false; + temperature = gb.GetFValue(); + } + else + { + break; // no target temperature given + } + + reprap.GetHeat()->SetActiveTemperature(heater, temperature); + reprap.GetHeat()->Activate(heater); + if (cancelWait || reprap.GetHeat()->HeaterAtSetTemperature(heater, waitWhenCooling)) + { + cancelWait = isWaiting = false; + break; + } + // In Marlin emulation mode we should return some sort of (undocumented) message here every second... + isWaiting = true; + return false; + } + } + break; + + case 201: // Set/print axis accelerations + { + bool seen = false; + for (size_t axis = 0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + platform->SetAcceleration(axis, gb.GetFValue() * distanceScale); + seen = true; + } + } + + if (gb.Seen(extrudeLetter)) + { + seen = true; + float eVals[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetFloatArray(eVals, eCount, true); + for (size_t e = 0; e < eCount; e++) + { + platform->SetAcceleration(numAxes + e, eVals[e] * distanceScale); + } + } + + if (gb.Seen('P')) + { + // Set max average printing acceleration + platform->SetMaxAverageAcceleration(gb.GetFValue() * distanceScale); + seen = true; + } + + if (!seen) + { + reply.printf("Accelerations: "); + for (size_t axis = 0; axis < numAxes; ++axis) + { + reply.catf("%c: %.1f, ", axisLetters[axis], platform->Acceleration(axis) / distanceScale); + } + reply.cat("E:"); + char sep = ' '; + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + reply.catf("%c%.1f", sep, platform->Acceleration(extruder + numAxes) / distanceScale); + sep = ':'; + } + reply.catf(", avg. printing: %.1f", platform->GetMaxAverageAcceleration()); + } + } + break; + + case 203: // Set/print maximum feedrates + { + bool seen = false; + for (size_t axis = 0; axis < numAxes; ++axis) + { + if (gb.Seen(axisLetters[axis])) + { + platform->SetMaxFeedrate(axis, gb.GetFValue() * distanceScale * secondsToMinutes); // G Code feedrates are in mm/minute; we need mm/sec + seen = true; + } + } + + if (gb.Seen(extrudeLetter)) + { + seen = true; + float eVals[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetFloatArray(eVals, eCount, true); + for (size_t e = 0; e < eCount; e++) + { + platform->SetMaxFeedrate(numAxes + e, eVals[e] * distanceScale * secondsToMinutes); + } + } + + if (!seen) + { + reply.copy("Maximum feedrates: "); + for (size_t axis = 0; axis < numAxes; ++axis) + { + reply.catf("%c: %.1f, ", axisLetters[axis], platform->MaxFeedrate(axis) / (distanceScale * secondsToMinutes)); + } + reply.cat("E:"); + char sep = ' '; + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + reply.catf("%c%.1f", sep, platform->MaxFeedrate(extruder + numAxes) / (distanceScale * secondsToMinutes)); + sep = ':'; + } + } + } + break; + + case 205: //M205 advanced settings: minimum travel speed S=while printing T=travel only, B=minimum segment time X= maximum xy jerk, Z=maximum Z jerk + // This is superseded in this firmware by M codes for the separate types (e.g. M566). + break; + + case 206: // Offset axes - Deprecated + result = OffsetAxes(gb); + break; + + case 207: // Set firmware retraction details + { + bool seen = false; + if (gb.Seen('S')) + { + retractLength = max<float>(gb.GetFValue(), 0.0); + seen = true; + } + if (gb.Seen('R')) + { + retractExtra = max<float>(gb.GetFValue(), -retractLength); + seen = true; + } + if (gb.Seen('F')) + { + unRetractSpeed = retractSpeed = max<float>(gb.GetFValue(), 60.0); + seen = true; + } + if (gb.Seen('T')) // must do this one after 'F' + { + unRetractSpeed = max<float>(gb.GetFValue(), 60.0); + seen = true; + } + if (gb.Seen('Z')) + { + retractHop = max<float>(gb.GetFValue(), 0.0); + seen = true; + } + if (!seen) + { + reply.printf("Retraction settings: length %.2f/%.2fmm, speed %d/%dmm/min, Z hop %.2fmm", + retractLength, retractLength + retractExtra, (int)retractSpeed, (int)unRetractSpeed, retractHop); + } + } + break; + + case 208: // Set/print maximum axis lengths. If there is an S parameter with value 1 then we set the min value, else we set the max value. + { + bool setMin = (gb.Seen('S') ? (gb.GetIValue() == 1) : false); + bool seen = false; + for (size_t axis = 0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + float value = gb.GetFValue() * distanceScale; + if (setMin) + { + platform->SetAxisMinimum(axis, value); + } + else + { + platform->SetAxisMaximum(axis, value); + } + seen = true; + } + } + + if (!seen) + { + reply.copy("Axis limits "); + char sep = '-'; + for (size_t axis = 0; axis < numAxes; axis++) + { + reply.catf("%c %c: %.1f min, %.1f max", sep, axisLetters[axis], platform->AxisMinimum(axis), + platform->AxisMaximum(axis)); + sep = ','; + } + } + } + break; + + case 210: // Set/print homing feed rates + // This is no longer used, but for backwards compatibility we don't report an error + break; + + case 220: // Set/report speed factor override percentage + if (gb.Seen('S')) + { + float newSpeedFactor = (gb.GetFValue() / 100.0) * secondsToMinutes; // include the conversion from mm/minute to mm/second + if (newSpeedFactor > 0.0) + { + gb.MachineState().feedrate *= newSpeedFactor / speedFactor; + if (segmentsLeft != 0 && !moveBuffer.isFirmwareRetraction) + { + // The last move has not gone yet, so we can update it + moveBuffer.feedRate *= newSpeedFactor / speedFactor; + } + speedFactor = newSpeedFactor; + } + else + { + reply.printf("Invalid speed factor specified."); + error = true; + } + } + else + { + reply.printf("Speed factor override: %.1f%%", speedFactor * minutesToSeconds * 100.0); + } + break; + + case 221: // Set/report extrusion factor override percentage + { + int32_t extruder = 0; + bool dummy; + gb.TryGetIValue('D', extruder, dummy); + + if (extruder >= 0 && extruder < (int32_t)numExtruders) + { + if (gb.Seen('S')) // S parameter sets the override percentage + { + const float extrusionFactor = gb.GetFValue() / 100.0; + if (extrusionFactor >= 0.0) + { + if (segmentsLeft != 0 && !moveBuffer.isFirmwareRetraction) + { + moveBuffer.coords[extruder + numAxes] *= extrusionFactor/extrusionFactors[extruder]; // last move not gone, so update it + } + extrusionFactors[extruder] = extrusionFactor; + } + } + else + { + reply.printf("Extrusion factor override for extruder %d: %.1f%%", extruder, extrusionFactors[extruder] * 100.0); + } + } + } + break; + + // For case 226, see case 25 + + case 280: // Servos + if (gb.Seen('P')) + { + const int servoIndex = gb.GetIValue(); + Pin servoPin; + bool invert; + if (platform->GetFirmwarePin(servoIndex, PinAccess::servo, servoPin, invert)) + { + if (gb.Seen('I') && gb.GetIValue() > 0) + { + invert = !invert; + } + + if (gb.Seen('S')) + { + float angleOrWidth = gb.GetFValue(); + if (angleOrWidth < 0.0) + { + // Disable the servo by setting the pulse width to zero + Platform::WriteAnalog(servoPin, (invert) ? 1.0 : 0.0, ServoRefreshFrequency); + } + else + { + if (angleOrWidth < MinServoPulseWidth) + { + // User gave an angle so convert it to a pulse width in microseconds + angleOrWidth = (min<float>(angleOrWidth, 180.0) * ((MaxServoPulseWidth - MinServoPulseWidth) / 180.0)) + MinServoPulseWidth; + } + else if (angleOrWidth > MaxServoPulseWidth) + { + angleOrWidth = MaxServoPulseWidth; + } + float pwm = angleOrWidth * (ServoRefreshFrequency/1e6); + if (invert) + { + pwm = 1.0 - pwm; + } + Platform::WriteAnalog(servoPin, pwm, ServoRefreshFrequency); + } + } + // We don't currently allow the servo position to be read back + } + else + { + platform->MessageF(GENERIC_MESSAGE, "Error: Invalid servo index %d in M280 command\n", servoIndex); + } + } + break; + + case 300: // Beep + { + const int ms = (gb.Seen('P')) ? gb.GetIValue() : 1000; // time in milliseconds + const int freq = (gb.Seen('S')) ? gb.GetIValue() : 4600; // 4600Hz produces the loudest sound on a PanelDue + reprap.Beep(freq, ms); + } + break; + + case 301: // Set/report hot end PID values + SetPidParameters(gb, 1, reply); + break; + + case 302: // Allow, deny or report cold extrudes + if (gb.Seen('P')) + { + reprap.GetHeat()->AllowColdExtrude(gb.GetIValue() > 0); + } + else + { + reply.printf("Cold extrusion is %s, use M302 P[1/0] to allow/deny it", + reprap.GetHeat()->ColdExtrude() ? "allowed" : "denied"); + } + break; + + case 303: // Run PID tuning + if (gb.Seen('H')) + { + const size_t heater = gb.GetIValue(); + const float temperature = (gb.Seen('S')) ? gb.GetFValue() : 225.0; + const float maxPwm = (gb.Seen('P')) ? gb.GetFValue() : 0.5; + if (heater < HEATERS && maxPwm >= 0.1 && maxPwm <= 1.0 && temperature >= 55.0 && temperature <= reprap.GetHeat()->GetTemperatureLimit(heater)) + { + reprap.GetHeat()->StartAutoTune(heater, temperature, maxPwm, reply); + } + else + { + reply.printf("Bad parameter in M303 command"); + } + } + else + { + reprap.GetHeat()->GetAutoTuneStatus(reply); + } + break; + + case 304: // Set/report heated bed PID values + { + const int8_t bedHeater = reprap.GetHeat()->GetBedHeater(); + if (bedHeater >= 0) + { + SetPidParameters(gb, bedHeater, reply); + } + } + break; + + case 305: // Set/report specific heater parameters + SetHeaterParameters(gb, reply); + break; + + case 307: // Set heater process model parameters + if (gb.Seen('H')) + { + size_t heater = gb.GetIValue(); + if (heater < HEATERS) + { + const FopDt& model = reprap.GetHeat()->GetHeaterModel(heater); + bool seen = false; + float gain = model.GetGain(), + tc = model.GetTimeConstant(), + td = model.GetDeadTime(), + maxPwm = model.GetMaxPwm(); + int32_t dontUsePid = model.UsePid() ? 0 : 1; + + gb.TryGetFValue('A', gain, seen); + gb.TryGetFValue('C', tc, seen); + gb.TryGetFValue('D', td, seen); + gb.TryGetIValue('B', dontUsePid, seen); + gb.TryGetFValue('S', maxPwm, seen); + + if (seen) + { + if (reprap.GetHeat()->SetHeaterModel(heater, gain, tc, td, maxPwm, dontUsePid == 0)) + { + reprap.GetHeat()->UseModel(heater, true); + } + else + { + reply.copy("Error: bad model parameters"); + } + } + else if (!model.IsEnabled()) + { + reply.printf("Heater %u is disabled", heater); + } + else + { + reply.printf("Heater %u model: gain %.1f, time constant %.1f, dead time %.1f, max PWM %.2f, in use: %s, mode: %s", + heater, model.GetGain(), model.GetTimeConstant(), model.GetDeadTime(), model.GetMaxPwm(), + (reprap.GetHeat()->IsModelUsed(heater)) ? "yes" : "no", + (model.UsePid()) ? "PID" : "bang-bang"); + if (model.UsePid()) + { + // When reporting the PID parameters, we scale them by 255 for compatibility with older firmware and other firmware + const PidParams& spParams = model.GetPidParameters(false); + const float scaledSpKp = 255.0 * spParams.kP; + reply.catf("\nSetpoint change: P%.1f, I%.2f, D%.1f", + scaledSpKp, scaledSpKp * spParams.recipTi, scaledSpKp * spParams.tD); + const PidParams& ldParams = model.GetPidParameters(true); + const float scaledLoadKp = 255.0 * ldParams.kP; + reply.catf("\nLoad change: P%.1f, I%.2f, D%.1f", + scaledLoadKp, scaledLoadKp * ldParams.recipTi, scaledLoadKp * ldParams.tD); + } + } + } + } + break; + + case 350: // Set/report microstepping + { + // interp is currently an int not a bool, because we use special values of interp to set the chopper control register + int32_t interp = 0; + bool dummy; + gb.TryGetIValue('I', interp, dummy); + + bool seen = false; + for (size_t axis = 0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + seen = true; + const int microsteps = gb.GetIValue(); + if (ChangeMicrostepping(axis, microsteps, interp)) + { + SetAxisNotHomed(axis); + } + else + { + platform->MessageF(GENERIC_MESSAGE, "Drive %c does not support %dx microstepping%s\n", + axisLetters[axis], microsteps, (interp) ? " with interpolation" : ""); + } + } + } + + if (gb.Seen(extrudeLetter)) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + seen = true; + long eVals[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetLongArray(eVals, eCount); + for (size_t e = 0; e < eCount; e++) + { + if (!ChangeMicrostepping(numAxes + e, (int)eVals[e], interp)) + { + platform->MessageF(GENERIC_MESSAGE, "Drive E%u does not support %dx microstepping%s\n", + e, (int)eVals[e], (interp) ? " with interpolation" : ""); + } + } + } + + if (!seen) + { + reply.copy("Microstepping - "); + for (size_t axis = 0; axis < numAxes; ++axis) + { + bool interp; + const int microsteps = platform->GetMicrostepping(axis, interp); + reply.catf("%c:%d%s, ", axisLetters[axis], microsteps, (interp) ? "(on)" : ""); + } + reply.cat("E"); + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + bool interp; + const int microsteps = platform->GetMicrostepping(extruder + numAxes, interp); + reply.catf(":%d%s", microsteps, (interp) ? "(on)" : ""); + } + } + } + break; + + case 400: // Wait for current moves to finish + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + break; + + case 404: // Filament width and nozzle diameter + { + bool seen = false; + + if (gb.Seen('N')) + { + platform->SetFilamentWidth(gb.GetFValue()); + seen = true; + } + if (gb.Seen('D')) + { + platform->SetNozzleDiameter(gb.GetFValue()); + seen = true; + } + + if (!seen) + { + reply.printf("Filament width: %.2fmm, nozzle diameter: %.2fmm", platform->GetFilamentWidth(), platform->GetNozzleDiameter()); + } + } + break; + + 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) + { + 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; + } + + if (statusResponse != nullptr) + { + UnlockAll(gb); + statusResponse->cat('\n'); + HandleReply(gb, false, statusResponse); + return true; + } + } + break; + + case 500: // Store parameters in EEPROM + reprap.GetPlatform()->WriteNvData(); + break; + + case 501: // Load parameters from EEPROM + reprap.GetPlatform()->ReadNvData(); + if (gb.Seen('S')) + { + reprap.GetPlatform()->SetAutoSave(gb.GetIValue() > 0); + } + break; + + case 502: // Revert to default "factory settings" + reprap.GetPlatform()->ResetNvData(); + break; + + case 503: // List variable settings + { + if (!LockFileSystem(gb)) + { + return false; + } + + // Need a valid output buffer to continue... + OutputBuffer *configResponse; + if (!OutputBuffer::Allocate(configResponse)) + { + // No buffer available, try again later + return false; + } + + // Read the entire file + FileStore * const f = platform->GetFileStore(platform->GetSysDir(), platform->GetConfigFile(), false); + if (f == nullptr) + { + error = true; + reply.copy("Configuration file not found!"); + } + else + { + char fileBuffer[FILE_BUFFER_SIZE]; + size_t bytesRead, + bytesLeftForWriting = OutputBuffer::GetBytesLeft(configResponse); + while ((bytesRead = f->Read(fileBuffer, FILE_BUFFER_SIZE)) > 0 && bytesLeftForWriting > 0) + { + // Don't write more data than we can process + if (bytesRead < bytesLeftForWriting) + { + bytesLeftForWriting -= bytesRead; + } + else + { + bytesRead = bytesLeftForWriting; + bytesLeftForWriting = 0; + } + + // Write it + configResponse->cat(fileBuffer, bytesRead); + } + f->Close(); + + UnlockAll(gb); + HandleReply(gb, false, configResponse); + return true; + } + } + break; + + case 540: // Set/report MAC address + if (gb.Seen('P')) + { + SetMACAddress(gb); + } + else + { + const byte* mac = platform->MACAddress(); + reply.printf("MAC: %x:%x:%x:%x:%x:%x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } + break; + + case 550: // Set/report machine name + if (gb.Seen('P')) + { + reprap.SetName(gb.GetString()); + } + else + { + reply.printf("RepRap name: %s", reprap.GetName()); + } + break; + + case 551: // Set password (no option to report it) + if (gb.Seen('P')) + { + reprap.SetPassword(gb.GetString()); + } + break; + + case 552: // Enable/Disable network and/or Set/Get IP address + { + bool seen = false; + if (gb.Seen('P')) + { + seen = true; + SetEthernetAddress(gb, code); + } + + if (gb.Seen('R')) + { + reprap.GetNetwork()->SetHttpPort(gb.GetIValue()); + seen = true; + } + + // Process this one last in case the IP address is changed and the network enabled in the same command + if (gb.Seen('S')) // Has the user turned the network on or off? + { + seen = true; + if (gb.GetIValue() != 0) + { + reprap.GetNetwork()->Enable(); + } + else + { + reprap.GetNetwork()->Disable(); + } + } + + if (!seen) + { + const byte *config_ip = platform->IPAddress(); + const byte *actual_ip = reprap.GetNetwork()->IPAddress(); + reply.printf("Network is %s, configured IP address: %d.%d.%d.%d, actual IP address: %d.%d.%d.%d, HTTP port: %d", + reprap.GetNetwork()->IsEnabled() ? "enabled" : "disabled", + config_ip[0], config_ip[1], config_ip[2], config_ip[3], actual_ip[0], actual_ip[1], actual_ip[2], actual_ip[3], + reprap.GetNetwork()->GetHttpPort()); + } + } + break; + + case 553: // Set/Get netmask + if (gb.Seen('P')) + { + SetEthernetAddress(gb, code); + } + else + { + const byte *nm = platform->NetMask(); + reply.printf("Net mask: %d.%d.%d.%d ", nm[0], nm[1], nm[2], nm[3]); + } + break; + + case 554: // Set/Get gateway + if (gb.Seen('P')) + { + SetEthernetAddress(gb, code); + } + else + { + const byte *gw = platform->GateWay(); + reply.printf("Gateway: %d.%d.%d.%d ", gw[0], gw[1], gw[2], gw[3]); + } + break; + + case 555: // Set/report firmware type to emulate + if (gb.Seen('P')) + { + platform->SetEmulating((Compatibility) gb.GetIValue()); + } + else + { + reply.copy("Emulating "); + switch (platform->Emulating()) + { + case me: + case reprapFirmware: + reply.cat("RepRap Firmware (i.e. in native mode)"); + break; + + case marlin: + reply.cat("Marlin"); + break; + + case teacup: + reply.cat("Teacup"); + break; + + case sprinter: + reply.cat("Sprinter"); + break; + + case repetier: + reply.cat("Repetier"); + break; + + default: + reply.catf("Unknown: (%d)", platform->Emulating()); + } + } + break; + + case 556: // Axis compensation (we support only X, Y, Z) + if (gb.Seen('S')) + { + float value = gb.GetFValue(); + for (size_t axis = 0; axis <= Z_AXIS; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + reprap.GetMove()->SetAxisCompensation(axis, gb.GetFValue() / value); + } + } + } + else + { + reply.printf("Axis compensations - XY: %.5f, YZ: %.5f, ZX: %.5f", + reprap.GetMove()->AxisCompensation(X_AXIS), reprap.GetMove()->AxisCompensation(Y_AXIS), + reprap.GetMove()->AxisCompensation(Z_AXIS)); + } + break; + + case 557: // Set/report Z probe point coordinates + if (gb.Seen('P')) + { + const int point = gb.GetIValue(); + if (point < 0 || (unsigned int)point >= MaxProbePoints) + { + reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Z probe point index out of range.\n"); + } + else + { + bool seen = false; + if (gb.Seen(axisLetters[X_AXIS])) + { + reprap.GetMove()->SetXBedProbePoint(point, gb.GetFValue()); + seen = true; + } + if (gb.Seen(axisLetters[Y_AXIS])) + { + reprap.GetMove()->SetYBedProbePoint(point, gb.GetFValue()); + seen = true; + } + + if (!seen) + { + reply.printf("Probe point %d - [%.1f, %.1f]", point, reprap.GetMove()->XBedProbePoint(point), reprap.GetMove()->YBedProbePoint(point)); + } + } + } + else + { + LockMovement(gb); // to ensure that probing is not already in progress + error = DefineGrid(gb, reply); + } + break; + + case 558: // Set or report Z probe type and for which axes it is used + { + bool seenAxes = false, seenType = false, seenParam = false; + uint32_t zProbeAxes = platform->GetZProbeAxes(); + for (size_t axis = 0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + if (gb.GetIValue() > 0) + { + zProbeAxes |= (1u << axis); + } + else + { + zProbeAxes &= ~(1u << axis); + } + seenAxes = true; + } + } + if (seenAxes) + { + platform->SetZProbeAxes(zProbeAxes); + } + + // We must get and set the Z probe type first before setting the dive height etc., because different probe types may have different parameters + if (gb.Seen('P')) // probe type + { + platform->SetZProbeType(gb.GetIValue()); + seenType = true; + } + + ZProbeParameters params = platform->GetZProbeParameters(); + gb.TryGetFValue('H', params.diveHeight, seenParam); // dive height + + if (gb.Seen('F')) // feed rate i.e. probing speed + { + params.probeSpeed = gb.GetFValue() * secondsToMinutes; + seenParam = true; + } + + if (gb.Seen('T')) // travel speed to probe point + { + params.travelSpeed = gb.GetFValue() * secondsToMinutes; + seenParam = true; + } + + if (gb.Seen('I')) + { + params.invertReading = (gb.GetIValue() != 0); + seenParam = true; + } + + gb.TryGetFValue('R', params.recoveryTime, seenParam); // Z probe recovery time + gb.TryGetFValue('S', params.extraParam, seenParam); // extra parameter for experimentation + + if (seenParam) + { + platform->SetZProbeParameters(params); + } + + if (!(seenAxes || seenType || seenParam)) + { + reply.printf("Z Probe type %d, invert %s, dive height %.1fmm, probe speed %dmm/min, travel speed %dmm/min, recovery time %.2f sec", + platform->GetZProbeType(), (params.invertReading) ? "yes" : "no", params.diveHeight, + (int)(params.probeSpeed * minutesToSeconds), (int)(params.travelSpeed * minutesToSeconds), params.recoveryTime); + if (platform->GetZProbeType() == ZProbeTypeDelta) + { + reply.catf(", extra parameter %.2f", params.extraParam); + } + reply.cat(", used for axes:"); + for (size_t axis = 0; axis < numAxes; axis++) + { + if ((zProbeAxes & (1u << axis)) != 0) + { + reply.catf(" %c", axisLetters[axis]); + } + } + } + } + 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; + } + } + 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; + } + } + break; + + case 561: + reprap.GetMove()->SetIdentityTransform(); + break; + + case 562: // Reset temperature fault - use with great caution + if (gb.Seen('P')) + { + const int heater = gb.GetIValue(); + if (heater >= 0 && heater < HEATERS) + { + reprap.ClearTemperatureFault(heater); + } + else + { + reply.copy("Invalid heater number.\n"); + error = true; + } + } + break; + + case 563: // Define tool + ManageTool(gb, reply); + break; + + case 564: // Think outside the box? + if (gb.Seen('S')) + { + limitAxes = (gb.GetIValue() != 0); + } + else + { + reply.printf("Movement outside the bed is %spermitted", (limitAxes) ? "not " : ""); + } + break; + + case 566: // Set/print maximum jerk speeds + { + bool seen = false; + for (size_t axis = 0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + platform->SetInstantDv(axis, gb.GetFValue() * distanceScale * secondsToMinutes); // G Code feedrates are in mm/minute; we need mm/sec + seen = true; + } + } + + if (gb.Seen(extrudeLetter)) + { + seen = true; + float eVals[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetFloatArray(eVals, eCount, true); + for (size_t e = 0; e < eCount; e++) + { + platform->SetInstantDv(numAxes + e, eVals[e] * distanceScale * secondsToMinutes); + } + } + else if (!seen) + { + reply.copy("Maximum jerk rates: "); + for (size_t axis = 0; axis < numAxes; ++axis) + { + reply.catf("%c: %.1f, ", axisLetters[axis], platform->ConfiguredInstantDv(axis) / (distanceScale * secondsToMinutes)); + } + reply.cat("E:"); + char sep = ' '; + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + reply.catf("%c%.1f", sep, platform->ConfiguredInstantDv(extruder + numAxes) / (distanceScale * secondsToMinutes)); + sep = ':'; + } + } + } + break; + + case 567: // Set/report tool mix ratios + if (gb.Seen('P')) + { + int8_t tNumber = gb.GetIValue(); + Tool* const tool = reprap.GetTool(tNumber); + if (tool != NULL) + { + if (gb.Seen(extrudeLetter)) + { + float eVals[MaxExtruders]; + size_t eCount = tool->DriveCount(); + gb.GetFloatArray(eVals, eCount, false); + if (eCount != tool->DriveCount()) + { + reply.printf("Setting mix ratios - wrong number of E drives: %s", gb.Buffer()); + } + else + { + tool->DefineMix(eVals); + } + } + else + { + reply.printf("Tool %d mix ratios:", tNumber); + char sep = ' '; + for (size_t drive = 0; drive < tool->DriveCount(); drive++) + { + reply.catf("%c%.3f", sep, tool->GetMix()[drive]); + sep = ':'; + } + } + } + } + break; + + case 568: // Turn on/off automatic tool mixing + if (gb.Seen('P')) + { + Tool* const tool = reprap.GetTool(gb.GetIValue()); + if (tool != NULL) + { + if (gb.Seen('S')) + { + tool->SetMixing(gb.GetIValue() != 0); + } + else + { + reply.printf("Tool %d mixing is %s", tool->Number(), (tool->GetMixing()) ? "enabled" : "disabled"); + } + } + } + break; + + case 569: // Set/report axis direction + if (gb.Seen('P')) + { + const size_t drive = gb.GetIValue(); + if (drive < DRIVES) + { + bool seen = false; + if (gb.Seen('S')) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + platform->SetDirectionValue(drive, gb.GetIValue() != 0); + seen = true; + } + if (gb.Seen('R')) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + platform->SetEnableValue(drive, gb.GetIValue() != 0); + seen = true; + } + if (gb.Seen('T')) + { + platform->SetDriverStepTiming(drive, gb.GetFValue()); + seen = true; + } + bool badParameter = false; + for (size_t axis = 0; axis < numAxes; ++axis) + { + if (gb.Seen(axisLetters[axis])) + { + badParameter = true; + } + } + if (gb.Seen(extrudeLetter)) + { + badParameter = true; + } + if (badParameter) + { + platform->Message(GENERIC_MESSAGE, "Error: M569 no longer accepts XYZE parameters; use M584 instead\n"); + } + else if (!seen) + { + reply.printf("A %d sends drive %u forwards, a %d enables it, and the minimum pulse width is %.1f microseconds", + (int)platform->GetDirectionValue(drive), drive, + (int)platform->GetEnableValue(drive), + platform->GetDriverStepTiming(drive)); + } + } + } + break; + + case 570: // Set/report heater timeout + if (gb.Seen('H')) + { + const size_t heater = gb.GetIValue(); + bool seen = false; + if (heater < HEATERS) + { + float maxTempExcursion, maxFaultTime; + reprap.GetHeat()->GetHeaterProtection(heater, maxTempExcursion, maxFaultTime); + gb.TryGetFValue('P', maxFaultTime, seen); + gb.TryGetFValue('T', maxTempExcursion, seen); + if (seen) + { + reprap.GetHeat()->SetHeaterProtection(heater, maxTempExcursion, maxFaultTime); + } + else + { + reply.printf("Heater %u allowed excursion %.1fC, fault trigger time %.1f seconds", heater, maxTempExcursion, maxFaultTime); + } + } + } + else if (gb.Seen('S')) + { + reply.copy("M570 S parameter is no longer required or supported"); + } + break; + + case 571: // Set output on extrude + { + bool seen = false; + if (gb.Seen('P')) + { + const int pwmPin = gb.GetIValue(); + if (!platform->SetExtrusionAncilliaryPwmPin(pwmPin)) + { + reply.printf("Logical pin %d is already in use or not available for use by M571", pwmPin); + break; // don't process 'S' parameter if the pin was wrong + } + seen = true; + } + if (gb.Seen('S')) + { + platform->SetExtrusionAncilliaryPwmValue(gb.GetFValue()); + seen = true; + } + if (!seen) + { + reply.printf("Extrusion ancillary PWM %.3f on pin %u", + platform->GetExtrusionAncilliaryPwmValue(), platform->GetExtrusionAncilliaryPwmPin()); + } + } + break; + + case 572: // Set/report elastic compensation + if (gb.Seen('D')) + { + // New usage: specify the extruder drive using the D parameter + const size_t extruder = gb.GetIValue(); + if (gb.Seen('S')) + { + platform->SetPressureAdvance(extruder, gb.GetFValue()); + } + else + { + reply.printf("Pressure advance for extruder %u is %.3f seconds", extruder, platform->GetPressureAdvance(extruder)); + } + } + break; + + case 573: // Report heater average PWM + if (gb.Seen('P')) + { + const int heater = gb.GetIValue(); + if (heater >= 0 && heater < HEATERS) + { + reply.printf("Average heater %d PWM: %.3f", heater, reprap.GetHeat()->GetAveragePWM(heater)); + } + } + break; + + case 574: // Set endstop configuration + { + bool seen = false; + const bool logicLevel = (gb.Seen('S')) ? (gb.GetIValue() != 0) : true; + for (size_t axis = 0; axis < numAxes; ++axis) + { + if (gb.Seen(axisLetters[axis])) + { + const int ival = gb.GetIValue(); + if (ival >= 0 && ival <= 3) + { + platform->SetEndStopConfiguration(axis, (EndStopType) ival, logicLevel); + seen = true; + } + } + } + if (!seen) + { + reply.copy("Endstop configuration:"); + EndStopType config; + bool logic; + for (size_t axis = 0; axis < numAxes; ++axis) + { + platform->GetEndStopConfiguration(axis, config, logic); + reply.catf(" %c %s (active %s),", axisLetters[axis], + (config == EndStopType::highEndStop) ? "high end" : (config == EndStopType::lowEndStop) ? "low end" : "none", + (config == EndStopType::noEndStop) ? "" : (logic) ? "high" : "low"); + } + } + } + break; + + case 575: // Set communications parameters + if (gb.Seen('P')) + { + size_t chan = gb.GetIValue(); + if (chan < NUM_SERIAL_CHANNELS) + { + bool seen = false; + if (gb.Seen('B')) + { + platform->SetBaudRate(chan, gb.GetIValue()); + seen = true; + } + if (gb.Seen('S')) + { + uint32_t val = gb.GetIValue(); + platform->SetCommsProperties(chan, val); + switch (chan) + { + case 0: + serialGCode->SetCommsProperties(val); + break; + case 1: + auxGCode->SetCommsProperties(val); + break; + default: + break; + } + seen = true; + } + if (!seen) + { + uint32_t cp = platform->GetCommsProperties(chan); + reply.printf("Channel %d: baud rate %d, %s checksum", chan, platform->GetBaudRate(chan), + (cp & 1) ? "requires" : "does not require"); + } + } + } + break; + + case 577: // Wait until endstop is triggered + if (gb.Seen('S')) + { + // Determine trigger type + EndStopHit triggerCondition; + switch (gb.GetIValue()) + { + case 1: + triggerCondition = EndStopHit::lowHit; + break; + case 2: + triggerCondition = EndStopHit::highHit; + break; + case 3: + triggerCondition = EndStopHit::lowNear; + break; + default: + triggerCondition = EndStopHit::noStop; + break; + } + + // Axis endstops + for (size_t axis=0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + if (platform->Stopped(axis) != triggerCondition) + { + result = false; + break; + } + } + } + + // Extruder drives + size_t eDriveCount = MaxExtruders; + long eDrives[MaxExtruders]; + if (gb.Seen(extrudeLetter)) + { + gb.GetLongArray(eDrives, eDriveCount); + for(size_t extruder = 0; extruder < eDriveCount; extruder++) + { + const size_t eDrive = eDrives[extruder]; + if (eDrive >= MaxExtruders) + { + reply.copy("Invalid extruder drive specified!"); + error = result = true; + break; + } + + if (platform->Stopped(eDrive + E0_AXIS) != triggerCondition) + { + result = false; + break; + } + } + } + } + break; + +#if SUPPORT_INKJET + case 578: // Fire Inkjet bits + if (!AllMovesAreFinishedAndMoveBufferIsLoaded()) + { + return false; + } + + if (gb.Seen('S')) // Need to handle the 'P' parameter too; see http://reprap.org/wiki/G-code#M578:_Fire_inkjet_bits + { + platform->Inkjet(gb.GetIValue()); + } + break; +#endif + + case 579: // Scale Cartesian axes (mostly for Delta configurations) + { + bool seen = false; + for (size_t axis = 0; axis < numAxes; axis++) + { + gb.TryGetFValue(axisLetters[axis], axisScaleFactors[axis], seen); + } + + if (!seen) + { + char sep = ':'; + reply.copy("Axis scale factors"); + for(size_t axis = 0; axis < numAxes; axis++) + { + reply.catf("%c %c: %.3f", sep, axisLetters[axis], axisScaleFactors[axis]); + sep = ','; + } + } + } + break; + +#if SUPPORT_ROLAND + case 580: // (De)Select Roland mill + if (gb.Seen('R')) + { + if (gb.GetIValue()) + { + reprap.GetRoland()->Activate(); + if (gb.Seen('P')) + { + result = reprap.GetRoland()->RawWrite(gb.GetString()); + } + } + else + { + result = reprap.GetRoland()->Deactivate(); + } + } + else + { + reply.printf("Roland is %s.", reprap.GetRoland()->Active() ? "active" : "inactive"); + } + break; +#endif + + case 581: // Configure external trigger + case 582: // Check external trigger + if (gb.Seen('T')) + { + unsigned int triggerNumber = gb.GetIValue(); + if (triggerNumber < MaxTriggers) + { + if (code == 582) + { + uint32_t states = platform->GetAllEndstopStates(); + if ((triggers[triggerNumber].rising & states) != 0 || (triggers[triggerNumber].falling & ~states) != 0) + { + triggersPending |= (1u << triggerNumber); + } + } + else + { + bool seen = false; + if (gb.Seen('C')) + { + seen = true; + triggers[triggerNumber].condition = gb.GetIValue(); + } + else if (triggers[triggerNumber].IsUnused()) + { + triggers[triggerNumber].condition = 0; // this is a new trigger, so set no condition + } + if (gb.Seen('S')) + { + seen = true; + int sval = gb.GetIValue(); + TriggerMask triggerMask = 0; + for (size_t axis = 0; axis < numAxes; ++axis) + { + if (gb.Seen(axisLetters[axis])) + { + triggerMask |= (1u << axis); + } + } + if (gb.Seen(extrudeLetter)) + { + long eStops[MaxExtruders]; + size_t numEntries = MaxExtruders; + gb.GetLongArray(eStops, numEntries); + for (size_t i = 0; i < numEntries; ++i) + { + if (eStops[i] >= 0 && (unsigned long)eStops[i] < MaxExtruders) + { + triggerMask |= (1u << (eStops[i] + E0_AXIS)); + } + } + } + switch(sval) + { + case -1: + if (triggerMask == 0) + { + triggers[triggerNumber].rising = triggers[triggerNumber].falling = 0; + } + else + { + triggers[triggerNumber].rising &= (~triggerMask); + triggers[triggerNumber].falling &= (~triggerMask); + } + break; + + case 0: + triggers[triggerNumber].falling |= triggerMask; + break; + + case 1: + triggers[triggerNumber].rising |= triggerMask; + break; + + default: + platform->Message(GENERIC_MESSAGE, "Bad S parameter in M581 command\n"); + } + } + if (!seen) + { + reply.printf("Trigger %u fires on a rising edge on ", triggerNumber); + ListTriggers(reply, triggers[triggerNumber].rising); + reply.cat(" or a falling edge on "); + ListTriggers(reply, triggers[triggerNumber].falling); + reply.cat(" endstop inputs"); + if (triggers[triggerNumber].condition == 1) + { + reply.cat(" when printing from SD card"); + } + } + } + } + else + { + platform->Message(GENERIC_MESSAGE, "Trigger number out of range\n"); + } + } + break; + + case 584: // Set axis/extruder to stepper driver(s) mapping + if (!LockMovementAndWaitForStandstill(gb)) // we also rely on this to retrieve the current motor positions to moveBuffer + { + return false; + } + { + bool seen = false, badDrive = false; + for (size_t drive = 0; drive < MAX_AXES; ++drive) + { + if (gb.Seen(axisLetters[drive])) + { + seen = true; + size_t numValues = MaxDriversPerAxis; + long drivers[MaxDriversPerAxis]; + gb.GetLongArray(drivers, numValues); + + // Check all the driver numbers are in range + bool badAxis = false; + AxisDriversConfig config; + config.numDrivers = numValues; + for (size_t i = 0; i < numValues; ++i) + { + if ((unsigned long)drivers[i] >= DRIVES) + { + badAxis = true; + } + else + { + config.driverNumbers[i] = (uint8_t)drivers[i]; + } + } + if (badAxis) + { + badDrive = true; + } + else + { + while (numAxes <= drive) + { + moveBuffer.coords[numAxes] = 0.0; // user has defined a new axis, so set its position + ++numAxes; + } + SetPositions(moveBuffer.coords); // tell the Move system where any new axes are + platform->SetAxisDriversConfig(drive, config); + if (numAxes + numExtruders > DRIVES) + { + numExtruders = DRIVES - numAxes; // if we added axes, we may have fewer extruders now + } + } + } + } + + if (gb.Seen(extrudeLetter)) + { + seen = true; + size_t numValues = DRIVES - numAxes; + long drivers[MaxExtruders]; + gb.GetLongArray(drivers, numValues); + numExtruders = numValues; + for (size_t i = 0; i < numValues; ++i) + { + if ((unsigned long)drivers[i] >= DRIVES) + { + badDrive = true; + } + else + { + platform->SetExtruderDriver(i, (uint8_t)drivers[i]); + } + } + } + + if (badDrive) + { + platform->Message(GENERIC_MESSAGE, "Error: invalid drive number in M584 command\n"); + } + else if (!seen) + { + reply.copy("Driver assignments:"); + for (size_t drive = 0; drive < numAxes; ++ drive) + { + reply.cat(' '); + const AxisDriversConfig& axisConfig = platform->GetAxisDriversConfig(drive); + char c = axisLetters[drive]; + for (size_t i = 0; i < axisConfig.numDrivers; ++i) + { + reply.catf("%c%u", c, axisConfig.driverNumbers[i]); + c = ':'; + } + } + reply.cat(' '); + char c = extrudeLetter; + for (size_t extruder = 0; extruder < numExtruders; ++extruder) + { + reply.catf("%c%u", c, platform->GetExtruderDriver(extruder)); + c = ':'; + } + } + } + break; + + case 665: // Set delta configuration + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + { + float positionNow[DRIVES]; + Move *move = reprap.GetMove(); + move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes()); // get the current position, we may need it later + DeltaParameters& params = move->AccessDeltaParams(); + const bool wasInDeltaMode = params.IsDeltaMode(); // remember whether we were in delta mode + bool seen = false; + + if (gb.Seen('L')) + { + params.SetDiagonal(gb.GetFValue() * distanceScale); + seen = true; + } + if (gb.Seen('R')) + { + params.SetRadius(gb.GetFValue() * distanceScale); + seen = true; + } + if (gb.Seen('B')) + { + params.SetPrintRadius(gb.GetFValue() * distanceScale); + seen = true; + } + if (gb.Seen('X')) + { + // X tower position correction + params.SetXCorrection(gb.GetFValue()); + seen = true; + } + if (gb.Seen('Y')) + { + // Y tower position correction + params.SetYCorrection(gb.GetFValue()); + seen = true; + } + if (gb.Seen('Z')) + { + // Y tower position correction + params.SetZCorrection(gb.GetFValue()); + seen = true; + } + + // The homed height must be done last, because it gets recalculated when some of the other factors are changed + if (gb.Seen('H')) + { + params.SetHomedHeight(gb.GetFValue() * distanceScale); + seen = true; + } + + if (seen) + { + move->SetCoreXYMode(0); // CoreXYMode needs to be zero when executing special moves on a delta + + // If we have changed between Cartesian and Delta mode, we need to reset the motor coordinates to agree with the XYZ coordinates. + // This normally happens only when we process the M665 command in config.g. Also flag that the machine is not homed. + if (params.IsDeltaMode() != wasInDeltaMode) + { + SetPositions(positionNow); + } + SetAllAxesNotHomed(); + } + else + { + if (params.IsDeltaMode()) + { + reply.printf("Diagonal %.3f, delta radius %.3f, homed height %.3f, bed radius %.1f" + ", X %.3f" DEGREE_SYMBOL ", Y %.3f" DEGREE_SYMBOL ", Z %.3f" DEGREE_SYMBOL, + params.GetDiagonal() / distanceScale, params.GetRadius() / distanceScale, + params.GetHomedHeight() / distanceScale, params.GetPrintRadius() / distanceScale, + params.GetXCorrection(), params.GetYCorrection(), params.GetZCorrection()); + } + else + { + reply.printf("Printer is not in delta mode"); + } + } + } + break; + + case 666: // Set delta endstop adjustments + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + { + DeltaParameters& params = reprap.GetMove()->AccessDeltaParams(); + bool seen = false; + if (gb.Seen('X')) + { + params.SetEndstopAdjustment(X_AXIS, gb.GetFValue()); + seen = true; + } + if (gb.Seen('Y')) + { + params.SetEndstopAdjustment(Y_AXIS, gb.GetFValue()); + seen = true; + } + if (gb.Seen('Z')) + { + params.SetEndstopAdjustment(Z_AXIS, gb.GetFValue()); + seen = true; + } + if (gb.Seen('A')) + { + params.SetXTilt(gb.GetFValue() * 0.01); + seen = true; + } + if (gb.Seen('B')) + { + params.SetYTilt(gb.GetFValue() * 0.01); + seen = true; + } + + if (seen) + { + SetAllAxesNotHomed(); + } + else + { + reply.printf("Endstop adjustments X%.2f Y%.2f Z%.2f, tilt X%.2f%% Y%.2f%%", + params.GetEndstopAdjustment(X_AXIS), params.GetEndstopAdjustment(Y_AXIS), params.GetEndstopAdjustment(Z_AXIS), + params.GetXTilt() * 100.0, params.GetYTilt() * 100.0); + } + } + break; + + case 667: // Set CoreXY mode + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + { + Move* const move = reprap.GetMove(); + bool seen = false; + float positionNow[DRIVES]; + move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes()); // get the current position, we may need it later + if (gb.Seen('S')) + { + move->SetCoreXYMode(gb.GetIValue()); + seen = true; + } + for (size_t axis = 0; axis < numAxes; ++axis) + { + if (gb.Seen(axisLetters[axis])) + { + move->SetCoreAxisFactor(axis, gb.GetFValue()); + seen = true; + } + } + + if (seen) + { + SetPositions(positionNow); + SetAllAxesNotHomed(); + } + else + { + reply.printf("Printer mode is %s with axis factors", move->GetGeometryString()); + for (size_t axis = 0; axis < numAxes; ++axis) + { + reply.catf(" %c:%f", axisLetters[axis], move->GetCoreAxisFactor(axis)); + } + } + } + break; + + case 905: // Set current RTC date and time + { + const time_t now = platform->GetDateTime(); + struct tm * const timeInfo = gmtime(&now); + bool seen = false; + + if (gb.Seen('P')) + { + // Set date + const char * const dateString = gb.GetString(); + if (strptime(dateString, "%Y-%m-%d", timeInfo) != nullptr) + { + if (!platform->SetDate(mktime(timeInfo))) + { + reply.copy("Could not set date"); + error = true; + break; + } + } + else + { + reply.copy("Invalid date format"); + error = true; + break; + } + + seen = true; + } + + if (gb.Seen('S')) + { + // Set time + const char * const timeString = gb.GetString(); + if (strptime(timeString, "%H:%M:%S", timeInfo) != nullptr) + { + if (!platform->SetTime(mktime(timeInfo))) + { + reply.copy("Could not set time"); + error = true; + break; + } + } + else + { + reply.copy("Invalid time format"); + error = true; + break; + } + seen = true; + } + + // TODO: Add correction parameters for SAM4E + + if (!seen) + { + // Report current date and time + reply.printf("Current date and time: %04u-%02u-%02u %02u:%02u:%02u", + timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday, + timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec); + + if (!platform->IsDateTimeSet()) + { + reply.cat("\nWarning: RTC has not been configured yet!"); + } + } + } + break; + + case 906: // Set/report Motor currents + case 913: // Set/report motor current percent + { + bool seen = false; + for (size_t axis = 0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + platform->SetMotorCurrent(axis, gb.GetFValue(), code == 913); + seen = true; + } + } + + if (gb.Seen(extrudeLetter)) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + + float eVals[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetFloatArray(eVals, eCount, true); + // 2014-09-29 DC42: we no longer insist that the user supplies values for all possible extruder drives + for (size_t e = 0; e < eCount; e++) + { + platform->SetMotorCurrent(numAxes + e, eVals[e], code == 913); + } + seen = true; + } + + if (code == 906 && gb.Seen('I')) + { + const float idleFactor = gb.GetFValue(); + if (idleFactor >= 0 && idleFactor <= 100.0) + { + platform->SetIdleCurrentFactor(idleFactor/100.0); + seen = true; + } + } + + if (!seen) + { + reply.copy((code == 913) ? "Motor current % of normal - " : "Motor current (mA) - "); + for (size_t axis = 0; axis < numAxes; ++axis) + { + reply.catf("%c:%d, ", axisLetters[axis], (int)platform->GetMotorCurrent(axis, code == 913)); + } + reply.cat("E"); + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + reply.catf(":%d", (int)platform->GetMotorCurrent(extruder + numAxes, code == 913)); + } + if (code == 906) + { + reply.catf(", idle factor %d%%", (int)(platform->GetIdleCurrentFactor() * 100.0)); + } + } + } + break; + + case 911: // Set power monitor threshold voltages + reply.printf("M911 not implemented yet"); + break; + + case 912: // Set electronics temperature monitor adjustment + // Currently we ignore the P parameter (i.e. temperature measurement channel) + if (gb.Seen('S')) + { + platform->SetMcuTemperatureAdjust(gb.GetFValue()); + } + else + { + reply.printf("MCU temperature calibration adjustment is %.1f" DEGREE_SYMBOL "C", platform->GetMcuTemperatureAdjust()); + } + break; + + // For case 913, see 906 + + case 997: // Perform firmware update + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + reprap.GetHeat()->SwitchOffAll(); // turn all heaters off because the main loop may get suspended + DisableDrives(); // all motors off + + if (firmwareUpdateModuleMap == 0) // have we worked out which modules to update? + { + // Find out which modules we have been asked to update + if (gb.Seen('S')) + { + long modulesToUpdate[3]; + size_t numUpdateModules = ARRAY_SIZE(modulesToUpdate); + gb.GetLongArray(modulesToUpdate, numUpdateModules); + for (size_t i = 0; i < numUpdateModules; ++i) + { + long t = modulesToUpdate[i]; + if (t < 0 || (unsigned long)t >= NumFirmwareUpdateModules) + { + platform->MessageF(GENERIC_MESSAGE, "Invalid module number '%ld'\n", t); + firmwareUpdateModuleMap = 0; + break; + } + firmwareUpdateModuleMap |= (1u << (unsigned int)t); + } + } + else + { + firmwareUpdateModuleMap = (1u << 0); // no modules specified, so update module 0 to match old behaviour + } + + if (firmwareUpdateModuleMap == 0) + { + break; // nothing to update + } + + // Check prerequisites of all modules to be updated, if any are not met then don't update any of them +#ifdef DUET_NG + if (!FirmwareUpdater::CheckFirmwareUpdatePrerequisites(firmwareUpdateModuleMap)) + { + firmwareUpdateModuleMap = 0; + break; + } +#endif + if ((firmwareUpdateModuleMap & 1) != 0 && !platform->CheckFirmwareUpdatePrerequisites()) + { + firmwareUpdateModuleMap = 0; + break; + } + } + + // If we get here then we have the module map, and all prerequisites are satisfied + isFlashing = true; // this tells the web interface and PanelDue that we are about to flash firmware + if (!DoDwellTime(1.0)) // wait a second so all HTTP clients and PanelDue are notified + { + return false; + } + + gb.SetState(GCodeState::flashing1); + break; + + case 998: + // The input handling code replaces the gcode by this when it detects a checksum error. + // Since we have no way of asking for the line to be re-sent, just report an error. + if (gb.Seen('P')) + { + const int val = gb.GetIValue(); + if (val != 0) + { + reply.printf("Checksum error on line %d", val); + } + } + break; + + case 999: + result = DoDwellTime(0.5); // wait half a second to allow the response to be sent back to the web server, otherwise it may retry + if (result) + { + reprap.EmergencyStop(); // this disables heaters and drives - Duet WiFi pre-production boards need drives disabled here + uint16_t reason = (gb.Seen('P') && StringStartsWith(gb.GetString(), "ERASE")) + ? (uint16_t)SoftwareResetReason::erase + : (uint16_t)SoftwareResetReason::user; + platform->SoftwareReset(reason); // doesn't return + } + break; + + default: + error = true; + reply.printf("unsupported command: %s", gb.Buffer()); + } + + if (result && gb.GetState() == GCodeState::normal) + { + UnlockAll(gb); + HandleReply(gb, error, reply.Pointer()); + } + return result; +} + +bool GCodes::HandleTcode(GCodeBuffer& gb, StringRef& reply) +{ + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + + newToolNumber = gb.GetIValue(); + newToolNumber += gb.GetToolNumberAdjust(); + + // TODO for the tool change restore point to be useful, we should undo any X axis mapping and remove any tool offsets + for (size_t drive = 0; drive < DRIVES; ++drive) + { + toolChangeRestorePoint.moveCoords[drive] = moveBuffer.coords[drive]; + } + toolChangeRestorePoint.feedRate = gb.MachineState().feedrate; + + if (simulationMode == 0) // we don't yet simulate any T codes + { + const Tool * const oldTool = reprap.GetCurrentTool(); + // If old and new are the same we no longer follow the sequence. User can deselect and then reselect the tool if he wants the macros run. + if (oldTool == nullptr || oldTool->Number() != newToolNumber) + { + StartToolChange(gb, false); + return true; // proceeding with state machine, so don't unlock or send a reply + } + } + + // If we get here, we have finished + UnlockAll(gb); + HandleReply(gb, false, ""); + return true; +} + +// End diff --git a/src/Heating/Heat.h b/src/Heating/Heat.h index 6290cd16..ab74e6a9 100644 --- a/src/Heating/Heat.h +++ b/src/Heating/Heat.h @@ -39,8 +39,7 @@ public: void Exit(); // Shut everything down bool ColdExtrude() const; // Is cold extrusion allowed? - void AllowColdExtrude(); // Allow cold extrusion - void DenyColdExtrude(); // Deny cold extrusion + void AllowColdExtrude(bool b); // Allow or deny cold extrusion int8_t GetBedHeater() const // Get hot bed heater number post(-1 <= result; result < HEATERS); @@ -132,14 +131,9 @@ inline bool Heat::ColdExtrude() const return coldExtrude; } -inline void Heat::AllowColdExtrude() +inline void Heat::AllowColdExtrude(bool b) { - coldExtrude = true; -} - -inline void Heat::DenyColdExtrude() -{ - coldExtrude = false; + coldExtrude = b; } inline int8_t Heat::GetBedHeater() const diff --git a/src/Movement/DDA.cpp b/src/Movement/DDA.cpp index 0eeea2b6..a42ac642 100644 --- a/src/Movement/DDA.cpp +++ b/src/Movement/DDA.cpp @@ -161,8 +161,8 @@ void DDA::Init() bool DDA::Init(const GCodes::RawMove &nextMove, bool doMotorMapping) { // 1. Compute the new endpoints and the movement vector - const int32_t *positionNow = prev->DriveCoordinates(); - const Move *move = reprap.GetMove(); + const int32_t * const positionNow = prev->DriveCoordinates(); + const Move * const move = reprap.GetMove(); if (doMotorMapping) { move->MotorTransform(nextMove.coords, endPoint); // transform the axis coordinates if on a delta or CoreXY printer diff --git a/src/Movement/Grid.cpp b/src/Movement/Grid.cpp index ba194537..3cd91e49 100644 --- a/src/Movement/Grid.cpp +++ b/src/Movement/Grid.cpp @@ -9,12 +9,11 @@ #include "RepRapFirmware.h" #include <cmath> -// Increase the version number in the following string whenever we change the format of the height map file. -const char *HeightMapComment = "RepRapFirmware height map file v1"; +const char *GridDefinition::HeightMapLabelLine = "xmin,xmax,ymin,ymax,radius,spacing,xnum,ynum"; // Initialise the grid to be invalid GridDefinition::GridDefinition() - : xMin(0.0), xMax(-1.0), yMin(0.0), yMax(-1.0), radius(-1.0), spacing(DefaultGridSpacing), gridHeights(nullptr), + : xMin(0.0), xMax(-1.0), yMin(0.0), yMax(-1.0), radius(-1.0), spacing(DefaultGridSpacing), numX(0), numY(0), recipSpacing(1.0/spacing), isValid(false) { } @@ -24,13 +23,13 @@ GridDefinition::GridDefinition(const float xRange[2], const float yRange[2], flo { numX = (xMax - xMin >= MinRange && spacing >= MinSpacing) ? (uint32_t)((xMax - xMin) * recipSpacing) + 1 : 0; numY = (yMax - yMin >= MinRange && spacing >= MinSpacing) ? (uint32_t)((yMax - yMin) * recipSpacing) + 1 : 0; - isValid = NumPoints() != 0 && NumPoints() <= MaxGridProbePoints && (radius < 0.0 || radius >= 1.0); + CheckValidity(); + } -void GridDefinition::SetStorage(const float *heightStorage, const uint32_t *heightSetStorage) +void GridDefinition::CheckValidity() { - gridHeights = heightStorage; - gridHeightSet = heightSetStorage; + isValid = NumPoints() != 0 && NumPoints() <= MaxGridProbePoints && (radius < 0.0 || radius >= 1.0); } float GridDefinition::GetXCoordinate(unsigned int xIndex) const @@ -49,27 +48,54 @@ bool GridDefinition::IsInRadius(float x, float y) const } // Append the grid parameters to the end of a string -void GridDefinition::PrintParameters(StringRef& r) const +void GridDefinition::PrintParameters(StringRef& s) const +{ + s.catf("X%.1f:%.1f, Y%.1f:%.1f, radius %.1f, spacing %.1f, %d points", xMin, xMax, yMin, yMax, radius, spacing, NumPoints()); +} + +// Write the parameter label line to a string +void GridDefinition::WriteHeadingAndParameters(StringRef& s) const +{ + s.printf("%s\n%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%u,%u\n", HeightMapLabelLine, xMin, xMax, yMin, yMax, radius, spacing, numX, numY); +} + +// Check the parameter label line returning true if correct +/*static*/ bool GridDefinition::CheckHeading(const StringRef& s) { - r.catf("X%.1f:%.1f, Y%.1f:%.1f, radius %.1f, spacing %.1f, %d points", xMin, xMax, yMin, yMax, radius, spacing, NumPoints()); + return StringStartsWith(s.Pointer(), HeightMapLabelLine); } -// Print what is wrong with the grid +// Read the grid parameters from a string returning true if success +bool GridDefinition::ReadParameters(const StringRef& s) +{ + bool ok = (sscanf(s.Pointer(), "%f,%f,%f,%f,%f,%f,%lu,%lu", &xMin, &xMax, &yMin, &yMax, &radius, &spacing, &numX, &numY) == 8); + if (ok) + { + CheckValidity(); + } + else + { + isValid = false; + } + return ok; +} + +// Print what is wrong with the grid, appending it to the existing string void GridDefinition::PrintError(StringRef& r) const { if (spacing < MinSpacing) { r.cat("Spacing too small"); } - else if (NumXpoints() == 0) + else if (numX == 0) { r.cat("X range too small"); } - else if (NumYpoints() == 0) + else if (numY == 0) { r.cat("Y range too small"); } - else if (NumPoints() > MaxGridProbePoints) + else if (numX > MaxGridProbePoints || numY > MaxGridProbePoints || NumPoints() > MaxGridProbePoints) // check X and Y individually in case X*Y overflows { r.catf("Too many grid points (maximum %d, needed %d)", MaxGridProbePoints, NumPoints()); } @@ -80,8 +106,44 @@ void GridDefinition::PrintError(StringRef& r) const } } +// Increase the version number in the following string whenever we change the format of the height map file. +const char *HeightMap::HeightMapComment = "RepRapFirmware height map file v1"; + +HeightMap::HeightMap(float *heightStorage) : gridHeights(heightStorage) { } + +void HeightMap::SetGrid(const GridDefinition& gd) +{ + def = gd; + ClearGridHeights(); +} + +void HeightMap::ClearGridHeights() +{ + for (size_t i = 0; i < MaxGridProbePoints/32; ++i) + { + gridHeightSet[i] = 0; + } +} + +// Set the height of a grid point +void HeightMap::SetGridHeight(size_t xIndex, size_t yIndex, float height) +{ + size_t index = yIndex * def.numX + xIndex; + if (index < MaxGridProbePoints) + { + gridHeights[index] = height; + gridHeightSet[index/32] |= 1u << (index & 31u); + } +} + +// Return the minimum number of segments for a move by this X or Y amount +unsigned int HeightMap::GetMinimumSegments(float distance) const +{ + return (distance > 0.0) ? (unsigned int)(distance * def.recipSpacing + 0.4) : 1; +} + // Save the grid to file returning true if an error occurred -bool GridDefinition::SaveToFile(FileStore *f) const +bool HeightMap::SaveToFile(FileStore *f) const { char bufferSpace[500]; StringRef buf(bufferSpace, ARRAY_SIZE(bufferSpace)); @@ -93,7 +155,7 @@ bool GridDefinition::SaveToFile(FileStore *f) const time_t timeNow = reprap.GetPlatform()->GetDateTime(); const struct tm * const timeInfo = gmtime(&timeNow); buf.catf(" generated at %04u-%02u-%02u %02u:%02u", - timeInfo->tm_year, timeInfo->tm_mon, timeInfo->tm_mday, timeInfo->tm_hour, timeInfo->tm_min); + timeInfo->tm_year + 1900, timeInfo->tm_mon, timeInfo->tm_mday, timeInfo->tm_hour, timeInfo->tm_min); } buf.cat('\n'); if (!f->Write(buf.Pointer())) @@ -102,10 +164,7 @@ bool GridDefinition::SaveToFile(FileStore *f) const } // Write the grid parameters - buf.printf("xmin,xmax,ymin,ymax,radius,spacing,xnum,ynum\n" - "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%u,%u\n", - xMin, xMax, yMin, yMax, radius, spacing, numX, numY - ); + def.WriteHeadingAndParameters(buf); if (!f->Write(buf.Pointer())) { return true; @@ -113,10 +172,10 @@ bool GridDefinition::SaveToFile(FileStore *f) const // Write the grid heights uint32_t index = 0; - for (uint32_t i = 0; i < numY; ++i) + for (uint32_t i = 0; i < def.numY; ++i) { buf.Clear(); - for (uint32_t j = 0; j < numX; ++j) + for (uint32_t j = 0; j < def.numX; ++j) { if (j != 0) { @@ -142,20 +201,93 @@ bool GridDefinition::SaveToFile(FileStore *f) const return false; } -// Load the grid from file returning true if an error occurred -bool GridDefinition::LoadFromFile(FileStore *f) +// Load the grid from file, returning true if an error occurred with the error reason appended to the buffer +bool HeightMap::LoadFromFile(FileStore *f, StringRef& r) { - //TODO - return true; + const size_t MaxLineLength = 200; // maximum length of a line in the height map file + const char* const readFailureText = "failed to read line from file"; + char buffer[MaxLineLength + 1]; + StringRef s(buffer, ARRAY_SIZE(buffer)); + + ClearGridHeights(); + GridDefinition newGrid; + + if (f->ReadLine(buffer, sizeof(buffer)) <= 0) + { + r.cat(readFailureText); + } + else if (!StringStartsWith(buffer, HeightMapComment)) // check the version line is as expected + { + r.cat("bad header line or wrong version header"); + } + else if (f->ReadLine(buffer, sizeof(buffer)) <= 0) + { + r.cat(readFailureText); + } + else if (!GridDefinition::CheckHeading(s)) // check the label line is as expected + { + r.cat("bad label line"); + } + else if (f->ReadLine(buffer, sizeof(buffer)) <= 0) // read the height map parameters + { + r.cat(readFailureText); + } + else if (!newGrid.ReadParameters(s)) + { + r.cat("failed to parse grid parameters"); + } + else if (!newGrid.IsValid()) + { + r.cat("invalid grid"); + } + else + { + SetGrid(newGrid); + for (uint32_t row = 0; row < def.numY; ++row) // read the grid a row at a time + { + if (f->ReadLine(buffer, sizeof(buffer)) <= 0) + { + r.cat(readFailureText); + return true; // failed to read a line + } + const char *p = buffer; + for (uint32_t col = 0; col < def.numX; ++col) + { + if (*p == '0' && (p[1] == ',' || p[1] == 0)) + { + // Values of 0 with no decimal places in un-probed values, so leave the point set as not valid + ++p; + } + else + { + char* np = nullptr; + const float f = strtod(p, &np); + if (np == p) + { + r.catf("number expected at line %u column %d", row + 3, (p - buffer) + 1); + return true; // failed to read a number + } + SetGridHeight(col, row, f); + p = np; + } + if (*p == ',') + { + ++p; + } + } + } + return false; // success! + } + return true; // an error occurred } // Compute the height error at the specified point -float GridDefinition::ComputeHeightError(float x, float y) const +float HeightMap::ComputeHeightError(float x, float y) const { - const float xf = (x - xMin) * recipSpacing; + const float xf = (x - def.xMin) * def.recipSpacing; const float xFloor = floor(xf); const int32_t xIndex = (int32_t)xFloor; - const float yf = (y - yMin) * recipSpacing; + const float yf = (y - def.yMin) * def.recipSpacing; const float yFloor = floor(yf); const int32_t yIndex = (int32_t)yFloor; @@ -166,29 +298,29 @@ float GridDefinition::ComputeHeightError(float x, float y) const // We are off the bottom left corner of the grid return GetHeightError(0, 0); } - else if (yIndex >= (int)NumYpoints()) + else if (yIndex >= (int)def.numY) { - return GetHeightError(0, NumYpoints()); + return GetHeightError(0, def.numY); } else { return InterpolateY(0, yIndex, yf - yFloor); } } - else if (xIndex >= (int)NumXpoints()) + else if (xIndex >= (int)def.numX) { if (yIndex < 0) { // We are off the bottom left corner of the grid - return GetHeightError(NumXpoints(), 0); + return GetHeightError(def.numX, 0); } - else if (yIndex >= (int)NumYpoints()) + else if (yIndex >= (int)def.numY) { - return GetHeightError(NumXpoints(), NumYpoints()); + return GetHeightError(def.numX, def.numY); } else { - return InterpolateY(NumXpoints(), yIndex, yf - yFloor); + return InterpolateY(def.numX, yIndex, yf - yFloor); } } else @@ -198,9 +330,9 @@ float GridDefinition::ComputeHeightError(float x, float y) const // We are off the bottom left corner of the grid return InterpolateX(xIndex, 0, xf - xFloor); } - else if (yIndex >= (int)NumYpoints()) + else if (yIndex >= (int)def.numY) { - return InterpolateX(xIndex, NumYpoints(), xf - xFloor); + return InterpolateX(xIndex, def.numY, xf - xFloor); } else { @@ -209,25 +341,25 @@ float GridDefinition::ComputeHeightError(float x, float y) const } } -float GridDefinition::GetHeightError(uint32_t xIndex, uint32_t yIndex) const +float HeightMap::GetHeightError(uint32_t xIndex, uint32_t yIndex) const { const uint32_t index = GetMapIndex(xIndex, yIndex); return (IsHeightSet(index)) ? gridHeights[index] : 0.0; } -float GridDefinition::InterpolateX(uint32_t xIndex, uint32_t yIndex, float xFrac) const +float HeightMap::InterpolateX(uint32_t xIndex, uint32_t yIndex, float xFrac) const { const uint32_t index1 = GetMapIndex(xIndex, yIndex); return Interpolate2(index1, index1 + 1, xFrac); } -float GridDefinition::InterpolateY(uint32_t xIndex, uint32_t yIndex, float yFrac) const +float HeightMap::InterpolateY(uint32_t xIndex, uint32_t yIndex, float yFrac) const { const uint32_t index1 = GetMapIndex(xIndex, yIndex); - return Interpolate2(index1, index1 + numX, yFrac); + return Interpolate2(index1, index1 + def.numX, yFrac); } -float GridDefinition::Interpolate2(uint32_t index1, uint32_t index2, float frac) const +float HeightMap::Interpolate2(uint32_t index1, uint32_t index2, float frac) const { const bool b1 = IsHeightSet(index1); const bool b2 = IsHeightSet(index2); @@ -237,11 +369,11 @@ float GridDefinition::Interpolate2(uint32_t index1, uint32_t index2, float frac) : 0.0; } -float GridDefinition::InterpolateXY(uint32_t xIndex, uint32_t yIndex, float xFrac, float yFrac) const +float HeightMap::InterpolateXY(uint32_t xIndex, uint32_t yIndex, float xFrac, float yFrac) const { const uint32_t indexX0Y0 = GetMapIndex(xIndex, yIndex); // (X0,Y0) const uint32_t indexX1Y0 = indexX0Y0 + 1; // (X1,Y0) - const uint32_t indexX0Y1 = indexX0Y0 + numX; // (X0 Y1) + const uint32_t indexX0Y1 = indexX0Y0 + def.numX; // (X0 Y1) const uint32_t indexX1Y1 = indexX0Y1 + 1; // (X1,Y1) const unsigned int cc = ((unsigned int)IsHeightSet(indexX0Y0) << 0) + ((unsigned int)IsHeightSet(indexX1Y0) << 1) @@ -291,7 +423,7 @@ float GridDefinition::InterpolateXY(uint32_t xIndex, uint32_t yIndex, float xFra } } -float GridDefinition::InterpolateCorner(uint32_t cornerIndex, uint32_t indexX, uint32_t indexY, float xFrac, float yFrac) const +float HeightMap::InterpolateCorner(uint32_t cornerIndex, uint32_t indexX, uint32_t indexY, float xFrac, float yFrac) const { return ((xFrac * gridHeights[indexX]) + (yFrac * gridHeights[indexY]) + ((2.0 - xFrac - yFrac) * gridHeights[cornerIndex]))/2; } diff --git a/src/Movement/Grid.h b/src/Movement/Grid.h index 9146d394..cb73b21f 100644 --- a/src/Movement/Grid.h +++ b/src/Movement/Grid.h @@ -11,6 +11,7 @@ #include <cstdint> #include "ecv.h" #include "Libraries/General/StringRef.h" +#include "Configuration.h" class FileStore; @@ -18,9 +19,10 @@ class FileStore; class GridDefinition { public: + friend class HeightMap; + GridDefinition(); GridDefinition(const float xRange[2], const float yRange[2], float pRadius, float pSpacing); - void SetStorage(const float *heightStorage, const uint32_t *heightSetStorage); uint32_t NumXpoints() const { return numX; } uint32_t NumYpoints() const { return numY; } @@ -31,35 +33,62 @@ public: bool IsValid() const { return isValid; } void PrintParameters(StringRef& r) const; + void WriteHeadingAndParameters(StringRef& r) const; + static bool CheckHeading(const StringRef& s); + bool ReadParameters(const StringRef& s); void PrintError(StringRef& r) const pre(!IsValid()); - bool SaveToFile(FileStore *f) const // Save the grid to file returning true if an error occurred - pre(IsValid()); - - bool LoadFromFile(FileStore *f); // Load the grid from file returning true if an error occurred - - float ComputeHeightError(float x, float y) const // Compute the height error at the specified point - pre(IsValid(); gridHeights != nullptr; gridHeights.upb >= NumPoints()); - private: - static constexpr float MinSpacing = 0.1; // The minimum point spacing allowed - static constexpr float MinRange = 1.0; // The minimum X and Y range allowed + void CheckValidity(); + + static constexpr float MinSpacing = 0.1; // The minimum point spacing allowed + static constexpr float MinRange = 1.0; // The minimum X and Y range allowed + static const char *HeightMapLabelLine; // The line we write to the height map file listing the parameter names // Primary parameters - float xMin, xMax, yMin, yMax; // The edges of the grid for G29 probing - float radius; // The grid radius to probe - float spacing; // The spacing of the grid probe points - const float *gridHeights; // The map of grid heights - const uint32_t *gridHeightSet; // Bitmap of which heights are set + float xMin, xMax, yMin, yMax; // The edges of the grid for G29 probing + float radius; // The grid radius to probe + float spacing; // The spacing of the grid probe points // Derived parameters uint32_t numX, numY; float recipSpacing; bool isValid; - uint32_t GetMapIndex(uint32_t xIndex, uint32_t yIndex) const { return (yIndex * numX) + xIndex; } +}; + +// Class to represent the height map +class HeightMap +{ +public: + HeightMap(float *heightStorage); + + const GridDefinition& GetGrid() const { return def; } + void SetGrid(const GridDefinition& gd); + + float ComputeHeightError(float x, float y) const // Compute the height error at the specified point + pre(IsValid(); gridHeights != nullptr; gridHeights.upb >= NumPoints()); + + void ClearGridHeights(); // Clear all grid height corrections + void SetGridHeight(size_t xIndex, size_t yIndex, float height); // Set the height of a grid point + + bool SaveToFile(FileStore *f) const // Save the grid to file returning true if an error occurred + pre(IsValid()); + + bool LoadFromFile(FileStore *f, StringRef& r); // Load the grid from file returning true if an error occurred + + unsigned int GetMinimumSegments(float distance) const; // Return the minimum number of segments for a move by this X or Y amount + +private: + static const char *HeightMapComment; // The start of the comment we write at the start of the height map file + + GridDefinition def; + float *gridHeights; // The map of grid heights, must have at least MaxGridProbePoints entries + uint32_t gridHeightSet[MaxGridProbePoints/32]; // Bitmap of which heights are set + + uint32_t GetMapIndex(uint32_t xIndex, uint32_t yIndex) const { return (yIndex * def.NumXpoints()) + xIndex; } bool IsHeightSet(uint32_t index) const { return (gridHeightSet[index/32] & (1 << (index & 31))) != 0; } float GetHeightError(uint32_t xIndex, uint32_t yIndex) const; float InterpolateX(uint32_t xIndex, uint32_t yIndex, float xFrac) const; diff --git a/src/Movement/Move.cpp b/src/Movement/Move.cpp index 0d7ccf78..e9ed842d 100644 --- a/src/Movement/Move.cpp +++ b/src/Movement/Move.cpp @@ -7,7 +7,7 @@ #include "RepRapFirmware.h" -Move::Move(Platform* p, GCodes* g) : currentDda(NULL) +Move::Move(Platform* p, GCodes* g) : currentDda(NULL), grid(zBedProbePoints) { active = false; @@ -66,18 +66,26 @@ void Move::Init() SetPositions(move); // Set up default bed probe points. This is only a guess, because we don't know the bed size yet. - for (size_t point = 0; point < MaxProbePoints; point++) + for (size_t point = 0; point < ARRAY_SIZE(zBedProbePoints); point++) { if (point < 4) { - xBedProbePoints[point] = (0.3 + 0.6*(float)(point%2))*reprap.GetPlatform()->AxisMaximum(X_AXIS); - yBedProbePoints[point] = (0.0 + 0.9*(float)(point/2))*reprap.GetPlatform()->AxisMaximum(Y_AXIS); + xBedProbePoints[point] = (0.1 + 0.8 * (float)(point%2)) * reprap.GetPlatform()->AxisMaximum(X_AXIS); + yBedProbePoints[point] = (0.1 + 0.8 * (float)(point/2)) * reprap.GetPlatform()->AxisMaximum(Y_AXIS); + } + else if (point == 4) + { + xBedProbePoints[point] = 0.5 * reprap.GetPlatform()->AxisMaximum(X_AXIS); + yBedProbePoints[point] = 0.5 * reprap.GetPlatform()->AxisMaximum(Y_AXIS); } zBedProbePoints[point] = 0.0; - probePointSet[point] = unset; + if (point < ARRAY_SIZE(probePointSet)) + { + probePointSet[point] = unset; + } } - xRectangle = 1.0/(0.8*reprap.GetPlatform()->AxisMaximum(X_AXIS)); + xRectangle = 1.0/(0.8 * reprap.GetPlatform()->AxisMaximum(X_AXIS)); yRectangle = xRectangle; longWait = reprap.GetPlatform()->Time(); @@ -174,38 +182,6 @@ void Move::Spin() if (simulationMode < 2) // in simulation mode 2 and higher, we don't process incoming moves beyond this point { -#if 0 //*** This code is not finished yet *** - // If we are doing bed compensation and the move crosses a compensation boundary by a significant amount, - // segment it so that we can apply proper bed compensation - // Issues here: - // 1. Are there enough DDAs? need to make nextMove static and remember whether we have the remains of a move in there. - // 2. Pause/restart: if we restart a segmented move when we have already executed part of it, we will extrude too much. - // Perhaps remember how much of the last move we executed? Or always insist on completing all the segments in a move? - bool isSegmented; - do - { - GCodes::RawMove tempMove = nextMove; - isSegmented = SegmentMove(tempMove); - if (isSegmented) - { - // Extruder moves are relative, so we need to adjust the extrusion amounts in the original move - for (size_t drive = AXES; drive < DRIVES; ++drive) - { - nextMove.coords[drive] -= tempMove.coords[drive]; - } - } - bool doMotorMapping = (moveType == 0) || (moveType == 1 && !IsDeltaMode()); - if (doMotorMapping) - { - Transform(tempMove); - } - if (ddaRingAddPointer->Init(tempMove.coords, nextMove.feedRate, nextMove.endStopsToCheck, doMotorMapping, nextMove.filePos)) - { - ddaRingAddPointer = ddaRingAddPointer->GetNext(); - idleCount = 0; - } - } while (isSegmented); -#else // Use old code bool doMotorMapping = (nextMove.moveType == 0) || (nextMove.moveType == 1 && !IsDeltaMode()); if (doMotorMapping) { @@ -216,7 +192,6 @@ void Move::Spin() ddaRingAddPointer = ddaRingAddPointer->GetNext(); idleCount = 0; } -#endif } } else @@ -432,6 +407,14 @@ void Move::Diagnostics(MessageType mtype) numLookaheadUnderruns = numPrepareUnderruns = 0; longestGcodeWaitInterval = 0; + // Show the current probe position heights + p->Message(mtype, "Bed probe heights:"); + for (size_t i = 0; i < MaxProbePoints; ++i) + { + p->MessageF(mtype, " %.3f", ZBedProbePoint(i)); + } + p->Message(mtype, "\n"); + #if DDA_LOG_PROBE_CHANGES // Temporary code to print Z probe trigger positions p->Message(mtype, "Probe change coordinates:"); @@ -1529,88 +1512,6 @@ size_t Move::NumberOfXYProbePoints() const return MaxProbePoints; } -// Set a new grid -void Move::SetBedProbeGrid(const GridDefinition& newGrid) -{ - useGridHeights = false; - grid = newGrid; - grid.SetStorage(zBedProbePoints, gridHeightSet); -} - -void Move::ClearGridHeights() -{ - useGridHeights = false; - for (size_t i = 0; i < ARRAY_SIZE(gridHeightSet); ++i) - { - gridHeightSet[i] = 0; - } -} - -// Set the height of a grid point -void Move::SetGridHeight(size_t xIndex, size_t yIndex, float height) -{ - size_t index = yIndex * grid.NumXpoints() + xIndex; - if (index < MaxGridProbePoints) - { - zBedProbePoints[index] = height; - gridHeightSet[index/32] |= 1u << (index & 31u); - } -} - -// Load the height map -bool Move::LoadHeightMapFromFile(const char *fname, StringRef& reply) -{ - Platform *platform = reprap.GetPlatform(); - FileStore * const f = platform->GetFileStore(platform->GetSysDir(), fname, false); - bool err; - if (f == nullptr) - { - reply.printf("Height map file %s not found", fname); - err = true; - } - else - { - //TODO - err = grid.LoadFromFile(f); - f->Close(); - } - - if (err) - { - ClearGridHeights(); // make sure we don't end up with a partial height map - } - return err; -} - -// Save the height map and write the success or error message to 'reply' -// Returning true if an error occurred -bool Move::SaveHeightMapToFile(const char *fname, StringRef& reply) const -{ - Platform *platform = reprap.GetPlatform(); - FileStore * const f = platform->GetFileStore(platform->GetSysDir(), fname, true); - bool err; - if (f == nullptr) - { - reply.printf("Failed to create height map file %s", fname); - err = true; - } - else - { - err = grid.SaveToFile(f); - f->Close(); - if (err) - { - platform->GetMassStorage()->Delete(platform->GetSysDir(), fname); - reply.printf("Failed to save height map to file %s", fname); - } - else - { - reply.printf("Height map saved to file %s", fname); - } - } - return err; -} - // Enter or leave simulation mode void Move::Simulate(uint8_t simMode) { diff --git a/src/Movement/Move.h b/src/Movement/Move.h index 401f0c1d..698fae2f 100644 --- a/src/Movement/Move.h +++ b/src/Movement/Move.h @@ -113,16 +113,10 @@ public: bool IsExtruding() const; // Is filament being extruded? - const GridDefinition& GetBedProbeGrid() const { return grid; } // Access the bed probing grid + HeightMap& AccessBedProbeGrid() { return grid; } // Access the bed probing grid - void SetBedProbeGrid(const GridDefinition& newGrid) // Set a new grid - pre(newGrid.IsValid()); - - void ClearGridHeights(); // Clear all grid height corrections - void SetGridHeight(size_t xIndex, size_t yIndex, float height); // Set the height of a grid point - void UseHeightMap() { useGridHeights = true; } // Start using the height map - bool LoadHeightMapFromFile(const char *fname, StringRef& reply); // Load the height map and *append* any error message to 'reply' - bool SaveHeightMapToFile(const char *fname, StringRef& reply) const; // Save the height map + void UseHeightMap(bool b) { useGridHeights = b; } // Start or stop using the height map + bool UsingHeightMap() const { return useGridHeights; } // Are we doing grid bed compensation? private: @@ -183,8 +177,7 @@ private: int numBedCompensationPoints; // The number of points we are actually using for bed compensation, 0 means identity bed transform float xRectangle, yRectangle; // The side lengths of the rectangle used for second-degree bed compensation - GridDefinition grid; // Grid definition for G29 bed probing. The probe heights are stored in zBedProbePoints, see above. - uint32_t gridHeightSet[MaxGridProbePoints/32]; // Bitmap of which points have been probed + HeightMap grid; // Grid definition and height map for G29 bed probing. The probe heights are stored in zBedProbePoints, see above. bool useGridHeights; // True if the zBedProbePoints came from valid bed probing and relate to the current grid float idleTimeout; // How long we wait with no activity before we reduce motor currents to idle @@ -5,7 +5,11 @@ #if !defined(PLATFORM) # if defined(__SAM3X8E__) -# define PLATFORM Duet +# if defined(__RADDS__) +# define PLATFORM RADDS +# else +# define PLATFORM Duet +# endif # elif defined(__SAM4E8E__) # define PLATFORM DuetNG # else diff --git a/src/Platform.cpp b/src/Platform.cpp index 72ddd3f4..3fb43cf9 100644 --- a/src/Platform.cpp +++ b/src/Platform.cpp @@ -21,7 +21,6 @@ #include "RepRapFirmware.h" #include "DueFlashStorage.h" -#include "RTCDue.h" #include "sam/drivers/tc/tc.h" #include "sam/drivers/hsmci/hsmci.h" @@ -54,6 +53,12 @@ static volatile uint32_t fanInterval = 0; // written by ISR, read outside the const float minStepPulseTiming = 0.2; // we assume that we always generate step high and low times at least this wide without special action +const int Heater0LogicalPin = 0; +const int Fan0LogicalPin = 20; +const int EndstopXLogicalPin = 40; +const int Special0LogicalPin = 60; +const int DueX5Gpio0LogicalPin = 100; + //#define MOVE_DEBUG #ifdef MOVE_DEBUG @@ -157,8 +162,7 @@ void Platform::Init() SetBoardType(BoardType::Auto); // Real-time clock - - RTCDue::Init(); + realTime = 0; // Comms @@ -202,22 +206,12 @@ void Platform::Init() fileStructureInitialised = true; -#if !defined(DUET_NG) +#if !defined(DUET_NG) && !defined(__RADDS__) mcpDuet.begin(); // only call begin once in the entire execution, this begins the I2C comms on that channel for all objects mcpExpansion.setMCP4461Address(0x2E); // not required for mcpDuet, as this uses the default address #endif - // Directories - - sysDir = SYS_DIR; - macroDir = MACRO_DIR; - webDir = WEB_DIR; - gcodeDir = GCODE_DIR; - configFile = CONFIG_FILE; - defaultFile = DEFAULT_FILE; - // DRIVES - ARRAY_INIT(endStopPins, END_STOP_PINS); ARRAY_INIT(maxFeedrates, MAX_FEEDRATES); ARRAY_INIT(accelerations, ACCELERATIONS); @@ -236,13 +230,11 @@ void Platform::Init() maxAverageAcceleration = 10000.0; // high enough to have no effect until it is changed // Z PROBE - zProbePin = Z_PROBE_PIN; zProbeAdcChannel = PinToAdcChannel(zProbePin); InitZProbe(); // this also sets up zProbeModulationPin // AXES - ARRAY_INIT(axisMaxima, AXIS_MAXIMA); ARRAY_INIT(axisMinima, AXIS_MINIMA); @@ -334,7 +326,11 @@ void Platform::Init() TMC2660::Init(ENABLE_PINS, numTMC2660Drivers); #endif - extrusionAncilliaryPWM = 0.0; + extrusionAncilliaryPwmValue = 0.0; + extrusionAncilliaryPwmLogicalPin = -1; + extrusionAncilliaryPwmFirmwarePin = NoPin; + extrusionAncilliaryPwmInvert = false; + SetExtrusionAncilliaryPwmPin(Fan0LogicalPin); ARRAY_INIT(tempSensePins, TEMP_SENSE_PINS); ARRAY_INIT(heatOnPins, HEAT_ON_PINS); @@ -1204,6 +1200,16 @@ void Platform::Spin() TMC2660::SetDriversPowered(driversPowered); #endif + // Update the time + if (realTime != 0) + { + if (millis() - timeLastUpdatedMillis >= 1000) + { + ++realTime; // this assumes that time_t is a seconds-since-epoch counter, which is not guaranteed by the C standard + timeLastUpdatedMillis += 1000; + } + } + ClassReport(longWait); } @@ -1372,7 +1378,7 @@ void Platform::Diagnostics(MessageType mtype) (unsigned int)(now/3600), (unsigned int)((now % 3600)/60), (unsigned int)(now % 60), resetReasons[(REG_RSTC_SR & RSTC_SR_RSTTYP_Msk) >> RSTC_SR_RSTTYP_Pos]); - // Show the error code stored at the last software reset + // Show the reset code stored at the last software reset { SoftwareResetData temp; temp.magic = 0; @@ -1387,14 +1393,6 @@ void Platform::Diagnostics(MessageType mtype) // Show the current error codes MessageF(mtype, "Error status: %u\n", errorCodeBits); - // Show the current probe position heights - Message(mtype, "Bed probe heights:"); - for (size_t i = 0; i < MaxProbePoints; ++i) - { - MessageF(mtype, " %.3f", reprap.GetMove()->ZBedProbePoint(i)); - } - Message(mtype, "\n"); - // Show the number of free entries in the file table unsigned int numFreeFiles = 0; for (size_t i = 0; i < MAX_FILES; i++) @@ -1449,11 +1447,13 @@ void Platform::Diagnostics(MessageType mtype) #endif // Show current RTC time - const time_t timeNow = RTCDue::GetDateTime(); - const struct tm * const timeInfo = gmtime(&timeNow); - MessageF(mtype, "Current date and time: %04u-%02u-%02u %02u:%02u:%02u\n", - timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday, - timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec); + struct tm timeInfo; + if (gmtime_r(&realTime, &timeInfo) != nullptr) + { + MessageF(mtype, "Current date and time: %04u-%02u-%02u %02u:%02u:%02u\n", + timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday, + timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec); + } // Debug //MessageF(mtype, "TC_FMR = %08x, PWM_FPE = %08x, PWM_FSR = %08x\n", TC2->TC_FMR, PWM->PWM_FPE, PWM->PWM_FSR); @@ -2608,50 +2608,50 @@ bool Platform::GetFirmwarePin(int logicalPin, PinAccess access, Pin& firmwarePin { // Pin number out of range, so nothing to do here } - else if (logicalPin < HEATERS) // pins 0-9 correspond to heater channels + else if (logicalPin >= Heater0LogicalPin && logicalPin < Heater0LogicalPin + HEATERS) // pins 0-9 correspond to heater channels { // For safety, we don't allow a heater channel to be used for servos until the heater has been disabled - if (!reprap.GetHeat()->IsHeaterEnabled(logicalPin)) + if (!reprap.GetHeat()->IsHeaterEnabled(logicalPin - Heater0LogicalPin)) { - firmwarePin = heatOnPins[logicalPin]; + firmwarePin = heatOnPins[logicalPin - Heater0LogicalPin]; invert = !HEAT_ON; } } - else if (logicalPin >= 20 && logicalPin < 20 + (int)NUM_FANS) // pins 100-107 correspond to fan channels + else if (logicalPin >= Fan0LogicalPin && logicalPin < Fan0LogicalPin + (int)NUM_FANS) // pins 20- correspond to fan channels { // Don't allow a fan channel to be used unless the fan has been disabled - if (!fans[logicalPin - 20].IsEnabled() + if (!fans[logicalPin - Fan0LogicalPin].IsEnabled() #ifdef DUET_NG // Fan pins on the DueX2/DueX5 cannot be used to control servos because the frequency is not well-defined. - && (logicalPin <= 22 || access != PinAccess::servo) + && (logicalPin <= Fan0LogicalPin + 2 || access != PinAccess::servo) #endif ) { - firmwarePin = COOLING_FAN_PINS[logicalPin - 20]; + firmwarePin = COOLING_FAN_PINS[logicalPin - Fan0LogicalPin]; } } - else if (logicalPin >= 40 && logicalPin < 40 + (int)ARRAY_SIZE(endStopPins)) // pins 40-49 correspond to endstop pins + else if (logicalPin >= EndstopXLogicalPin && logicalPin < EndstopXLogicalPin + (int)ARRAY_SIZE(endStopPins)) // pins 40-49 correspond to endstop pins { if (access == PinAccess::read #ifdef DUET_NG // Endstop pins on the DueX2/DueX5 can be used as digital outputs too - || (access == PinAccess::write && logicalPin >= 45) + || (access == PinAccess::write && logicalPin >= EndstopXLogicalPin + 5) #endif ) { - firmwarePin = endStopPins[logicalPin - 40]; + firmwarePin = endStopPins[logicalPin - EndstopXLogicalPin]; } } - else if (logicalPin >= 60 && logicalPin < 60 + (int)ARRAY_SIZE(SpecialPinMap)) + else if (logicalPin >= Special0LogicalPin && logicalPin < Special0LogicalPin + (int)ARRAY_SIZE(SpecialPinMap)) { - firmwarePin = SpecialPinMap[logicalPin - 60]; + firmwarePin = SpecialPinMap[logicalPin - Special0LogicalPin]; } #ifdef DUET_NG - else if (logicalPin >= 100 && logicalPin < 100 + (int)ARRAY_SIZE(DueX5GpioPinMap)) // Pins 100-103 are the GPIO pins on the DueX2/X5 + else if (logicalPin >= DueX5Gpio0LogicalPin && logicalPin < DueX5Gpio0LogicalPin + (int)ARRAY_SIZE(DueX5GpioPinMap)) // Pins 100-103 are the GPIO pins on the DueX2/X5 { if (access != PinAccess::servo) { - firmwarePin = DueX5GpioPinMap[logicalPin - 100]; + firmwarePin = DueX5GpioPinMap[logicalPin - DueX5Gpio0LogicalPin]; } } #endif @@ -2682,6 +2682,11 @@ bool Platform::GetFirmwarePin(int logicalPin, PinAccess access, Pin& firmwarePin return false; } +bool Platform::SetExtrusionAncilliaryPwmPin(int logicalPin) +{ + return GetFirmwarePin(logicalPin, PinAccess::pwm, extrusionAncilliaryPwmFirmwarePin, extrusionAncilliaryPwmInvert); +} + #if SUPPORT_INKJET // Fire the inkjet (if any) in the given pattern @@ -2791,27 +2796,76 @@ void Platform::GetPowerVoltages(float& minV, float& currV, float& maxV) const bool Platform::IsDateTimeSet() const { - return RTCDue::IsDateTimeSet(); + return realTime != 0; } time_t Platform::GetDateTime() const { - return RTCDue::GetDateTime(); + return realTime; } bool Platform::SetDateTime(time_t time) { - return RTCDue::SetDateTime(time); + struct tm brokenDateTime; + const bool ok = (gmtime_r(&time, &brokenDateTime) != nullptr); + if (ok) + { + realTime = time; + timeLastUpdatedMillis = millis(); + } + return ok; } bool Platform::SetDate(time_t date) { - return RTCDue::SetDate(date); + // Check the validity of the date passed in + struct tm brokenNewDate; + const bool ok = (gmtime_r(&date, &brokenNewDate) != nullptr); + if (ok) + { + struct tm brokenTimeNow; + if (realTime == 0 || gmtime_r(&realTime, &brokenTimeNow) == nullptr) + { + // We didn't have a valid date/time set, so set the date and time to the value passed in + realTime = date; + timeLastUpdatedMillis = millis(); + } + else + { + // Merge the existing time into the date passed in + brokenNewDate.tm_hour = brokenTimeNow.tm_hour; + brokenNewDate.tm_min = brokenTimeNow.tm_min; + brokenNewDate.tm_sec = brokenTimeNow.tm_sec; + realTime = mktime(&brokenNewDate); + } + } + return ok; } bool Platform::SetTime(time_t time) { - return RTCDue::SetTime(time); + // Check the validity of the date passed in + struct tm brokenNewTime; + const bool ok = (gmtime_r(&time, &brokenNewTime) != nullptr); + if (ok) + { + struct tm brokenTimeNow; + if (realTime == 0 || gmtime_r(&realTime, &brokenTimeNow) == nullptr) + { + // We didn't have a valid date/time set, so set the date and time to the value passed in + realTime = time; + } + else + { + // Merge the new time into the current date/time + brokenTimeNow.tm_hour = brokenNewTime.tm_hour; + brokenTimeNow.tm_min = brokenNewTime.tm_min; + brokenTimeNow.tm_sec = brokenNewTime.tm_sec; + realTime = mktime(&brokenTimeNow); + } + timeLastUpdatedMillis = millis(); + } + return ok; } // Pragma pop_options is not supported on this platform, so we put this time-critical code right at the end of the file diff --git a/src/Platform.h b/src/Platform.h index 036084f9..8c9af3cb 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -236,7 +236,8 @@ struct ZProbeParameters float diveHeight; // the dive height we use when probing float probeSpeed; // the initial speed of probing float travelSpeed; // the speed at which we travel to the probe point - float param1, param2; // extra parameters used by some types of probe e.g. Delta probe + float recoveryTime; // Z probe recovery time + float extraParam; // extra parameters used by some types of probe e.g. Delta probe bool invertReading; // true if we need to invert the reading void Init(float h) @@ -249,7 +250,7 @@ struct ZProbeParameters diveHeight = DEFAULT_Z_DIVE; probeSpeed = DEFAULT_PROBE_SPEED; travelSpeed = DEFAULT_TRAVEL_SPEED; - param1 = param2 = 0.0; + recoveryTime = extraParam = 0.0; invertReading = false; } @@ -269,8 +270,8 @@ struct ZProbeParameters && diveHeight == other.diveHeight && probeSpeed == other.probeSpeed && travelSpeed == other.travelSpeed - && param1 == other.param1 - && param2 == other.param2 + && recoveryTime == other.recoveryTime + && extraParam == other.extraParam && invertReading == other.invertReading; } @@ -527,8 +528,13 @@ public: bool IsAccessibleProbePoint(float x, float y) const; float GetPressureAdvance(size_t drive) const; void SetPressureAdvance(size_t extruder, float factor); - void SetEndStopConfiguration(size_t axis, EndStopType endstopType, bool logicLevel); - void GetEndStopConfiguration(size_t axis, EndStopType& endstopType, bool& logicLevel) const; + + void SetEndStopConfiguration(size_t axis, EndStopType endstopType, bool logicLevel) + pre(axis < MAX_AXES); + + void GetEndStopConfiguration(size_t axis, EndStopType& endstopType, bool& logicLevel) const + pre(axis < MAX_AXES); + uint32_t GetAllEndstopStates() const; void SetAxisDriversConfig(size_t drive, const AxisDriversConfig& config); const AxisDriversConfig& GetAxisDriversConfig(size_t drive) const @@ -559,8 +565,10 @@ public: void SetZProbeParameters(const struct ZProbeParameters& params); bool MustHomeXYBeforeZ() const; - void SetExtrusionAncilliaryPWM(float v); - float GetExtrusionAncilliaryPWM() const; + void SetExtrusionAncilliaryPwmValue(float v); + float GetExtrusionAncilliaryPwmValue() const; + bool SetExtrusionAncilliaryPwmPin(int logicalPin); + int GetExtrusionAncilliaryPwmPin() const { return extrusionAncilliaryPwmLogicalPin; } void ExtrudeOn(); void ExtrudeOff(); @@ -766,7 +774,7 @@ private: #if defined(DUET_NG) size_t numTMC2660Drivers; // the number of TMC2660 drivers we have, the remaining are simple enable/step/dir drivers -#else +#elif !defined(__RADDS__) // Digipots MCP4461 mcpDuet; MCP4461 mcpExpansion; @@ -784,7 +792,10 @@ private: volatile ZProbeAveragingFilter zProbeOffFilter; // Z probe readings we took with the IR turned off volatile ThermistorAveragingFilter thermistorFilters[HEATERS]; // bed and extruder thermistor readings - float extrusionAncilliaryPWM; + float extrusionAncilliaryPwmValue; + int extrusionAncilliaryPwmLogicalPin; + Pin extrusionAncilliaryPwmFirmwarePin; + bool extrusionAncilliaryPwmInvert; void InitZProbe(); uint16_t GetRawZProbeReading() const; @@ -831,12 +842,6 @@ private: MassStorage* massStorage; FileStore* files[MAX_FILES]; bool fileStructureInitialised; - const char* webDir; - const char* gcodeDir; - const char* sysDir; - const char* macroDir; - const char* configFile; - const char* defaultFile; // Data used by the tick interrupt handler @@ -896,6 +901,10 @@ private: bool driversPowered; #endif + // RTC + time_t realTime; // the current date/time, or zero if never set + uint32_t timeLastUpdatedMillis; // the milliseconds counter when we last incremented the time + // Direct pin manipulation int8_t logicalPinModes[HighestLogicalPin + 1]; // what mode each logical pin is set to - would ideally be class PinMode not int8_t }; @@ -968,51 +977,50 @@ private: inline const char* Platform::GetWebDir() const { - return webDir; + return WEB_DIR; } // Where the gcodes are inline const char* Platform::GetGCodeDir() const { - return gcodeDir; + return GCODE_DIR; } // Where the system files are inline const char* Platform::GetSysDir() const { - return sysDir; + return SYS_DIR; } inline const char* Platform::GetMacroDir() const { - return macroDir; + return MACRO_DIR; } inline const char* Platform::GetConfigFile() const { - return configFile; + return CONFIG_FILE; } inline const char* Platform::GetDefaultFile() const { - return defaultFile; + return DEFAULT_FILE; } - //***************************************************************************************************************** // Drive the RepRap machine - Movement inline float Platform::DriveStepsPerUnit(size_t drive) const { - return driveStepsPerUnit[drive]; + return driveStepsPerUnit[drive]; } inline void Platform::SetDriveStepsPerUnit(size_t drive, float value) { - driveStepsPerUnit[drive] = value; + driveStepsPerUnit[drive] = value; } inline float Platform::Acceleration(size_t drive) const @@ -1032,7 +1040,7 @@ inline void Platform::SetAcceleration(size_t drive, float value) inline float Platform::MaxFeedrate(size_t drive) const { - return maxFeedrates[drive]; + return maxFeedrates[drive]; } inline const float* Platform::MaxFeedrates() const @@ -1067,7 +1075,7 @@ inline bool Platform::GetDirectionValue(size_t drive) const inline void Platform::SetDriverDirection(uint8_t driver, bool direction) { - bool d = (direction == FORWARDS) ? directions[driver] : !directions[driver]; + const bool d = (direction == FORWARDS) ? directions[driver] : !directions[driver]; digitalWrite(DIRECTION_PINS[driver], d); } @@ -1107,14 +1115,14 @@ inline float Platform::AxisTotalLength(size_t axis) const return axisMaxima[axis] - axisMinima[axis]; } -inline void Platform::SetExtrusionAncilliaryPWM(float v) +inline void Platform::SetExtrusionAncilliaryPwmValue(float v) { - extrusionAncilliaryPWM = v; + extrusionAncilliaryPwmValue = v; } -inline float Platform::GetExtrusionAncilliaryPWM() const +inline float Platform::GetExtrusionAncilliaryPwmValue() const { - return extrusionAncilliaryPWM; + return extrusionAncilliaryPwmValue; } // For the Duet we use the fan output for this @@ -1123,9 +1131,9 @@ inline float Platform::GetExtrusionAncilliaryPWM() const // Caution: this is often called from an ISR, or with interrupts disabled! inline void Platform::ExtrudeOn() { - if (extrusionAncilliaryPWM > 0.0) + if (extrusionAncilliaryPwmValue > 0.0) { - SetFanValue(0,extrusionAncilliaryPWM); + WriteAnalog(extrusionAncilliaryPwmFirmwarePin, extrusionAncilliaryPwmValue, DefaultPinWritePwmFreq); } } @@ -1134,9 +1142,9 @@ inline void Platform::ExtrudeOn() // Caution: this is often called from an ISR, or with interrupts disabled! inline void Platform::ExtrudeOff() { - if (extrusionAncilliaryPWM > 0.0) + if (extrusionAncilliaryPwmValue > 0.0) { - SetFanValue(0,0.0); + WriteAnalog(extrusionAncilliaryPwmFirmwarePin, 0.0, DefaultPinWritePwmFreq); } } @@ -1204,14 +1212,12 @@ inline float Platform::GetPressureAdvance(size_t extruder) const } inline void Platform::SetEndStopConfiguration(size_t axis, EndStopType esType, bool logicLevel) -pre(axis < MAX_AXES) { endStopType[axis] = esType; endStopLogicLevel[axis] = logicLevel; } inline void Platform::GetEndStopConfiguration(size_t axis, EndStopType& esType, bool& logicLevel) const -pre(axis < MAX_AXES) { esType = endStopType[axis]; logicLevel = endStopLogicLevel[axis]; @@ -1325,7 +1331,7 @@ inline float Platform::AdcReadingToPowerVoltage(uint16_t adcVal) #if defined(DUET_NG) return pinDesc.ulPin; #elif defined(__RADDS__) -# error needs writing + return (pinDesc.pPort == PIOC) ? pinDesc.ulPin << 1 : pinDesc.ulPin; #else return (pinDesc.pPort == PIOA) ? pinDesc.ulPin << 1 : pinDesc.ulPin; #endif @@ -1339,7 +1345,10 @@ inline float Platform::AdcReadingToPowerVoltage(uint16_t adcVal) #if defined(DUET_NG) PIOD->PIO_ODSR = driverMap; // on Duet WiFi all step pins are on port D #elif defined(__RADDS__) -# error need to write this + PIOA->PIO_ODSR = driverMap; + PIOB->PIO_ODSR = driverMap; + PIOD->PIO_ODSR = driverMap; + PIOC->PIO_ODSR = driverMap >> 1; // do this last, it means the processor doesn't need to preserve the register containing driverMap #else // Duet PIOD->PIO_ODSR = driverMap; PIOC->PIO_ODSR = driverMap; @@ -1355,7 +1364,10 @@ inline float Platform::AdcReadingToPowerVoltage(uint16_t adcVal) #if defined(DUET_NG) PIOD->PIO_ODSR = 0; // on Duet WiFi all step pins are on port D #elif defined(__RADDS__) -# error need to write this + PIOD->PIO_ODSR = 0; + PIOC->PIO_ODSR = 0; + PIOB->PIO_ODSR = 0; + PIOA->PIO_ODSR = 0; #else // Duet PIOD->PIO_ODSR = 0; PIOC->PIO_ODSR = 0; diff --git a/src/RADDS/Network.cpp b/src/RADDS/Network.cpp new file mode 100644 index 00000000..901f3b45 --- /dev/null +++ b/src/RADDS/Network.cpp @@ -0,0 +1,8 @@ +#include "Network.h" + +static const uint8_t dummy_ipv4[4] = { 0, 0, 0, 0 }; + +const uint8_t *Network::IPAddress() const +{ + return dummy_ipv4; +} diff --git a/src/RADDS/Network.h b/src/RADDS/Network.h new file mode 100644 index 00000000..0cc3db50 --- /dev/null +++ b/src/RADDS/Network.h @@ -0,0 +1,34 @@ +#ifndef NETWORK_H +#define NETWORK_H + +#include <inttypes.h> +#include "Platform.h" + +const uint8_t MAC_ADDRESS[6] = { 0, 0, 0, 0, 0, 0 }; +const uint8_t IP_ADDRESS[4] = { 0, 0, 0, 0 }; +const uint8_t NET_MASK[4] = { 0, 0, 0, 0 }; +const uint8_t GATE_WAY[4] = { 0, 0, 0, 0 }; + +// The main network class that drives the network. +class Network +{ +public: + Network(Platform* p) { }; + void Init() const { }; + void Activate() const { }; + void Disable() const { }; + void Enable() const { }; + void Exit() const { } + void Spin() const { }; + void Interrupt() const { }; + void Diagnostics(MessageType mtype) const { }; + + boolean IsEnabled() const { return false; } + boolean InLwip() const { return false; } + void SetHostname(const char *name) const { }; + void SetHttpPort(uint16_t port) const { }; + uint16_t GetHttpPort() const { return (uint16_t)0; } + const uint8_t *IPAddress() const; +}; + +#endif diff --git a/src/RADDS/Pins_radds.h b/src/RADDS/Pins_radds.h new file mode 100644 index 00000000..09beff9c --- /dev/null +++ b/src/RADDS/Pins_radds.h @@ -0,0 +1,248 @@ +#ifndef PINS_DUET_H__ +#define PINS_DUET_H__ + +#define NAME "RepRapFirmware for Duet" + +const size_t NumFirmwareUpdateModules = 1; +#define IAP_UPDATE_FILE "iapradds.bin" +#define IAP_FIRMWARE_FILE "RepRapFirmware.bin" + +// Default board type +#define DEFAULT_BOARD_TYPE BoardType::RADDS_15 +#define ELECTRONICS "RADDS (+ Extension)" + +#define SUPPORT_INKJET 0 // set nonzero to support inkjet control +#define SUPPORT_ROLAND 0 // set nonzero to support Roland mill + +// The physical capabilities of the machine + +// The number of drives in the machine, including X, Y, and Z plus extruder drives +const size_t DRIVES = 8; + +// Initialization macro used in statements needing to initialize values in arrays of size DRIVES. E.g., +// max_feed_rates[DRIVES] = {DRIVES_(1, 1, 1, 1, 1, 1, 1, 1, 1)} +#define DRIVES_(a,b,c,d,e,f,g,h,i,j) { a,b,c,d,e,f,g,h } +const size_t MaxDriversPerAxis = 4; // The maximum number of stepper drivers assigned to one axis + +// The number of heaters in the machine +// 0 is the heated bed even if there isn't one. +const int8_t HEATERS = 4; + +// Initialization macro used in statements needing to initialize values in arrays of size HEATERS. E.g., +// defaultPidKis[HEATERS] = {HEATERS_(5.0, 0.1, 0.1, 0.1, 0.1, 0.1)}; +#define HEATERS_(a,b,c,d,e,f,g,h) { a,b,c,d } + +const size_t MAX_AXES = 6; // FIXME The maximum number of movement axes in the machine, usually just X, Y and Z, <= DRIVES +const size_t MIN_AXES = 3; // The minimum and default number of axes +const size_t DELTA_AXES = 3; // The number of axis involved in delta movement +const size_t CART_AXES = 3; // The number of Cartesian axes +const size_t MaxExtruders = DRIVES - MIN_AXES; // The maximum number of extruders + +const size_t NUM_SERIAL_CHANNELS = 2; +// Use TX0/RX0 for the auxiliary serial line +#define SERIAL_MAIN_DEVICE SerialUSB +#define SERIAL_AUX_DEVICE Serial1 + +// The numbers of entries in each array must correspond with the values of DRIVES, AXES, or HEATERS. Set values to -1 to flag unavailability. +// DRIVES +// X Y Z E1 E2 E3 E4 E5 +const Pin ENABLE_PINS[DRIVES] = { 26, 22, 15, 62, 65, 49, 37, 31 }; +const bool ENABLE_VALUES[DRIVES] = { false, false, false, false, false, false, false, false }; +// A15 D04 B25 A02 B19 C12 C03 D06 +const Pin STEP_PINS[DRIVES] = { 24, 17, 2, 61, 64, 51, 35, 29 }; +const Pin DIRECTION_PINS[DRIVES] = { 23, 16, 3, 60, 63, 53, 33, 27 }; + +// Endstops +// E Stops not currently used +// Note: RepRapFirmware only as a single endstop per axis +// gcode defines if it is a max ("high end") or min ("low end") +// endstop. gcode also sets if it is active HIGH or LOW +// +// 28 = RADDS X min +// 30 = RADDS Y min +// 32 = RADDS Z min +// 39 = RADDS PWM3 +// +// This leaves 34, 36, and 38 as spare pins (X, Y, Z max) + +const Pin END_STOP_PINS[DRIVES] = { 28, 30, 32, 39, NoPin, NoPin, NoPin, NoPin }; + +// HEATERS - The bed is assumed to be the at index 0 + +// 0 for inverted heater (e.g. Duet v0.6) +// 1 for not (e.g. Duet v0.4; RADDS) +const bool HEAT_ON = true; + +// Analogue pin numbers +const Pin TEMP_SENSE_PINS[HEATERS] = HEATERS_(4, 0, 1, 2, e, f, g, h); + +// Heater outputs +// Bed PMW: D7 has hardware PWM so bed has PWM +// h0, h1 PMW: D13 & D12 are on TIOB0 & B8 which are both TC B channels, so they get PWM +// h2 bang-bang: D11 is on TIOA8 which is a TC A channel shared with h1, it gets bang-bang control + +const Pin HEAT_ON_PINS[HEATERS] = HEATERS_(7, 13, 12, 11, e, f, g, h); // bed, h0, h1, h2 + +// Default thermistor betas +const float BED_R25 = 10000.0; +const float BED_BETA = 4066.0; +const float EXT_R25 = 100000.0; +const float EXT_BETA = 4066.0; + +// Thermistor series resistor value in Ohms +const float THERMISTOR_SERIES_RS = 4700.0; + +const size_t MaxSpiTempSensors = 2; + +// Digital pins the 31855s have their select lines tied to +const Pin SpiTempSensorCsPins[MaxSpiTempSensors] = { 38, 36 }; + +// Digital pin number that controls the ATX power on/off +const Pin ATX_POWER_PIN = 40; + +// Z Probe pin +// Must be an ADC capable pin. Can be any of the ARM's A/D capable +// pins even a non-Arduino pin. +const Pin Z_PROBE_PIN = 5; // RADDS "ADC" pin + +// Digital pin number to turn the IR LED on (high) or off (low) +// channel 1: Z_PROBE_MOD_PIN07 +// channel 0: Z_PROBE_MOD_PIN +// Make channel 0 == channel 1 for RADDS. Difference in channels +// is an artifact of difference in different versions of Duet electronics +// +// D34 -- unused X-max on RADDS +const Pin Z_PROBE_MOD_PIN = 34; +const Pin Z_PROBE_MOD_PIN07 = 34; + +// Use a PWM capable pin +// Firmware uses SamNonDue so feel free to use a non-Arduino pin +const size_t NUM_FANS = 2; +const Pin COOLING_FAN_PINS[NUM_FANS] = { 9, 8 }; // Fan 0, Fan 1 + +// Firmware will attach a FALLING interrupt to this pin +// see FanInterrupt() in Platform.cpp +// +// D25 -- Unused GPIO on AUX1 +const Pin COOLING_FAN_RPM_PIN = 25; + +// SD cards +const size_t NumSdCards = 1; +const Pin SdCardDetectPins[NumSdCards] = {NoPin}; +const Pin SdWriteProtectPins[NumSdCards] = { NoPin}; +const Pin SdSpiCSPins[1] = {zz}; //FIXME + +// Definition of which pins we allow to be controlled using M42 +// +// Spare pins on the Arduino Due are +// +// D5 / TIOA6 / C.25 +// D6 / PWML7 / C.24 +// ### Removed: now E0_AXIS endstop D39 / PWMH2 / C.7 +// D58 / AD3 / A.6 +// D59 / AD2 / A.4 +// D66 / DAC0 / B.15 +// D67 / DAC1 / B.16 +// D68 / CANRX0 / A.1 +// D69 / CANTX0 / A.0 +// D70 / SDA1 / A.17 +// D71 / SCL1 / A.18 +// D72 / RX LED / C.30 +// D73 / TX LED / A.21 + +const size_t NUM_PINS_ALLOWED = 80; +#define PINS_ALLOWED { \ + /* pins 00-07 */ 0b01100000, \ + /* pins 08-15 */ 0, \ + /* pins 16-23 */ 0, \ + /* pins 24-31 */ 0, \ + /* pins 32-39 */ 0b00000000, \ + /* pins 40-47 */ 0, \ + /* pins 48-55 */ 0, \ + /* pins 56-63 */ 0b00001100, \ + /* pins 64-71 */ 0b11111100, \ + /* pins 72-79 */ 0b00000011 \ +} + + +// SAM3X Flash locations (may be expanded in the future) +const uint32_t IAP_FLASH_START = 0x000F0000; +const uint32_t IAP_FLASH_END = 0x000FFBFF; // don't touch the last 1KB, it's used for NvData + +// Timer allocation +#define NETWORK_TC (TC1) +#define NETWORK_TC_CHAN (1) +#define NETWORK_TC_IRQN TC4_IRQn +#define NETWORK_TC_HANDLER TC4_Handler + +#define STEP_TC (TC1) +#define STEP_TC_CHAN (0) +#define STEP_TC_IRQN TC3_IRQn +#define STEP_TC_HANDLER TC3_Handler + +// Hardware SPI support for SD cards + +// SD select +#define SD_SS 4 +#define SD_DETECT_PIN 14 // card detect switch; needs pullup asserted +#define SD_DETECT_VAL 0 // detect switch active low +#define SD_DETECT_PIO_ID ID_PIOD + +#ifdef LCD_UI + +// Hardware I2C support for LCD +#define TWI_ID ID_TWI1 + +#define FEATURE_CONTROLLER 7 +#define UI_PAGES_DURATION 4000 +#define UI_ANIMATION 0 +#define UI_SPEEDDEPENDENT_POSITIONING 0 +#define UI_DISABLE_AUTO_PAGESWITCH 1 +#define UI_AUTORETURN_TO_MENU_AFTER 30000 +#define UI_ENCODER_SPEED 1 +#define UI_KEY_BOUNCETIME 10 +#define UI_KEY_FIRST_REPEAT 500 +#define UI_KEY_REDUCE_REPEAT 50 +#define UI_KEY_MIN_REPEAT 50 +#define FEATURE_BEEPER 1 +#define UI_START_SCREEN_DELAY 1000 + +#define CASE_LIGHTS_PIN -1 +#define SPI_PIN 77 +#define SPI_CHAN 0 +#define UI_HAS_KEYS 1 +#define UI_HAS_BACK_KEY 1 +#define UI_DISPLAY_TYPE 1 +#define UI_DISPLAY_CHARSET 1 +#define BEEPER_TYPE 1 +#define UI_COLS 20 +#define UI_ROWS 4 +#define BEEPER_PIN 41 +#define UI_DISPLAY_RS_PIN 42 +#define UI_DISPLAY_RW_PIN -1 +#define UI_DISPLAY_ENABLE_PIN 43 +#define UI_DISPLAY_D0_PIN 44 +#define UI_DISPLAY_D1_PIN 45 +#define UI_DISPLAY_D2_PIN 46 +#define UI_DISPLAY_D3_PIN 47 +#define UI_DISPLAY_D4_PIN 44 +#define UI_DISPLAY_D5_PIN 45 +#define UI_DISPLAY_D6_PIN 46 +#define UI_DISPLAY_D7_PIN 47 +#define UI_ENCODER_A 52 +#define UI_ENCODER_B 50 +#define UI_ENCODER_CLICK 48 +#define UI_RESET_PIN -1 +#define UI_DELAYPERCHAR 40 +#define UI_INVERT_MENU_DIRECTION 0 +#define UI_BUTTON_BACK 71 + +// Beeper sound definitions for short beeps during key actions and longer +// beeps for important actions. Parameters are the delay in microseconds +// followed by the number of repetitions. Values must be in range 1..255 +#define BEEPER_SHORT_SEQUENCE 2,2 +#define BEEPER_LONG_SEQUENCE 8,8 + +#endif // LCD_UI + +#endif diff --git a/src/RADDS/Webserver.h b/src/RADDS/Webserver.h new file mode 100644 index 00000000..0c8accb0 --- /dev/null +++ b/src/RADDS/Webserver.h @@ -0,0 +1,38 @@ +#ifndef WEBSERVER_H +#define WEBSERVER_H + +#include <inttypes.h> +#include "OutputMemory.h" + +// List of protocols that can execute G-Codes +enum class WebSource +{ + HTTP, + Telnet +}; + +class Webserver +{ +public: + Webserver(Platform* p, Network *n) { }; + void Init() const { }; + void Spin() const { }; + 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 { }; +}; + +inline void Webserver::HandleGCodeReply(const WebSource source, OutputBuffer *reply) const +{ + if (reply != (OutputBuffer *)0) + OutputBuffer::ReleaseAll(reply); +} + +#endif diff --git a/src/Reprap.cpp b/src/Reprap.cpp index d3cd3288..aa372a27 100644 --- a/src/Reprap.cpp +++ b/src/Reprap.cpp @@ -166,8 +166,8 @@ void RepRap::Spin() // Keep track of the loop time - float t = platform->Time(); - float dt = t - lastTime; + const float t = platform->Time(); + const float dt = t - lastTime; if(dt < fastLoop) { fastLoop = dt; @@ -279,6 +279,7 @@ void RepRap::PrintDebug() // Add a tool. // Prior to calling this, delete any existing tool with the same number +// The tool list is maintained in tool number order. void RepRap::AddTool(Tool* tool) { Tool** t = &toolList; @@ -401,9 +402,12 @@ Tool* RepRap::GetTool(int toolNumber) const return nullptr; // Not an error } -Tool* RepRap::GetOnlyTool() const +// Get the current tool, or failing that the default tool. May return nullptr if we can't +// Called when a M104 or M109 command doesn't specify a tool number. +Tool* RepRap::GetCurrentOrDefaultTool() const { - return (toolList != nullptr && toolList->Next() == nullptr) ? toolList : nullptr; + // If a tool is already selected, use that one, else use the lowest-numbered tool which is the one at the start of the tool list + return (currentTool != nullptr) ? currentTool : toolList; } void RepRap::SetToolVariables(int toolNumber, const float* standbyTemperatures, const float* activeTemperatures) @@ -449,6 +453,16 @@ bool RepRap::IsHeaterAssignedToTool(int8_t heater) const return false; } +unsigned int RepRap::GetNumberOfContiguousTools() const +{ + unsigned int numTools = 0; + while (GetTool(numTools) != nullptr) + { + ++numTools; + } + return numTools; +} + void RepRap::Tick() { if (active) @@ -561,7 +575,7 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) } // Current tool number - int toolNumber = (currentTool == nullptr) ? -1 : currentTool->Number(); + const int toolNumber = (currentTool == nullptr) ? -1 : currentTool->Number(); response->catf("]},\"currentTool\":%d", toolNumber); // Output - only reported once @@ -633,7 +647,7 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) response->cat(",\"sensors\":{"); // Probe - int v0 = platform->ZProbe(); + const int v0 = platform->ZProbe(); int v1, v2; switch (platform->GetZProbeSecondaryValues(v1, v2)) { @@ -1117,7 +1131,7 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq) const size_t numAxes = reprap.GetGCodes()->GetNumAxes(); float liveCoordinates[DRIVES]; reprap.GetMove()->LiveCoordinates(liveCoordinates, GetCurrentXAxes()); - const Tool* currentTool = reprap.GetCurrentTool(); + const Tool* const currentTool = reprap.GetCurrentTool(); if (currentTool != nullptr) { const float *offset = currentTool->GetOffset(); @@ -1155,11 +1169,11 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq) response->cat((ch == '[') ? "[]" : "]"); // Send the current tool number - int toolNumber = (currentTool == nullptr) ? 0 : currentTool->Number(); + const int toolNumber = (currentTool == nullptr) ? 0 : currentTool->Number(); response->catf(",\"tool\":%d", toolNumber); // Send the Z probe value - int v0 = platform->ZProbe(); + const int v0 = platform->ZProbe(); int v1, v2; switch (platform->GetZProbeSecondaryValues(v1, v2)) { @@ -1218,24 +1232,24 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq) } else if (type == 3) { - // Add the static fields. For now this is just geometry and the machine name, but other fields could be added e.g. axis lengths. - response->catf(",\"geometry\":\"%s\",\"axes\":%u,\"volumes\":%u,\"myName\":", move->GetGeometryString(), numAxes, NumSdCards); + // Add the static fields + response->catf(",\"geometry\":\"%s\",\"axes\":%u,\"volumes\":%u,\"numTools\":%u,\"myName\":", + move->GetGeometryString(), numAxes, NumSdCards, GetNumberOfContiguousTools()); response->EncodeString(myName, ARRAY_SIZE(myName), false); } - int auxSeq = (int)platform->GetAuxSeq(); - if (type < 2 || (seq != -1 && (int)auxSeq != seq)) + const int auxSeq = (int)platform->GetAuxSeq(); + if (type < 2 || (seq != -1 && auxSeq != seq)) { // Send the response to the last command. Do this last because it can be long and may need to be truncated. - response->catf(",\"seq\":%u,\"resp\":", auxSeq); // send the response sequence number + response->catf(",\"seq\":%d,\"resp\":", auxSeq); // send the response sequence number // Send the JSON response response->EncodeReply(platform->GetAuxGCodeReply(), true); // also releases the OutputBuffer chain } response->cat("}"); - return response; } diff --git a/src/Reprap.h b/src/Reprap.h index f7408bdd..8b80d527 100644 --- a/src/Reprap.h +++ b/src/Reprap.h @@ -58,12 +58,12 @@ public: void StandbyTool(int toolNumber); Tool* GetCurrentTool() const; Tool* GetTool(int toolNumber) const; - Tool* GetOnlyTool() const; + Tool* GetCurrentOrDefaultTool() const; uint32_t GetCurrentXAxes() const; // Get the current axes used as X axes - //Tool* GetToolByDrive(int driveNumber); void SetToolVariables(int toolNumber, const float* standbyTemperatures, const float* activeTemperatures); bool ToolWarningsAllowed(); bool IsHeaterAssignedToTool(int8_t heater) const; + unsigned int GetNumberOfContiguousTools() const; unsigned int GetProhibitedExtruderMovements(unsigned int extrusions, unsigned int retractions); void PrintTool(int toolNumber, StringRef& reply) const; diff --git a/src/Storage/FileStore.cpp b/src/Storage/FileStore.cpp index e62c8f1d..680032f8 100644 --- a/src/Storage/FileStore.cpp +++ b/src/Storage/FileStore.cpp @@ -265,6 +265,40 @@ int FileStore::Read(char* extBuf, size_t nBytes) return (int)bytes_read; } +// As Read but stop after '\n' or '\r\n' and null-terminate the string. +// If the next line is too long to fit in the buffer then the line will be split. +int FileStore::ReadLine(char* buf, size_t nBytes) +{ + const FilePosition lineStart = Position(); + const int r = Read(buf, nBytes); + if (r < 0) + { + return r; + } + + int i = 0; + while (i < r && buf[i] != '\r' && buf[i] != '\n') + { + ++i; + } + + if (i + 1 < r && buf[i] == '\r' && buf[i + 1] == '\n') // if stopped at CRLF (Windows-style line end) + { + Seek(lineStart + i + 2); // seek to just after the CRLF + } + else if (i < r) // if stopped at CR or LF + { + Seek(lineStart + i + 1); // seek to just after the CR or LF + } + else if (i == (int)nBytes) + { + --i; // make room for the null terminator + Seek(lineStart + i); + } + buf[i] = 0; + return i; +} + bool FileStore::WriteBuffer() { if (bufferPointer != 0) diff --git a/src/Storage/FileStore.h b/src/Storage/FileStore.h index 23e4e97d..e998e3e8 100644 --- a/src/Storage/FileStore.h +++ b/src/Storage/FileStore.h @@ -28,6 +28,7 @@ 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 bool Write(char b); // Write 1 byte bool Write(const char *s, size_t len); // Write a block of len bytes bool Write(const char* s); // Write a string |