diff options
author | David Crocker <dcrocker@eschertech.com> | 2021-05-15 12:26:22 +0300 |
---|---|---|
committer | David Crocker <dcrocker@eschertech.com> | 2021-05-15 12:26:22 +0300 |
commit | 3fd84c1b908cc3bf0afb1ad4fe225b12ba81096d (patch) | |
tree | fe0ed44483f60f820a7fa78407285fc7eca1ca65 /src/GCodes/GCodes2.cpp | |
parent | 92005bdf862e80f0cd18bf1b15cb4be1693a81b2 (diff) |
Allow macros to implement unsupported commands with fractions
Diffstat (limited to 'src/GCodes/GCodes2.cpp')
-rw-r--r-- | src/GCodes/GCodes2.cpp | 6626 |
1 files changed, 3325 insertions, 3301 deletions
diff --git a/src/GCodes/GCodes2.cpp b/src/GCodes/GCodes2.cpp index 637f03e4..3bebd532 100644 --- a/src/GCodes/GCodes2.cpp +++ b/src/GCodes/GCodes2.cpp @@ -136,316 +136,316 @@ bool GCodes::HandleGcode(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeEx const int code = gb.GetCommandNumber(); if (simulationMode != 0 && code > 4 && code != 10 && code != 11 && code != 20 && code != 21 && (code < 53 || code > 59) && (code < 90 || code > 92)) { - HandleReply(gb, GCodeResult::ok, ""); - return true; // we only simulate some gcodes + HandleReply(gb, result, ""); + return true; // we only simulate some gcodes } - switch (code) + if (gb.GetCommandFraction() > 0 + && code != 38 && code != 59 // these are the only G-codes we implement that can have fractional parts + ) { - case 0: // Rapid move - case 1: // Ordinary move - if (moveBuffer.segmentsLeft != 0) // do this check first to avoid locking movement unnecessarily - { - return false; - } - if (!LockMovement(gb)) - { - return false; - } + result = TryMacroFile(gb); + } + else + { + switch (code) { - const char* err = nullptr; - if (!DoStraightMove(gb, code == 1, err)) + case 0: // Rapid move + case 1: // Ordinary move + if (moveBuffer.segmentsLeft != 0) // do this check first to avoid locking movement unnecessarily { return false; } - if (err != nullptr) + if (!LockMovement(gb)) { - gb.SetState(GCodeState::abortWhenMovementFinished); // empty the queue before ending simulation, and force the user position to be restored - gb.LatestMachineState().SetError(err); // must do this *after* calling SetState + return false; } - } - break; + { + const char* err = nullptr; + if (!DoStraightMove(gb, code == 1, err)) + { + return false; + } + if (err != nullptr) + { + gb.SetState(GCodeState::abortWhenMovementFinished); // empty the queue before ending simulation, and force the user position to be restored + gb.LatestMachineState().SetError(err); // must do this *after* calling SetState + } + } + break; - case 2: // Clockwise arc - case 3: // Anti clockwise arc - // We only support X and Y axes in these (and optionally Z for corkscrew moves), but you can map them to other axes in the tool definitions - if (moveBuffer.segmentsLeft != 0) // do this check first to avoid locking movement unnecessarily - { - return false; - } - if (!LockMovement(gb)) - { - return false; - } - { - const char* err = nullptr; - if (!DoArcMove(gb, code == 2, err)) + case 2: // Clockwise arc + case 3: // Anti clockwise arc + // We only support X and Y axes in these (and optionally Z for corkscrew moves), but you can map them to other axes in the tool definitions + if (moveBuffer.segmentsLeft != 0) // do this check first to avoid locking movement unnecessarily { return false; } - if (err != nullptr) + if (!LockMovement(gb)) { - gb.SetState(GCodeState::abortWhenMovementFinished); // empty the queue before ending simulation, and force the user position to be restored - gb.LatestMachineState().SetError(err); // must do this *after* calling SetState + return false; } - } - break; - - case 4: // Dwell - result = DoDwell(gb); - break; - - case 10: // Set/report offsets and temperatures, or retract - { -#if SUPPORT_WORKPLACE_COORDINATES - if (gb.Seen('L')) { - const uint32_t ival = gb.GetUIValue(); - switch (ival) + const char* err = nullptr; + if (!DoArcMove(gb, code == 2, err)) { - case 1: - result = SetOrReportOffsets(gb, reply, 10); // same as G10 with offsets and no L parameter - break; - - case 2: - result = GetSetWorkplaceCoordinates(gb, reply, false); - break; - - case 20: - result = GetSetWorkplaceCoordinates(gb, reply, true); - break; - - default: - result = GCodeResult::badOrMissingParameter; - break; + return false; } - } - else -#endif - { - bool modifyingTool = gb.Seen('P') || gb.Seen('R') || gb.Seen('S') || gb.Seen('F'); - for (size_t axis = 0; axis < numVisibleAxes; ++axis) + if (err != nullptr) { - modifyingTool |= gb.Seen(axisLetters[axis]); + gb.SetState(GCodeState::abortWhenMovementFinished); // empty the queue before ending simulation, and force the user position to be restored + gb.LatestMachineState().SetError(err); // must do this *after* calling SetState } + } + break; + + case 4: // Dwell + result = DoDwell(gb); + break; - if (modifyingTool) + case 10: // Set/report offsets and temperatures, or retract + { +#if SUPPORT_WORKPLACE_COORDINATES + if (gb.Seen('L')) { - if (simulationMode != 0) + const uint32_t ival = gb.GetUIValue(); + switch (ival) { + case 1: + result = SetOrReportOffsets(gb, reply, 10); // same as G10 with offsets and no L parameter + break; + + case 2: + result = GetSetWorkplaceCoordinates(gb, reply, false); + break; + + case 20: + result = GetSetWorkplaceCoordinates(gb, reply, true); + break; + + default: + result = GCodeResult::badOrMissingParameter; break; } - result = SetOrReportOffsets(gb, reply, 10); } else +#endif { - result = RetractFilament(gb, true); + bool modifyingTool = gb.Seen('P') || gb.Seen('R') || gb.Seen('S') || gb.Seen('F'); + for (size_t axis = 0; axis < numVisibleAxes; ++axis) + { + modifyingTool |= gb.Seen(axisLetters[axis]); + } + + if (modifyingTool) + { + if (simulationMode != 0) + { + break; + } + result = SetOrReportOffsets(gb, reply, 10); + } + else + { + result = RetractFilament(gb, true); + } } } - } - break; + break; - case 11: // Un-retract - result = RetractFilament(gb, false); - break; + case 11: // Un-retract + result = RetractFilament(gb, false); + break; - case 17: // Select XY plane for G2/G3 - case 18: // Select XZ plane - case 19: // Select YZ plane - if (!LockMovementAndWaitForStandstill(gb)) // do this in case a G2 or G3 command is in progress - { - return false; - } + case 17: // Select XY plane for G2/G3 + case 18: // Select XZ plane + case 19: // Select YZ plane + if (!LockMovementAndWaitForStandstill(gb)) // do this in case a G2 or G3 command is in progress + { + return false; + } - gb.LatestMachineState().selectedPlane = code - 17; - break; + gb.LatestMachineState().selectedPlane = code - 17; + break; - case 20: // Inches (which century are we living in, here?) - gb.LatestMachineState().usingInches = true; - reprap.InputsUpdated(); - break; + case 20: // Inches (which century are we living in, here?) + gb.LatestMachineState().usingInches = true; + reprap.InputsUpdated(); + break; - case 21: // mm - gb.LatestMachineState().usingInches = false; - reprap.InputsUpdated(); - break; + case 21: // mm + gb.LatestMachineState().usingInches = false; + reprap.InputsUpdated(); + break; - case 28: // Home - result = DoHome(gb, reply); - break; + case 28: // Home + result = DoHome(gb, reply); + break; - case 29: // Grid-based bed probing + case 29: // Grid-based bed probing #if HAS_LINUX_INTERFACE - // Pass file- and system-related commands to DSF if they came from somewhere else. - // They will be passed back to us via a binary buffer or separate SPI message if necessary. - if (reprap.UsingLinuxInterface() && reprap.GetLinuxInterface().IsConnected() && !gb.IsBinary()) - { - gb.SendToSbc(); - return false; - } -#endif - if (!LockMovementAndWaitForStandstill(gb)) // do this first to make sure that a new grid isn't being defined - { - return false; - } - { - int sparam; - if (gb.Seen('S')) + // Pass file- and system-related commands to DSF if they came from somewhere else. + // They will be passed back to us via a binary buffer or separate SPI message if necessary. + if (reprap.UsingLinuxInterface() && reprap.GetLinuxInterface().IsConnected() && !gb.IsBinary()) { - sparam = gb.GetIValue(); + gb.SendToSbc(); + return false; } - else if (DoFileMacro(gb, MESH_G, false, 29)) // no S parameter found so try to execute mesh.g +#endif + if (!LockMovementAndWaitForStandstill(gb)) // do this first to make sure that a new grid isn't being defined { - break; + return false; } - else { - sparam = 0; // mesh.g not found, so treat G29 the same as G29 S0 - } + int sparam; + if (gb.Seen('S')) + { + sparam = gb.GetIValue(); + } + else if (DoFileMacro(gb, MESH_G, false, 29)) // no S parameter found so try to execute mesh.g + { + break; + } + else + { + sparam = 0; // mesh.g not found, so treat G29 the same as G29 S0 + } - switch(sparam) - { - case 0: // probe and save height map - result = ProbeGrid(gb, reply); - break; + switch(sparam) + { + case 0: // probe and save height map + result = ProbeGrid(gb, reply); + break; - case 1: // load height map file + case 1: // load height map file #if HAS_MASS_STORAGE - result = LoadHeightMap(gb, reply); + result = LoadHeightMap(gb, reply); #else - result = GCodeResult::errorNotSupported; + result = GCodeResult::errorNotSupported; #endif - break; + break; - case 2: // clear height map - ClearBedMapping(); - break; + case 2: // clear height map + ClearBedMapping(); + break; - case 3: // save height map to names file + case 3: // save height map to names file #if HAS_MASS_STORAGE - result = SaveHeightMap(gb, reply); + result = SaveHeightMap(gb, reply); #else - result = GCodeResult::errorNotSupported; + result = GCodeResult::errorNotSupported; #endif - break; + break; - default: - result = GCodeResult::badOrMissingParameter; - break; + default: + result = GCodeResult::badOrMissingParameter; + break; + } } - } - break; + break; - case 30: // Z probe/manually set at a position and set that as point P - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } + case 30: // Z probe/manually set at a position and set that as point P + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } - if (reprap.GetMove().GetKinematics().AxesToHomeBeforeProbing().Intersects(~axesVirtuallyHomed)) - { - reply.copy("Insufficient axes homed for bed probing"); - result = GCodeResult::error; - } - else - { - result = ExecuteG30(gb, reply); - } - break; + if (reprap.GetMove().GetKinematics().AxesToHomeBeforeProbing().Intersects(~axesVirtuallyHomed)) + { + reply.copy("Insufficient axes homed for bed probing"); + result = GCodeResult::error; + } + else + { + result = ExecuteG30(gb, reply); + } + break; - case 31: // Return the probe value, or set probe variables - result = platform.GetEndstops().HandleG31(gb, reply); - break; + case 31: // Return the probe value, or set probe variables + result = platform.GetEndstops().HandleG31(gb, reply); + break; - case 32: // Probe Z at multiple positions and generate the bed transform - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } + case 32: // Probe Z at multiple positions and generate the bed transform + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } - // We need to unlock the movement system here in case there is no Z probe and we are doing manual probing. - // Otherwise, even though the bed probing code calls UnlockAll when doing a manual bed probe, the movement system - // remains locked because the current MachineState object already held the lock when the macro file was started, - // which means that no gcode source other than the one that executed G32 is allowed to jog the Z axis. - UnlockAll(gb); + // We need to unlock the movement system here in case there is no Z probe and we are doing manual probing. + // Otherwise, even though the bed probing code calls UnlockAll when doing a manual bed probe, the movement system + // remains locked because the current MachineState object already held the lock when the macro file was started, + // which means that no gcode source other than the one that executed G32 is allowed to jog the Z axis. + UnlockAll(gb); - DoFileMacro(gb, BED_EQUATION_G, true, 32); // Try to execute bed.g - break; + DoFileMacro(gb, BED_EQUATION_G, true, 32); // Try to execute bed.g + break; - case 38: // Straight probe - move until either the probe is triggered or the commanded move ends - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - result = StraightProbe(gb, reply); - break; + case 38: // Straight probe - move until either the probe is triggered or the commanded move ends + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + result = StraightProbe(gb, reply); + break; - case 53: // Temporarily use machine coordinates - gb.LatestMachineState().g53Active = true; - break; + case 53: // Temporarily use machine coordinates + gb.LatestMachineState().g53Active = true; + break; #if SUPPORT_WORKPLACE_COORDINATES - case 54: // Switch to coordinate system 1 - case 55: // Switch to coordinate system 2 - case 56: // Switch to coordinate system 3 - case 57: // Switch to coordinate system 4 - case 58: // Switch to coordinate system 5 - case 59: // Switch to coordinate system 6,7,8,9 - { - unsigned int cs = code - 54; - if (code == 59) + case 54: // Switch to coordinate system 1 + case 55: // Switch to coordinate system 2 + case 56: // Switch to coordinate system 3 + case 57: // Switch to coordinate system 4 + case 58: // Switch to coordinate system 5 + case 59: // Switch to coordinate system 6,7,8,9 { - const int8_t fraction = gb.GetCommandFraction(); - if (fraction > 0) + unsigned int cs = code - 54; + if (code == 59) { - cs += (unsigned int)fraction; + const int8_t fraction = gb.GetCommandFraction(); + if (fraction > 0) + { + cs += (unsigned int)fraction; + } + } + if (cs < NumCoordinateSystems) + { + currentCoordinateSystem = cs; // this is the zero-base coordinate system number + reprap.MoveUpdated(); + gb.LatestMachineState().g53Active = false; // cancel any active G53 + } + else + { + result = GCodeResult::errorNotSupported; } } - if (cs < NumCoordinateSystems) - { - currentCoordinateSystem = cs; // this is the zero-base coordinate system number - reprap.MoveUpdated(); - gb.LatestMachineState().g53Active = false; // cancel any active G53 - } - else - { - result = GCodeResult::errorNotSupported; - } - } - break; + break; #endif - case 60: // Save position - result = SavePosition(gb, reply); - break; + case 60: // Save position + result = SavePosition(gb, reply); + break; - case 90: // Absolute coordinates - gb.LatestMachineState().axesRelative = false; - reprap.InputsUpdated(); - break; + case 90: // Absolute coordinates + gb.LatestMachineState().axesRelative = false; + reprap.InputsUpdated(); + break; - case 91: // Relative coordinates - gb.LatestMachineState().axesRelative = true; // Axis movements (i.e. X, Y and Z) - reprap.InputsUpdated(); - break; + case 91: // Relative coordinates + gb.LatestMachineState().axesRelative = true; // Axis movements (i.e. X, Y and Z) + reprap.InputsUpdated(); + break; - case 92: // Set position - result = SetPositions(gb); - break; + case 92: // Set position + result = SetPositions(gb); + break; - default: - // See if there is a file in /sys named Gxx.g - if (code >= 0 && code < 10000) - { - String<StringLength20> macroName; - macroName.printf("G%d.g", code); - if (DoFileMacro(gb, macroName.c_str(), false, 98)) - { - break; - } + default: + result = TryMacroFile(gb); + break; } - result = GCodeResult::warningNotSupported; } return HandleResult(gb, result, reply, nullptr); @@ -497,1282 +497,1276 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeEx } #endif - GCodeResult result = GCodeResult::ok; - switch (code) + GCodeResult result; + if (gb.GetCommandFraction() > 0 + && code != 569 // these are the only G-codes we implement that can have fractional parts + ) { - case 0: // Stop - case 1: // Sleep - // Don't allow M0 or M1 to stop a print, unless the print is paused or the command comes from the file being printed itself. - if (reprap.GetPrintMonitor().IsPrinting() && &gb != fileGCode && pauseState != PauseState::paused) - { - reply.copy("Pause the print before attempting to cancel it"); - result = GCodeResult::error; - } - else if ( !LockMovementAndWaitForStandstill(gb) // wait until everything has stopped - || !IsCodeQueueIdle() // must also wait until deferred command queue has caught up - ) - { - return false; - } - else + result = TryMacroFile(gb); + } + else + { + result = GCodeResult::ok; + switch (code) { - const auto oldPauseState = pauseState; // pauseState gets reset by CancelPrint - const bool wasSimulating = IsSimulating(); // simulationMode may get cleared by CancelPrint - isWaiting = cancelWait = false; // we may have been waiting for temperatures to be reached - StopPrint((&gb == fileGCode) ? StopPrintReason::normalCompletion : StopPrintReason::userCancelled); - - if (!wasSimulating) // don't run any macro files or turn heaters off etc. if we were simulating before we stopped the print + case 0: // Stop + case 1: // Sleep + // Don't allow M0 or M1 to stop a print, unless the print is paused or the command comes from the file being printed itself. + if (reprap.GetPrintMonitor().IsPrinting() && &gb != fileGCode && pauseState != PauseState::paused) { - // If we are cancelling a paused print with M0 and we are homed and cancel.g exists then run it and do nothing else - if (oldPauseState != PauseState::notPaused && code == 0 && AllAxesAreHomed() && DoFileMacro(gb, CANCEL_G, false, SystemHelperMacroCode)) + reply.copy("Pause the print before attempting to cancel it"); + result = GCodeResult::error; + } + else if ( !LockMovementAndWaitForStandstill(gb) // wait until everything has stopped + || !IsCodeQueueIdle() // must also wait until deferred command queue has caught up + ) + { + return false; + } + else + { + const auto oldPauseState = pauseState; // pauseState gets reset by CancelPrint + const bool wasSimulating = IsSimulating(); // simulationMode may get cleared by CancelPrint + isWaiting = cancelWait = false; // we may have been waiting for temperatures to be reached + StopPrint((&gb == fileGCode) ? StopPrintReason::normalCompletion : StopPrintReason::userCancelled); + + if (!wasSimulating) // don't run any macro files or turn heaters off etc. if we were simulating before we stopped the print { - break; - } + // If we are cancelling a paused print with M0 and we are homed and cancel.g exists then run it and do nothing else + if (oldPauseState != PauseState::notPaused && code == 0 && AllAxesAreHomed() && DoFileMacro(gb, CANCEL_G, false, SystemHelperMacroCode)) + { + break; + } - const bool leaveHeatersOn = (gb.Seen('H') && gb.GetIValue() > 0); - gb.SetState((leaveHeatersOn) ? GCodeState::stoppingWithHeatersOn : GCodeState::stoppingWithHeatersOff); - (void)DoFileMacro(gb, (code == 0) ? STOP_G : SLEEP_G, false, SystemHelperMacroCode); + const bool leaveHeatersOn = (gb.Seen('H') && gb.GetIValue() > 0); + gb.SetState((leaveHeatersOn) ? GCodeState::stoppingWithHeatersOn : GCodeState::stoppingWithHeatersOff); + (void)DoFileMacro(gb, (code == 0) ? STOP_G : SLEEP_G, false, SystemHelperMacroCode); + } } - } - break; + break; - case 3: // Spin spindle clockwise - { - if (machineType == MachineType::cnc) + case 3: // Spin spindle clockwise { - Tool * const currentTool = reprap.GetCurrentTool(); - if (gb.Seen('S')) + if (machineType == MachineType::cnc) { - if (currentTool != nullptr && currentTool->GetSpindleNumber() > -1) + Tool * const currentTool = reprap.GetCurrentTool(); + if (gb.Seen('S')) + { + if (currentTool != nullptr && currentTool->GetSpindleNumber() > -1) + { + currentTool->SetSpindleRpm(gb.GetUIValue()); + platform.AccessSpindle(currentTool->GetSpindleNumber()).SetState(SpindleState::forward); + } + else + { + const uint32_t slot = gb.GetLimitedUIValue('P', MaxSpindles); // Direct spindle speed can only be set when slot is provided as well + Spindle& spindle = platform.AccessSpindle(slot); + spindle.SetConfiguredRpm(gb.GetIValue(), false); + spindle.SetState(SpindleState::forward); + } + } + else if (currentTool != nullptr && currentTool->GetSpindleNumber() > -1) { - currentTool->SetSpindleRpm(gb.GetUIValue()); platform.AccessSpindle(currentTool->GetSpindleNumber()).SetState(SpindleState::forward); } else { - const uint32_t slot = gb.GetLimitedUIValue('P', MaxSpindles); // Direct spindle speed can only be set when slot is provided as well - Spindle& spindle = platform.AccessSpindle(slot); - spindle.SetConfiguredRpm(gb.GetIValue(), false); - spindle.SetState(SpindleState::forward); + reply.copy("No spindle selected via P and no active tool with spindle"); + result = GCodeResult::warning; } } - else if (currentTool != nullptr && currentTool->GetSpindleNumber() > -1) + else if (gb.Seen('S')) { - platform.AccessSpindle(currentTool->GetSpindleNumber()).SetState(SpindleState::forward); + switch (machineType) + { +#if SUPPORT_LASER + case MachineType::laser: + if (moveBuffer.segmentsLeft != 0) + { + return false; // don't modify moves that haven't gone yet + } + moveBuffer.laserPwmOrIoBits.laserPwm = ConvertLaserPwm(gb.GetFValue()); + break; +#endif + + default: +#if SUPPORT_ROLAND + if (reprap.GetRoland()->Active()) + { + result = reprap.GetRoland()->ProcessSpindle(gb.GetFValue()); + } + else +#endif + { + result = GCodeResult::notSupportedInCurrentMode; + } + break; + } } else { - reply.copy("No spindle selected via P and no active tool with spindle"); - result = GCodeResult::warning; + result = GCodeResult::notSupportedInCurrentMode; } } - else if (gb.Seen('S')) + break; + + case 4: // Spin spindle counter clockwise + if (machineType == MachineType::cnc) { - switch (machineType) + Tool * const currentTool = reprap.GetCurrentTool(); + if (gb.Seen('S')) { -#if SUPPORT_LASER - case MachineType::laser: - if (moveBuffer.segmentsLeft != 0) - { - return false; // don't modify moves that haven't gone yet - } - moveBuffer.laserPwmOrIoBits.laserPwm = ConvertLaserPwm(gb.GetFValue()); - break; -#endif - - default: -#if SUPPORT_ROLAND - if (reprap.GetRoland()->Active()) + if (currentTool != nullptr && currentTool->GetSpindleNumber() > -1) { - result = reprap.GetRoland()->ProcessSpindle(gb.GetFValue()); + currentTool->SetSpindleRpm(gb.GetUIValue()); + platform.AccessSpindle(currentTool->GetSpindleNumber()).SetState(SpindleState::reverse); } else -#endif { - result = GCodeResult::notSupportedInCurrentMode; + const uint32_t slot = gb.GetLimitedUIValue('P', MaxSpindles); // Direct spindle speed can only be set when slot is provided as well + Spindle& spindle = platform.AccessSpindle(slot); + spindle.SetConfiguredRpm(gb.GetIValue(), false); + spindle.SetState(SpindleState::reverse); } - break; } - } - else - { - result = GCodeResult::notSupportedInCurrentMode; - } - } - break; - - case 4: // Spin spindle counter clockwise - if (machineType == MachineType::cnc) - { - Tool * const currentTool = reprap.GetCurrentTool(); - if (gb.Seen('S')) - { - if (currentTool != nullptr && currentTool->GetSpindleNumber() > -1) + else if (currentTool != nullptr && currentTool->GetSpindleNumber() > -1) { - currentTool->SetSpindleRpm(gb.GetUIValue()); + // At this point slot = currentTool->GetSpindleNumber() platform.AccessSpindle(currentTool->GetSpindleNumber()).SetState(SpindleState::reverse); } else { - const uint32_t slot = gb.GetLimitedUIValue('P', MaxSpindles); // Direct spindle speed can only be set when slot is provided as well - Spindle& spindle = platform.AccessSpindle(slot); - spindle.SetConfiguredRpm(gb.GetIValue(), false); - spindle.SetState(SpindleState::reverse); + reply.copy("No spindle selected via P and no active tool with spindle"); + result = GCodeResult::warning; } } - else if (currentTool != nullptr && currentTool->GetSpindleNumber() > -1) - { - // At this point slot = currentTool->GetSpindleNumber() - platform.AccessSpindle(currentTool->GetSpindleNumber()).SetState(SpindleState::reverse); - } else { - reply.copy("No spindle selected via P and no active tool with spindle"); - result = GCodeResult::warning; + result = GCodeResult::notSupportedInCurrentMode; } - } - else - { - result = GCodeResult::notSupportedInCurrentMode; - } - break; + break; - case 5: // Spindle motor off - switch (machineType) - { - case MachineType::cnc: + case 5: // Spindle motor off + switch (machineType) { - if (gb.Seen('P')) - { - // Turn off specific spindle - const uint32_t slot = gb.GetLimitedUIValue('P', MaxSpindles); - platform.AccessSpindle(slot).SetState(SpindleState::stopped); - } - else + case MachineType::cnc: { - Tool * const currentTool = reprap.GetCurrentTool(); - if (currentTool != nullptr && currentTool->GetSpindleNumber() > -1) // Turn off spindle of current tool + if (gb.Seen('P')) { - platform.AccessSpindle(currentTool->GetSpindleNumber()).SetState(SpindleState::stopped); + // Turn off specific spindle + const uint32_t slot = gb.GetLimitedUIValue('P', MaxSpindles); + platform.AccessSpindle(slot).SetState(SpindleState::stopped); } else { - // Turn off every spindle if no 'P' parameter is present and the current tool - // does not have a spindle - for (size_t i = 0; i < MaxSpindles; i++) + Tool * const currentTool = reprap.GetCurrentTool(); + if (currentTool != nullptr && currentTool->GetSpindleNumber() > -1) // Turn off spindle of current tool { - platform.AccessSpindle(i).SetState(SpindleState::stopped); + platform.AccessSpindle(currentTool->GetSpindleNumber()).SetState(SpindleState::stopped); + } + else + { + // Turn off every spindle if no 'P' parameter is present and the current tool + // does not have a spindle + for (size_t i = 0; i < MaxSpindles; i++) + { + platform.AccessSpindle(i).SetState(SpindleState::stopped); + } } } } - } - break; + break; #if SUPPORT_LASER - case MachineType::laser: - if (moveBuffer.segmentsLeft != 0) - { - return false; // don't modify moves that haven't gone yet - } - moveBuffer.laserPwmOrIoBits.Clear(); - break; + case MachineType::laser: + if (moveBuffer.segmentsLeft != 0) + { + return false; // don't modify moves that haven't gone yet + } + moveBuffer.laserPwmOrIoBits.Clear(); + break; #endif - default: + default: #if SUPPORT_ROLAND - if (reprap.GetRoland()->Active()) - { - result = reprap.GetRoland()->ProcessSpindle(0.0); - } - else + if (reprap.GetRoland()->Active()) + { + result = reprap.GetRoland()->ProcessSpindle(0.0); + } + else #endif - { - result = GCodeResult::notSupportedInCurrentMode; + { + result = GCodeResult::notSupportedInCurrentMode; + } + break; } break; - } - break; - case 18: // Motors off - case 84: - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - // no break - case 17: // Motors on - { - bool seen = false; - for (size_t axis = 0; axis < numTotalAxes; axis++) + case 18: // Motors off + case 84: + if (!LockMovementAndWaitForStandstill(gb)) { - if (gb.Seen(axisLetters[axis])) + return false; + } + // no break + case 17: // Motors on + { + bool seen = false; + for (size_t axis = 0; axis < numTotalAxes; axis++) { - if (code == 17) + if (gb.Seen(axisLetters[axis])) { - platform.EnableDrivers(axis); + if (code == 17) + { + platform.EnableDrivers(axis); + } + else + { + SetAxisNotHomed(axis); + platform.DisableDrivers(axis); + } + seen = true; } - else + } + + if (gb.Seen(extrudeLetter)) + { + uint32_t eDrive[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetUnsignedArray(eDrive, eCount, false); + for (size_t i = 0; i < eCount; i++) { - SetAxisNotHomed(axis); - platform.DisableDrivers(axis); + seen = true; + if (eDrive[i] >= numExtruders) + { + reply.printf("Invalid extruder number specified: %" PRIu32, eDrive[i]); + result = GCodeResult::error; + break; + } + if (code == 17) + { + platform.EnableDrivers(ExtruderToLogicalDrive(eDrive[i])); + } + else + { + platform.DisableDrivers(ExtruderToLogicalDrive(eDrive[i])); + } } - seen = true; } - } - if (gb.Seen(extrudeLetter)) - { - uint32_t eDrive[MaxExtruders]; - size_t eCount = numExtruders; - gb.GetUnsignedArray(eDrive, eCount, false); - for (size_t i = 0; i < eCount; i++) + if (gb.Seen('S')) { seen = true; - if (eDrive[i] >= numExtruders) + + const float idleTimeout = gb.GetFValue(); + if (idleTimeout < 0.0) { - reply.printf("Invalid extruder number specified: %" PRIu32, eDrive[i]); + reply.copy("Idle timeouts cannot be negative"); result = GCodeResult::error; - break; } + else + { + reprap.GetMove().SetIdleTimeout(idleTimeout); + } + } + + if (!seen) + { if (code == 17) { - platform.EnableDrivers(ExtruderToLogicalDrive(eDrive[i])); + for (size_t axis = 0; axis < numTotalAxes; ++axis) + { + reprap.GetPlatform().EnableDrivers(axis); + } + for (size_t extruder = 0; extruder < numExtruders; ++extruder) + { + reprap.GetPlatform().EnableDrivers(ExtruderToLogicalDrive(extruder)); + } } else { - platform.DisableDrivers(ExtruderToLogicalDrive(eDrive[i])); + DisableDrives(); } } } + break; - if (gb.Seen('S')) +#if HAS_MASS_STORAGE + 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 { - seen = true; - - const float idleTimeout = gb.GetFValue(); - if (idleTimeout < 0.0) + return false; + } + { + const int sparam = (gb.Seen('S')) ? gb.GetIValue() : 0; + const unsigned int rparam = (gb.Seen('R')) ? gb.GetUIValue() : 0; + String<MaxFilenameLength> dir; + if (gb.Seen('P')) { - reply.copy("Idle timeouts cannot be negative"); - result = GCodeResult::error; + gb.GetPossiblyQuotedString(dir.GetRef()); } else { - reprap.GetMove().SetIdleTimeout(idleTimeout); + dir.copy(platform.GetGCodeDir()); } - } - if (!seen) - { - if (code == 17) + if (sparam == 2) { - for (size_t axis = 0; axis < numTotalAxes; ++axis) + outBuf = reprap.GetFilesResponse(dir.c_str(), rparam, true); // send the file list in JSON format + if (outBuf == nullptr) { - reprap.GetPlatform().EnableDrivers(axis); + reply.copy("{\"err\":-1}"); } - for (size_t extruder = 0; extruder < numExtruders; ++extruder) + } + else if (sparam == 3) + { + outBuf = reprap.GetFilelistResponse(dir.c_str(), rparam); + if (outBuf == nullptr) { - reprap.GetPlatform().EnableDrivers(ExtruderToLogicalDrive(extruder)); + reply.copy("{\"err\":-1}"); } } else { - DisableDrives(); + if (!OutputBuffer::Allocate(outBuf)) + { + return false; // cannot allocate an output buffer, try again later + } + + // 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 (gb.LatestMachineState().compatibility == Compatibility::Default || gb.LatestMachineState().compatibility == Compatibility::RepRapFirmware) + { + outBuf->copy("GCode files:\n"); + } + + const bool encapsulateList = gb.LatestMachineState().compatibility != Compatibility::Marlin; + FileInfo fileInfo; + if (MassStorage::FindFirst(dir.c_str(), fileInfo)) + { + // Iterate through all entries and append each file name + bool first = true; + do + { + if (encapsulateList) + { + outBuf->catf((first) ? "\"%s\"" : ",\"%s\"", fileInfo.fileName.c_str()); + first = false; + } + else + { + outBuf->catf("%s\n", fileInfo.fileName.c_str()); + } + } while (MassStorage::FindNext(fileInfo)); + } + else if (!encapsulateList) + { + outBuf->cat("NONE\n"); + } } } - } - break; + break; -#if HAS_MASS_STORAGE - 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; - } - { - const int sparam = (gb.Seen('S')) ? gb.GetIValue() : 0; - const unsigned int rparam = (gb.Seen('R')) ? gb.GetUIValue() : 0; - String<MaxFilenameLength> dir; - if (gb.Seen('P')) + case 21: // Initialise SD card + if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers { - gb.GetPossiblyQuotedString(dir.GetRef()); + return false; } - else { - dir.copy(platform.GetGCodeDir()); + const size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0; + result = MassStorage::Mount(card, reply, true); } + break; - if (sparam == 2) + case 22: // Release SD card + if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers { - outBuf = reprap.GetFilesResponse(dir.c_str(), rparam, true); // send the file list in JSON format - if (outBuf == nullptr) - { - reply.copy("{\"err\":-1}"); - } + return false; } - else if (sparam == 3) { - outBuf = reprap.GetFilelistResponse(dir.c_str(), rparam); - if (outBuf == nullptr) - { - reply.copy("{\"err\":-1}"); - } + const size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0; + result = MassStorage::Unmount(card, reply); } - else + break; + + case 23: // Set file to print + case 32: // Select file and start SD print + // We now allow a file that is being printed to chain to another file. This is required for the resume-after-power-fail functionality. + if (fileGCode->IsDoingFile() && (&gb) != fileGCode) { - if (!OutputBuffer::Allocate(outBuf)) - { - return false; // cannot allocate an output buffer, try again later - } + reply.copy("Cannot set file to print, because a file is already being printed"); + result = GCodeResult::error; + break; + } - // 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 (gb.LatestMachineState().compatibility == Compatibility::Default || gb.LatestMachineState().compatibility == Compatibility::RepRapFirmware) + if (code == 32 && !LockMovementAndWaitForStandstill(gb)) + { + return false; + } + { + String<MaxFilenameLength> filename; + gb.GetUnprecedentedString(filename.GetRef()); + if (QueueFileToPrint(filename.c_str(), reply)) { - outBuf->copy("GCode files:\n"); - } + reprap.GetPrintMonitor().StartingPrint(filename.c_str()); + if (gb.LatestMachineState().compatibility == Compatibility::Marlin) + { + 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.c_str()); + } - const bool encapsulateList = gb.LatestMachineState().compatibility != Compatibility::Marlin; - FileInfo fileInfo; - if (MassStorage::FindFirst(dir.c_str(), fileInfo)) - { - // Iterate through all entries and append each file name - bool first = true; - do + if (code == 32) { - if (encapsulateList) - { - outBuf->catf((first) ? "\"%s\"" : ",\"%s\"", fileInfo.fileName.c_str()); - first = false; - } - else - { - outBuf->catf("%s\n", fileInfo.fileName.c_str()); - } - } while (MassStorage::FindNext(fileInfo)); + StartPrinting(true); + } } - else if (!encapsulateList) + else { - outBuf->cat("NONE\n"); + result = GCodeResult::error; } } - } - break; - - 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; - } - { - const size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0; - result = MassStorage::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; - } - { - const size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0; - result = MassStorage::Unmount(card, reply); - } - break; - - case 23: // Set file to print - case 32: // Select file and start SD print - // We now allow a file that is being printed to chain to another file. This is required for the resume-after-power-fail functionality. - if (fileGCode->IsDoingFile() && (&gb) != fileGCode) - { - reply.copy("Cannot set file to print, because a file is already being printed"); - result = GCodeResult::error; break; - } +#endif - if (code == 32 && !LockMovementAndWaitForStandstill(gb)) - { - return false; - } - { - String<MaxFilenameLength> filename; - gb.GetUnprecedentedString(filename.GetRef()); - if (QueueFileToPrint(filename.c_str(), reply)) + case 24: // Print/resume-printing the selected file + if (pauseState == PauseState::pausing || pauseState == PauseState::resuming) + { + // ignore the resume request + } + else { - reprap.GetPrintMonitor().StartingPrint(filename.c_str()); - if (gb.LatestMachineState().compatibility == Compatibility::Marlin) + if (!LockMovementAndWaitForStandstill(gb)) { - reply.copy("File opened\nFile selected"); + return false; } - else + + if (pauseState == PauseState::paused) { - // Command came from web interface or PanelDue, or not emulating Marlin, so send a nicer response - reply.printf("File %s selected for printing", filename.c_str()); +#if HAS_VOLTAGE_MONITOR + if (!platform.IsPowerOk()) + { + reply.copy("Cannot resume while power voltage is low"); + result = GCodeResult::error; + } + else +#endif + { + pauseState = PauseState::resuming; + gb.SetState(GCodeState::resuming1); + if (AllAxesAreHomed() && (!gb.Seen('P') || gb.GetUIValue() != 0)) // P0 parameter skips running resume.g + { + DoFileMacro(gb, RESUME_G, true, SystemHelperMacroCode); + } + } } - - if (code == 32) +#if HAS_MASS_STORAGE + else if (!fileToPrint.IsLive()) { - StartPrinting(true); + reply.copy("Cannot print, because no file is selected!"); + result = GCodeResult::error; + } + else + { +# if HAS_VOLTAGE_MONITOR + if (!platform.IsPowerOk()) + { + reply.copy("Cannot start a print while power voltage is low"); + result = GCodeResult::error; + } + else +# endif + { + bool fromStart = (fileOffsetToPrint == 0); + if (!fromStart) + { + // We executed M26 to set the file offset, which normally means that we are executing resurrect.g. + // We need to copy the absolute/relative and volumetric extrusion flags over + fileGCode->OriginalMachineState().CopyStateFrom(gb.LatestMachineState()); + fileToPrint.Seek(fileOffsetToPrint); + moveFractionToSkip = restartMoveFractionDone; + } + StartPrinting(fromStart); + } } - } - else - { - result = GCodeResult::error; - } - } - break; #endif - - case 24: // Print/resume-printing the selected file - if (pauseState == PauseState::pausing || pauseState == PauseState::resuming) - { - // ignore the resume request - } - else - { - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; } + break; - if (pauseState == PauseState::paused) + case 226: // Synchronous pause, normally initiated from within the file being printed + case 601: + if (pauseState == PauseState::notPaused) { -#if HAS_VOLTAGE_MONITOR - if (!platform.IsPowerOk()) + if (gb.IsDoingFileMacro()) { - reply.copy("Cannot resume while power voltage is low"); - result = GCodeResult::error; + if (deferredPauseCommandPending == nullptr) // filament change pause takes priority + { + deferredPauseCommandPending = (gb.Seen('P') && gb.GetUIValue() == 0) ? "M226 P0" : "M226"; + } } else -#endif { - pauseState = PauseState::resuming; - gb.SetState(GCodeState::resuming1); - if (AllAxesAreHomed() && (!gb.Seen('P') || gb.GetUIValue() != 0)) // P0 parameter skips running resume.g + if (!LockMovementAndWaitForStandstill(gb)) // lock movement before calling DoPause, also wait for movement to complete { - DoFileMacro(gb, RESUME_G, true, SystemHelperMacroCode); + return false; } + DoPause(gb, PauseReason::gcode, nullptr); } } -#if HAS_MASS_STORAGE - else if (!fileToPrint.IsLive()) - { - reply.copy("Cannot print, because no file is selected!"); - result = GCodeResult::error; - } - else + break; + + case 600: // Filament change pause, synchronous + if (pauseState == PauseState::notPaused) { -# if HAS_VOLTAGE_MONITOR - if (!platform.IsPowerOk()) + if (fileGCode->IsDoingFileMacro()) { - reply.copy("Cannot start a print while power voltage is low"); - result = GCodeResult::error; + deferredPauseCommandPending = "M600"; + if (&gb != fileGCode) + { + return false; // wait for the current macro to finish + } } else -# endif { - bool fromStart = (fileOffsetToPrint == 0); - if (!fromStart) + if (!LockMovementAndWaitForStandstill(gb)) // lock movement before calling DoPause, also wait for movement to complete { - // We executed M26 to set the file offset, which normally means that we are executing resurrect.g. - // We need to copy the absolute/relative and volumetric extrusion flags over - fileGCode->OriginalMachineState().CopyStateFrom(gb.LatestMachineState()); - fileToPrint.Seek(fileOffsetToPrint); - moveFractionToSkip = restartMoveFractionDone; + return false; } - StartPrinting(fromStart); + DoPause(gb, PauseReason::filamentChange, nullptr); } } -#endif - } - break; + break; - case 226: // Synchronous pause, normally initiated from within the file being printed - case 601: - if (pauseState == PauseState::notPaused) - { - if (gb.IsDoingFileMacro()) + case 25: // Pause the print + if (pauseState != PauseState::notPaused) { - if (deferredPauseCommandPending == nullptr) // filament change pause takes priority - { - deferredPauseCommandPending = (gb.Seen('P') && gb.GetUIValue() == 0) ? "M226 P0" : "M226"; - } + reply.copy("Printing is already paused!"); + result = GCodeResult::error; } - else + else if (!reprap.GetPrintMonitor().IsPrinting()) { - if (!LockMovementAndWaitForStandstill(gb)) // lock movement before calling DoPause, also wait for movement to complete - { - return false; - } - DoPause(gb, PauseReason::gcode, nullptr); + reply.copy("Cannot pause print, because no file is being printed!"); + result = GCodeResult::error; } - } - break; - - case 600: // Filament change pause, synchronous - if (pauseState == PauseState::notPaused) - { - if (fileGCode->IsDoingFileMacro()) + else if (fileGCode->IsDoingFileMacro()) { - deferredPauseCommandPending = "M600"; + if (deferredPauseCommandPending == nullptr) // filament change pause takes priority + { + deferredPauseCommandPending = (gb.Seen('P') && gb.GetUIValue() == 0) ? "M226 P0" : "M226"; + } if (&gb != fileGCode) { - return false; // wait for the current macro to finish + return false; // wait for the current macro to finish } } else { - if (!LockMovementAndWaitForStandstill(gb)) // lock movement before calling DoPause, also wait for movement to complete + if (!LockMovement(gb)) // lock movement before calling DoPause { return false; } - DoPause(gb, PauseReason::filamentChange, nullptr); - } - } - break; - - case 25: // Pause the print - if (pauseState != PauseState::notPaused) - { - reply.copy("Printing is already paused!"); - result = GCodeResult::error; - } - else if (!reprap.GetPrintMonitor().IsPrinting()) - { - reply.copy("Cannot pause print, because no file is being printed!"); - result = GCodeResult::error; - } - else if (fileGCode->IsDoingFileMacro()) - { - if (deferredPauseCommandPending == nullptr) // filament change pause takes priority - { - deferredPauseCommandPending = (gb.Seen('P') && gb.GetUIValue() == 0) ? "M226 P0" : "M226"; - } - if (&gb != fileGCode) - { - return false; // wait for the current macro to finish + DoPause(gb, PauseReason::user, nullptr); } - } - else - { - if (!LockMovement(gb)) // lock movement before calling DoPause - { - return false; - } - DoPause(gb, PauseReason::user, nullptr); - } - break; + break; #if HAS_MASS_STORAGE - case 26: // Set SD position - // This is used between executing M23 to set up the file to print, and M25 to print it - gb.MustSee('S'); - fileOffsetToPrint = (FilePosition)gb.GetUIValue(); - restartMoveFractionDone = (gb.Seen('P')) ? constrain<float>(gb.GetFValue(), 0.0, 1.0) : 0.0; - { - const unsigned int selectedPlane = gb.LatestMachineState().selectedPlane; - const char c0 = (selectedPlane == 2) ? 'Y' : 'X'; - const char c1 = (selectedPlane == 0) ? 'Y' : 'Z'; - restartInitialUserC0 = (gb.Seen(c0)) ? gb.GetFValue() : 0.0; - restartInitialUserC1 = (gb.Seen(c1)) ? gb.GetFValue() : 0.0; - } - 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; - // In case there are short periods of time when PrintMonitor says a file is printing but the file is not open, or DSF passes M27 to us, check that we have a file - if (fileBeingPrinted.IsLive()) + case 26: // Set SD position + // This is used between executing M23 to set up the file to print, and M25 to print it + gb.MustSee('S'); + fileOffsetToPrint = (FilePosition)gb.GetUIValue(); + restartMoveFractionDone = (gb.Seen('P')) ? constrain<float>(gb.GetFValue(), 0.0, 1.0) : 0.0; { - reply.printf("SD printing byte %lu/%lu", GetFilePosition(), fileBeingPrinted.Length()); - break; + const unsigned int selectedPlane = gb.LatestMachineState().selectedPlane; + const char c0 = (selectedPlane == 2) ? 'Y' : 'X'; + const char c1 = (selectedPlane == 0) ? 'Y' : 'Z'; + restartInitialUserC0 = (gb.Seen(c0)) ? gb.GetFValue() : 0.0; + restartInitialUserC1 = (gb.Seen(c1)) ? gb.GetFValue() : 0.0; } - } - reply.copy("Not SD printing."); - break; + break; - case 28: // Write to file - { - String<MaxFilenameLength> filename; - gb.GetUnprecedentedString(filename.GetRef()); - const bool ok = gb.OpenFileToWrite(platform.GetGCodeDir(), filename.c_str(), 0, false, 0); - if (ok) + case 27: // Report print status - Deprecated + if (reprap.GetPrintMonitor().IsPrinting()) { - reply.printf("Writing to file: %s", filename.c_str()); + // Pronterface keeps sending M27 commands if "Monitor status" is checked, and it specifically expects the following response syntax + FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState; + // In case there are short periods of time when PrintMonitor says a file is printing but the file is not open, or DSF passes M27 to us, check that we have a file + if (fileBeingPrinted.IsLive()) + { + reply.printf("SD printing byte %lu/%lu", GetFilePosition(), fileBeingPrinted.Length()); + break; + } } - else + reply.copy("Not SD printing."); + break; + + case 28: // Write to file { - reply.printf("Can't open file %s for writing.", filename.c_str()); - result = GCodeResult::error; + String<MaxFilenameLength> filename; + gb.GetUnprecedentedString(filename.GetRef()); + const bool ok = gb.OpenFileToWrite(platform.GetGCodeDir(), filename.c_str(), 0, false, 0); + if (ok) + { + reply.printf("Writing to file: %s", filename.c_str()); + } + else + { + reply.printf("Can't open file %s for writing.", filename.c_str()); + result = GCodeResult::error; + } } - } - break; + break; - case 29: // End of file being written; should be intercepted before getting here - reply.copy("GCode end-of-file being interpreted."); - 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 - { - String<MaxFilenameLength> filename; - gb.GetUnprecedentedString(filename.GetRef()); - result = (platform.Delete(platform.GetGCodeDir(), filename.c_str())) ? GCodeResult::ok : GCodeResult::warning; - } - break; + case 30: // Delete file + { + String<MaxFilenameLength> filename; + gb.GetUnprecedentedString(filename.GetRef()); + result = (platform.Delete(platform.GetGCodeDir(), filename.c_str())) ? GCodeResult::ok : GCodeResult::warning; + } + break; #endif - // For case 32, see case 23 + // For case 32, see case 23 #if HAS_MASS_STORAGE || HAS_LINUX_INTERFACE - case 36: // Return file information + case 36: // Return file information # if HAS_LINUX_INTERFACE - if (reprap.UsingLinuxInterface()) - { - reprap.GetFileInfoResponse(nullptr, outBuf, true); - } - else -# endif - { -# if HAS_MASS_STORAGE - if (!LockFileSystem(gb)) // getting file info takes several calls and isn't reentrant + if (reprap.UsingLinuxInterface()) { - return false; + reprap.GetFileInfoResponse(nullptr, outBuf, true); } - - String<MaxFilenameLength> filename; - gb.GetUnprecedentedString(filename.GetRef(), true); - result = reprap.GetFileInfoResponse((filename.IsEmpty()) ? nullptr : filename.c_str(), outBuf, false); + else # endif - } - break; + { +# if HAS_MASS_STORAGE + if (!LockFileSystem(gb)) // getting file info takes several calls and isn't reentrant + { + return false; + } - case 37: // Simulation mode on/off, or simulate a whole file -# if HAS_LINUX_INTERFACE - if (reprap.UsingLinuxInterface() && !gb.IsBinary()) - { - reply.copy("M37 can be only started from the SBC interface"); - result = GCodeResult::error; - } - else + String<MaxFilenameLength> filename; + gb.GetUnprecedentedString(filename.GetRef(), true); + result = reprap.GetFileInfoResponse((filename.IsEmpty()) ? nullptr : filename.c_str(), outBuf, false); # endif - { - bool seen = false; - String<MaxFilenameLength> simFileName; + } + break; - gb.TryGetPossiblyQuotedString('P', simFileName.GetRef(), seen); - if (seen) + case 37: // Simulation mode on/off, or simulate a whole file +# if HAS_LINUX_INTERFACE + if (reprap.UsingLinuxInterface() && !gb.IsBinary()) { - const bool updateFile = !gb.Seen('F') || gb.GetUIValue() == 1; - result = SimulateFile(gb, reply, simFileName.GetRef(), updateFile); + reply.copy("M37 can be only started from the SBC interface"); + result = GCodeResult::error; } else +# endif { - uint32_t newSimulationMode; - gb.TryGetUIValue('S', newSimulationMode, seen); + bool seen = false; + String<MaxFilenameLength> simFileName; + + gb.TryGetPossiblyQuotedString('P', simFileName.GetRef(), seen); if (seen) { - result = ChangeSimulationMode(gb, reply, newSimulationMode); + const bool updateFile = !gb.Seen('F') || gb.GetUIValue() == 1; + result = SimulateFile(gb, reply, simFileName.GetRef(), updateFile); } else { - reply.printf("Simulation mode: %s, move time: %.1f sec, other time: %.1f sec", - (simulationMode != 0) ? "on" : "off", (double)reprap.GetMove().GetSimulationTime(), (double)simulationTime); + uint32_t newSimulationMode; + gb.TryGetUIValue('S', newSimulationMode, seen); + if (seen) + { + result = ChangeSimulationMode(gb, reply, newSimulationMode); + } + else + { + reply.printf("Simulation mode: %s, move time: %.1f sec, other time: %.1f sec", + (simulationMode != 0) ? "on" : "off", (double)reprap.GetMove().GetSimulationTime(), (double)simulationTime); + } } } - } - break; + break; #endif #if HAS_MASS_STORAGE - 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 - String<MaxFilenameLength> filename; - gb.GetUnprecedentedString(filename.GetRef()); - if (StartHash(filename.c_str())) + case 38: // Report SHA1 of file + if (!LockFileSystem(gb)) // getting file hash takes several calls and isn't reentrant { - // Hashing is now in progress... - result = GCodeResult::notFinished; + return false; } - else + if (fileBeingHashed == nullptr) { - reply.printf("Cannot open file: %s", filename.c_str()); - result = GCodeResult::error; - } - } - else - { - // This can take some time. All the actual heavy lifting is in dedicated methods - result = AdvanceHash(reply); - } - break; - - case 39: // Return SD card info - { - uint32_t slot = 0; - bool dummy; - gb.TryGetUIValue('P', slot, dummy); - int32_t format = 0; - gb.TryGetIValue('S', format, dummy); - uint64_t capacity, freeSpace; - uint32_t speed; - uint32_t clSize; - const MassStorage::InfoResult res = MassStorage::GetCardInfo(slot, capacity, freeSpace, speed, clSize); - if (format == 2) - { - reply.printf("{\"SDinfo\":{\"slot\":%" PRIu32 ",\"present\":", slot); - if (res == MassStorage::InfoResult::ok) - { - reply.catf("1,\"capacity\":%" PRIu64 ",\"free\":%" PRIu64 ",\"speed\":%" PRIu32 ",\"clsize\":%" PRIu32 "}}", capacity, freeSpace, speed, clSize); + // See if we can open the file and start hashing + String<MaxFilenameLength> filename; + gb.GetUnprecedentedString(filename.GetRef()); + if (StartHash(filename.c_str())) + { + // Hashing is now in progress... + result = GCodeResult::notFinished; } else { - reply.cat("0}}"); + reply.printf("Cannot open file: %s", filename.c_str()); + result = GCodeResult::error; } } else { - switch(res) - { - case MassStorage::InfoResult::badSlot: - default: - reply.printf("Bad SD slot number: %" PRIu32, slot); - result = GCodeResult::error; - break; - - case MassStorage::InfoResult::noCard: - reply.printf("No SD card mounted in slot %" PRIu32, slot); - result = GCodeResult::error; - break; + // This can take some time. All the actual heavy lifting is in dedicated methods + result = AdvanceHash(reply); + } + break; - case MassStorage::InfoResult::ok: - reply.printf("SD card in slot %" PRIu32 ": capacity %.2fGb, free space %.2fGb, speed %.2fMBytes/sec, cluster size ", - slot, (double)capacity/(1000*1000*1000), (double)freeSpace/(1000*1000*1000), (double)speed/(1000*1000)); - if (clSize < 1024) + case 39: // Return SD card info + { + uint32_t slot = 0; + bool dummy; + gb.TryGetUIValue('P', slot, dummy); + int32_t format = 0; + gb.TryGetIValue('S', format, dummy); + uint64_t capacity, freeSpace; + uint32_t speed; + uint32_t clSize; + const MassStorage::InfoResult res = MassStorage::GetCardInfo(slot, capacity, freeSpace, speed, clSize); + if (format == 2) + { + reply.printf("{\"SDinfo\":{\"slot\":%" PRIu32 ",\"present\":", slot); + if (res == MassStorage::InfoResult::ok) { - reply.catf("%" PRIu32 " bytes", clSize); + reply.catf("1,\"capacity\":%" PRIu64 ",\"free\":%" PRIu64 ",\"speed\":%" PRIu32 ",\"clsize\":%" PRIu32 "}}", capacity, freeSpace, speed, clSize); } else { - reply.catf("%" PRIu32 "kb", clSize/1024); + reply.cat("0}}"); + } + } + else + { + switch(res) + { + case MassStorage::InfoResult::badSlot: + default: + reply.printf("Bad SD slot number: %" PRIu32, slot); + result = GCodeResult::error; + break; + + case MassStorage::InfoResult::noCard: + reply.printf("No SD card mounted in slot %" PRIu32, slot); + result = GCodeResult::error; + break; + + case MassStorage::InfoResult::ok: + reply.printf("SD card in slot %" PRIu32 ": capacity %.2fGb, free space %.2fGb, speed %.2fMBytes/sec, cluster size ", + slot, (double)capacity/(1000*1000*1000), (double)freeSpace/(1000*1000*1000), (double)speed/(1000*1000)); + if (clSize < 1024) + { + reply.catf("%" PRIu32 " bytes", clSize); + } + else + { + reply.catf("%" PRIu32 "kb", clSize/1024); + } + break; } - break; } } - } - break; + break; #endif - case 42: // Turn an output pin on or off - { - const uint32_t gpioPortNumber = gb.GetLimitedUIValue('P', MaxGpOutPorts); - gb.MustSee('S'); - result = platform.GetGpOutPort(gpioPortNumber).WriteAnalog(gpioPortNumber, false, gb.GetPwmValue(), gb, reply); - } - break; + case 42: // Turn an output pin on or off + { + const uint32_t gpioPortNumber = gb.GetLimitedUIValue('P', MaxGpOutPorts); + gb.MustSee('S'); + result = platform.GetGpOutPort(gpioPortNumber).WriteAnalog(gpioPortNumber, false, gb.GetPwmValue(), gb, reply); + } + break; - case 73: // Slicer-inserted print time values - result = reprap.GetPrintMonitor().ProcessM73(gb, reply); - break; + case 73: // Slicer-inserted print time values + result = reprap.GetPrintMonitor().ProcessM73(gb, reply); + break; - case 80: // ATX power on - atxPowerControlled = true; - platform.AtxPowerOn(); - break; + case 80: // ATX power on + atxPowerControlled = true; + platform.AtxPowerOn(); + break; - case 81: // ATX power off - atxPowerControlled = true; - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - platform.AtxPowerOff(gb.Seen('S') && gb.GetUIValue() != 0); - break; + case 81: // ATX power off + atxPowerControlled = true; + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + platform.AtxPowerOff(gb.Seen('S') && gb.GetUIValue() != 0); + break; - case 82: // Use absolute extruder positioning - gb.LatestMachineState().drivesRelative = false; - reprap.InputsUpdated(); - break; + case 82: // Use absolute extruder positioning + gb.LatestMachineState().drivesRelative = false; + reprap.InputsUpdated(); + break; - case 83: // Use relative extruder positioning - gb.LatestMachineState().drivesRelative = true; - reprap.InputsUpdated(); - break; + case 83: // Use relative extruder positioning + gb.LatestMachineState().drivesRelative = true; + reprap.InputsUpdated(); + break; - // For case 84, see case 18 + // For case 84, see case 18 - case 85: // Set inactive time - break; + case 85: // Set inactive time + break; - case 92: // Set/report steps/mm for some axes - { - bool seenUstepMultiplier = false; - uint32_t ustepMultiplier = 0; - gb.TryGetUIValue('S', ustepMultiplier, seenUstepMultiplier); + case 92: // Set/report steps/mm for some axes + { + bool seenUstepMultiplier = false; + uint32_t ustepMultiplier = 0; + gb.TryGetUIValue('S', ustepMultiplier, seenUstepMultiplier); - bool seen = false; + bool seen = false; #if SUPPORT_CAN_EXPANSION - AxesBitmap axesToUpdate; + AxesBitmap axesToUpdate; #endif - for (size_t axis = 0; axis < numTotalAxes; axis++) - { - if (gb.Seen(axisLetters[axis])) + for (size_t axis = 0; axis < numTotalAxes; axis++) { - if (!LockMovementAndWaitForStandstill(gb)) + if (gb.Seen(axisLetters[axis])) { - return false; - } - platform.SetDriveStepsPerUnit(axis, gb.GetFValue(), ustepMultiplier); + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + platform.SetDriveStepsPerUnit(axis, gb.GetFValue(), ustepMultiplier); #if SUPPORT_CAN_EXPANSION - axesToUpdate.SetBit(axis); + axesToUpdate.SetBit(axis); #endif - seen = true; + seen = true; + } } - } - if (gb.Seen(extrudeLetter)) - { - if (!LockMovementAndWaitForStandstill(gb)) + if (gb.Seen(extrudeLetter)) { - return false; - } - seen = true; - float eVals[MaxExtruders]; - size_t eCount = numExtruders; - gb.GetFloatArray(eVals, eCount, true); + 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++) - { - const size_t drive = ExtruderToLogicalDrive(e); + // 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++) + { + const size_t drive = ExtruderToLogicalDrive(e); #if SUPPORT_CAN_EXPANSION - axesToUpdate.SetBit(drive); + axesToUpdate.SetBit(drive); #endif - platform.SetDriveStepsPerUnit(drive, eVals[e], ustepMultiplier); + platform.SetDriveStepsPerUnit(drive, eVals[e], ustepMultiplier); + } } - } - if (seen) - { - // On a delta, if we change the drive steps/mm then we need to recalculate the motor positions - reprap.GetMove().SetNewPosition(moveBuffer.coords, true); + if (seen) + { + // On a delta, if we change the drive steps/mm then we need to recalculate the motor positions + reprap.GetMove().SetNewPosition(moveBuffer.coords, true); #if SUPPORT_CAN_EXPANSION - result = platform.UpdateRemoteStepsPerMmAndMicrostepping(axesToUpdate, reply); + result = platform.UpdateRemoteStepsPerMmAndMicrostepping(axesToUpdate, reply); #endif - } - else - { - reply.copy("Steps/mm: "); - for (size_t axis = 0; axis < numTotalAxes; ++axis) - { - reply.catf("%c: %.3f, ", axisLetters[axis], (double)platform.DriveStepsPerUnit(axis)); } - reply.catf("E:"); - char sep = ' '; - for (size_t extruder = 0; extruder < numExtruders; extruder++) + else { - reply.catf("%c%.3f", sep, (double)platform.DriveStepsPerUnit(ExtruderToLogicalDrive(extruder))); - sep = ':'; + reply.copy("Steps/mm: "); + for (size_t axis = 0; axis < numTotalAxes; ++axis) + { + reply.catf("%c: %.3f, ", axisLetters[axis], (double)platform.DriveStepsPerUnit(axis)); + } + reply.catf("E:"); + char sep = ' '; + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + reply.catf("%c%.3f", sep, (double)platform.DriveStepsPerUnit(ExtruderToLogicalDrive(extruder))); + sep = ':'; + } } } - } - break; - - case 98: // Call Macro/Subprogram - { - gb.MustSee('P'); - String<MaxFilenameLength> filename; - gb.GetPossiblyQuotedString(filename.GetRef()); - DoFileMacro(gb, filename.c_str(), true, code); - } - break; + break; - case 99: // Return from Macro/Subprogram - FileMacroCyclesReturn(gb); - break; + case 98: // Call Macro/Subprogram + { + gb.MustSee('P'); + String<MaxFilenameLength> filename; + gb.GetPossiblyQuotedString(filename.GetRef()); + DoFileMacro(gb, filename.c_str(), true, code); + } + break; - case 101: // Un-retract, generated by S3D if "Include M101/101/103" is enabled - result = RetractFilament(gb, false); - break; + case 99: // Return from Macro/Subprogram + FileMacroCyclesReturn(gb); + break; - case 102: - // S3D generates this command just before each explicit retraction command if both explicit retraction and "Include M101/101/103" are enabled. - // Old versions of S3D also generate it once at the start of each print file if "Include M101/101/103" is enabled. - // It's not documented, so we just ignore it rather than generate an error message. - break; + case 101: // Un-retract, generated by S3D if "Include M101/101/103" is enabled + result = RetractFilament(gb, false); + break; - case 103: // Retract, generated by S3D if "Include M101/101/103" is enabled - result = RetractFilament(gb, true); - break; + case 102: + // S3D generates this command just before each explicit retraction command if both explicit retraction and "Include M101/101/103" are enabled. + // Old versions of S3D also generate it once at the start of each print file if "Include M101/101/103" is enabled. + // It's not documented, so we just ignore it rather than generate an error message. + break; - // For case 104, see 109 + case 103: // Retract, generated by S3D if "Include M101/101/103" is enabled + result = RetractFilament(gb, true); + break; - case 105: // Get temperatures - GenerateTemperatureReport(reply); - break; + // For case 104, see 109 - case 106: // Set/report fan values - { - bool seenFanNum = false; - uint32_t fanNum; - gb.TryGetUIValue('P', fanNum, seenFanNum); - bool processed; + case 105: // Get temperatures + GenerateTemperatureReport(reply); + break; - // 2018-08-09: only configure the fan if a fan number was given. - // This avoids M106 Snn failing if we have disabled Fan 0 and mapped the print cooling fan to a different fan. - if (seenFanNum) - { - bool error = false; - processed = reprap.GetFansManager().ConfigureFan(code, fanNum, gb, reply, error); - result = GetGCodeResultFromError(error); - } - else + case 106: // Set/report fan values { - processed = false; - } + bool seenFanNum = false; + uint32_t fanNum; + gb.TryGetUIValue('P', fanNum, seenFanNum); + bool processed; - // ConfigureFan only processes S parameters if there were other parameters to process - if (!processed && gb.Seen('S')) - { - // Convert the parameter to an interval in 0.0..1.0 here so that we save the correct value in lastDefaultFanSpeed - const float f = gb.GetPwmValue(); + // 2018-08-09: only configure the fan if a fan number was given. + // This avoids M106 Snn failing if we have disabled Fan 0 and mapped the print cooling fan to a different fan. if (seenFanNum) { - result = reprap.GetFansManager().SetFanValue(fanNum, f, reply); - if (IsMappedFan(fanNum)) - { - lastDefaultFanSpeed = f; - } + bool error = false; + processed = reprap.GetFansManager().ConfigureFan(code, fanNum, gb, reply, error); + result = GetGCodeResultFromError(error); } 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. - SetMappedFanSpeed(f); + processed = false; } - } - // ConfigureFan doesn't process R parameters - if (gb.Seen('R')) - { - // Restore fan speed to value when print was paused - if (seenFanNum) + // ConfigureFan only processes S parameters if there were other parameters to process + if (!processed && gb.Seen('S')) { - result = reprap.GetFansManager().SetFanValue(fanNum, pausedFanSpeeds[fanNum], reply); + // Convert the parameter to an interval in 0.0..1.0 here so that we save the correct value in lastDefaultFanSpeed + const float f = gb.GetPwmValue(); + if (seenFanNum) + { + result = reprap.GetFansManager().SetFanValue(fanNum, f, reply); + if (IsMappedFan(fanNum)) + { + lastDefaultFanSpeed = 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. + SetMappedFanSpeed(f); + } } - else + + // ConfigureFan doesn't process R parameters + if (gb.Seen('R')) { - const size_t restorePointNumber = gb.GetLimitedUIValue('R', NumRestorePoints); - SetMappedFanSpeed(numberedRestorePoints[restorePointNumber].fanSpeed); + // Restore fan speed to value when print was paused + if (seenFanNum) + { + result = reprap.GetFansManager().SetFanValue(fanNum, pausedFanSpeeds[fanNum], reply); + } + else + { + const size_t restorePointNumber = gb.GetLimitedUIValue('R', NumRestorePoints); + SetMappedFanSpeed(numberedRestorePoints[restorePointNumber].fanSpeed); + } } } - } - break; - - case 107: // Fan off - deprecated - SetMappedFanSpeed(0.0); - break; - - case 108: // Cancel waiting for temperature - if (isWaiting) - { - cancelWait = true; - } - break; - - case 109: // Deprecated in RRF, but widely generated by slicers - { - const bool movementWasLocked = gb.LatestMachineState().lockedResources.IsBitSet(MoveResource); - if ( !LockMovementAndWaitForStandstill(gb) // wait until movement has finished - || !IsCodeQueueIdle() // also wait until deferred command queue has caught up to avoid out-of-order execution - ) - { - return false; - } - - // 2020-02-04 Don't unlock movement if it was already locked, e.g. because M109 was used in a macro - if (!movementWasLocked) - { - UnlockMovement(gb); // allow babystepping and pausing while heating - } - } - - // no break - case 104: - // New behaviour from 1.20beta12: - // M109 Snnn - // - If no tools are active, set Tool 0 to active - // - Set active tool's active and standby temperatures to Snnn - // - // M109 Tnnn Snnn - // - If no tools are active, set Tnnn to active - // - If another tool is active but Tnnn is off, set Tnnn to standby - // - Set Tnnn's active and standby temperatures to Snnn - // M104 does the same but doesn't ever select a tool - { - // Get the temperature to set - float temperature; - if (gb.Seen('R')) - { - gb.LatestMachineState().waitWhileCooling = true; - temperature = gb.GetFValue(); - } - else if (gb.Seen('S')) - { - gb.LatestMachineState().waitWhileCooling = false; - temperature = gb.GetFValue(); - } - else - { - break; // no target temperature given - } - - // Find the tool that the command applies to. - // This is the tool specified in the T parameter, else the current tool if there is one, else the default tool - int32_t toolNumber = 0; - bool seenT = false; - gb.TryGetIValue('T', toolNumber, seenT); - ReadLockedPointer<Tool> const applicableTool = (seenT) ? reprap.GetTool(toolNumber) : reprap.GetCurrentOrDefaultTool(); + break; - // Check that we have a tool - if (applicableTool.IsNull()) - { - reply.copy("Invalid tool number"); - result = GCodeResult::error; - break; - } + case 107: // Fan off - deprecated + SetMappedFanSpeed(0.0); + break; - // Set the heater temperatures for that tool. We set the standby temperatures as well as the active ones, - // because any slicer that uses M109 doesn't understand that there are separate active and standby temperatures. - if (simulationMode == 0) + case 108: // Cancel waiting for temperature + if (isWaiting) { - SetToolHeaters(applicableTool.Ptr(), temperature, true); // this may throw + cancelWait = true; } + break; - Tool * const currentTool = reprap.GetCurrentTool(); - if (code == 109 && currentTool == nullptr) + case 109: // Deprecated in RRF, but widely generated by slicers { - // Switch to the tool - if (!LockMovementAndWaitForStandstill(gb)) + const bool movementWasLocked = gb.LatestMachineState().lockedResources.IsBitSet(MoveResource); + if ( !LockMovementAndWaitForStandstill(gb) // wait until movement has finished + || !IsCodeQueueIdle() // also wait until deferred command queue has caught up to avoid out-of-order execution + ) { return false; } - newToolNumber = applicableTool->Number(); - toolChangeParam = (simulationMode != 0) ? 0 : DefaultToolChangeParam; - gb.SetState(GCodeState::m109ToolChange0); - result = GCodeResult::ok; + // 2020-02-04 Don't unlock movement if it was already locked, e.g. because M109 was used in a macro + if (!movementWasLocked) + { + UnlockMovement(gb); // allow babystepping and pausing while heating + } } - else + + // no break + case 104: + // New behaviour from 1.20beta12: + // M109 Snnn + // - If no tools are active, set Tool 0 to active + // - Set active tool's active and standby temperatures to Snnn + // + // M109 Tnnn Snnn + // - If no tools are active, set Tnnn to active + // - If another tool is active but Tnnn is off, set Tnnn to standby + // - Set Tnnn's active and standby temperatures to Snnn + // M104 does the same but doesn't ever select a tool { - if (applicableTool.Ptr() == currentTool) + // Get the temperature to set + float temperature; + if (gb.Seen('R')) + { + gb.LatestMachineState().waitWhileCooling = true; + temperature = gb.GetFValue(); + } + else if (gb.Seen('S')) { - // Even though the tool is selected, we may have turned it off e.g. when upgrading the WiFi firmware or following a heater fault that has been cleared. - // So make sure the tool heaters are on. - reprap.SelectTool(applicableTool->Number(), simulationMode != 0); + gb.LatestMachineState().waitWhileCooling = false; + temperature = gb.GetFValue(); } else { - // If we already have an active tool and we are setting temperatures for a different tool, set that tool's heaters to standby in case it is off - reprap.StandbyTool(applicableTool->Number(), simulationMode != 0); + break; // no target temperature given + } + + // Find the tool that the command applies to. + // This is the tool specified in the T parameter, else the current tool if there is one, else the default tool + int32_t toolNumber = 0; + bool seenT = false; + gb.TryGetIValue('T', toolNumber, seenT); + ReadLockedPointer<Tool> const applicableTool = (seenT) ? reprap.GetTool(toolNumber) : reprap.GetCurrentOrDefaultTool(); + + // Check that we have a tool + if (applicableTool.IsNull()) + { + reply.copy("Invalid tool number"); + result = GCodeResult::error; + break; + } + + // Set the heater temperatures for that tool. We set the standby temperatures as well as the active ones, + // because any slicer that uses M109 doesn't understand that there are separate active and standby temperatures. + if (simulationMode == 0) + { + SetToolHeaters(applicableTool.Ptr(), temperature, true); // this may throw } - if (code == 109 && simulationMode == 0) + Tool * const currentTool = reprap.GetCurrentTool(); + if (code == 109 && currentTool == nullptr) { - gb.SetState(GCodeState::m109WaitForTemperature); + // Switch to the tool + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + + newToolNumber = applicableTool->Number(); + toolChangeParam = (simulationMode != 0) ? 0 : DefaultToolChangeParam; + gb.SetState(GCodeState::m109ToolChange0); result = GCodeResult::ok; } + else + { + if (applicableTool.Ptr() == currentTool) + { + // Even though the tool is selected, we may have turned it off e.g. when upgrading the WiFi firmware or following a heater fault that has been cleared. + // So make sure the tool heaters are on. + reprap.SelectTool(applicableTool->Number(), simulationMode != 0); + } + else + { + // If we already have an active tool and we are setting temperatures for a different tool, set that tool's heaters to standby in case it is off + reprap.StandbyTool(applicableTool->Number(), simulationMode != 0); + } + + if (code == 109 && simulationMode == 0) + { + gb.SetState(GCodeState::m109WaitForTemperature); + result = GCodeResult::ok; + } + } } - } - break; + break; - case 110: // Set line numbers - //TODO - break; + case 110: // Set line numbers + //TODO + break; - case 111: // Debug level - { - bool seen = false; - uint32_t flags = 0; - Module module = Module::noModule; - if (gb.Seen('S')) + case 111: // Debug level { - flags = gb.GetUIValue(); - if (flags != 0) + bool seen = false; + uint32_t flags = 0; + Module module = Module::noModule; + if (gb.Seen('S')) { - flags = 0xFFFFFFFF; + flags = gb.GetUIValue(); + if (flags != 0) + { + flags = 0xFFFFFFFF; + } + seen = true; } - seen = true; - } - else if (gb.Seen('D')) - { - flags = gb.GetUIValue(); - seen = true; - } - if (gb.Seen('P')) - { - module = static_cast<Module>(gb.GetLimitedUIValue('P', Module::numModules)); - seen = true; - } - if (seen) - { - if (module != Module::noModule) + else if (gb.Seen('D')) { - reprap.SetDebug(module, flags); - reprap.PrintDebug(gb.GetResponseMessageType()); - return true; + flags = gb.GetUIValue(); + seen = true; } - else if (flags != 0) + if (gb.Seen('P')) { - // Repetier Host sends M111 with various S parameters to enable echo and similar features, which used to turn on all out debugging. - // But it's not useful to enable all debugging anyway. So we no longer allow debugging to be enabled without a P parameter. - reply.copy("Use P parameter to specify which module to debug"); + module = static_cast<Module>(gb.GetLimitedUIValue('P', Module::numModules)); + seen = true; + } + if (seen) + { + if (module != Module::noModule) + { + reprap.SetDebug(module, flags); + reprap.PrintDebug(gb.GetResponseMessageType()); + return true; + } + else if (flags != 0) + { + // Repetier Host sends M111 with various S parameters to enable echo and similar features, which used to turn on all out debugging. + // But it's not useful to enable all debugging anyway. So we no longer allow debugging to be enabled without a P parameter. + reply.copy("Use P parameter to specify which module to debug"); + } + else + { + // M111 S0 still clears all debugging + reprap.ClearDebug(); + } } else { - // M111 S0 still clears all debugging - reprap.ClearDebug(); + reprap.PrintDebug(gb.GetResponseMessageType()); + return true; } } - else - { - reprap.PrintDebug(gb.GetResponseMessageType()); - return true; - } - } - break; + break; - case 112: // Emergency stop - acted upon in Webserver, but also here in case it comes from USB etc. - DoEmergencyStop(); - 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 114: + GetCurrentCoordinates(reply); + break; - case 115: // Print firmware version or set hardware type + case 115: // Print firmware version or set hardware type #if defined(DUET_NG) || defined(DUET_06_85) - if (gb.Seen('P')) - { - if (runningConfigFile) + if (gb.Seen('P')) { - platform.SetBoardType((BoardType)gb.GetIValue()); + if (runningConfigFile) + { + platform.SetBoardType((BoardType)gb.GetIValue()); + } + else + { + reply.copy("Board type can only be set within config.g"); + result = GCodeResult::error; + } } else - { - reply.copy("Board type can only be set within config.g"); - result = GCodeResult::error; - } - } - else #endif - { -#if SUPPORT_CAN_EXPANSION - if (gb.Seen('B')) { - const uint32_t board = gb.GetUIValue(); - if (board != CanInterface::GetCanAddress()) +#if SUPPORT_CAN_EXPANSION + if (gb.Seen('B')) { - result = CanInterface::GetRemoteFirmwareDetails(board, gb, reply); - break; + const uint32_t board = gb.GetUIValue(); + if (board != CanInterface::GetCanAddress()) + { + result = CanInterface::GetRemoteFirmwareDetails(board, gb, reply); + break; + } } - } #endif - reply.printf("FIRMWARE_NAME: %s FIRMWARE_VERSION: %s ELECTRONICS: %s", FIRMWARE_NAME, VERSION, platform.GetElectronicsString()); + reply.printf("FIRMWARE_NAME: %s FIRMWARE_VERSION: %s ELECTRONICS: %s", FIRMWARE_NAME, VERSION, platform.GetElectronicsString()); #ifdef DUET_NG - const char* const expansionName = DuetExpansion::GetExpansionBoardName(); - if (expansionName != nullptr) - { - reply.catf(" + %s", expansionName); - } - const char* const additionalExpansionName = DuetExpansion::GetAdditionalExpansionBoardName(); - if (additionalExpansionName != nullptr) - { - reply.catf(" + %s", additionalExpansionName); - } -#endif - reply.catf(" FIRMWARE_DATE: %s%s", DATE, TIME_SUFFIX); - } - break; - - case 116: // Wait for set temperatures - if ( !LockMovementAndWaitForStandstill(gb) // wait until movement has finished - || !IsCodeQueueIdle() // also wait until deferred command queue has caught up to avoid out-of-order execution - ) - { - return false; - } - - if (!cancelWait) - { - const float tolerance = (gb.Seen('S')) ? max<float>(gb.GetFValue(), 0.1) : TEMPERATURE_CLOSE_ENOUGH; - bool seen = false; - if (gb.Seen('P')) - { - // Wait for the heaters associated with the specified tool to be ready - if (!ToolHeatersAtSetTemperatures(reprap.GetTool(gb.GetIValue()).Ptr(), true, tolerance)) + const char* const expansionName = DuetExpansion::GetExpansionBoardName(); + if (expansionName != nullptr) { - isWaiting = true; - return false; + reply.catf(" + %s", expansionName); } - seen = true; + const char* const additionalExpansionName = DuetExpansion::GetAdditionalExpansionBoardName(); + if (additionalExpansionName != nullptr) + { + reply.catf(" + %s", additionalExpansionName); + } +#endif + reply.catf(" FIRMWARE_DATE: %s%s", DATE, TIME_SUFFIX); } + break; - if (gb.Seen('H')) + case 116: // Wait for set temperatures + if ( !LockMovementAndWaitForStandstill(gb) // wait until movement has finished + || !IsCodeQueueIdle() // also wait until deferred command queue has caught up to avoid out-of-order execution + ) { - // Wait for specified heaters to be ready - uint32_t heaters[MaxHeaters]; - size_t heaterCount = MaxHeaters; - gb.GetUnsignedArray(heaters, heaterCount, false); + return false; + } - for (size_t i = 0; i < heaterCount; i++) + if (!cancelWait) + { + const float tolerance = (gb.Seen('S')) ? max<float>(gb.GetFValue(), 0.1) : TEMPERATURE_CLOSE_ENOUGH; + bool seen = false; + if (gb.Seen('P')) { - if (!reprap.GetHeat().HeaterAtSetTemperature(heaters[i], true, tolerance)) + // Wait for the heaters associated with the specified tool to be ready + if (!ToolHeatersAtSetTemperatures(reprap.GetTool(gb.GetIValue()).Ptr(), true, tolerance)) { isWaiting = true; return false; } + seen = true; } - seen = true; - } - if (gb.Seen('C')) - { - // Wait for specified chamber(s) to be ready - uint32_t chamberIndices[MaxChamberHeaters]; - size_t chamberCount = MaxChamberHeaters; - gb.GetUnsignedArray(chamberIndices, chamberCount, false); - - if (chamberCount == 0) + if (gb.Seen('H')) { - // If no values are specified, wait for all chamber heaters - for (size_t i = 0; i < MaxChamberHeaters; i++) + // Wait for specified heaters to be ready + uint32_t heaters[MaxHeaters]; + size_t heaterCount = MaxHeaters; + gb.GetUnsignedArray(heaters, heaterCount, false); + + for (size_t i = 0; i < heaterCount; i++) { - const int8_t heater = reprap.GetHeat().GetChamberHeater(i); - if (heater >= 0 && !reprap.GetHeat().HeaterAtSetTemperature(heater, true, tolerance)) + if (!reprap.GetHeat().HeaterAtSetTemperature(heaters[i], true, tolerance)) { isWaiting = true; return false; } } + seen = true; } - else + + if (gb.Seen('C')) { - // Otherwise wait only for the specified chamber heaters - for (size_t i = 0; i < chamberCount; i++) + // Wait for specified chamber(s) to be ready + uint32_t chamberIndices[MaxChamberHeaters]; + size_t chamberCount = MaxChamberHeaters; + gb.GetUnsignedArray(chamberIndices, chamberCount, false); + + if (chamberCount == 0) { - if (chamberIndices[i] >= 0 && chamberIndices[i] < MaxChamberHeaters) + // If no values are specified, wait for all chamber heaters + for (size_t i = 0; i < MaxChamberHeaters; i++) { - const int8_t heater = reprap.GetHeat().GetChamberHeater(chamberIndices[i]); + const int8_t heater = reprap.GetHeat().GetChamberHeater(i); if (heater >= 0 && !reprap.GetHeat().HeaterAtSetTemperature(heater, true, tolerance)) { isWaiting = true; @@ -1780,2118 +1774,2181 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeEx } } } + else + { + // Otherwise wait only for the specified chamber heaters + for (size_t i = 0; i < chamberCount; i++) + { + if (chamberIndices[i] >= 0 && chamberIndices[i] < MaxChamberHeaters) + { + const int8_t heater = reprap.GetHeat().GetChamberHeater(chamberIndices[i]); + if (heater >= 0 && !reprap.GetHeat().HeaterAtSetTemperature(heater, true, tolerance)) + { + isWaiting = true; + return false; + } + } + } + } + seen = true; } - seen = true; - } - // Wait for all heaters except chamber(s) to be ready - if (!seen && !reprap.GetHeat().AllHeatersAtSetTemperatures(true, tolerance)) - { - isWaiting = true; - return false; + // Wait for all heaters except chamber(s) to be ready + if (!seen && !reprap.GetHeat().AllHeatersAtSetTemperatures(true, tolerance)) + { + isWaiting = true; + return false; + } } - } - // If we get here, there is nothing more to wait for - cancelWait = isWaiting = false; - break; + // If we get here, there is nothing more to wait for + cancelWait = isWaiting = false; + break; - case 117: // Display message - { - String<MediumStringLength> msg; - gb.GetUnprecedentedString(msg.GetRef(), true); - reprap.SetMessage(msg.c_str()); - } - break; + case 117: // Display message + { + String<MediumStringLength> msg; + gb.GetUnprecedentedString(msg.GetRef(), true); + reprap.SetMessage(msg.c_str()); + } + break; - case 118: // Echo message on host - { - gb.MustSee('S'); - String<GCODE_LENGTH> message; - gb.GetQuotedString(message.GetRef()); + case 118: // Echo message on host + { + gb.MustSee('S'); + String<GCODE_LENGTH> message; + gb.GetQuotedString(message.GetRef()); - MessageType type = GenericMessage; + MessageType type = GenericMessage; #if HAS_MASS_STORAGE - bool seenP = false; + bool seenP = false; #endif - if (gb.Seen('P')) - { + if (gb.Seen('P')) + { #if HAS_MASS_STORAGE - seenP = true; + seenP = true; #endif - const int32_t param = gb.GetIValue(); - switch (param) - { - case 0: // Generic (default) - // no need to set it twice - break; - case 1: // USB - type = UsbMessage; - break; - case 2: // UART port - type = DirectAuxMessage; - break; - case 3: // HTTP - type = HttpMessage; - break; - case 4: // Telnet - type = TelnetMessage; - break; + const int32_t param = gb.GetIValue(); + switch (param) + { + case 0: // Generic (default) + // no need to set it twice + break; + case 1: // USB + type = UsbMessage; + break; + case 2: // UART port + type = DirectAuxMessage; + break; + case 3: // HTTP + type = HttpMessage; + break; + case 4: // Telnet + type = TelnetMessage; + break; #ifdef SERIAL_AUX2_DEVICE - case 5: // AUX2 - type = Aux2Message; - break; + case 5: // AUX2 + type = Aux2Message; + break; #endif - default: - reply.printf("Invalid message type: %" PRIi32, param); - result = GCodeResult::error; - break; + default: + reply.printf("Invalid message type: %" PRIi32, param); + result = GCodeResult::error; + break; + } } - } #if HAS_MASS_STORAGE - if (gb.Seen('L')) - { - // If we haven't seen a P parameter but seen the L parameter we are going to log - // only to log file so reset message type first - if (!seenP) - { - type = MessageType::NoDestinationMessage; - } - const LogLevel logLevel = (LogLevel) gb.GetLimitedUIValue('L', LogLevel::off, LogLevel::NumValues); - switch (logLevel.ToBaseType()) + if (gb.Seen('L')) { - case LogLevel::off: - type = RemoveLogging(type); - break; - case LogLevel::warn: - type = AddLogWarn(type); - break; - case LogLevel::info: - type = AddLogInfo(type); - break; - case LogLevel::debug: - type = AddLogDebug(type); - break; + // If we haven't seen a P parameter but seen the L parameter we are going to log + // only to log file so reset message type first + if (!seenP) + { + type = MessageType::NoDestinationMessage; + } + const LogLevel logLevel = (LogLevel) gb.GetLimitedUIValue('L', LogLevel::off, LogLevel::NumValues); + switch (logLevel.ToBaseType()) + { + case LogLevel::off: + type = RemoveLogging(type); + break; + case LogLevel::warn: + type = AddLogWarn(type); + break; + case LogLevel::info: + type = AddLogInfo(type); + break; + case LogLevel::debug: + type = AddLogDebug(type); + break; + } } - } #endif - if (result != GCodeResult::error) - { - // Append newline and send the message to the destinations - message.cat('\n'); - platform.Message(type, message.c_str()); - } - } - break; - - case 119: - platform.GetEndstops().GetM119report(reply); - break; - - case 120: - Push(gb, true); - break; - - case 121: - Pop(gb, true); - break; - - case 122: - { - const unsigned int type = (gb.Seen('P')) ? gb.GetIValue() : 0; - const MessageType mt = (MessageType)(gb.GetResponseMessageType() | PushFlag); // set the Push flag to combine multiple messages into a single OutputBuffer chain -#if SUPPORT_CAN_EXPANSION - const uint32_t board = (gb.Seen('B')) ? gb.GetUIValue() : CanInterface::GetCanAddress(); - if (board != CanInterface::GetCanAddress()) - { - result = CanInterface::RemoteDiagnostics(mt, board, type, gb, reply); - break; - } -#endif - if (type == 0) - { - reprap.Diagnostics(mt); - } - else - { - result = platform.DiagnosticTest(gb, reply, outBuf, type); + if (result != GCodeResult::error) + { + // Append newline and send the message to the destinations + message.cat('\n'); + platform.Message(type, message.c_str()); + } } - } - break; + break; - // M135 (set PID sample interval) is no longer supported + case 119: + platform.GetEndstops().GetM119report(reply); + break; - case 140: // Bed temperature - case 141: // Chamber temperature - { - Heat& heat = reprap.GetHeat(); - bool seen = false; + case 120: + Push(gb, true); + break; - // Check if the heater index is passed - const unsigned int index = gb.Seen('P') ? gb.GetLimitedUIValue('P', (code == 140) ? MaxBedHeaters : MaxChamberHeaters) : 0; + case 121: + Pop(gb, true); + break; - // See if the heater number is being set - if (gb.Seen('H')) + case 122: { - seen = true; - int heater = gb.GetIValue(); - if (heater < 0) - { - heater = -1; - } - else if (heater >= (int)MaxHeaters) + const unsigned int type = (gb.Seen('P')) ? gb.GetIValue() : 0; + const MessageType mt = (MessageType)(gb.GetResponseMessageType() | PushFlag); // set the Push flag to combine multiple messages into a single OutputBuffer chain +#if SUPPORT_CAN_EXPANSION + const uint32_t board = (gb.Seen('B')) ? gb.GetUIValue() : CanInterface::GetCanAddress(); + if (board != CanInterface::GetCanAddress()) { - reply.printf("Invalid heater number '%d'", heater); - result = GCodeResult::error; + result = CanInterface::RemoteDiagnostics(mt, board, type, gb, reply); break; } - - if (code == 141) +#endif + if (type == 0) { - heat.SetChamberHeater(index, heater); + reprap.Diagnostics(mt); } else { - heat.SetBedHeater(index, heater); + result = platform.DiagnosticTest(gb, reply, outBuf, type); } - platform.UpdateConfiguredHeaters(); } + break; - const int8_t currentHeater = (code == 141) ? heat.GetChamberHeater(index) : heat.GetBedHeater(index); - const char* const heaterName = (code == 141) ? "chamber" : "bed"; + // M135 (set PID sample interval) is no longer supported - // Active temperature - if (gb.Seen('S')) + case 140: // Bed temperature + case 141: // Chamber temperature { - seen = true; - const float temperature = gb.GetFValue(); - if (currentHeater < 0) + Heat& heat = reprap.GetHeat(); + bool seen = false; + + // Check if the heater index is passed + const unsigned int index = gb.Seen('P') ? gb.GetLimitedUIValue('P', (code == 140) ? MaxBedHeaters : MaxChamberHeaters) : 0; + + // See if the heater number is being set + if (gb.Seen('H')) { - if (temperature > 0.0) // turning off a non-existent bed or chamber heater is not an error + seen = true; + int heater = gb.GetIValue(); + if (heater < 0) { - reply.printf("No %s heater has been configured for slot %d", heaterName, index); + heater = -1; + } + else if (heater >= (int)MaxHeaters) + { + reply.printf("Invalid heater number '%d'", heater); result = GCodeResult::error; + break; } - } - else - { - if (temperature < NEARLY_ABS_ZERO) + + if (code == 141) { - heat.SwitchOff(currentHeater); + heat.SetChamberHeater(index, heater); } else { - heat.SetActiveTemperature(currentHeater, temperature); // may throw - result = heat.Activate(currentHeater, reply); + heat.SetBedHeater(index, heater); } + platform.UpdateConfiguredHeaters(); } - } - // Standby temperature - if (gb.Seen('R')) - { - seen = true; - if (currentHeater < 0) - { - reply.printf("No %s heater has been configured for slot %d", heaterName, index); - result = GCodeResult::error; - } - else + const int8_t currentHeater = (code == 141) ? heat.GetChamberHeater(index) : heat.GetBedHeater(index); + const char* const heaterName = (code == 141) ? "chamber" : "bed"; + + // Active temperature + if (gb.Seen('S')) { - heat.SetStandbyTemperature(currentHeater, gb.GetFValue()); + seen = true; + const float temperature = gb.GetFValue(); + if (currentHeater < 0) + { + if (temperature > 0.0) // turning off a non-existent bed or chamber heater is not an error + { + reply.printf("No %s heater has been configured for slot %d", heaterName, index); + result = GCodeResult::error; + } + } + else + { + if (temperature < NEARLY_ABS_ZERO) + { + heat.SwitchOff(currentHeater); + } + else + { + heat.SetActiveTemperature(currentHeater, temperature); // may throw + result = heat.Activate(currentHeater, reply); + } + } } - } - if (!seen) - { - if (currentHeater < 0) + // Standby temperature + if (gb.Seen('R')) { - reply.printf("No %s heater has been configured for slot %d", heaterName, index); + seen = true; + if (currentHeater < 0) + { + reply.printf("No %s heater has been configured for slot %d", heaterName, index); + result = GCodeResult::error; + } + else + { + heat.SetStandbyTemperature(currentHeater, gb.GetFValue()); + } } - else + + if (!seen) { - reply.printf("%c%s heater %d (slot %d) is currently at %.1f" DEGREE_SYMBOL "C", - toupper(heaterName[0]), heaterName + 1, currentHeater, index, (double)reprap.GetHeat().GetHeaterTemperature(currentHeater)); + if (currentHeater < 0) + { + reply.printf("No %s heater has been configured for slot %d", heaterName, index); + } + else + { + reply.printf("%c%s heater %d (slot %d) is currently at %.1f" DEGREE_SYMBOL "C", + toupper(heaterName[0]), heaterName + 1, currentHeater, index, (double)reprap.GetHeat().GetHeaterTemperature(currentHeater)); + } } } - } - break; + break; - case 143: // Configure heater protection - result = reprap.GetHeat().HandleM143(gb, reply); - break; + case 143: // Configure heater protection + result = reprap.GetHeat().HandleM143(gb, reply); + break; - case 144: // Set bed to standby, or to active if S1 parameter given - { - const unsigned int index = gb.Seen('P') ? gb.GetLimitedUIValue('P', MaxBedHeaters) : 0; - const int8_t bedHeater = reprap.GetHeat().GetBedHeater(index); - if (bedHeater >= 0) + case 144: // Set bed to standby, or to active if S1 parameter given { - if (gb.Seen('S') && gb.GetIValue() == 1) - { - result = reprap.GetHeat().Activate(bedHeater, reply); - } - else + const unsigned int index = gb.Seen('P') ? gb.GetLimitedUIValue('P', MaxBedHeaters) : 0; + const int8_t bedHeater = reprap.GetHeat().GetBedHeater(index); + if (bedHeater >= 0) { - reprap.GetHeat().Standby(bedHeater, nullptr); + if (gb.Seen('S') && gb.GetIValue() == 1) + { + result = reprap.GetHeat().Activate(bedHeater, reply); + } + else + { + reprap.GetHeat().Standby(bedHeater, nullptr); + } } } - } - break; + break; #if SUPPORT_LED_STRIPS - case 150: - result = LedStripDriver::SetColours(gb, reply); - break; + case 150: + result = LedStripDriver::SetColours(gb, reply); + break; #endif - case 190: // Set bed temperature and wait - case 191: // Set chamber temperature and wait - if ( !LockMovementAndWaitForStandstill(gb) // wait until movement has finished - || !IsCodeQueueIdle() // also wait until deferred command queue has caught up to avoid out-of-order execution - ) - { - return false; - } + case 190: // Set bed temperature and wait + case 191: // Set chamber temperature and wait + if ( !LockMovementAndWaitForStandstill(gb) // wait until movement has finished + || !IsCodeQueueIdle() // also wait until deferred command queue has caught up to avoid out-of-order execution + ) + { + return false; + } - UnlockMovement(gb); // allow babystepping and pausing while heating - { - // Check if the heater index is passed - const uint32_t index = gb.Seen('P') ? gb.GetLimitedUIValue('P', (code == 190) ? MaxBedHeaters : MaxChamberHeaters) : 0; - const int8_t heater = (code == 191) ? reprap.GetHeat().GetChamberHeater(index) : reprap.GetHeat().GetBedHeater(index); - if (heater >= 0) + UnlockMovement(gb); // allow babystepping and pausing while heating { - float temperature; - bool waitWhenCooling; - if (gb.Seen('R')) - { - waitWhenCooling = true; - temperature = gb.GetFValue(); - } - else if (gb.Seen('S')) - { - waitWhenCooling = false; - temperature = gb.GetFValue(); - } - else + // Check if the heater index is passed + const uint32_t index = gb.Seen('P') ? gb.GetLimitedUIValue('P', (code == 190) ? MaxBedHeaters : MaxChamberHeaters) : 0; + const int8_t heater = (code == 191) ? reprap.GetHeat().GetChamberHeater(index) : reprap.GetHeat().GetBedHeater(index); + if (heater >= 0) { - break; // no target temperature given + 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); // may throw + result = reprap.GetHeat().Activate(heater, reply); + if (cancelWait || reprap.GetHeat().HeaterAtSetTemperature(heater, waitWhenCooling, TEMPERATURE_CLOSE_ENOUGH)) + { + cancelWait = isWaiting = false; + break; + } + + isWaiting = true; + return false; } + } + break; - reprap.GetHeat().SetActiveTemperature(heater, temperature); // may throw - result = reprap.GetHeat().Activate(heater, reply); - if (cancelWait || reprap.GetHeat().HeaterAtSetTemperature(heater, waitWhenCooling, TEMPERATURE_CLOSE_ENOUGH)) + case 200: // Set filament diameter for volumetric extrusion and enable/disable volumetric extrusion + if (gb.Seen('D')) + { + float diameters[MaxExtruders]; + size_t len = MaxExtruders; + gb.GetFloatArray(diameters, len, true); + for (size_t i = 0; i < len; ++i) { - cancelWait = isWaiting = false; - break; + const float d = diameters[i]; + volumetricExtrusionFactors[i] = (d <= 0.0) ? 1.0 : 4.0/(fsquare(d) * Pi); } - - isWaiting = true; - return false; + gb.LatestMachineState().volumetricExtrusion = (diameters[0] > 0.0); + reprap.InputsUpdated(); } - } - break; - - case 200: // Set filament diameter for volumetric extrusion and enable/disable volumetric extrusion - if (gb.Seen('D')) - { - float diameters[MaxExtruders]; - size_t len = MaxExtruders; - gb.GetFloatArray(diameters, len, true); - for (size_t i = 0; i < len; ++i) + else if (!gb.LatestMachineState().volumetricExtrusion) { - const float d = diameters[i]; - volumetricExtrusionFactors[i] = (d <= 0.0) ? 1.0 : 4.0/(fsquare(d) * Pi); + reply.copy("Volumetric extrusion is disabled for this input source"); } - gb.LatestMachineState().volumetricExtrusion = (diameters[0] > 0.0); - reprap.InputsUpdated(); - } - else if (!gb.LatestMachineState().volumetricExtrusion) - { - reply.copy("Volumetric extrusion is disabled for this input source"); - } - else - { - reply.copy("Filament diameters for volumetric extrusion:"); - for (size_t i = 0; i < numExtruders; ++i) + else { - const float vef = volumetricExtrusionFactors[i]; - if (vef == 1.0) - { - reply.cat(" n/a"); - } - else + reply.copy("Filament diameters for volumetric extrusion:"); + for (size_t i = 0; i < numExtruders; ++i) { - reply.catf(" %.03f", (double)(2.0/fastSqrtf(vef * Pi))); + const float vef = volumetricExtrusionFactors[i]; + if (vef == 1.0) + { + reply.cat(" n/a"); + } + else + { + reply.catf(" %.03f", (double)(2.0/fastSqrtf(vef * Pi))); + } } } - } - break; + break; - case 201: // Set/print axis accelerations - { - bool seen = false; - for (size_t axis = 0; axis < numTotalAxes; axis++) + case 201: // Set/print axis accelerations { - if (gb.Seen(axisLetters[axis])) + bool seen = false; + for (size_t axis = 0; axis < numTotalAxes; axis++) { - platform.SetAcceleration(axis, gb.GetDistance()); - seen = true; + if (gb.Seen(axisLetters[axis])) + { + platform.SetAcceleration(axis, gb.GetDistance()); + 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++) + if (gb.Seen(extrudeLetter)) { - platform.SetAcceleration(ExtruderToLogicalDrive(e), gb.ConvertDistance(eVals[e])); + seen = true; + float eVals[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetFloatArray(eVals, eCount, true); + for (size_t e = 0; e < eCount; e++) + { + platform.SetAcceleration(ExtruderToLogicalDrive(e), gb.ConvertDistance(eVals[e])); + } } - } - if (seen) - { - reprap.MoveUpdated(); - } - else - { - reply.printf("Accelerations (mm/sec^2): "); - for (size_t axis = 0; axis < numTotalAxes; ++axis) + if (seen) { - reply.catf("%c: %.1f, ", axisLetters[axis], (double)platform.Acceleration(axis)); + reprap.MoveUpdated(); } - reply.cat("E:"); - char sep = ' '; - for (size_t extruder = 0; extruder < numExtruders; extruder++) + else { - reply.catf("%c%.1f", sep, (double)platform.Acceleration(ExtruderToLogicalDrive(extruder))); - sep = ':'; + reply.printf("Accelerations (mm/sec^2): "); + for (size_t axis = 0; axis < numTotalAxes; ++axis) + { + reply.catf("%c: %.1f, ", axisLetters[axis], (double)platform.Acceleration(axis)); + } + reply.cat("E:"); + char sep = ' '; + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + reply.catf("%c%.1f", sep, (double)platform.Acceleration(ExtruderToLogicalDrive(extruder))); + sep = ':'; + } } } - } - break; - - case 203: // Set/print minimum/maximum feedrates - { - // Units are mm/sec if S1 is given, else mm/min - const bool usingMmPerSec = (gb.Seen('S') && gb.GetIValue() == 1); - const float settingMultiplier = (usingMmPerSec) ? 1.0 : SecondsToMinutes; - bool seen = false; + break; - // Do the minimum first, because we constrain the maximum rates to be no lower than it - if (gb.Seen('I')) + case 203: // Set/print minimum/maximum feedrates { - seen = true; - platform.SetMinMovementSpeed(gb.GetDistance() * settingMultiplier); - } + // Units are mm/sec if S1 is given, else mm/min + const bool usingMmPerSec = (gb.Seen('S') && gb.GetIValue() == 1); + const float settingMultiplier = (usingMmPerSec) ? 1.0 : SecondsToMinutes; + bool seen = false; - for (size_t axis = 0; axis < numTotalAxes; ++axis) - { - if (gb.Seen(axisLetters[axis])) + // Do the minimum first, because we constrain the maximum rates to be no lower than it + if (gb.Seen('I')) { seen = true; - platform.SetMaxFeedrate(axis, gb.GetDistance() * settingMultiplier); + platform.SetMinMovementSpeed(gb.GetDistance() * settingMultiplier); } - } - 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++) + for (size_t axis = 0; axis < numTotalAxes; ++axis) { - platform.SetMaxFeedrate(ExtruderToLogicalDrive(e), gb.ConvertDistance(eVals[e]) * settingMultiplier); + if (gb.Seen(axisLetters[axis])) + { + seen = true; + platform.SetMaxFeedrate(axis, gb.GetDistance() * settingMultiplier); + } } - } - if (seen) - { - reprap.MoveUpdated(); - } - else - { - const float reportingMultiplier = (usingMmPerSec) ? 1.0 : MinutesToSeconds; - reply.printf("Max speeds (mm/%s): ", (usingMmPerSec) ? "sec" : "min"); - for (size_t axis = 0; axis < numTotalAxes; ++axis) + 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(ExtruderToLogicalDrive(e), gb.ConvertDistance(eVals[e]) * settingMultiplier); + } + } + + if (seen) { - reply.catf("%c: %.1f, ", axisLetters[axis], (double)(platform.MaxFeedrate(axis) * reportingMultiplier)); + reprap.MoveUpdated(); } - reply.cat("E:"); - char sep = ' '; - for (size_t extruder = 0; extruder < numExtruders; extruder++) + else { - reply.catf("%c%.1f", sep, (double)(platform.MaxFeedrate(ExtruderToLogicalDrive(extruder)) * reportingMultiplier)); - sep = ':'; + const float reportingMultiplier = (usingMmPerSec) ? 1.0 : MinutesToSeconds; + reply.printf("Max speeds (mm/%s): ", (usingMmPerSec) ? "sec" : "min"); + for (size_t axis = 0; axis < numTotalAxes; ++axis) + { + reply.catf("%c: %.1f, ", axisLetters[axis], (double)(platform.MaxFeedrate(axis) * reportingMultiplier)); + } + reply.cat("E:"); + char sep = ' '; + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + reply.catf("%c%.1f", sep, (double)(platform.MaxFeedrate(ExtruderToLogicalDrive(extruder)) * reportingMultiplier)); + sep = ':'; + } + reply.catf(", min. speed %.2f", (double)(platform.MinMovementSpeed() * reportingMultiplier)); } - reply.catf(", min. speed %.2f", (double)(platform.MinMovementSpeed() * reportingMultiplier)); } - } - break; + break; - case 204: // Set max travel and printing accelerations - result = reprap.GetMove().ConfigureAccelerations(gb, reply); - break; + case 204: // Set max travel and printing accelerations + result = reprap.GetMove().ConfigureAccelerations(gb, reply); + break; - // For case 205 see case 566 + // For case 205 see case 566 - case 206: // Offset axes - result = OffsetAxes(gb, reply); - break; + case 206: // Offset axes + result = OffsetAxes(gb, reply); + break; - case 207: // Set firmware retraction details - if (gb.Seen('P')) - { - const unsigned int toolNumber = gb.GetUIValue(); - auto tool = reprap.GetTool(toolNumber); - if (tool.IsNull()) + case 207: // Set firmware retraction details + if (gb.Seen('P')) { - reply.printf("Tool %u does not exist", toolNumber); - result = GCodeResult::error; + const unsigned int toolNumber = gb.GetUIValue(); + auto tool = reprap.GetTool(toolNumber); + if (tool.IsNull()) + { + reply.printf("Tool %u does not exist", toolNumber); + result = GCodeResult::error; + } + else + { + result = tool->SetFirmwareRetraction(gb, reply, outBuf); + } } else { - result = tool->SetFirmwareRetraction(gb, reply, outBuf); + result = reprap.SetAllToolsFirmwareRetraction(gb, reply, outBuf); } - } - else - { - result = reprap.SetAllToolsFirmwareRetraction(gb, reply, outBuf); - } - break; + 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 < numTotalAxes; axis++) + 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. { - if (gb.Seen(axisLetters[axis])) + bool setMin = (gb.Seen('S') ? (gb.GetIValue() == 1) : false); + bool seen = false; + for (size_t axis = 0; axis < numTotalAxes; axis++) { - seen = true; - float values[2]; - size_t numValues = 2; - gb.GetFloatArray(values, numValues, false); - bool ok; - if (numValues == 2) - { - ok = values[1] > values[0]; - if (ok) + if (gb.Seen(axisLetters[axis])) + { + seen = true; + float values[2]; + size_t numValues = 2; + gb.GetFloatArray(values, numValues, false); + bool ok; + if (numValues == 2) { - platform.SetAxisMinimum(axis, values[0], gb.LatestMachineState().runningM501); - platform.SetAxisMaximum(axis, values[1], gb.LatestMachineState().runningM501); + ok = values[1] > values[0]; + if (ok) + { + platform.SetAxisMinimum(axis, values[0], gb.LatestMachineState().runningM501); + platform.SetAxisMaximum(axis, values[1], gb.LatestMachineState().runningM501); + } } - } - else if (setMin) - { - ok = platform.AxisMaximum(axis) > values[0]; - if (ok) + else if (setMin) { - platform.SetAxisMinimum(axis, values[0], gb.LatestMachineState().runningM501); + ok = platform.AxisMaximum(axis) > values[0]; + if (ok) + { + platform.SetAxisMinimum(axis, values[0], gb.LatestMachineState().runningM501); + } } - } - else - { - ok = values[0] > platform.AxisMinimum(axis); - if (ok) + else { - platform.SetAxisMaximum(axis, values[0], gb.LatestMachineState().runningM501); + ok = values[0] > platform.AxisMinimum(axis); + if (ok) + { + platform.SetAxisMaximum(axis, values[0], gb.LatestMachineState().runningM501); + } + } + + if (!ok) + { + reply.printf("%c axis maximum must be greater than minimum", axisLetters[axis]); + result = GCodeResult::error; } } + } - if (!ok) + if (!seen) + { + reply.copy("Axis limit"); + char sep = 's'; + for (size_t axis = 0; axis < numTotalAxes; axis++) { - reply.printf("%c axis maximum must be greater than minimum", axisLetters[axis]); - result = GCodeResult::error; + reply.catf("%c %c%.1f:%.1f", sep, axisLetters[axis], (double)platform.AxisMinimum(axis), (double)platform.AxisMaximum(axis)); + sep = ','; } } } + break; - if (!seen) + case 220: // Set/report speed factor override percentage + if (gb.Seen('S')) { - reply.copy("Axis limit"); - char sep = 's'; - for (size_t axis = 0; axis < numTotalAxes; axis++) + const float newSpeedFactor = gb.GetFValue() * 0.01; + if (newSpeedFactor >= 0.01) { - reply.catf("%c %c%.1f:%.1f", sep, axisLetters[axis], (double)platform.AxisMinimum(axis), (double)platform.AxisMaximum(axis)); - sep = ','; + // If the last move hasn't gone yet, update its feed rate if it is not a firmware retraction + if (moveBuffer.segmentsLeft != 0 && moveBuffer.applyM220M221) + { + moveBuffer.feedRate *= newSpeedFactor / speedFactor; + } + speedFactor = newSpeedFactor; + reprap.MoveUpdated(); } - } - } - break; - - case 220: // Set/report speed factor override percentage - if (gb.Seen('S')) - { - const float newSpeedFactor = gb.GetFValue() * 0.01; - if (newSpeedFactor >= 0.01) - { - // If the last move hasn't gone yet, update its feed rate if it is not a firmware retraction - if (moveBuffer.segmentsLeft != 0 && moveBuffer.applyM220M221) + else { - moveBuffer.feedRate *= newSpeedFactor / speedFactor; + reply.copy("Invalid speed factor"); + result = GCodeResult::error; } - speedFactor = newSpeedFactor; - reprap.MoveUpdated(); } else { - reply.copy("Invalid speed factor"); - result = GCodeResult::error; + reply.printf("Speed factor: %.1f%%", (double)(speedFactor * 100.0)); } - } - else - { - reply.printf("Speed factor: %.1f%%", (double)(speedFactor * 100.0)); - } - break; + break; - case 221: // Set/report extrusion factor override percentage - { - uint32_t extruder = 0; - const bool seenD = gb.Seen('D'); - if (seenD) + case 221: // Set/report extrusion factor override percentage { - extruder = gb.GetLimitedUIValue('D', numExtruders); - } + uint32_t extruder = 0; + const bool seenD = gb.Seen('D'); + if (seenD) + { + extruder = gb.GetLimitedUIValue('D', numExtruders); + } - const Tool * const ct = reprap.GetCurrentTool(); - if (!seenD && ct == nullptr) - { - reply.copy("No tool selected"); - result = GCodeResult::error; - } - else if (gb.Seen('S')) // S parameter sets the override percentage - { - const float extrusionFactor = gb.GetFValue() * 0.01; - if (extrusionFactor >= 0.01) + const Tool * const ct = reprap.GetCurrentTool(); + if (!seenD && ct == nullptr) { - if (seenD) - { - ChangeExtrusionFactor(extruder, extrusionFactor); - } - else + reply.copy("No tool selected"); + result = GCodeResult::error; + } + else if (gb.Seen('S')) // S parameter sets the override percentage + { + const float extrusionFactor = gb.GetFValue() * 0.01; + if (extrusionFactor >= 0.01) { - ct->IterateExtruders([this, extrusionFactor](unsigned int extruder) { ChangeExtrusionFactor(extruder, extrusionFactor); }); + if (seenD) + { + ChangeExtrusionFactor(extruder, extrusionFactor); + } + else + { + ct->IterateExtruders([this, extrusionFactor](unsigned int extruder) { ChangeExtrusionFactor(extruder, extrusionFactor); }); + } } } + else if (seenD) + { + reply.printf("Extrusion factor for extruder %" PRIu32 ": %.1f%%", extruder, (double)(extrusionFactors[extruder] * 100.0)); + } + else + { + reply.copy("Extrusion factor(s) for current tool:"); + ct->IterateExtruders([reply, this](unsigned int extruder) { reply.catf(" %.1f%%", (double)(extrusionFactors[extruder] * 100.0)); }); + } } - else if (seenD) - { - reply.printf("Extrusion factor for extruder %" PRIu32 ": %.1f%%", extruder, (double)(extrusionFactors[extruder] * 100.0)); - } - else - { - reply.copy("Extrusion factor(s) for current tool:"); - ct->IterateExtruders([reply, this](unsigned int extruder) { reply.catf(" %.1f%%", (double)(extrusionFactors[extruder] * 100.0)); }); - } - } - break; + break; - // For case 226, see case 25 + // For case 226, see case 25 - case 260: // I2C send - result = SendI2c(gb, reply); - break; + case 260: // I2C send + result = SendI2c(gb, reply); + break; - case 261: // I2C send - result = ReceiveI2c(gb, reply); - break; + case 261: // I2C send + result = ReceiveI2c(gb, reply); + break; - case 280: // Servos - { - const uint32_t gpioPortNumber = gb.GetLimitedUIValue('P', MaxGpOutPorts); - gb.MustSee('S'); - float angleOrWidth = gb.GetFValue(); - if (angleOrWidth < 0.0) - { - // Disable the servo by setting the pulse width to zero - angleOrWidth = 0.0; - } - 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) + case 280: // Servos { - angleOrWidth = MaxServoPulseWidth; - } + const uint32_t gpioPortNumber = gb.GetLimitedUIValue('P', MaxGpOutPorts); + gb.MustSee('S'); + float angleOrWidth = gb.GetFValue(); + if (angleOrWidth < 0.0) + { + // Disable the servo by setting the pulse width to zero + angleOrWidth = 0.0; + } + 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; + } - const float pwm = angleOrWidth * (ServoRefreshFrequency/1e6); - result = platform.GetGpOutPort(gpioPortNumber).WriteAnalog(gpioPortNumber, true, pwm, gb, reply); - } - break; + const float pwm = angleOrWidth * (ServoRefreshFrequency/1e6); + result = platform.GetGpOutPort(gpioPortNumber).WriteAnalog(gpioPortNumber, true, pwm, gb, reply); + } + break; - case 290: // Baby stepping - { - const bool absolute = (gb.Seen('R') && gb.GetIValue() == 0); - bool seen = false; - float differences[MaxAxes]; - for (size_t axis = 0; axis < numVisibleAxes; ++axis) + case 290: // Baby stepping { - if (gb.Seen(axisLetters[axis]) || (axis == 2 && gb.Seen('S'))) // S is a synonym for Z + const bool absolute = (gb.Seen('R') && gb.GetIValue() == 0); + bool seen = false; + float differences[MaxAxes]; + for (size_t axis = 0; axis < numVisibleAxes; ++axis) { - seen = true; - const float fval = gb.GetFValue(); - if (absolute) + if (gb.Seen(axisLetters[axis]) || (axis == 2 && gb.Seen('S'))) // S is a synonym for Z { - differences[axis] = fval - GetTotalBabyStepOffset(axis); + seen = true; + const float fval = gb.GetFValue(); + if (absolute) + { + differences[axis] = fval - GetTotalBabyStepOffset(axis); + } + else + { + differences[axis] = constrain<float>(fval, -1.0, 1.0); + } } else { - differences[axis] = constrain<float>(fval, -1.0, 1.0); + differences[axis] = 0.0; } } - else - { - differences[axis] = 0.0; - } - } - - if (seen) - { - if (!LockMovement(gb)) - { - return false; - } - // Perform babystepping synchronously with moves - bool haveResidual = false; - for (size_t axis = 0; axis < numVisibleAxes; ++axis) + if (seen) { - currentBabyStepOffsets[axis] += differences[axis]; - reprap.MoveUpdated(); - const float amountPushed = reprap.GetMove().PushBabyStepping(axis, differences[axis]); - moveBuffer.initialCoords[axis] += amountPushed; + if (!LockMovement(gb)) + { + return false; + } - // The following causes all the remaining baby stepping that we didn't manage to push to be added to the [remainder of the] currently-executing move, if there is one. - // This could result in an abrupt Z movement, however the move will be processed as normal so the jerk limit will be honoured. - moveBuffer.coords[axis] += differences[axis]; - if (amountPushed != differences[axis]) + // Perform babystepping synchronously with moves + bool haveResidual = false; + for (size_t axis = 0; axis < numVisibleAxes; ++axis) { - haveResidual = true; + currentBabyStepOffsets[axis] += differences[axis]; + reprap.MoveUpdated(); + const float amountPushed = reprap.GetMove().PushBabyStepping(axis, differences[axis]); + moveBuffer.initialCoords[axis] += amountPushed; + + // The following causes all the remaining baby stepping that we didn't manage to push to be added to the [remainder of the] currently-executing move, if there is one. + // This could result in an abrupt Z movement, however the move will be processed as normal so the jerk limit will be honoured. + moveBuffer.coords[axis] += differences[axis]; + if (amountPushed != differences[axis]) + { + haveResidual = true; + } } - } - if (haveResidual && moveBuffer.segmentsLeft == 0 && reprap.GetMove().NoLiveMovement()) - { - // The pipeline is empty, so execute the babystepping move immediately - SetMoveBufferDefaults(); - moveBuffer.feedRate = DefaultFeedRate; - moveBuffer.tool = reprap.GetCurrentTool(); - NewMoveAvailable(1); + if (haveResidual && moveBuffer.segmentsLeft == 0 && reprap.GetMove().NoLiveMovement()) + { + // The pipeline is empty, so execute the babystepping move immediately + SetMoveBufferDefaults(); + moveBuffer.feedRate = DefaultFeedRate; + moveBuffer.tool = reprap.GetCurrentTool(); + NewMoveAvailable(1); + } } - } - else - { - reply.printf("Baby stepping offsets (mm):"); - for (size_t axis = 0; axis < numVisibleAxes; ++axis) + else { - reply.catf(" %c:%.3f", axisLetters[axis], (double)GetTotalBabyStepOffset(axis)); + reply.printf("Baby stepping offsets (mm):"); + for (size_t axis = 0; axis < numVisibleAxes; ++axis) + { + reply.catf(" %c:%.3f", axisLetters[axis], (double)GetTotalBabyStepOffset(axis)); + } } } - } - break; - - case 291: // Display message, optionally wait for acknowledgement - { - gb.MustSee('P'); - String<MaxMessageLength> message; - gb.GetQuotedString(message.GetRef()); - - bool dummy = false; - String<MaxMessageLength> title; - gb.TryGetQuotedString('R', title.GetRef(), dummy); - - int32_t sParam = 1; - gb.TryGetIValue('S', sParam, dummy); - if (sParam < 0 || sParam > 3) - { - reply.copy("Invalid message box mode"); - result = GCodeResult::error; - break; - } + break; - float tParam; - if (sParam == 0 || sParam == 1) - { - tParam = DefaultMessageTimeout; - gb.TryGetFValue('T', tParam, dummy); - } - else + case 291: // Display message, optionally wait for acknowledgement { - tParam = 0.0; - } + gb.MustSee('P'); + String<MaxMessageLength> message; + gb.GetQuotedString(message.GetRef()); - if (sParam == 0 && tParam <= 0.0) - { - reply.copy("Attempt to create a message box that cannot be dismissed"); - result = GCodeResult::error; - break; - } + bool dummy = false; + String<MaxMessageLength> title; + gb.TryGetQuotedString('R', title.GetRef(), dummy); - AxesBitmap axisControls; - for (size_t axis = 0; axis < numTotalAxes; axis++) - { - if (gb.Seen(axisLetters[axis]) && gb.GetIValue() > 0) + int32_t sParam = 1; + gb.TryGetIValue('S', sParam, dummy); + if (sParam < 0 || sParam > 3) { - axisControls.SetBit(axis); + reply.copy("Invalid message box mode"); + result = GCodeResult::error; + break; } - } - // Don't lock the movement system, because if we do then only the channel that issues the M291 can move the axes - if (sParam == 2 || sParam == 3) - { -#if HAS_LINUX_INTERFACE - if (reprap.UsingLinuxInterface()) + float tParam; + if (sParam == 0 || sParam == 1) { - gb.SetState(GCodeState::waitingForAcknowledgement); + tParam = DefaultMessageTimeout; + gb.TryGetFValue('T', tParam, dummy); } -#endif - if (Push(gb, true)) // stack the machine state including the file position + else { - UnlockMovement(gb); // allow movement so that e.g. an SD card print can call M291 and then DWC or PanelDue can be used to jog axes - gb.WaitForAcknowledgement(); // flag that we are waiting for acknowledgement + tParam = 0.0; } - } - - // Display the message box on all relevant devices. Acknowledging any one of them clears them all. - const MessageType mt = GetMessageBoxDevice(gb); // get the display device - platform.SendAlert(mt, message.c_str(), title.c_str(), (int)sParam, tParam, axisControls); - } - break; - case 292: // Acknowledge message - { - reprap.ClearAlert(); + if (sParam == 0 && tParam <= 0.0) + { + reply.copy("Attempt to create a message box that cannot be dismissed"); + result = GCodeResult::error; + break; + } - const bool cancelled = (gb.Seen('P') && gb.GetIValue() == 1); - for (GCodeBuffer* targetGb : gcodeSources) - { - if (targetGb != nullptr) + AxesBitmap axisControls; + for (size_t axis = 0; axis < numTotalAxes; axis++) { - targetGb->MessageAcknowledged(cancelled); + if (gb.Seen(axisLetters[axis]) && gb.GetIValue() > 0) + { + axisControls.SetBit(axis); + } } - } - platform.MessageF(MessageType::LogInfo, "M292: cancelled: %s", (cancelled ? "true" : "false")); - } - break; - case 300: // Beep - { - const unsigned int ms = (gb.Seen('P')) ? gb.GetUIValue() : 1000; // time in milliseconds - const unsigned int freq = (gb.Seen('S')) ? gb.GetUIValue() : 4600; // 4600Hz produces the loudest sound on a PanelDue - reprap.Beep(freq, ms); - } - break; + // Don't lock the movement system, because if we do then only the channel that issues the M291 can move the axes + if (sParam == 2 || sParam == 3) + { +#if HAS_LINUX_INTERFACE + if (reprap.UsingLinuxInterface()) + { + gb.SetState(GCodeState::waitingForAcknowledgement); + } +#endif + if (Push(gb, true)) // stack the machine state including the file position + { + UnlockMovement(gb); // allow movement so that e.g. an SD card print can call M291 and then DWC or PanelDue can be used to jog axes + gb.WaitForAcknowledgement(); // flag that we are waiting for acknowledgement + } + } - case 301: // Set/report hot end PID values - result = reprap.GetHeat().SetPidParameters(1, gb, reply); - break; + // Display the message box on all relevant devices. Acknowledging any one of them clears them all. + const MessageType mt = GetMessageBoxDevice(gb); // get the display device + platform.SendAlert(mt, message.c_str(), title.c_str(), (int)sParam, tParam, axisControls); + } + break; - case 302: // Allow, deny or report cold extrudes and configure minimum extrusion/retraction temps - { - bool seen = false; - if (gb.Seen('P')) + case 292: // Acknowledge message { - if (!LockMovementAndWaitForStandstill(gb)) + reprap.ClearAlert(); + + const bool cancelled = (gb.Seen('P') && gb.GetIValue() == 1); + for (GCodeBuffer* targetGb : gcodeSources) { - return false; + if (targetGb != nullptr) + { + targetGb->MessageAcknowledged(cancelled); + } } - seen = true; - reprap.GetHeat().AllowColdExtrude(gb.GetIValue() > 0); + platform.MessageF(MessageType::LogInfo, "M292: cancelled: %s", (cancelled ? "true" : "false")); } - if (gb.Seen('S')) + break; + + case 300: // Beep { - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - seen = true; - reprap.GetHeat().SetExtrusionMinTemp(gb.GetFValue()); + const unsigned int ms = (gb.Seen('P')) ? gb.GetUIValue() : 1000; // time in milliseconds + const unsigned int freq = (gb.Seen('S')) ? gb.GetUIValue() : 4600; // 4600Hz produces the loudest sound on a PanelDue + reprap.Beep(freq, ms); } - if (gb.Seen('R')) + break; + + case 301: // Set/report hot end PID values + result = reprap.GetHeat().SetPidParameters(1, gb, reply); + break; + + case 302: // Allow, deny or report cold extrudes and configure minimum extrusion/retraction temps { - if (!LockMovementAndWaitForStandstill(gb)) + bool seen = false; + if (gb.Seen('P')) { - return false; + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + seen = true; + reprap.GetHeat().AllowColdExtrude(gb.GetIValue() > 0); } - seen = true; - reprap.GetHeat().SetRetractionMinTemp(gb.GetFValue()); - } - if (!seen) - { - if (reprap.GetHeat().ColdExtrude()) + if (gb.Seen('S')) { - reply.copy("Cold extrusion is allowed (use M302 P0 to forbid it)"); + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + seen = true; + reprap.GetHeat().SetExtrusionMinTemp(gb.GetFValue()); } - else + if (gb.Seen('R')) { - reply.printf("Cold extrusion is forbidden (use M302 P1 to allow it), min. extrusion temperature %.1fC, min. retraction temperature %.1fC", - (double)reprap.GetHeat().GetExtrusionMinTemp(), (double)reprap.GetHeat().GetRetractionMinTemp()); + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + seen = true; + reprap.GetHeat().SetRetractionMinTemp(gb.GetFValue()); + } + if (!seen) + { + if (reprap.GetHeat().ColdExtrude()) + { + reply.copy("Cold extrusion is allowed (use M302 P0 to forbid it)"); + } + else + { + reply.printf("Cold extrusion is forbidden (use M302 P1 to allow it), min. extrusion temperature %.1fC, min. retraction temperature %.1fC", + (double)reprap.GetHeat().GetExtrusionMinTemp(), (double)reprap.GetHeat().GetRetractionMinTemp()); + } } } - } - break; + break; - case 303: // Run PID tuning - result = reprap.GetHeat().TuneHeater(gb, reply); - break; + case 303: // Run PID tuning + result = reprap.GetHeat().TuneHeater(gb, reply); + break; - case 304: // Set/report heated bed PID values - result = reprap.GetHeat().SetPidParameters(0, gb, reply); - break; + case 304: // Set/report heated bed PID values + result = reprap.GetHeat().SetPidParameters(0, gb, reply); + break; - case 305: // Set/report specific heater parameters - reply.copy("M305 has been replaced by M308 and M950 in RepRapFirmware 3"); - result = GCodeResult::error; - break; + case 305: // Set/report specific heater parameters + reply.copy("M305 has been replaced by M308 and M950 in RepRapFirmware 3"); + result = GCodeResult::error; + break; - case 307: // Set heater process model parameters - result = reprap.GetHeat().SetOrReportHeaterModel(gb, reply); - break; + case 307: // Set heater process model parameters + result = reprap.GetHeat().SetOrReportHeaterModel(gb, reply); + break; - case 308: - result = reprap.GetHeat().ConfigureSensor(gb, reply); - break; + case 308: + result = reprap.GetHeat().ConfigureSensor(gb, reply); + break; - case 350: // Set/report microstepping - { + case 350: // Set/report microstepping + { #if SUPPORT_CAN_EXPANSION - AxesBitmap axesToUpdate; + AxesBitmap axesToUpdate; #endif - bool interp = (gb.Seen('I') && gb.GetIValue() > 0); - bool seen = false; - for (size_t axis = 0; axis < numTotalAxes; axis++) - { - if (gb.Seen(axisLetters[axis])) + bool interp = (gb.Seen('I') && gb.GetIValue() > 0); + bool seen = false; + for (size_t axis = 0; axis < numTotalAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + seen = true; +#if SUPPORT_CAN_EXPANSION + axesToUpdate.SetBit(axis); +#endif + const unsigned int microsteps = gb.GetUIValue(); + if (ChangeMicrostepping(axis, microsteps, interp, reply)) + { + SetAxisNotHomed(axis); + } + else + { + result = GCodeResult::error; + } + } + } + + if (gb.Seen(extrudeLetter)) { if (!LockMovementAndWaitForStandstill(gb)) { return false; } seen = true; + uint32_t eVals[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetUnsignedArray(eVals, eCount, true); + for (size_t e = 0; e < eCount; e++) + { + const size_t drive = ExtruderToLogicalDrive(e); #if SUPPORT_CAN_EXPANSION - axesToUpdate.SetBit(axis); + axesToUpdate.SetBit(drive); #endif - const unsigned int microsteps = gb.GetUIValue(); - if (ChangeMicrostepping(axis, microsteps, interp, reply)) - { - SetAxisNotHomed(axis); - } - else - { - result = GCodeResult::error; + if (!ChangeMicrostepping(drive, eVals[e], interp, reply)) + { + result = GCodeResult::error; + } } } - } - if (gb.Seen(extrudeLetter)) - { - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - seen = true; - uint32_t eVals[MaxExtruders]; - size_t eCount = numExtruders; - gb.GetUnsignedArray(eVals, eCount, true); - for (size_t e = 0; e < eCount; e++) + if (seen) { - const size_t drive = ExtruderToLogicalDrive(e); #if SUPPORT_CAN_EXPANSION - axesToUpdate.SetBit(drive); + result = max(result, platform.UpdateRemoteStepsPerMmAndMicrostepping(axesToUpdate, reply)); #endif - if (!ChangeMicrostepping(drive, eVals[e], interp, reply)) + } + else + { + reply.copy("Microstepping - "); + for (size_t axis = 0; axis < numTotalAxes; ++axis) { - result = GCodeResult::error; + bool actualInterp; + const unsigned int microsteps = platform.GetMicrostepping(axis, actualInterp); + reply.catf("%c:%u%s, ", axisLetters[axis], microsteps, (actualInterp) ? "(on)" : ""); + } + reply.cat("E"); + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + bool actualInterp; + const unsigned int microsteps = platform.GetMicrostepping(ExtruderToLogicalDrive(extruder), actualInterp); + reply.catf(":%u%s", microsteps, (actualInterp) ? "(on)" : ""); } } } + break; + +#if HAS_MASS_STORAGE + case 374: // Save grid and height map to file + result = SaveHeightMap(gb, reply); + break; - if (seen) + case 375: // Load grid and height map from file and enable compensation + if (!LockMovementAndWaitForStandstill(gb)) { -#if SUPPORT_CAN_EXPANSION - result = max(result, platform.UpdateRemoteStepsPerMmAndMicrostepping(axesToUpdate, reply)); -#endif + return false; } - else + result = LoadHeightMap(gb, reply); + break; +#endif + + case 376: // Set taper height { - reply.copy("Microstepping - "); - for (size_t axis = 0; axis < numTotalAxes; ++axis) + Move& move = reprap.GetMove(); + if (gb.Seen('H')) + { + move.SetTaperHeight(gb.GetFValue()); + } + else if (move.GetTaperHeight() > 0.0) { - bool actualInterp; - const unsigned int microsteps = platform.GetMicrostepping(axis, actualInterp); - reply.catf("%c:%u%s, ", axisLetters[axis], microsteps, (actualInterp) ? "(on)" : ""); + reply.printf("Bed compensation taper height is %.1fmm", (double)move.GetTaperHeight()); } - reply.cat("E"); - for (size_t extruder = 0; extruder < numExtruders; extruder++) + else { - bool actualInterp; - const unsigned int microsteps = platform.GetMicrostepping(ExtruderToLogicalDrive(extruder), actualInterp); - reply.catf(":%u%s", microsteps, (actualInterp) ? "(on)" : ""); + reply.copy("Bed compensation is not tapered"); } } - } - break; - -#if HAS_MASS_STORAGE - case 374: // Save grid and height map to file - result = SaveHeightMap(gb, reply); - break; - - case 375: // Load grid and height map from file and enable compensation - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - result = LoadHeightMap(gb, reply); - break; -#endif + break; - case 376: // Set taper height - { - Move& move = reprap.GetMove(); - if (gb.Seen('H')) - { - move.SetTaperHeight(gb.GetFValue()); - } - else if (move.GetTaperHeight() > 0.0) + case 400: // Wait for current moves to finish + if (!LockMovementAndWaitForStandstill(gb)) { - reply.printf("Bed compensation taper height is %.1fmm", (double)move.GetTaperHeight()); + return false; } - else + break; + + case 401: // Deploy Z probe { - reply.copy("Bed compensation is not tapered"); + currentZProbeNumber = (gb.Seen('P')) ? gb.GetUIValue() : 0; + auto zp = platform.GetEndstops().GetZProbe(currentZProbeNumber); + if (zp.IsNotNull() && zp->GetProbeType() != ZProbeType::none) + { + zp->SetDeployedByUser(false); // pretend that the probe isn't deployed, to make sure we deploy it unconditionally + DeployZProbe(gb); + zp->SetDeployedByUser(true); // probe is now deployed + } } - } - break; - - case 400: // Wait for current moves to finish - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - break; + break; - case 401: // Deploy Z probe - { - currentZProbeNumber = (gb.Seen('P')) ? gb.GetUIValue() : 0; - auto zp = platform.GetEndstops().GetZProbe(currentZProbeNumber); - if (zp.IsNotNull() && zp->GetProbeType() != ZProbeType::none) + case 402: // Retract Z probe { - zp->SetDeployedByUser(false); // pretend that the probe isn't deployed, to make sure we deploy it unconditionally - DeployZProbe(gb); - zp->SetDeployedByUser(true); // probe is now deployed + currentZProbeNumber = (gb.Seen('P')) ? gb.GetUIValue() : 0; + auto zp = platform.GetEndstops().GetZProbe(currentZProbeNumber); + if (zp.IsNotNull() && zp->GetProbeType() != ZProbeType::none) + { + zp->SetDeployedByUser(false); // do this first, otherwise the probe won't be retracted + RetractZProbe(gb); + } } - } - break; + break; - case 402: // Retract Z probe - { - currentZProbeNumber = (gb.Seen('P')) ? gb.GetUIValue() : 0; - auto zp = platform.GetEndstops().GetZProbe(currentZProbeNumber); - if (zp.IsNotNull() && zp->GetProbeType() != ZProbeType::none) + case 404: // Filament width and nozzle diameter { - zp->SetDeployedByUser(false); // do this first, otherwise the probe won't be retracted - RetractZProbe(gb); - } - } - break; + bool seen = false; - 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 (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", (double)platform.GetFilamentWidth(), (double)platform.GetNozzleDiameter()); + } } + break; - if (!seen) + case 408: // Get status in JSON format { - reply.printf("Filament width: %.2fmm, nozzle diameter: %.2fmm", (double)platform.GetFilamentWidth(), (double)platform.GetNozzleDiameter()); - } - } - break; - - case 408: // Get status in JSON format - { - const unsigned int type = gb.Seen('S') ? gb.GetUIValue() : 0; + const unsigned int type = gb.Seen('S') ? gb.GetUIValue() : 0; #if SUPPORT_CAN_EXPANSION - const uint32_t board = (gb.Seen('B')) ? gb.GetUIValue() : 0; - if (board != 0) - { - result = CanInterface::RemoteM408(board, type, gb, reply); - break; - } + const uint32_t board = (gb.Seen('B')) ? gb.GetUIValue() : 0; + if (board != 0) + { + result = CanInterface::RemoteM408(board, type, gb, reply); + break; + } #endif - const int seq = gb.Seen('R') ? gb.GetIValue() : -1; - if (&gb == auxGCode && (type == 0 || type == 2)) - { - lastAuxStatusReportType = type; - } + const int seq = gb.Seen('R') ? gb.GetIValue() : -1; + if (&gb == auxGCode && (type == 0 || type == 2)) + { + lastAuxStatusReportType = type; + } - outBuf = GenerateJsonStatusResponse(type, seq, (&gb == auxGCode) ? ResponseSource::AUX : ResponseSource::Generic); - if (outBuf == nullptr) - { - result = GCodeResult::notFinished; // we ran out of buffers, so try again later + outBuf = GenerateJsonStatusResponse(type, seq, (&gb == auxGCode) ? ResponseSource::AUX : ResponseSource::Generic); + if (outBuf == nullptr) + { + result = GCodeResult::notFinished; // we ran out of buffers, so try again later + } } - } - break; + break; #if SUPPORT_OBJECT_MODEL - case 409: // Get object model values in JSON format - { - String<StringLength100> key; - String<StringLength20> flags; - bool dummy; - gb.TryGetQuotedString('K', key.GetRef(), dummy, true); - gb.TryGetQuotedString('F', flags.GetRef(), dummy, true); - if (&gb == auxGCode) - { - lastAuxStatusReportType = ObjectModelAuxStatusReportType; - } - outBuf = reprap.GetModelResponse(key.c_str(), flags.c_str()); - if (outBuf == nullptr) + case 409: // Get object model values in JSON format { - OutputBuffer::ReleaseAll(outBuf); - // We don't delay and retry here, in case the user asked for too much of the object model in one go for the output buffers to contain it - reply.copy("{\"err\":-1}\n"); - } - if (&gb == auxGCode) - { - gb.ResetReportDueTimer(); + String<StringLength100> key; + String<StringLength20> flags; + bool dummy; + gb.TryGetQuotedString('K', key.GetRef(), dummy, true); + gb.TryGetQuotedString('F', flags.GetRef(), dummy, true); + if (&gb == auxGCode) + { + lastAuxStatusReportType = ObjectModelAuxStatusReportType; + } + outBuf = reprap.GetModelResponse(key.c_str(), flags.c_str()); + if (outBuf == nullptr) + { + OutputBuffer::ReleaseAll(outBuf); + // We don't delay and retry here, in case the user asked for too much of the object model in one go for the output buffers to contain it + reply.copy("{\"err\":-1}\n"); + } + if (&gb == auxGCode) + { + gb.ResetReportDueTimer(); + } } - } - break; + break; #endif - case 450: // Report printer mode - reply.printf("PrinterMode:%s", GetMachineModeString()); - break; + case 450: // Report printer mode + reply.printf("PrinterMode:%s", GetMachineModeString()); + break; - case 451: // Select FFF printer mode - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - machineType = MachineType::fff; - reprap.StateUpdated(); - break; + case 451: // Select FFF printer mode + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + machineType = MachineType::fff; + reprap.StateUpdated(); + break; #if SUPPORT_LASER - case 452: // Select laser mode - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } + case 452: // Select laser mode + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } - machineType = MachineType::laser; - Move::CreateLaserTask(); + machineType = MachineType::laser; + Move::CreateLaserTask(); - if (gb.Seen('C')) - { - if (!platform.AssignLaserPin(gb, reply)) + if (gb.Seen('C')) { - result = GCodeResult::error; + if (!platform.AssignLaserPin(gb, reply)) + { + result = GCodeResult::error; + } } - } - if (gb.Seen('F') || gb.Seen('Q')) - { - platform.SetLaserPwmFrequency(gb.GetPwmFrequency()); - } - if (result == GCodeResult::ok) - { - if (gb.Seen('S')) + if (gb.Seen('F') || gb.Seen('Q')) { - laserPowerSticky = (gb.GetUIValue() == 1); + platform.SetLaserPwmFrequency(gb.GetPwmFrequency()); } - if (gb.Seen('R')) + if (result == GCodeResult::ok) { - laserMaxPower = max<float>(1.0, gb.GetFValue()); + if (gb.Seen('S')) + { + laserPowerSticky = (gb.GetUIValue() == 1); + } + if (gb.Seen('R')) + { + laserMaxPower = max<float>(1.0, gb.GetFValue()); + } } - } - reprap.StateUpdated(); - break; + reprap.StateUpdated(); + break; #endif - case 453: // Select CNC mode - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } + case 453: // Select CNC mode + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } - // M453 may be repeated to set up multiple spindles, so only print the message on the initial switch - if (machineType != MachineType::cnc) - { - machineType = MachineType::cnc; // switch to CNC mode even if the spindle parameter is bad - reprap.StateUpdated(); - } + // M453 may be repeated to set up multiple spindles, so only print the message on the initial switch + if (machineType != MachineType::cnc) + { + machineType = MachineType::cnc; // switch to CNC mode even if the spindle parameter is bad + reprap.StateUpdated(); + } - if (gb.Seen('S')) - { - reply.copy("Spindle management has been moved to M950"); - result = GCodeResult::error; - } - break; + if (gb.Seen('S')) + { + reply.copy("Spindle management has been moved to M950"); + result = GCodeResult::error; + } + break; #if HAS_MASS_STORAGE - case 470: // mkdir - { - gb.MustSee('P'); - String<MaxFilenameLength> dirName; - gb.GetQuotedString(dirName.GetRef()); - result = (MassStorage::MakeDirectory(dirName.c_str(), true)) ? GCodeResult::ok : GCodeResult::error; - } - break; + case 470: // mkdir + { + gb.MustSee('P'); + String<MaxFilenameLength> dirName; + gb.GetQuotedString(dirName.GetRef()); + result = (MassStorage::MakeDirectory(dirName.c_str(), true)) ? GCodeResult::ok : GCodeResult::error; + } + break; - case 471: // move/rename file/directory - { - gb.MustSee('S'); - String<MaxFilenameLength> oldVal; - gb.GetQuotedString(oldVal.GetRef()); - String<MaxFilenameLength> newVal; - gb.MustSee('T'); - gb.GetQuotedString(newVal.GetRef()); - const bool deleteExisting = (gb.Seen('D') && gb.GetUIValue() == 1); - result = (MassStorage::Rename(oldVal.c_str(), newVal.c_str(), deleteExisting, true)) ? GCodeResult::ok : GCodeResult::error; - } - break; + case 471: // move/rename file/directory + { + gb.MustSee('S'); + String<MaxFilenameLength> oldVal; + gb.GetQuotedString(oldVal.GetRef()); + String<MaxFilenameLength> newVal; + gb.MustSee('T'); + gb.GetQuotedString(newVal.GetRef()); + const bool deleteExisting = (gb.Seen('D') && gb.GetUIValue() == 1); + result = (MassStorage::Rename(oldVal.c_str(), newVal.c_str(), deleteExisting, true)) ? GCodeResult::ok : GCodeResult::error; + } + break; - case 486: // number object or cancel object - result = buildObjects.HandleM486(gb, reply, outBuf); - break; + case 486: // number object or cancel object + result = buildObjects.HandleM486(gb, reply, outBuf); + break; - case 500: // Store parameters in config-override.g - result = WriteConfigOverrideFile(gb, reply); - break; + case 500: // Store parameters in config-override.g + result = WriteConfigOverrideFile(gb, reply); + break; #endif - case 501: // Load parameters from config-override.g - if (!gb.LatestMachineState().runningM502 && !gb.LatestMachineState().runningM501) // when running M502 we ignore config-override.g - { - if (runningConfigFile) + case 501: // Load parameters from config-override.g + if (!gb.LatestMachineState().runningM502 && !gb.LatestMachineState().runningM501) // when running M502 we ignore config-override.g { - m501SeenInConfigFile = true; + if (runningConfigFile) + { + m501SeenInConfigFile = true; + } + DoFileMacro(gb, CONFIG_OVERRIDE_G, true, code); } - DoFileMacro(gb, CONFIG_OVERRIDE_G, true, code); - } - break; + break; - case 502: // Revert to default "factory settings" ignoring values in config-override.g - if (!gb.LatestMachineState().runningM502) // avoid recursion - { - if (!LockMovementAndWaitForStandstill(gb)) + case 502: // Revert to default "factory settings" ignoring values in config-override.g + if (!gb.LatestMachineState().runningM502) // avoid recursion { - return false; + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + reprap.GetHeat().SwitchOffAll(true); // turn heaters off before changing the models + reprap.GetHeat().ResetHeaterModels(); // in case some heaters have no M307 commands in config.g + reprap.GetMove().GetKinematics().SetCalibrationDefaults(); // in case M665/M666/M667/M669 in config.g don't define all the parameters + platform.GetEndstops().SetZProbeDefaults(); + DoFileMacro(gb, CONFIG_FILE, true, code); } - reprap.GetHeat().SwitchOffAll(true); // turn heaters off before changing the models - reprap.GetHeat().ResetHeaterModels(); // in case some heaters have no M307 commands in config.g - reprap.GetMove().GetKinematics().SetCalibrationDefaults(); // in case M665/M666/M667/M669 in config.g don't define all the parameters - platform.GetEndstops().SetZProbeDefaults(); - DoFileMacro(gb, CONFIG_FILE, true, code); - } - break; + break; #if HAS_MASS_STORAGE - case 503: // List variable settings - { - if (!LockFileSystem(gb)) + case 503: // List variable settings { - return false; - } + if (!LockFileSystem(gb)) + { + return false; + } - // Need a valid output buffer to continue - if (!OutputBuffer::Allocate(outBuf)) - { - // No buffer available, try again later - return false; - } + // Need a valid output buffer to continue + if (!OutputBuffer::Allocate(outBuf)) + { + // No buffer available, try again later + return false; + } - // Read the entire file - FileStore * const f = platform.OpenSysFile(CONFIG_FILE, OpenMode::read); - if (f == nullptr) - { - reply.copy("Configuration file not found"); - result = GCodeResult::error; - } - else - { - char fileBuffer[FILE_BUFFER_SIZE]; - size_t bytesRead, - bytesLeftForWriting = OutputBuffer::GetBytesLeft(outBuf); - while ((bytesRead = f->Read(fileBuffer, FILE_BUFFER_SIZE)) > 0 && bytesLeftForWriting > 0) + // Read the entire file + FileStore * const f = platform.OpenSysFile(CONFIG_FILE, OpenMode::read); + if (f == nullptr) { - // Don't write more data than we can process - if (bytesRead < bytesLeftForWriting) - { - bytesLeftForWriting -= bytesRead; - } - else + reply.copy("Configuration file not found"); + result = GCodeResult::error; + } + else + { + char fileBuffer[FILE_BUFFER_SIZE]; + size_t bytesRead, + bytesLeftForWriting = OutputBuffer::GetBytesLeft(outBuf); + while ((bytesRead = f->Read(fileBuffer, FILE_BUFFER_SIZE)) > 0 && bytesLeftForWriting > 0) { - bytesRead = bytesLeftForWriting; - bytesLeftForWriting = 0; - } + // Don't write more data than we can process + if (bytesRead < bytesLeftForWriting) + { + bytesLeftForWriting -= bytesRead; + } + else + { + bytesRead = bytesLeftForWriting; + bytesLeftForWriting = 0; + } - // Write it - outBuf->cat(fileBuffer, bytesRead); + // Write it + outBuf->cat(fileBuffer, bytesRead); + } + f->Close(); } - f->Close(); } - } - break; - - case 505: // set sys folder - if (gb.Seen('P')) - { - // Lock movement to try to prevent other threads opening system files while we change the system path - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - String<MaxFilenameLength> path; - gb.GetQuotedString(path.GetRef()); - result = platform.SetSysDir(path.c_str(), reply); - } - else - { - String<MaxFilenameLength> path; - platform.AppendSysDir(path.GetRef()); - reply.printf("Sys file path is %s", path.c_str()); - } - break; -#endif + break; - case 540: // Set/report MAC address - if (CheckNetworkCommandAllowed(gb, reply, result)) - { - const unsigned int interface = (gb.Seen('I') ? gb.GetUIValue() : 0); + case 505: // set sys folder if (gb.Seen('P')) { - MacAddress mac; - gb.GetMacAddress(mac); - result = reprap.GetNetwork().SetMacAddress(interface, mac, reply); + // Lock movement to try to prevent other threads opening system files while we change the system path + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + String<MaxFilenameLength> path; + gb.GetQuotedString(path.GetRef()); + result = platform.SetSysDir(path.c_str(), reply); } else { - const MacAddress& mac = reprap.GetNetwork().GetMacAddress(interface); - reply.printf("MAC: %02x:%02x:%02x:%02x:%02x:%02x", mac.bytes[0], mac.bytes[1], mac.bytes[2], mac.bytes[3], mac.bytes[4], mac.bytes[5]); + String<MaxFilenameLength> path; + platform.AppendSysDir(path.GetRef()); + reply.printf("Sys file path is %s", path.c_str()); } - } - break; + break; +#endif + + case 540: // Set/report MAC address + if (CheckNetworkCommandAllowed(gb, reply, result)) + { + const unsigned int interface = (gb.Seen('I') ? gb.GetUIValue() : 0); + if (gb.Seen('P')) + { + MacAddress mac; + gb.GetMacAddress(mac); + result = reprap.GetNetwork().SetMacAddress(interface, mac, reply); + } + else + { + const MacAddress& mac = reprap.GetNetwork().GetMacAddress(interface); + reply.printf("MAC: %02x:%02x:%02x:%02x:%02x:%02x", mac.bytes[0], mac.bytes[1], mac.bytes[2], mac.bytes[3], mac.bytes[4], mac.bytes[5]); + } + } + break; - case 550: // Set/report machine name + case 550: // Set/report machine name #if HAS_LINUX_INTERFACE - if (reprap.UsingLinuxInterface() && !gb.IsBinary()) - { - result = GCodeResult::errorNotSupported; - } - else -#endif - { - String<MachineNameLength> name; - bool seen = false; - gb.TryGetPossiblyQuotedString('P', name.GetRef(), seen); - if (seen) + if (reprap.UsingLinuxInterface() && !gb.IsBinary()) { - reprap.SetName(name.c_str()); + result = GCodeResult::errorNotSupported; } else +#endif { - reply.printf("RepRap name: %s", reprap.GetName()); + String<MachineNameLength> name; + bool seen = false; + gb.TryGetPossiblyQuotedString('P', name.GetRef(), seen); + if (seen) + { + reprap.SetName(name.c_str()); + } + else + { + reply.printf("RepRap name: %s", reprap.GetName()); + } } - } - break; + break; - case 551: // Set password (no option to report it) - { - String<RepRapPasswordLength> password; - bool seen = false; - gb.TryGetPossiblyQuotedString('P', password.GetRef(), seen); - if (seen) + case 551: // Set password (no option to report it) { - reprap.SetPassword(password.c_str()); + String<RepRapPasswordLength> password; + bool seen = false; + gb.TryGetPossiblyQuotedString('P', password.GetRef(), seen); + if (seen) + { + reprap.SetPassword(password.c_str()); + } } - } - break; - - case 552: // Enable/Disable network and/or Set/Get IP address - if (CheckNetworkCommandAllowed(gb, reply, result)) - { - bool seen = false; - const unsigned int interface = (gb.Seen('I')) ? gb.GetUIValue() : 0; + break; - String<SsidBufferLength> ssid; - if (reprap.GetNetwork().IsWiFiInterface(interface)) + case 552: // Enable/Disable network and/or Set/Get IP address + if (CheckNetworkCommandAllowed(gb, reply, result)) { - if (gb.Seen('S')) // Has the user turned the network on or off? + bool seen = false; + const unsigned int interface = (gb.Seen('I')) ? gb.GetUIValue() : 0; + + String<SsidBufferLength> ssid; + if (reprap.GetNetwork().IsWiFiInterface(interface)) { - const int enableValue = gb.GetIValue(); - seen = true; + if (gb.Seen('S')) // Has the user turned the network on or off? + { + const int enableValue = gb.GetIValue(); + seen = true; + if (gb.Seen('P')) + { + gb.GetQuotedString(ssid.GetRef()); + } + result = reprap.GetNetwork().EnableInterface(interface, enableValue, ssid.GetRef(), reply); + } + } + else + { if (gb.Seen('P')) { - gb.GetQuotedString(ssid.GetRef()); + seen = true; + IPAddress eth; + gb.GetIPAddress(eth); + platform.SetIPAddress(eth); } - result = reprap.GetNetwork().EnableInterface(interface, enableValue, ssid.GetRef(), reply); + + // 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; + result = reprap.GetNetwork().EnableInterface(interface, gb.GetIValue(), ssid.GetRef(), reply); + } + } + + if (!seen) + { + result = reprap.GetNetwork().GetNetworkState(interface, reply); } } - else + break; + + case 553: // Set/Get netmask + if (CheckNetworkCommandAllowed(gb, reply, result)) { if (gb.Seen('P')) { - seen = true; IPAddress eth; gb.GetIPAddress(eth); - platform.SetIPAddress(eth); + platform.SetNetMask(eth); } - - // 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? + else { - seen = true; - result = reprap.GetNetwork().EnableInterface(interface, gb.GetIValue(), ssid.GetRef(), reply); + const IPAddress nm = platform.NetMask(); + reply.printf("Net mask: %d.%d.%d.%d ", nm.GetQuad(0), nm.GetQuad(1), nm.GetQuad(2), nm.GetQuad(3)); } } + break; - if (!seen) - { - result = reprap.GetNetwork().GetNetworkState(interface, reply); - } - } - break; - - case 553: // Set/Get netmask - if (CheckNetworkCommandAllowed(gb, reply, result)) - { - if (gb.Seen('P')) - { - IPAddress eth; - gb.GetIPAddress(eth); - platform.SetNetMask(eth); - } - else + case 554: // Set/Get gateway + if (CheckNetworkCommandAllowed(gb, reply, result)) { - const IPAddress nm = platform.NetMask(); - reply.printf("Net mask: %d.%d.%d.%d ", nm.GetQuad(0), nm.GetQuad(1), nm.GetQuad(2), nm.GetQuad(3)); + if (gb.Seen('P')) + { + IPAddress eth; + gb.GetIPAddress(eth); + platform.SetGateWay(eth); + } + else + { + const IPAddress gw = platform.GateWay(); + reply.printf("Gateway: %d.%d.%d.%d ", gw.GetQuad(0), gw.GetQuad(1), gw.GetQuad(2), gw.GetQuad(3)); + } } - } - break; + break; - case 554: // Set/Get gateway - if (CheckNetworkCommandAllowed(gb, reply, result)) - { + case 555: // Set/report firmware type to emulate if (gb.Seen('P')) { - IPAddress eth; - gb.GetIPAddress(eth); - platform.SetGateWay(eth); + gb.LatestMachineState().compatibility.Assign(gb.GetIValue()); + reprap.InputsUpdated(); } else { - const IPAddress gw = platform.GateWay(); - reply.printf("Gateway: %d.%d.%d.%d ", gw.GetQuad(0), gw.GetQuad(1), gw.GetQuad(2), gw.GetQuad(3)); + reply.printf("Output mode: %s", gb.LatestMachineState().compatibility.ToString()); } - } - break; + break; - case 555: // Set/report firmware type to emulate - if (gb.Seen('P')) - { - gb.LatestMachineState().compatibility.Assign(gb.GetIValue()); - reprap.InputsUpdated(); - } - else + case 556: // Axis compensation (we support only X, Y, Z) { - reply.printf("Output mode: %s", gb.LatestMachineState().compatibility.ToString()); - } - break; - - case 556: // Axis compensation (we support only X, Y, Z) - { - bool seen = false; + bool seen = false; - if (gb.Seen('S')) - { - const float value = gb.GetFValue(); - if (value >= 10.0) // avoid divide by zero and silly results + if (gb.Seen('S')) { - for (size_t axis = 0; axis <= Z_AXIS; axis++) + const float value = gb.GetFValue(); + if (value >= 10.0) // avoid divide by zero and silly results { - if (gb.Seen(axisLetters[axis])) + for (size_t axis = 0; axis <= Z_AXIS; axis++) { - reprap.GetMove().SetAxisCompensation(axis, gb.GetFValue() / value); - seen = true; + if (gb.Seen(axisLetters[axis])) + { + reprap.GetMove().SetAxisCompensation(axis, gb.GetFValue() / value); + seen = true; + } } } } - } - if (gb.Seen('P')) - { - reprap.GetMove().SetXYCompensation(gb.GetIValue() <= 0); - seen = true; - } + if (gb.Seen('P')) + { + reprap.GetMove().SetXYCompensation(gb.GetIValue() <= 0); + seen = true; + } - if (!seen) - { - reply.printf("Axis compensations - %s: %.5f, YZ: %.5f, ZX: %.5f", - reprap.GetMove().IsXYCompensated() ? "XY" : "YX", - (double)reprap.GetMove().AxisCompensation(X_AXIS), (double)reprap.GetMove().AxisCompensation(Y_AXIS), (double)reprap.GetMove().AxisCompensation(Z_AXIS)); + if (!seen) + { + reply.printf("Axis compensations - %s: %.5f, YZ: %.5f, ZX: %.5f", + reprap.GetMove().IsXYCompensated() ? "XY" : "YX", + (double)reprap.GetMove().AxisCompensation(X_AXIS), (double)reprap.GetMove().AxisCompensation(Y_AXIS), (double)reprap.GetMove().AxisCompensation(Z_AXIS)); + } + break; } - break; - } - case 557: // Set/report Z probe point coordinates - result = DefineGrid(gb, reply); - break; + case 557: // Set/report Z probe point coordinates + result = DefineGrid(gb, reply); + break; - case 558: // Set or report Z probe type and for which axes it is used - result = platform.GetEndstops().HandleM558(gb, reply); - break; + case 558: // Set or report Z probe type and for which axes it is used + result = platform.GetEndstops().HandleM558(gb, reply); + break; #if HAS_MASS_STORAGE - case 559: - case 560: // Binary writing - { - String<MaxFilenameLength> defaultFolder; - if (code == 560) + case 559: + case 560: // Binary writing { - defaultFolder.copy(platform.GetWebDir()); - } - else - { - platform.AppendSysDir(defaultFolder.GetRef()); - } - String<MaxFilenameLength> filename; - gb.MustSee('P'); - gb.GetQuotedString(filename.GetRef()); - const FilePosition size = (gb.Seen('S') ? (FilePosition)gb.GetIValue() : 0); - const uint32_t crc32 = (gb.Seen('C') ? gb.GetUIValue() : 0); - const bool ok = gb.OpenFileToWrite(defaultFolder.c_str(), filename.c_str(), size, true, crc32); - if (ok) - { - reply.printf("Writing to file: %s", filename.c_str()); - } - else - { - reply.printf("Can't open file %s for writing", filename.c_str()); - result = GCodeResult::error; + String<MaxFilenameLength> defaultFolder; + if (code == 560) + { + defaultFolder.copy(platform.GetWebDir()); + } + else + { + platform.AppendSysDir(defaultFolder.GetRef()); + } + String<MaxFilenameLength> filename; + gb.MustSee('P'); + gb.GetQuotedString(filename.GetRef()); + const FilePosition size = (gb.Seen('S') ? (FilePosition)gb.GetIValue() : 0); + const uint32_t crc32 = (gb.Seen('C') ? gb.GetUIValue() : 0); + const bool ok = gb.OpenFileToWrite(defaultFolder.c_str(), filename.c_str(), size, true, crc32); + if (ok) + { + reply.printf("Writing to file: %s", filename.c_str()); + } + else + { + reply.printf("Can't open file %s for writing", filename.c_str()); + result = GCodeResult::error; + } } - } - break; + break; #endif - case 561: // Set identity transform and disable height map - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - ClearBedMapping(); - break; - - case 562: // Reset temperature fault - use with great caution - if (gb.Seen('P')) - { - const unsigned int heater = gb.GetLimitedUIValue('P', MaxHeaters); - result = reprap.ClearTemperatureFault(heater, reply); - } - else - { - // Clear all heater faults - for (unsigned int heater = 0; heater < MaxHeaters; ++heater) + case 561: // Set identity transform and disable height map + if (!LockMovementAndWaitForStandstill(gb)) { - result = max<GCodeResult>(result, reprap.ClearTemperatureFault(heater, reply)); + return false; } - } - heaterFaultState = HeaterFaultState::noFault; - break; - - case 563: // Define tool - result = ManageTool(gb, reply); - break; + ClearBedMapping(); + break; - case 564: // Think outside the box? - { - bool seen = false; - if (gb.Seen('S')) - { - seen = true; - limitAxes = (gb.GetIValue() > 0); - } - if (gb.Seen('H')) + case 562: // Reset temperature fault - use with great caution + if (gb.Seen('P')) { - seen = true; - noMovesBeforeHoming = (gb.GetIValue() > 0); + const unsigned int heater = gb.GetLimitedUIValue('P', MaxHeaters); + result = reprap.ClearTemperatureFault(heater, reply); } - if (!seen) + else { - reply.printf("Movement outside the bed is %spermitted, movement before homing is %spermitted", (limitAxes) ? "not " : "", (noMovesBeforeHoming) ? "not " : ""); + // Clear all heater faults + for (unsigned int heater = 0; heater < MaxHeaters; ++heater) + { + result = max<GCodeResult>(result, reprap.ClearTemperatureFault(heater, reply)); + } } - } - break; + heaterFaultState = HeaterFaultState::noFault; + break; - case 205: // Set/print maximum jerk speeds in mm/sec - case 566: // Set/print maximum jerk speeds in mm/min - { - const float multiplier1 = (code == 566) ? SecondsToMinutes : 1.0; - bool seenAxis = false, seenExtruder = false; - for (size_t axis = 0; axis < numTotalAxes; axis++) + case 563: // Define tool + result = ManageTool(gb, reply); + break; + + case 564: // Think outside the box? { - if (gb.Seen(axisLetters[axis])) + bool seen = false; + if (gb.Seen('S')) { - platform.SetInstantDv(axis, gb.GetDistance() * multiplier1); - seenAxis = true; + seen = true; + limitAxes = (gb.GetIValue() > 0); + } + if (gb.Seen('H')) + { + seen = true; + noMovesBeforeHoming = (gb.GetIValue() > 0); + } + if (!seen) + { + reply.printf("Movement outside the bed is %spermitted, movement before homing is %spermitted", (limitAxes) ? "not " : "", (noMovesBeforeHoming) ? "not " : ""); } } + break; - if (gb.Seen(extrudeLetter)) + case 205: // Set/print maximum jerk speeds in mm/sec + case 566: // Set/print maximum jerk speeds in mm/min { - seenExtruder = true; - float eVals[MaxExtruders]; - size_t eCount = numExtruders; - gb.GetFloatArray(eVals, eCount, true); - for (size_t e = 0; e < eCount; e++) + const float multiplier1 = (code == 566) ? SecondsToMinutes : 1.0; + bool seenAxis = false, seenExtruder = false; + for (size_t axis = 0; axis < numTotalAxes; axis++) { - platform.SetInstantDv(ExtruderToLogicalDrive(e), eVals[e] * multiplier1); + if (gb.Seen(axisLetters[axis])) + { + platform.SetInstantDv(axis, gb.GetDistance() * multiplier1); + seenAxis = true; + } } - } - if (code == 566 && gb.Seen('P')) - { - seenAxis = true; - reprap.GetMove().SetJerkPolicy(gb.GetUIValue()); - } + if (gb.Seen(extrudeLetter)) + { + seenExtruder = true; + float eVals[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetFloatArray(eVals, eCount, true); + for (size_t e = 0; e < eCount; e++) + { + platform.SetInstantDv(ExtruderToLogicalDrive(e), eVals[e] * multiplier1); + } + } - if (seenAxis) - { - reprap.MoveUpdated(); - } - else if (!seenExtruder) - { - const float multiplier2 = (code == 566) ? MinutesToSeconds : 1.0; - reply.printf("Maximum jerk rates (mm/%s): ", (code == 566) ? "min" : "sec"); - for (size_t axis = 0; axis < numTotalAxes; ++axis) + if (code == 566 && gb.Seen('P')) { - reply.catf("%c: %.1f, ", axisLetters[axis], (double)(platform.GetInstantDv(axis) * multiplier2)); + seenAxis = true; + reprap.GetMove().SetJerkPolicy(gb.GetUIValue()); } - reply.cat("E:"); - char sep = ' '; - for (size_t extruder = 0; extruder < numExtruders; extruder++) + + if (seenAxis) { - reply.catf("%c%.1f", sep, (double)(platform.GetInstantDv(ExtruderToLogicalDrive(extruder)) * multiplier2)); - sep = ':'; + reprap.MoveUpdated(); } - if (code == 566) + else if (!seenExtruder) { - reply.catf(", jerk policy: %u", reprap.GetMove().GetJerkPolicy()); + const float multiplier2 = (code == 566) ? MinutesToSeconds : 1.0; + reply.printf("Maximum jerk rates (mm/%s): ", (code == 566) ? "min" : "sec"); + for (size_t axis = 0; axis < numTotalAxes; ++axis) + { + reply.catf("%c: %.1f, ", axisLetters[axis], (double)(platform.GetInstantDv(axis) * multiplier2)); + } + reply.cat("E:"); + char sep = ' '; + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + reply.catf("%c%.1f", sep, (double)(platform.GetInstantDv(ExtruderToLogicalDrive(extruder)) * multiplier2)); + sep = ':'; + } + if (code == 566) + { + reply.catf(", jerk policy: %u", reprap.GetMove().GetJerkPolicy()); + } } } - } - break; + break; - case 567: // Set/report tool mix ratios - if (gb.Seen('P')) - { - const int8_t tNumber = gb.GetIValue(); - ReadLockedPointer<Tool> const tool = reprap.GetTool(tNumber); - if (tool.IsNotNull()) + case 567: // Set/report tool mix ratios + if (gb.Seen('P')) { - if (gb.Seen(extrudeLetter)) + const int8_t tNumber = gb.GetIValue(); + ReadLockedPointer<Tool> const tool = reprap.GetTool(tNumber); + if (tool.IsNotNull()) { - float eVals[MaxExtruders]; - size_t eCount = tool->DriveCount(); - gb.GetFloatArray(eVals, eCount, false); - if (eCount != tool->DriveCount()) + if (gb.Seen(extrudeLetter)) { - reply.copy("Setting mix ratios - wrong number of E drives: "); - gb.AppendFullCommand(reply); + float eVals[MaxExtruders]; + size_t eCount = tool->DriveCount(); + gb.GetFloatArray(eVals, eCount, false); + if (eCount != tool->DriveCount()) + { + reply.copy("Setting mix ratios - wrong number of E drives: "); + gb.AppendFullCommand(reply); + } + else + { + tool->DefineMix(eVals); + } } 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, (double)tool->GetMix()[drive]); - sep = ':'; + reply.printf("Tool %d mix ratios:", tNumber); + char sep = ' '; + for (size_t drive = 0; drive < tool->DriveCount(); drive++) + { + reply.catf("%c%.3f", sep, (double)tool->GetMix()[drive]); + sep = ':'; + } } } } - } - break; + break; - case 568: // Tool Settings - { - const unsigned int toolNumber = gb.GetLimitedUIValue('P', MaxTools); - if (reprap.GetTool(toolNumber).IsNull()) + case 568: // Tool Settings { - reply.cat("Tool settings can only be set for existing tools"); - result = GCodeResult::error; - break; + const unsigned int toolNumber = gb.GetLimitedUIValue('P', MaxTools); + if (reprap.GetTool(toolNumber).IsNull()) + { + reply.cat("Tool settings can only be set for existing tools"); + result = GCodeResult::error; + break; + } + + if (simulationMode != 0) + { + break; + } + result = SetOrReportOffsets(gb, reply, 568); } + break; + + case 569: // Set/report axis direction + result = ConfigureDriver(gb, reply); + break; - if (simulationMode != 0) + case 570: // Set/report heater monitoring { - break; + bool seen = false; + if (gb.Seen('S')) + { + seen = true; + heaterFaultTimeout = gb.GetUIValue() * (60 * 1000); + } + if (gb.Seen('H')) + { + seen = true; + result = reprap.GetHeat().ConfigureHeaterMonitoring(gb.GetUIValue(), gb, reply); + } + if (!seen) + { + reply.printf("Print will be terminated if a heater fault is not reset within %" PRIu32 " minutes", heaterFaultTimeout/(60 * 1000)); + } } - result = SetOrReportOffsets(gb, reply, 568); - } - break; + break; - case 569: // Set/report axis direction - result = ConfigureDriver(gb, reply); - break; + case 571: // Set output on extrude + result = platform.GetSetAncillaryPwm(gb, reply); + break; - case 570: // Set/report heater monitoring - { - bool seen = false; + case 572: // Set/report pressure advance if (gb.Seen('S')) { - seen = true; - heaterFaultTimeout = gb.GetUIValue() * (60 * 1000); + const float advance = gb.GetFValue(); + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + result = platform.SetPressureAdvance(advance, gb, reply); } - if (gb.Seen('H')) + else { - seen = true; - result = reprap.GetHeat().ConfigureHeaterMonitoring(gb.GetUIValue(), gb, reply); + reply.copy("Extruder pressure advance"); + char c = ':'; + for (size_t i = 0; i < numExtruders; ++i) + { + reply.catf("%c %.3f", c, (double)platform.GetPressureAdvance(i)); + c = ','; + } } - if (!seen) + break; + + case 573: // Report heater average PWM { - reply.printf("Print will be terminated if a heater fault is not reset within %" PRIu32 " minutes", heaterFaultTimeout/(60 * 1000)); + const unsigned int heater = gb.GetLimitedUIValue('P', MaxHeaters); + reply.printf("Average heater %u PWM: %.3f", heater, (double)reprap.GetHeat().GetAveragePWM(heater)); } - } - break; - - case 571: // Set output on extrude - result = platform.GetSetAncillaryPwm(gb, reply); - break; + break; - case 572: // Set/report pressure advance - if (gb.Seen('S')) - { - const float advance = gb.GetFValue(); + case 574: // Set endstop configuration + // We may be about to delete endstops, so make sure we are not executing a move that uses them if (!LockMovementAndWaitForStandstill(gb)) { return false; } - result = platform.SetPressureAdvance(advance, gb, reply); - } - else - { - reply.copy("Extruder pressure advance"); - char c = ':'; - for (size_t i = 0; i < numExtruders; ++i) - { - reply.catf("%c %.3f", c, (double)platform.GetPressureAdvance(i)); - c = ','; - } - } - break; - - case 573: // Report heater average PWM - { - const unsigned int heater = gb.GetLimitedUIValue('P', MaxHeaters); - reply.printf("Average heater %u PWM: %.3f", heater, (double)reprap.GetHeat().GetAveragePWM(heater)); - } - break; - - case 574: // Set endstop configuration - // We may be about to delete endstops, so make sure we are not executing a move that uses them - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - result = platform.GetEndstops().HandleM574(gb, reply, outBuf); - break; - - case 575: // Set communications parameters - { - const size_t chan = gb.GetLimitedUIValue('P', NumSerialChannels); - bool seen = false; - if (gb.Seen('B')) - { - platform.SetBaudRate(chan, gb.GetIValue()); - seen = true; - } + result = platform.GetEndstops().HandleM574(gb, reply, outBuf); + break; - if (gb.Seen('S')) + case 575: // Set communications parameters { - const uint32_t val = gb.GetIValue(); - platform.SetCommsProperties(chan, val); - if (chan == 0) + const size_t chan = gb.GetLimitedUIValue('P', NumSerialChannels); + bool seen = false; + if (gb.Seen('B')) { - usbGCode->SetCommsProperties(val); + platform.SetBaudRate(chan, gb.GetIValue()); + seen = true; } -#if HAS_AUX_DEVICES - else if (chan < NumSerialChannels) + + if (gb.Seen('S')) { - GCodeBuffer *& gbp = (chan == 1) ? auxGCode : aux2GCode; - if (gbp != nullptr) + const uint32_t val = gb.GetIValue(); + platform.SetCommsProperties(chan, val); + if (chan == 0) + { + usbGCode->SetCommsProperties(val); + } +#if HAS_AUX_DEVICES + else if (chan < NumSerialChannels) { - gbp->SetCommsProperties(val); - const bool rawMode = (val & 2u) != 0; - platform.SetAuxRaw(chan - 1, rawMode); - if (rawMode && !platform.IsAuxEnabled(chan - 1)) // if enabling aux for the first time and in raw mode, set Marlin compatibility + GCodeBuffer *& gbp = (chan == 1) ? auxGCode : aux2GCode; + if (gbp != nullptr) { - gbp->LatestMachineState().compatibility = Compatibility::Marlin; + gbp->SetCommsProperties(val); + const bool rawMode = (val & 2u) != 0; + platform.SetAuxRaw(chan - 1, rawMode); + if (rawMode && !platform.IsAuxEnabled(chan - 1)) // if enabling aux for the first time and in raw mode, set Marlin compatibility + { + gbp->LatestMachineState().compatibility = Compatibility::Marlin; + } } } - } #endif - seen = true; - } + seen = true; + } - if (seen) - { -#if HAS_AUX_DEVICES - if (chan != 0 && !platform.IsAuxEnabled(chan - 1)) + if (seen) { - platform.EnableAux(chan - 1); +#if HAS_AUX_DEVICES + if (chan != 0 && !platform.IsAuxEnabled(chan - 1)) + { + platform.EnableAux(chan - 1); + } + else + { + platform.ResetChannel(chan); + } } - else + else if (chan != 0 && !platform.IsAuxEnabled(chan - 1)) { - platform.ResetChannel(chan); - } - } - else if (chan != 0 && !platform.IsAuxEnabled(chan - 1)) - { - reply.printf("Channel %u is disabled", chan); + reply.printf("Channel %u is disabled", chan); #endif - } - else - { - const uint32_t cp = platform.GetCommsProperties(chan); - reply.printf("Channel %d: baud rate %" PRIu32 ", %s checksum", chan, platform.GetBaudRate(chan), (cp & 1) ? "requires" : "does not require"); - if (chan == 0 && SERIAL_MAIN_DEVICE.IsConnected()) - { - reply.cat(", connected"); } -#if HAS_AUX_DEVICES - else if (chan != 0 && platform.IsAuxRaw(chan - 1)) + else { - reply.cat(", raw mode"); - } + const uint32_t cp = platform.GetCommsProperties(chan); + reply.printf("Channel %d: baud rate %" PRIu32 ", %s checksum", chan, platform.GetBaudRate(chan), (cp & 1) ? "requires" : "does not require"); + if (chan == 0 && SERIAL_MAIN_DEVICE.IsConnected()) + { + reply.cat(", connected"); + } +#if HAS_AUX_DEVICES + else if (chan != 0 && platform.IsAuxRaw(chan - 1)) + { + reply.cat(", raw mode"); + } #endif + } } - } - break; + break; - case 577: // Wait until endstop input is triggered - result = WaitForPin(gb, reply); - break; + case 577: // Wait until endstop input is triggered + result = WaitForPin(gb, reply); + break; #if SUPPORT_INKJET - case 578: // Fire Inkjet bits - if (!LockMovementAndWaitForStandstill()) - { - 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 578: // Fire Inkjet bits + if (!LockMovementAndWaitForStandstill()) + { + return false; + } - case 579: // Scale Cartesian axes (mostly for Delta configurations) - { - bool seen = false; - for (size_t axis = 0; axis < numVisibleAxes; axis++) + if (gb.Seen('S')) // Need to handle the 'P' parameter too; see http://reprap.org/wiki/G-code#M578:_Fire_inkjet_bits { - gb.TryGetFValue(axisLetters[axis], axisScaleFactors[axis], seen); + platform.Inkjet(gb.GetIValue()); } + break; +#endif - if (!seen) + case 579: // Scale Cartesian axes (mostly for Delta configurations) { - char sep = ':'; - reply.copy("Axis scale factors"); - for(size_t axis = 0; axis < numVisibleAxes; axis++) + bool seen = false; + for (size_t axis = 0; axis < numVisibleAxes; axis++) + { + gb.TryGetFValue(axisLetters[axis], axisScaleFactors[axis], seen); + } + + if (!seen) { - reply.catf("%c %c: %.3f", sep, axisLetters[axis], (double)axisScaleFactors[axis]); - sep = ','; + char sep = ':'; + reply.copy("Axis scale factors"); + for(size_t axis = 0; axis < numVisibleAxes; axis++) + { + reply.catf("%c %c: %.3f", sep, axisLetters[axis], (double)axisScaleFactors[axis]); + sep = ','; + } } } - } - break; + break; #if SUPPORT_ROLAND - case 580: // (De)Select Roland mill - if (gb.Seen('R')) - { - if (gb.GetIValue()) + case 580: // (De)Select Roland mill + if (gb.Seen('R')) { - reprap.GetRoland()->Activate(); - if (gb.Seen('P')) + if (gb.GetIValue()) + { + reprap.GetRoland()->Activate(); + if (gb.Seen('P')) + { + result = reprap.GetRoland()->RawWrite(gb.GetString()); + } + } + else { - result = reprap.GetRoland()->RawWrite(gb.GetString()); + result = reprap.GetRoland()->Deactivate(); } } else { - result = reprap.GetRoland()->Deactivate(); + reply.printf("Roland is %s.", reprap.GetRoland()->Active() ? "active" : "inactive"); } - } - else - { - reply.printf("Roland is %s.", reprap.GetRoland()->Active() ? "active" : "inactive"); - } - break; + break; #endif - case 581: // Configure external trigger - result = ConfigureTrigger(gb, reply); - break; + case 581: // Configure external trigger + result = ConfigureTrigger(gb, reply); + break; - case 582: // Check external trigger - result = CheckTrigger(gb, reply); - break; + case 582: // Check external trigger + result = CheckTrigger(gb, reply); + break; - case 584: // Set axis/extruder to stepper driver(s) mapping - result = DoDriveMapping(gb, reply); - break; + case 584: // Set axis/extruder to stepper driver(s) mapping + result = DoDriveMapping(gb, reply); + break; - case 585: // Probe Tool - result = ProbeTool(gb, reply); - break; + case 585: // Probe Tool + result = ProbeTool(gb, reply); + break; - case 586: // Configure network protocols - if (CheckNetworkCommandAllowed(gb, reply, result)) - { - const unsigned int interface = (gb.Seen('I') ? gb.GetUIValue() : 0); + case 586: // Configure network protocols + if (CheckNetworkCommandAllowed(gb, reply, result)) + { + const unsigned int interface = (gb.Seen('I') ? gb.GetUIValue() : 0); - bool seen = false; + bool seen = false; #if SUPPORT_HTTP - if (gb.Seen('C')) - { - String<StringLength20> corsSite; - gb.GetQuotedString(corsSite.GetRef(), true); - reprap.GetNetwork().SetCorsSite(corsSite.c_str()); - seen = true; - } + if (gb.Seen('C')) + { + String<StringLength20> corsSite; + gb.GetQuotedString(corsSite.GetRef(), true); + reprap.GetNetwork().SetCorsSite(corsSite.c_str()); + seen = true; + } #endif - if (gb.Seen('P')) - { - const unsigned int protocol = gb.GetUIValue(); - if (gb.Seen('S')) + if (gb.Seen('P')) { - const bool enable = (gb.GetIValue() == 1); - if (enable) + const unsigned int protocol = gb.GetUIValue(); + if (gb.Seen('S')) { - const int port = (gb.Seen('R')) ? gb.GetIValue() : -1; - const int secure = (gb.Seen('T')) ? gb.GetIValue() : -1; - result = reprap.GetNetwork().EnableProtocol(interface, protocol, port, secure, reply); + const bool enable = (gb.GetIValue() == 1); + if (enable) + { + const int port = (gb.Seen('R')) ? gb.GetIValue() : -1; + const int secure = (gb.Seen('T')) ? gb.GetIValue() : -1; + result = reprap.GetNetwork().EnableProtocol(interface, protocol, port, secure, reply); + } + else + { + result = reprap.GetNetwork().DisableProtocol(interface, protocol, reply); + } + seen = true; + } + } + + + if (!seen) + { +#if SUPPORT_HTTP + if (reprap.GetNetwork().GetCorsSite() != nullptr) + { + reply.printf("CORS enabled for site '%s'", reprap.GetNetwork().GetCorsSite()); } else { - result = reprap.GetNetwork().DisableProtocol(interface, protocol, reply); + reply.copy("CORS disabled"); } - seen = true; +#endif + // Default to reporting current protocols if P or S parameter missing + result = reprap.GetNetwork().ReportProtocols(interface, reply); } } + break; +#if HAS_WIFI_NETWORKING + case 587: // Add WiFi network or list remembered networks + case 588: // Forget WiFi network + case 589: // Configure access point + if (CheckNetworkCommandAllowed(gb, reply, result)) + { + result = reprap.GetNetwork().HandleWiFiCode(code, gb, reply, outBuf); + } + break; +#endif - if (!seen) + case 591: // Configure filament sensor { -#if SUPPORT_HTTP - if (reprap.GetNetwork().GetCorsSite() != nullptr) + const unsigned int extruder = gb.GetLimitedUIValue('D', numExtruders); + result = FilamentMonitor::Configure(gb, reply, extruder); + } + break; + +#if SUPPORT_NONLINEAR_EXTRUSION + case 592: // Configure nonlinear extrusion + { + const unsigned int extruder = gb.GetLimitedUIValue('D', MaxExtruders); + bool seen = false; + float a = 0.0, b = 0.0, limit = DefaultNonlinearExtrusionLimit; + gb.TryGetFValue('A', a, seen); + gb.TryGetFValue('B', b, seen); + gb.TryGetFValue('L', limit, seen); + if (seen) { - reply.printf("CORS enabled for site '%s'", reprap.GetNetwork().GetCorsSite()); + platform.SetNonlinearExtrusion(extruder, a, b, limit); } else { - reply.copy("CORS disabled"); + platform.GetExtrusionCoefficients(extruder, a, b, limit); + reply.printf("Drive %u nonlinear extrusion coefficients: A=%.3f, B=%.4f, limit=%.2f", extruder, (double)a, (double)b, (double)limit); } -#endif - // Default to reporting current protocols if P or S parameter missing - result = reprap.GetNetwork().ReportProtocols(interface, reply); } - } - break; - -#if HAS_WIFI_NETWORKING - case 587: // Add WiFi network or list remembered networks - case 588: // Forget WiFi network - case 589: // Configure access point - if (CheckNetworkCommandAllowed(gb, reply, result)) - { - result = reprap.GetNetwork().HandleWiFiCode(code, gb, reply, outBuf); - } - break; + break; #endif - case 591: // Configure filament sensor - { - const unsigned int extruder = gb.GetLimitedUIValue('D', numExtruders); - result = FilamentMonitor::Configure(gb, reply, extruder); - } - break; - -#if SUPPORT_NONLINEAR_EXTRUSION - case 592: // Configure nonlinear extrusion - { - const unsigned int extruder = gb.GetLimitedUIValue('D', MaxExtruders); - bool seen = false; - float a = 0.0, b = 0.0, limit = DefaultNonlinearExtrusionLimit; - gb.TryGetFValue('A', a, seen); - gb.TryGetFValue('B', b, seen); - gb.TryGetFValue('L', limit, seen); - if (seen) - { - platform.SetNonlinearExtrusion(extruder, a, b, limit); - } - else + case 593: // Configure dynamic ringing cancellation + if (!LockMovementAndWaitForStandstill(gb)) { - platform.GetExtrusionCoefficients(extruder, a, b, limit); - reply.printf("Drive %u nonlinear extrusion coefficients: A=%.3f, B=%.4f, limit=%.2f", extruder, (double)a, (double)b, (double)limit); + return false; } - } - break; -#endif - - case 593: // Configure dynamic ringing cancellation - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - result = reprap.GetMove().GetShaper().Configure(gb, reply); - break; + result = reprap.GetMove().GetShaper().Configure(gb, reply); + break; #if SUPPORT_ASYNC_MOVES - case 594: // Enter or leave height following mode - result = reprap.GetMove().StartHeightFollowing(gb, reply); - break; + case 594: // Enter or leave height following mode + result = reprap.GetMove().StartHeightFollowing(gb, reply); + break; #endif - case 595: // Configure movement queue size - result = reprap.GetMove().ConfigureMovementQueue(gb, reply); - break; - - // For cases 600 and 601, see 226 + case 595: // Configure movement queue size + result = reprap.GetMove().ConfigureMovementQueue(gb, reply); + break; - // M650 (set peel move parameters) and M651 (execute peel move) are no longer handled specially. Use macros to specify what they should do. + // For cases 600 and 601, see 226 - case 665: // Set delta configuration - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - { - Move& move = reprap.GetMove(); + // M650 (set peel move parameters) and M651 (execute peel move) are no longer handled specially. Use macros to specify what they should do. - bool changedMode = false; - if ((gb.Seen('L') || gb.Seen('D')) && move.GetKinematics().GetKinematicsType() != KinematicsType::linearDelta) + case 665: // Set delta configuration + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } { - // Not in delta mode, so switch to it - changedMode = true; - move.SetKinematics(KinematicsType::linearDelta); + Move& move = reprap.GetMove(); + + bool changedMode = false; + if ((gb.Seen('L') || gb.Seen('D')) && move.GetKinematics().GetKinematicsType() != KinematicsType::linearDelta) + { + // Not in delta mode, so switch to it + changedMode = true; + move.SetKinematics(KinematicsType::linearDelta); + } + bool error = false; + const bool changed = move.GetKinematics().Configure(code, gb, reply, error); + if (changedMode) + { + move.GetKinematics().GetAssumedInitialPosition(numVisibleAxes, moveBuffer.coords); + ToolOffsetInverseTransform(moveBuffer.coords, currentUserPosition); + } + if (changed || changedMode) + { + if (reprap.GetMove().GetKinematics().LimitPosition(moveBuffer.coords, nullptr, numVisibleAxes, axesVirtuallyHomed, false, false) != LimitPositionResult::ok) + { + ToolOffsetInverseTransform(moveBuffer.coords, currentUserPosition); // make sure the limits are reflected in the user position + } + reprap.GetMove().SetNewPosition(moveBuffer.coords, true); + SetAllAxesNotHomed(); + reprap.MoveUpdated(); + } + result = GetGCodeResultFromError(error); } - bool error = false; - const bool changed = move.GetKinematics().Configure(code, gb, reply, error); - if (changedMode) + break; + + case 666: // Set delta endstop adjustments + if (!LockMovementAndWaitForStandstill(gb)) { - move.GetKinematics().GetAssumedInitialPosition(numVisibleAxes, moveBuffer.coords); - ToolOffsetInverseTransform(moveBuffer.coords, currentUserPosition); + return false; } - if (changed || changedMode) { - if (reprap.GetMove().GetKinematics().LimitPosition(moveBuffer.coords, nullptr, numVisibleAxes, axesVirtuallyHomed, false, false) != LimitPositionResult::ok) + bool error = false; + const bool changed = reprap.GetMove().GetKinematics().Configure(code, gb, reply, error); + if (changed) { - ToolOffsetInverseTransform(moveBuffer.coords, currentUserPosition); // make sure the limits are reflected in the user position + SetAllAxesNotHomed(); + reprap.MoveUpdated(); } - reprap.GetMove().SetNewPosition(moveBuffer.coords, true); - SetAllAxesNotHomed(); - reprap.MoveUpdated(); + result = GetGCodeResultFromError(error); } - result = GetGCodeResultFromError(error); - } - break; + break; - case 666: // Set delta endstop adjustments - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - { - bool error = false; - const bool changed = reprap.GetMove().GetKinematics().Configure(code, gb, reply, error); - if (changed) + case 667: // Set CoreXY mode + if (!LockMovementAndWaitForStandstill(gb)) { - SetAllAxesNotHomed(); - reprap.MoveUpdated(); + return false; } - result = GetGCodeResultFromError(error); - } - break; - - case 667: // Set CoreXY mode - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - { - Move& move = reprap.GetMove(); - const KinematicsType oldK = move.GetKinematics().GetKinematicsType(); // get the current kinematics type so we can tell whether it changed - - bool seen = false; - if (gb.Seen('S')) { - // Switch to the correct CoreXY mode - const int mode = gb.GetIValue(); - switch (mode) + Move& move = reprap.GetMove(); + const KinematicsType oldK = move.GetKinematics().GetKinematicsType(); // get the current kinematics type so we can tell whether it changed + + bool seen = false; + if (gb.Seen('S')) { - case 0: - move.SetKinematics(KinematicsType::cartesian); - break; + // Switch to the correct CoreXY mode + const int mode = gb.GetIValue(); + switch (mode) + { + case 0: + move.SetKinematics(KinematicsType::cartesian); + break; - case 1: - move.SetKinematics(KinematicsType::coreXY); - break; + case 1: + move.SetKinematics(KinematicsType::coreXY); + break; - case 2: - move.SetKinematics(KinematicsType::coreXZ); - break; + case 2: + move.SetKinematics(KinematicsType::coreXZ); + break; - default: - reply.printf("Mode %d is not valid", mode); - result = GCodeResult::error; - break; + default: + reply.printf("Mode %d is not valid", mode); + result = GCodeResult::error; + break; + } + seen = true; + } + + if (result == GCodeResult::ok) + { + if (gb.Seen('X') || gb.Seen('Y') || gb.Seen('Z')) + { + reply.copy("M667 XYZ parameters are no longer supported, use M669 matrix parameters instead"); + result = GCodeResult::error; + } + + if (seen) + { + // We changed something, so reset the positions and set all axes not homed + if (move.GetKinematics().GetKinematicsType() != oldK) + { + move.GetKinematics().GetAssumedInitialPosition(numVisibleAxes, moveBuffer.coords); + ToolOffsetInverseTransform(moveBuffer.coords, currentUserPosition); + } + if (reprap.GetMove().GetKinematics().LimitPosition(moveBuffer.coords, nullptr, numVisibleAxes, axesVirtuallyHomed, false, false) != LimitPositionResult::ok) + { + ToolOffsetInverseTransform(moveBuffer.coords, currentUserPosition); // make sure the limits are reflected in the user position + } + reprap.GetMove().SetNewPosition(moveBuffer.coords, true); + SetAllAxesNotHomed(); + reprap.MoveUpdated(); + } } - seen = true; } + break; - if (result == GCodeResult::ok) + case 669: // Set kinematics and parameters for SCARA and other kinematics that don't use M665, M666 or M667 + if (!LockMovementAndWaitForStandstill(gb)) { - if (gb.Seen('X') || gb.Seen('Y') || gb.Seen('Z')) + return false; + } + { + Move& move = reprap.GetMove(); + const KinematicsType oldK = move.GetKinematics().GetKinematicsType(); // get the current kinematics type so we can tell whether it changed + + bool seen = false; + if (gb.Seen('K')) { - reply.copy("M667 XYZ parameters are no longer supported, use M669 matrix parameters instead"); - result = GCodeResult::error; + const int nk = gb.GetIValue(); + if (nk < 0 || nk >= (int)KinematicsType::unknown || !move.SetKinematics(static_cast<KinematicsType>(nk))) + { + reply.printf("Unknown kinematics type %d", nk); + result = GCodeResult::error; + break; + } + seen = true; + } + bool error = false; + if (move.GetKinematics().Configure(code, gb, reply, error)) + { + seen = true; } + result = GetGCodeResultFromError(error); if (seen) { - // We changed something, so reset the positions and set all axes not homed + // We changed something significant, so reset the positions and set all axes not homed if (move.GetKinematics().GetKinematicsType() != oldK) { move.GetKinematics().GetAssumedInitialPosition(numVisibleAxes, moveBuffer.coords); @@ -3906,281 +3963,254 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeEx reprap.MoveUpdated(); } } - } - break; + break; - case 669: // Set kinematics and parameters for SCARA and other kinematics that don't use M665, M666 or M667 - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - { - Move& move = reprap.GetMove(); - const KinematicsType oldK = move.GetKinematics().GetKinematicsType(); // get the current kinematics type so we can tell whether it changed +#if SUPPORT_IOBITS + case 670: + Move::CreateLaserTask(); + result = GetGCodeResultFromError(reprap.GetPortControl().Configure(gb, reply)); + break; +#endif - bool seen = false; - if (gb.Seen('K')) + case 671: // Set Z leadscrew positions + if (!LockMovementAndWaitForStandstill(gb)) { - const int nk = gb.GetIValue(); - if (nk < 0 || nk >= (int)KinematicsType::unknown || !move.SetKinematics(static_cast<KinematicsType>(nk))) - { - reply.printf("Unknown kinematics type %d", nk); - result = GCodeResult::error; - break; - } - seen = true; + return false; } - bool error = false; - if (move.GetKinematics().Configure(code, gb, reply, error)) { - seen = true; + bool error = false; + (void)reprap.GetMove().GetKinematics().Configure(code, gb, reply, error); + result = GetGCodeResultFromError(error); } - result = GetGCodeResultFromError(error); + break; + + case 672: // Program Z probe + result = platform.GetEndstops().ProgramZProbe(gb, reply); + break; - if (seen) + case 673: // Align plane on rotary axis + if (numTotalAxes < U_AXIS) + { + reply.copy("Insufficient axes configured"); + result = GCodeResult::error; + } + else if (LockMovementAndWaitForStandstill(gb)) { - // We changed something significant, so reset the positions and set all axes not homed - if (move.GetKinematics().GetKinematicsType() != oldK) + if (reprap.GetMove().GetNumProbedProbePoints() < 2) { - move.GetKinematics().GetAssumedInitialPosition(numVisibleAxes, moveBuffer.coords); - ToolOffsetInverseTransform(moveBuffer.coords, currentUserPosition); + reply.copy("Insufficient probe points"); + result = GCodeResult::error; } - if (reprap.GetMove().GetKinematics().LimitPosition(moveBuffer.coords, nullptr, numVisibleAxes, axesVirtuallyHomed, false, false) != LimitPositionResult::ok) + else if (!AllAxesAreHomed()) { - ToolOffsetInverseTransform(moveBuffer.coords, currentUserPosition); // make sure the limits are reflected in the user position + reply.copy("Home the axes first"); + result = GCodeResult::error; } - reprap.GetMove().SetNewPosition(moveBuffer.coords, true); - SetAllAxesNotHomed(); - reprap.MoveUpdated(); - } - } - break; - -#if SUPPORT_IOBITS - case 670: - Move::CreateLaserTask(); - result = GetGCodeResultFromError(reprap.GetPortControl().Configure(gb, reply)); - break; -#endif + else + { + // See which rotary axis needs to be compensated (if any) + size_t axisToUse = 0; + for (size_t axis = U_AXIS; axis < numVisibleAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + axisToUse = axis; + break; + } + } - case 671: // Set Z leadscrew positions - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - { - bool error = false; - (void)reprap.GetMove().GetKinematics().Configure(code, gb, reply, error); - result = GetGCodeResultFromError(error); - } - break; + // Get the coordinates of the first two G30 points and calculate how far off the axis is + float x1, y1, x2, y2; + const float z1 = reprap.GetMove().GetProbeCoordinates(0, x1, y1, true); + const float z2 = reprap.GetMove().GetProbeCoordinates(1, x2, y2, true); + const float a1 = (x1 == x2) ? y1 : x1; + const float a2 = (x1 == x2) ? y2 : x2; - case 672: // Program Z probe - result = platform.GetEndstops().ProgramZProbe(gb, reply); - break; + // See what kind of compensation we need to perform + SetMoveBufferDefaults(); + if (axisToUse != 0) + { + // An axis letter is given, so try to level the given axis + const float correctionAngle = atanf((z2 - z1) / (a2 - a1)) * 180.0 / M_PI; + const float correctionFactor = gb.Seen('S') ? gb.GetFValue() : 1.0; + moveBuffer.coords[axisToUse] += correctionAngle * correctionFactor; - case 673: // Align plane on rotary axis - if (numTotalAxes < U_AXIS) - { - reply.copy("Insufficient axes configured"); - result = GCodeResult::error; - } - else if (LockMovementAndWaitForStandstill(gb)) - { - if (reprap.GetMove().GetNumProbedProbePoints() < 2) - { - reply.copy("Insufficient probe points"); - result = GCodeResult::error; - } - else if (!AllAxesAreHomed()) - { - reply.copy("Home the axes first"); - result = GCodeResult::error; - } - else - { - // See which rotary axis needs to be compensated (if any) - size_t axisToUse = 0; - for (size_t axis = U_AXIS; axis < numVisibleAxes; axis++) - { - if (gb.Seen(axisLetters[axis])) + reply.printf("%c axis is off by %.2f deg", axisLetters[axisToUse], (double)correctionAngle); + HandleReply(gb, GCodeResult::notFinished, reply.c_str()); + } + else if (reprap.GetMove().GetNumProbedProbePoints() >= 4) + { + // At least four G30 points are given. This lets us figure out how far off the centre of the axis is + const float z3 = reprap.GetMove().GetProbeCoordinates(2, x1, y1, true); + const float z4 = reprap.GetMove().GetProbeCoordinates(3, x2, y2, true); + const float a3 = (x1 == x2) ? y1 : x1; + const float a4 = (x1 == x2) ? y2 : x2; + + // Calculate intersection points in [XY] and Z directions + const float aS = ((a4 - a3) * (a2 * z1 - a1 * z2) - (a2 - a1) * (a4 * z3 - a3 * z4)) / + ((z4 - z3) * (a2 - a1) - (z2 - z1) * (a4 - a3)); + const float zS = ((z1 - z2) * (a4 * z3 - a3 * z4) - (z3 - z4) * (a2 * z1 - a1 * z2)) / + ((z4 - z3) * (a2 - a1) - (z2 - z1) * (a4 - a3)); + moveBuffer.coords[(x1 == x2) ? Y_AXIS : X_AXIS] += aS; + moveBuffer.coords[Z_AXIS] += zS; + + reply.printf("%c is offset by %.2fmm, Z is offset by %.2fmm", (x2 == x1) ? 'Y' : 'X', (double)aS, (double)zS); + HandleReply(gb, GCodeResult::notFinished, reply.c_str()); + } + else { - axisToUse = axis; + reply.copy("No rotary axis letter and/or not enough probe points for rotary axis alignment"); + result = GCodeResult::error; break; } - } - // Get the coordinates of the first two G30 points and calculate how far off the axis is - float x1, y1, x2, y2; - const float z1 = reprap.GetMove().GetProbeCoordinates(0, x1, y1, true); - const float z2 = reprap.GetMove().GetProbeCoordinates(1, x2, y2, true); - const float a1 = (x1 == x2) ? y1 : x1; - const float a2 = (x1 == x2) ? y2 : x2; - - // See what kind of compensation we need to perform - SetMoveBufferDefaults(); - if (axisToUse != 0) - { - // An axis letter is given, so try to level the given axis - const float correctionAngle = atanf((z2 - z1) / (a2 - a1)) * 180.0 / M_PI; - const float correctionFactor = gb.Seen('S') ? gb.GetFValue() : 1.0; - moveBuffer.coords[axisToUse] += correctionAngle * correctionFactor; + // Get the feedrate (if any) and kick off a new move + if (gb.Seen(feedrateLetter)) + { + const float rate = gb.ConvertDistance(gb.GetFValue()); + gb.LatestMachineState().feedRate = rate * SecondsToMinutes; // don't apply the speed factor + } + moveBuffer.feedRate = gb.LatestMachineState().feedRate; + moveBuffer.usingStandardFeedrate = true; + moveBuffer.tool = reprap.GetCurrentTool(); + NewMoveAvailable(1); - reply.printf("%c axis is off by %.2f deg", axisLetters[axisToUse], (double)correctionAngle); - HandleReply(gb, GCodeResult::notFinished, reply.c_str()); + gb.SetState(GCodeState::waitingForSpecialMoveToComplete); } - else if (reprap.GetMove().GetNumProbedProbePoints() >= 4) - { - // At least four G30 points are given. This lets us figure out how far off the centre of the axis is - const float z3 = reprap.GetMove().GetProbeCoordinates(2, x1, y1, true); - const float z4 = reprap.GetMove().GetProbeCoordinates(3, x2, y2, true); - const float a3 = (x1 == x2) ? y1 : x1; - const float a4 = (x1 == x2) ? y2 : x2; - - // Calculate intersection points in [XY] and Z directions - const float aS = ((a4 - a3) * (a2 * z1 - a1 * z2) - (a2 - a1) * (a4 * z3 - a3 * z4)) / - ((z4 - z3) * (a2 - a1) - (z2 - z1) * (a4 - a3)); - const float zS = ((z1 - z2) * (a4 * z3 - a3 * z4) - (z3 - z4) * (a2 * z1 - a1 * z2)) / - ((z4 - z3) * (a2 - a1) - (z2 - z1) * (a4 - a3)); - moveBuffer.coords[(x1 == x2) ? Y_AXIS : X_AXIS] += aS; - moveBuffer.coords[Z_AXIS] += zS; + } + else + { + result = GCodeResult::notFinished; + } + break; - reply.printf("%c is offset by %.2fmm, Z is offset by %.2fmm", (x2 == x1) ? 'Y' : 'X', (double)aS, (double)zS); - HandleReply(gb, GCodeResult::notFinished, reply.c_str()); - } - else +#if false + // This code is not finished yet + case 674: // Set Z to center point + if (LockMovementAndWaitForStandstill(gb)) + { + if (reprap.GetMove().GetNumProbedProbePoints() < 2) { - reply.copy("No rotary axis letter and/or not enough probe points for rotary axis alignment"); + reply.copy("Insufficient probe points"); result = GCodeResult::error; - break; } - - // Get the feedrate (if any) and kick off a new move - if (gb.Seen(feedrateLetter)) + else if (!AllAxesAreHomed()) { - const float rate = gb.ConvertDistance(gb.GetFValue()); - gb.LatestMachineState().feedRate = rate * SecondsToMinutes; // don't apply the speed factor + reply.copy("Home the axes first"); + result = GCodeResult::error; } - moveBuffer.feedRate = gb.LatestMachineState().feedRate; - moveBuffer.usingStandardFeedrate = true; - moveBuffer.tool = reprap.GetCurrentTool(); - NewMoveAvailable(1); - - gb.SetState(GCodeState::waitingForSpecialMoveToComplete); - } - } - else - { - result = GCodeResult::notFinished; - } - break; + else + { + float x, y; + const float z1 = reprap.GetMove().GetProbeCoordinates(0, x, y, true); + const float z2 = reprap.GetMove().GetProbeCoordinates(1, x, y, true); + const float offset = gb.Seen('P') ? gb.GetFValue() : 0.0; + currentUserPosition[Z_AXIS] -= (z1 + z2) / 2.0 + offset; -#if false - // This code is not finished yet - case 674: // Set Z to center point - if (LockMovementAndWaitForStandstill(gb)) - { - if (reprap.GetMove().GetNumProbedProbePoints() < 2) - { - reply.copy("Insufficient probe points"); - result = GCodeResult::error; - } - else if (!AllAxesAreHomed()) - { - reply.copy("Home the axes first"); - result = GCodeResult::error; - } - else - { - float x, y; - const float z1 = reprap.GetMove().GetProbeCoordinates(0, x, y, true); - const float z2 = reprap.GetMove().GetProbeCoordinates(1, x, y, true); - const float offset = gb.Seen('P') ? gb.GetFValue() : 0.0; - currentUserPosition[Z_AXIS] -= (z1 + z2) / 2.0 + offset; + ToolOffsetTransform(currentUserPosition, moveBuffer.coords); + if (reprap.GetMove().GetKinematics().LimitPosition(moveBuffer.coords, numVisibleAxes, LowestNBits<AxesBitmap>(numVisibleAxes), false)) // pretend that all axes are homed + { + ToolOffsetInverseTransform(moveBuffer.coords, currentUserPosition); // make sure the limits are reflected in the user position + } + reprap.GetMove().SetNewPosition(moveBuffer.coords, true); + axesHomed |= reprap.GetMove().GetKinematics().AxesAssumedHomed(MakeBitmap<AxesBitmap>(Z_AXIS)); - ToolOffsetTransform(currentUserPosition, moveBuffer.coords); - if (reprap.GetMove().GetKinematics().LimitPosition(moveBuffer.coords, numVisibleAxes, LowestNBits<AxesBitmap>(numVisibleAxes), false)) // pretend that all axes are homed - { - ToolOffsetInverseTransform(moveBuffer.coords, currentUserPosition); // make sure the limits are reflected in the user position + reply.printf("Probe points at %.2f %.2f, setting new Z to %.2f", (double)z1, (double)z2, (double)currentUserPosition[Z_AXIS]); } - reprap.GetMove().SetNewPosition(moveBuffer.coords, true); - axesHomed |= reprap.GetMove().GetKinematics().AxesAssumedHomed(MakeBitmap<AxesBitmap>(Z_AXIS)); - - reply.printf("Probe points at %.2f %.2f, setting new Z to %.2f", (double)z1, (double)z2, (double)currentUserPosition[Z_AXIS]); } - } - break; + break; #endif - case 675: // Find center of cavity - result = FindCenterOfCavity(gb, reply); - break; + case 675: // Find center of cavity + result = FindCenterOfCavity(gb, reply); + break; - case 701: // Load filament - result = LoadFilament(gb, reply); - break; + case 701: // Load filament + result = LoadFilament(gb, reply); + break; - case 702: // Unload filament - result = UnloadFilament(gb, reply); - break; + case 702: // Unload filament + result = UnloadFilament(gb, reply); + break; - case 703: // Configure Filament - if (reprap.GetCurrentTool() != nullptr) - { - const Filament *filament = reprap.GetCurrentTool()->GetFilament(); - if (filament != nullptr && filament->IsLoaded()) + case 703: // Configure Filament + if (reprap.GetCurrentTool() != nullptr) { - String<StringLength256> scratchString; - scratchString.printf("%s%s/%s", FILAMENTS_DIRECTORY, filament->GetName(), CONFIG_FILAMENT_G); - DoFileMacro(gb, scratchString.c_str(), false, SystemHelperMacroCode); + const Filament *filament = reprap.GetCurrentTool()->GetFilament(); + if (filament != nullptr && filament->IsLoaded()) + { + String<StringLength256> scratchString; + scratchString.printf("%s%s/%s", FILAMENTS_DIRECTORY, filament->GetName(), CONFIG_FILAMENT_G); + DoFileMacro(gb, scratchString.c_str(), false, SystemHelperMacroCode); + } } - } - else - { - result = GCodeResult::error; - reply.copy("No tool selected"); - } - break; + else + { + result = GCodeResult::error; + reply.copy("No tool selected"); + } + break; #if SUPPORT_SCANNER - case 750: // Enable 3D scanner extension - reprap.GetScanner().Enable(); - break; + case 750: // Enable 3D scanner extension + reprap.GetScanner().Enable(); + break; - case 751: // Register 3D scanner extension over USB - if (&gb == usbGCode) - { - if (reprap.GetScanner().IsEnabled()) + case 751: // Register 3D scanner extension over USB + if (&gb == usbGCode) { - reprap.GetScanner().Register(); + if (reprap.GetScanner().IsEnabled()) + { + reprap.GetScanner().Register(); + } + else + { + reply.copy("Scanner extension is not enabled"); + result = GCodeResult::error; + } } else { - reply.copy("Scanner extension is not enabled"); + reply.copy("Invalid source"); result = GCodeResult::error; } - } - else - { - reply.copy("Invalid source"); - result = GCodeResult::error; - } - break; + break; - case 752: // Start 3D scan - { - gb.MustSee('P'); - String<MaxFilenameLength> file; - gb.GetPossiblyQuotedString(file.GetRef()); - gb.MustSee('S'); - const int range = gb.GetIValue(); + case 752: // Start 3D scan + { + gb.MustSee('P'); + String<MaxFilenameLength> file; + gb.GetPossiblyQuotedString(file.GetRef()); + gb.MustSee('S'); + const int range = gb.GetIValue(); + if (reprap.GetScanner().IsEnabled()) + { + if (reprap.GetScanner().IsRegistered()) + { + const int resolution = gb.Seen('R') ? gb.GetIValue() : 100; + const int mode = gb.Seen('N') ? gb.GetIValue() : 0; + result = GetGCodeResultFromFinished(reprap.GetScanner().StartScan(file.c_str(), range, resolution, mode)); + } + else + { + reply.copy("Scanner is not registered"); + result = GCodeResult::error; + } + } + else + { + reply.copy("Scanner extension is not enabled"); + result = GCodeResult::error; + } + } + break; + + case 753: // Cancel current 3D scanner action if (reprap.GetScanner().IsEnabled()) { if (reprap.GetScanner().IsRegistered()) { - const int resolution = gb.Seen('R') ? gb.GetIValue() : 100; - const int mode = gb.Seen('N') ? gb.GetIValue() : 0; - result = GetGCodeResultFromFinished(reprap.GetScanner().StartScan(file.c_str(), range, resolution, mode)); + result = GetGCodeResultFromFinished(reprap.GetScanner().Cancel()); } else { @@ -4193,468 +4223,438 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeEx reply.copy("Scanner extension is not enabled"); result = GCodeResult::error; } - } - break; - - case 753: // Cancel current 3D scanner action - if (reprap.GetScanner().IsEnabled()) - { - if (reprap.GetScanner().IsRegistered()) - { - result = GetGCodeResultFromFinished(reprap.GetScanner().Cancel()); - } - else - { - reply.copy("Scanner is not registered"); - result = GCodeResult::error; - } - } - else - { - reply.copy("Scanner extension is not enabled"); - result = GCodeResult::error; - } - break; + break; - case 754: // Calibrate scanner - if (reprap.GetScanner().IsEnabled()) - { - if (reprap.GetScanner().IsRegistered()) + case 754: // Calibrate scanner + if (reprap.GetScanner().IsEnabled()) { - const int mode = gb.Seen('N') ? gb.GetIValue() : 0; - result = GetGCodeResultFromFinished(reprap.GetScanner().Calibrate(mode)); + if (reprap.GetScanner().IsRegistered()) + { + const int mode = gb.Seen('N') ? gb.GetIValue() : 0; + result = GetGCodeResultFromFinished(reprap.GetScanner().Calibrate(mode)); + } + else + { + reply.copy("Scanner is not registered"); + result = GCodeResult::error; + } } else { - reply.copy("Scanner is not registered"); + reply.copy("Scanner extension is not enabled"); result = GCodeResult::error; } - } - else - { - reply.copy("Scanner extension is not enabled"); - result = GCodeResult::error; - } - break; + break; - case 755: // Set alignment mode for 3D scanner - if (reprap.GetScanner().IsEnabled()) - { - if (reprap.GetScanner().IsRegistered()) + case 755: // Set alignment mode for 3D scanner + if (reprap.GetScanner().IsEnabled()) { - const bool on = (gb.Seen('P') && gb.GetIValue() > 0); - result = GetGCodeResultFromFinished(reprap.GetScanner().SetAlignment(on)); + if (reprap.GetScanner().IsRegistered()) + { + const bool on = (gb.Seen('P') && gb.GetIValue() > 0); + result = GetGCodeResultFromFinished(reprap.GetScanner().SetAlignment(on)); + } + else + { + reply.copy("Scanner is not registered"); + result = GCodeResult::error; + } } else { - reply.copy("Scanner is not registered"); + reply.copy("Scanner extension is not enabled"); result = GCodeResult::error; } - } - else - { - reply.copy("Scanner extension is not enabled"); - result = GCodeResult::error; - } - break; + break; - case 756: // Shutdown 3D scanner - if (reprap.GetScanner().IsEnabled()) - { - if (reprap.GetScanner().IsRegistered()) + case 756: // Shutdown 3D scanner + if (reprap.GetScanner().IsEnabled()) { - result = GetGCodeResultFromFinished(reprap.GetScanner().Shutdown()); + if (reprap.GetScanner().IsRegistered()) + { + result = GetGCodeResultFromFinished(reprap.GetScanner().Shutdown()); + } + else + { + reply.copy("Scanner is not registered"); + result = GCodeResult::error; + } } else { - reply.copy("Scanner is not registered"); + reply.copy("Scanner extension is not enabled"); result = GCodeResult::error; } - } - else - { - reply.copy("Scanner extension is not enabled"); - result = GCodeResult::error; - } - break; + break; #else - case 750: - case 751: - case 752: - case 753: - case 754: - case 755: - case 756: - reply.copy("Scanner support not built-in"); - result = GCodeResult::error; - break; + case 750: + case 751: + case 752: + case 753: + case 754: + case 755: + case 756: + reply.copy("Scanner support not built-in"); + result = GCodeResult::error; + break; #endif - case 851: // Set Z probe offset, only for Marlin compatibility - { - auto zp = platform.GetZProbeOrDefault(0); - if (gb.Seen('Z')) + case 851: // Set Z probe offset, only for Marlin compatibility { - zp->SetTriggerHeight(-gb.GetFValue()); - zp->SetSaveToConfigOverride(); - reprap.SensorsUpdated(); - } - else - { - reply.printf("Z probe offset is %.2fmm", (double)(-zp->GetConfiguredTriggerHeight())); + auto zp = platform.GetZProbeOrDefault(0); + if (gb.Seen('Z')) + { + zp->SetTriggerHeight(-gb.GetFValue()); + zp->SetSaveToConfigOverride(); + reprap.SensorsUpdated(); + } + else + { + reply.printf("Z probe offset is %.2fmm", (double)(-zp->GetConfiguredTriggerHeight())); + } } - } - break; + break; - case 905: // Set current RTC date and time - result = SetDateTime(gb, reply); - break; + case 905: // Set current RTC date and time + result = SetDateTime(gb, reply); + break; - case 906: // Set/report Motor currents - case 913: // Set/report motor current percent + case 906: // Set/report Motor currents + case 913: // Set/report motor current percent #if HAS_SMART_DRIVERS - case 917: // Set/report standstill motor current percentage + case 917: // Set/report standstill motor current percentage #endif #if HAS_VOLTAGE_MONITOR - if (gb.GetState() != GCodeState::powerFailPausing1) // we don't wait for movement to stop if we are running the power fail script + if (gb.GetState() != GCodeState::powerFailPausing1) // we don't wait for movement to stop if we are running the power fail script #endif - { - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } - } - { - bool seen = false; - for (size_t axis = 0; axis < numTotalAxes; axis++) { - if (gb.Seen(axisLetters[axis])) + if (!LockMovementAndWaitForStandstill(gb)) { - result = max(result, platform.SetMotorCurrent(axis, gb.GetFValue(), code, reply)); - seen = true; + return false; } } - - 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++) + bool seen = false; + for (size_t axis = 0; axis < numTotalAxes; axis++) { - result = max(result, platform.SetMotorCurrent(ExtruderToLogicalDrive(e), eVals[e], code, reply)); + if (gb.Seen(axisLetters[axis])) + { + result = max(result, platform.SetMotorCurrent(axis, gb.GetFValue(), code, reply)); + seen = true; + } } - } - - if (code == 906 && gb.Seen('I')) - { - seen = true; - platform.SetIdleCurrentFactor(gb.GetFValue()/100.0); - } - if (!seen) - { - reply.copy( (code == 913) ? "Motor current % of normal - " -#if HAS_SMART_DRIVERS - : (code == 917) ? "Motor standstill current % of normal - " -#endif - : "Motor current (mA) - " - ); - for (size_t axis = 0; axis < numTotalAxes; ++axis) + if (gb.Seen(extrudeLetter)) { - reply.catf("%c:%d, ", axisLetters[axis], (int)platform.GetMotorCurrent(axis, code)); + seen = true; + float eVals[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetFloatArray(eVals, eCount, true); + for (size_t e = 0; e < eCount; e++) + { + result = max(result, platform.SetMotorCurrent(ExtruderToLogicalDrive(e), eVals[e], code, reply)); + } } - reply.cat("E"); - for (size_t extruder = 0; extruder < numExtruders; extruder++) + + if (code == 906 && gb.Seen('I')) { - reply.catf(":%d", (int)platform.GetMotorCurrent(ExtruderToLogicalDrive(extruder), code)); + seen = true; + platform.SetIdleCurrentFactor(gb.GetFValue()/100.0); } - if (code == 906) + + if (!seen) { - reply.catf(", idle factor %d%%", (int)(platform.GetIdleCurrentFactor() * 100.0)); + reply.copy( (code == 913) ? "Motor current % of normal - " +#if HAS_SMART_DRIVERS + : (code == 917) ? "Motor standstill current % of normal - " +#endif + : "Motor current (mA) - " + ); + for (size_t axis = 0; axis < numTotalAxes; ++axis) + { + reply.catf("%c:%d, ", axisLetters[axis], (int)platform.GetMotorCurrent(axis, code)); + } + reply.cat("E"); + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + reply.catf(":%d", (int)platform.GetMotorCurrent(ExtruderToLogicalDrive(extruder), code)); + } + if (code == 906) + { + reply.catf(", idle factor %d%%", (int)(platform.GetIdleCurrentFactor() * 100.0)); + } } } - } - break; + break; #if HAS_VOLTAGE_MONITOR - case 911: // Enable auto save on loss of power - if (gb.Seen('S')) - { - const float saveVoltage = gb.GetFValue(); - if (saveVoltage < 10.0) + case 911: // Enable auto save on loss of power + if (gb.Seen('S')) { - platform.DisableAutoSave(); + const float saveVoltage = gb.GetFValue(); + if (saveVoltage < 10.0) + { + platform.DisableAutoSave(); + } + else + { + float resumeVoltage = saveVoltage + 1.0; // set up default resume voltage + bool dummy; + gb.TryGetFValue('R', resumeVoltage, dummy); + + String<80> powerFailString; + bool seenCommandString = false; + gb.TryGetQuotedString('P', powerFailString.GetRef(), seenCommandString); + if (seenCommandString) + { + // Replace the power fail script atomically + char *newPowerFailScript = new char[powerFailString.strlen() + 1]; + strcpy(newPowerFailScript, powerFailString.c_str()); + std::swap(newPowerFailScript, powerFailScript); + delete[] newPowerFailScript; + reprap.StateUpdated(); + } + else if (powerFailScript == nullptr) + { + reply.copy("No power fail script provided"); + result = GCodeResult::error; + break; + } + platform.EnableAutoSave(saveVoltage, resumeVoltage); + } } else { - float resumeVoltage = saveVoltage + 1.0; // set up default resume voltage - bool dummy; - gb.TryGetFValue('R', resumeVoltage, dummy); - - String<80> powerFailString; - bool seenCommandString = false; - gb.TryGetQuotedString('P', powerFailString.GetRef(), seenCommandString); - if (seenCommandString) + float saveVoltage, resumeVoltage; + if (platform.GetAutoSaveSettings(saveVoltage, resumeVoltage)) { - // Replace the power fail script atomically - char *newPowerFailScript = new char[powerFailString.strlen() + 1]; - strcpy(newPowerFailScript, powerFailString.c_str()); - std::swap(newPowerFailScript, powerFailScript); - delete[] newPowerFailScript; - reprap.StateUpdated(); + reply.printf("Auto save voltage %.1fV, resume %.1fV, script \"%s\"", (double)saveVoltage, (double)resumeVoltage, (powerFailScript == nullptr) ? "" : powerFailScript); } - else if (powerFailScript == nullptr) + else { - reply.copy("No power fail script provided"); - result = GCodeResult::error; - break; + reply.copy("Auto save is disabled"); } - platform.EnableAutoSave(saveVoltage, resumeVoltage); } - } - else - { - float saveVoltage, resumeVoltage; - if (platform.GetAutoSaveSettings(saveVoltage, resumeVoltage)) + break; +#endif + +#if HAS_CPU_TEMP_SENSOR + case 912: // Set electronics temperature monitor adjustment + // Currently we ignore the P parameter (i.e. temperature measurement channel) + if (gb.Seen('S')) { - reply.printf("Auto save voltage %.1fV, resume %.1fV, script \"%s\"", (double)saveVoltage, (double)resumeVoltage, (powerFailScript == nullptr) ? "" : powerFailScript); + platform.SetMcuTemperatureAdjust(gb.GetFValue()); } else { - reply.copy("Auto save is disabled"); + reply.printf("MCU temperature calibration adjustment is %.1f" DEGREE_SYMBOL "C", (double)platform.GetMcuTemperatureAdjust()); } - } - break; -#endif - -#if HAS_CPU_TEMP_SENSOR - 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", (double)platform.GetMcuTemperatureAdjust()); - } - break; + break; #endif - // For case 913, see 906 + // For case 913, see 906 #if defined(__ALLIGATOR__) - case 914: // Set/Get J14 Expansion Voltage Level Translator on Port J5, 5.5V or 3.3V - // Get Piggy module presence status - if (gb.Seen('S')) - { - const int voltageValue = gb.GetIValue(); - if (voltageValue != 5 && voltageValue != 3 ) + case 914: // Set/Get J14 Expansion Voltage Level Translator on Port J5, 5.5V or 3.3V + // Get Piggy module presence status + if (gb.Seen('S')) { - reply.printf("The Expansion Voltage Translator does not support %dV. \n Only 5V or 3V are supported.",voltageValue); + const int voltageValue = gb.GetIValue(); + if (voltageValue != 5 && voltageValue != 3 ) + { + reply.printf("The Expansion Voltage Translator does not support %dV. \n Only 5V or 3V are supported.",voltageValue); + } + else + { + // Change Voltage translator level + digitalWrite(ExpansionVoltageLevelPin, voltageValue == 5); + } } else { - // Change Voltage translator level - digitalWrite(ExpansionVoltageLevelPin, voltageValue == 5); + // Change Voltage translator level Status + reply.printf("The Voltage of Expansion Translator is %dV \nPiggy module %s", + digitalRead(ExpansionVoltageLevelPin) ? 5 : 3 , + digitalRead(ExpansionPiggyDetectPin) ? "not detected" : "detected"); } - } - else - { - // Change Voltage translator level Status - reply.printf("The Voltage of Expansion Translator is %dV \nPiggy module %s", - digitalRead(ExpansionVoltageLevelPin) ? 5 : 3 , - digitalRead(ExpansionPiggyDetectPin) ? "not detected" : "detected"); - } - break; + break; #endif #if HAS_STALL_DETECT - case 915: - result = platform.ConfigureStallDetection(gb, reply, outBuf); - break; + case 915: + result = platform.ConfigureStallDetection(gb, reply, outBuf); + break; #endif #if HAS_MASS_STORAGE - case 916: - if (!platform.SysFileExists(RESUME_AFTER_POWER_FAIL_G)) - { - reply.copy("No resume file found"); - result = GCodeResult::error; - } - else if (!platform.SysFileExists(RESUME_PROLOGUE_G)) - { - reply.printf("Resume prologue file '%s' not found", RESUME_PROLOGUE_G); - result = GCodeResult::error; - } - else - { - DoFileMacro(gb, RESUME_AFTER_POWER_FAIL_G, true, SystemHelperMacroCode); - } - break; + case 916: + if (!platform.SysFileExists(RESUME_AFTER_POWER_FAIL_G)) + { + reply.copy("No resume file found"); + result = GCodeResult::error; + } + else if (!platform.SysFileExists(RESUME_PROLOGUE_G)) + { + reply.printf("Resume prologue file '%s' not found", RESUME_PROLOGUE_G); + result = GCodeResult::error; + } + else + { + DoFileMacro(gb, RESUME_AFTER_POWER_FAIL_G, true, SystemHelperMacroCode); + } + break; #endif - // For case 917, see 906 + // For case 917, see 906 #if SUPPORT_12864_LCD - case 918: // Configure direct-connect display + case 918: // Configure direct-connect display # ifdef DUET_NG - // On Duet 2 configuring the display may affect the number of supported stepper drivers, so wait until there is no movement - if (!LockMovementAndWaitForStandstill(gb)) - { - return false; - } + // On Duet 2 configuring the display may affect the number of supported stepper drivers, so wait until there is no movement + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } # endif - result = reprap.GetDisplay().Configure(gb, reply); + result = reprap.GetDisplay().Configure(gb, reply); # ifdef DUET_NG - platform.AdjustNumDrivers((reprap.GetDisplay().IsPresent()) ? 2 : 0); + platform.AdjustNumDrivers((reprap.GetDisplay().IsPresent()) ? 2 : 0); # endif - break; + break; #endif - case 929: // Start/stop event logging + case 929: // Start/stop event logging #if HAS_MASS_STORAGE - result = platform.ConfigureLogging(gb, reply); + result = platform.ConfigureLogging(gb, reply); #else - result = GCodeResult::warningNotSupported; + result = GCodeResult::warningNotSupported; #endif - break; + break; - case 950: // configure I/O pins - result = platform.ConfigurePort(gb, reply); - break; + case 950: // configure I/O pins + result = platform.ConfigurePort(gb, reply); + break; #if SUPPORT_ASYNC_MOVES - case 951: // configure height control - result = reprap.GetMove().ConfigureHeightFollowing(gb, reply); - break; + case 951: // configure height control + result = reprap.GetMove().ConfigureHeightFollowing(gb, reply); + break; #endif #if SUPPORT_CAN_EXPANSION - case 952: // change expansion board CAN address - result = CanInterface::ChangeAddressAndNormalTiming(gb, reply); - break; + case 952: // change expansion board CAN address + result = CanInterface::ChangeAddressAndNormalTiming(gb, reply); + break; - case 953: // set CAN-FD data rate - result = CanInterface::ChangeFastTiming(gb, reply); - break; + case 953: // set CAN-FD data rate + result = CanInterface::ChangeFastTiming(gb, reply); + break; #endif #if SUPPORT_REMOTE_COMMANDS - case 954: // configure as expansion board - { - CanAddress addr = gb.GetLimitedUIValue('A', 1, CanId::MaxCanAddress + 1); - CanInterface::SwitchToExpansionMode(addr); - } - break; + case 954: // configure as expansion board + { + CanAddress addr = gb.GetLimitedUIValue('A', 1, CanId::MaxCanAddress + 1); + CanInterface::SwitchToExpansionMode(addr); + } + break; #endif #if SUPPORT_ACCELEROMETERS - case 955: - result = Accelerometers::ConfigureAccelerometer(gb, reply); - break; + case 955: + result = Accelerometers::ConfigureAccelerometer(gb, reply); + break; - case 956: - result = Accelerometers::StartAccelerometer(gb, reply); - break; + case 956: + result = Accelerometers::StartAccelerometer(gb, reply); + break; #endif - case 997: // Perform firmware update + case 997: // Perform firmware update #ifdef DUET3_ATE - Duet3Ate::PowerOffEUT(); + Duet3Ate::PowerOffEUT(); #endif - result = UpdateFirmware(gb, reply); - break; + result = UpdateFirmware(gb, reply); + 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) + 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')) { - reply.printf("Checksum error on line %d", val); + const int val = gb.GetIValue(); + if (val != 0) + { + reply.printf("Checksum error on line %d", val); + } } - } - break; + break; - case 999: + case 999: #ifdef DUET3_ATE - Duet3Ate::PowerOffEUT(); + Duet3Ate::PowerOffEUT(); #endif #if SUPPORT_CAN_EXPANSION - if (gb.Seen('B')) - { - const uint32_t address = gb.GetUIValue(); - if (address != CanInterface::GetCanAddress()) + if (gb.Seen('B')) { - result = reprap.GetExpansion().ResetRemote(address, gb, reply); - break; + const uint32_t address = gb.GetUIValue(); + if (address != CanInterface::GetCanAddress()) + { + result = reprap.GetExpansion().ResetRemote(address, gb, reply); + break; + } } - } #endif #if HAS_AUX_DEVICES - if (gb.Seen('A')) - { - const uint32_t serialChannel = gb.GetLimitedUIValue('A', 1, NumSerialChannels); - const uint32_t auxChannel = serialChannel - 1; - if (platform.IsAuxEnabled(auxChannel)) + if (gb.Seen('A')) { - if (gb.Seen('P')) + const uint32_t serialChannel = gb.GetLimitedUIValue('A', 1, NumSerialChannels); + const uint32_t auxChannel = serialChannel - 1; + if (platform.IsAuxEnabled(auxChannel)) { - String<StringLength20> eraseString; - gb.GetQuotedString(eraseString.GetRef()); - if (eraseString.Equals("ERASE")) + if (gb.Seen('P')) { - platform.AppendAuxReply(auxChannel, panelDueCommandEraseAndReset, true); + String<StringLength20> eraseString; + gb.GetQuotedString(eraseString.GetRef()); + if (eraseString.Equals("ERASE")) + { + platform.AppendAuxReply(auxChannel, panelDueCommandEraseAndReset, true); + } } + else + { + platform.AppendAuxReply(auxChannel, panelDueCommandReset, true); + } + break; } - else - { - platform.AppendAuxReply(auxChannel, panelDueCommandReset, true); - } - break; } - } #endif - if (!gb.DoDwellTime(1000)) // wait a second to allow the response to be sent back to the web server, otherwise it may retry - { - return false; - } + if (!gb.DoDwellTime(1000)) // wait a second to allow the response to be sent back to the web server, otherwise it may retry + { + return false; + } - reprap.EmergencyStop(); // this disables heaters and drives - Duet WiFi pre-production boards need drives disabled here - { - SoftwareResetReason reason = SoftwareResetReason::user; - if (gb.Seen('P')) + reprap.EmergencyStop(); // this disables heaters and drives - Duet WiFi pre-production boards need drives disabled here { - String<StringLength20> eraseString; - gb.GetQuotedString(eraseString.GetRef()); - if (eraseString.Equals("ERASE")) + SoftwareResetReason reason = SoftwareResetReason::user; + if (gb.Seen('P')) { - reason = SoftwareResetReason::erase; + String<StringLength20> eraseString; + gb.GetQuotedString(eraseString.GetRef()); + if (eraseString.Equals("ERASE")) + { + reason = SoftwareResetReason::erase; + } } + SoftwareReset(reason); // doesn't return } - SoftwareReset(reason); // doesn't return - } - break; + break; - default: - // See if there is a file in /sys named Mxx.g - if (code >= 0 && code < 10000) - { - String<StringLength20> macroName; - macroName.printf("M%d.g", code); - if (DoFileMacro(gb, macroName.c_str(), false, code)) - { - break; - } + default: + result = TryMacroFile(gb); + break; } - result = GCodeResult::warningNotSupported; - break; } return HandleResult(gb, result, reply, outBuf); @@ -4666,6 +4666,30 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeEx } } +// Try to find a macro file that implements a G or M command +GCodeResult GCodes::TryMacroFile(GCodeBuffer& gb) noexcept +{ + const int code = gb.GetCommandNumber(); + if (code >= 0 && code < 10000) + { + String<StringLength20> macroName; + const int fraction = gb.GetCommandFraction(); + if (fraction > 0) + { + macroName.printf("%c%d.%d.g", gb.GetCommandLetter(), code, fraction); + } + else + { + macroName.printf("%c%d.g", gb.GetCommandLetter(), code); + } + if (DoFileMacro(gb, macroName.c_str(), false, code)) + { + return GCodeResult::ok; + } + } + return GCodeResult::warningNotSupported; +} + bool GCodes::HandleTcode(GCodeBuffer& gb, const StringRef& reply) { if (gb.LatestMachineState().runningM502) |