diff options
Diffstat (limited to 'src/GCodes/GCodes2.cpp')
-rw-r--r-- | src/GCodes/GCodes2.cpp | 3481 |
1 files changed, 3481 insertions, 0 deletions
diff --git a/src/GCodes/GCodes2.cpp b/src/GCodes/GCodes2.cpp new file mode 100644 index 00000000..5c7ef148 --- /dev/null +++ b/src/GCodes/GCodes2.cpp @@ -0,0 +1,3481 @@ +/* + * GCodes2.cpp + * + * Created on: 3 Dec 2016 + * Author: David + * + * This file contains the code to see what G, M or T command we have and start processing it. + */ + +#include "RepRapFirmware.h" + +#ifdef DUET_NG +#include "FirmwareUpdater.h" +#endif + +const char* BED_EQUATION_G = "bed.g"; +const char* RESUME_G = "resume.g"; +const char* CANCEL_G = "cancel.g"; +const char* STOP_G = "stop.g"; +const char* SLEEP_G = "sleep.g"; + +const float MinServoPulseWidth = 544.0, MaxServoPulseWidth = 2400.0; +const uint16_t ServoRefreshFrequency = 50; + +// If the code to act on is completed, this returns true, +// otherwise false. It is called repeatedly for a given +// code until it returns true for that code. +bool GCodes::ActOnCode(GCodeBuffer& gb, StringRef& reply) +{ + // Discard empty buffers right away + if (gb.IsEmpty()) + { + return true; + } + + // M-code parameters might contain letters T and G, e.g. in filenames. + // dc42 assumes that G-and T-code parameters never contain the letter M. + // Therefore we must check for an M-code first. + if (gb.Seen('M')) + { + return HandleMcode(gb, reply); + } + // dc42 doesn't think a G-code parameter ever contains letter T, or a T-code ever contains letter G. + // So it doesn't matter in which order we look for them. + if (gb.Seen('G')) + { + return HandleGcode(gb, reply); + } + if (gb.Seen('T')) + { + return HandleTcode(gb, reply); + } + + // An invalid or queued buffer gets discarded + HandleReply(gb, false, ""); + return true; +} + +bool GCodes::HandleGcode(GCodeBuffer& gb, StringRef& reply) +{ + bool result = true; + bool error = false; + + int code = gb.GetIValue(); + if (simulationMode != 0 && code != 0 && code != 1 && code != 4 && code != 10 && code != 20 && code != 21 && code != 90 && code != 91 && code != 92) + { + return true; // we only simulate some gcodes + } + + switch (code) + { + case 0: // There are no rapid moves... + case 1: // Ordinary move + if (!LockMovement(gb)) + { + return false; + } + { + // Check for 'R' parameter here to go back to the coordinates at which the print was paused + // NOTE: restore point 2 (tool change) won't work when changing tools on dual axis machines because of X axis mapping. + // We could possibly fix this by saving the virtual X axis position instead of the physical axis positions. + // However, slicers normally command the tool to the correct place after a tool change, so we don't need this feature anyway. + int rParam = (gb.Seen('R')) ? gb.GetIValue() : 0; + RestorePoint *rp = (rParam == 1) ? &pauseRestorePoint : (rParam == 2) ? &toolChangeRestorePoint : nullptr; + if (rp != nullptr) + { + if (segmentsLeft != 0) + { + return false; + } + for (size_t axis = 0; axis < numAxes; ++axis) + { + float offset = gb.Seen(axisLetters[axis]) ? gb.GetFValue() * distanceScale : 0.0; + moveBuffer.coords[axis] = rp->moveCoords[axis] + offset; + } + // For now we don't handle extrusion at the same time + for (size_t drive = numAxes; drive < DRIVES; ++drive) + { + moveBuffer.coords[drive] = 0.0; + } + moveBuffer.feedRate = (gb.Seen(feedrateLetter)) ? gb.GetFValue() : gb.MachineState().feedrate; + moveBuffer.filePos = noFilePosition; + moveBuffer.usePressureAdvance = false; + segmentsLeft = 1; + } + else + { + int res = SetUpMove(gb, reply); + if (res == 2) + { + gb.SetState(GCodeState::waitingForMoveToComplete); + } + result = (res != 0); + } + } + break; + + case 4: // Dwell + result = DoDwell(gb); + break; + + case 10: // Set/report offsets and temperatures, or retract + if (gb.Seen('P')) + { + if (!SetOrReportOffsets(gb, reply)) + { + return false; + } + } + else + { + if (!LockMovement(gb)) + { + return false; + } + result = RetractFilament(true); + } + break; + + case 11: // Un-retract + if (!LockMovement(gb)) + { + return false; + } + result = RetractFilament(false); + break; + + case 20: // Inches (which century are we living in, here?) + distanceScale = INCH_TO_MM; + break; + + case 21: // mm + distanceScale = 1.0; + break; + + case 28: // Home + result = DoHome(gb, reply, error); + break; + + case 29: // Grid-based bed probing + if (!LockMovementAndWaitForStandstill(gb)) // do this first to make sure that a new grid isn't being defined + { + return false; + } + error = ProbeGrid(gb, reply); + break; + + case 30: // Z probe/manually set at a position and set that as point P + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + if (reprap.GetMove()->IsDeltaMode() && !AllAxesAreHomed()) + { + reply.copy("Must home a delta printer before bed probing"); + error = true; + } + else + { + result = SetSingleZProbeAtAPosition(gb, reply); + } + break; + + case 31: // Return the probe value, or set probe variables + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + result = SetPrintZProbe(gb, reply); + break; + + case 32: // Probe Z at multiple positions and generate the bed transform + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + + // Try to execute bed.g + if (!DoFileMacro(gb, BED_EQUATION_G, reprap.GetMove()->IsDeltaMode())) + { + // If we get here then we are not on a delta printer and there is no bed.g file + if (GetAxisIsHomed(X_AXIS) && GetAxisIsHomed(Y_AXIS)) + { + gb.SetState(GCodeState::setBed1); // no bed.g file, so use the coordinates specified by M557 + } + else + { + // We can only do bed levelling if X and Y have already been homed + reply.copy("Must home X and Y before bed probing"); + error = true; + } + } + break; + + case 90: // Absolute coordinates + gb.MachineState().axesRelative = false; + break; + + case 91: // Relative coordinates + gb.MachineState().axesRelative = true; // Axis movements (i.e. X, Y and Z) + break; + + case 92: // Set position + result = SetPositions(gb); + break; + + default: + error = true; + reply.printf("invalid G Code: %s", gb.Buffer()); + } + + if (result && gb.GetState() == GCodeState::normal) + { + UnlockAll(gb); + HandleReply(gb, error, reply.Pointer()); + } + return result; +} + +bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) +{ + bool result = true; + bool error = false; + + const int code = gb.GetIValue(); + if (simulationMode != 0 && (code < 20 || code > 37) && code != 0 && code != 1 && code != 82 && code != 83 && code != 105 && code != 111 && code != 112 && code != 122 && code != 408 && code != 999) + { + return true; // we don't yet simulate most M codes + } + + switch (code) + { + case 0: // Stop + case 1: // Sleep + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + { + bool wasPaused = isPaused; // isPaused gets cleared by CancelPrint + CancelPrint(); + if (wasPaused) + { + reply.copy("Print cancelled"); + // If we are cancelling a paused print with M0 and cancel.g exists then run it and do nothing else + if (code == 0) + { + if (DoFileMacro(gb, CANCEL_G, false)) + { + break; + } + } + } + } + + gb.SetState((code == 0) ? GCodeState::stopping : GCodeState::sleeping); + DoFileMacro(gb, (code == 0) ? STOP_G : SLEEP_G, false); + break; + +#if SUPPORT_ROLAND + case 3: // Spin spindle + if (reprap.GetRoland()->Active()) + { + if (gb.Seen('S')) + { + result = reprap.GetRoland()->ProcessSpindle(gb.GetFValue()); + } + } + break; +#endif + + case 18: // Motors off + case 84: + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + { + bool seen = false; + for (size_t axis = 0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + SetAxisNotHomed(axis); + platform->DisableDrive(axis); + seen = true; + } + } + + if (gb.Seen(extrudeLetter)) + { + long int eDrive[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetLongArray(eDrive, eCount); + for (size_t i = 0; i < eCount; i++) + { + seen = true; + if (eDrive[i] < 0 || (size_t)eDrive[i] >= numExtruders) + { + reply.printf("Invalid extruder number specified: %ld", eDrive[i]); + error = true; + break; + } + platform->DisableDrive(numAxes + eDrive[i]); + } + } + + if (gb.Seen('S')) + { + seen = true; + + float idleTimeout = gb.GetFValue(); + if (idleTimeout < 0.0) + { + reply.copy("Idle timeouts cannot be negative!"); + error = true; + } + else + { + reprap.GetMove()->SetIdleTimeout(idleTimeout); + } + } + + if (!seen) + { + DisableDrives(); + } + } + break; + + case 20: // List files on SD card + if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers + { + return false; + } + { + OutputBuffer *fileResponse; + const int sparam = (gb.Seen('S')) ? gb.GetIValue() : 0; + const char* dir = (gb.Seen('P')) ? gb.GetString() : platform->GetGCodeDir(); + + if (sparam == 2) + { + fileResponse = reprap.GetFilesResponse(dir, true); // Send the file list in JSON format + fileResponse->cat('\n'); + } + else + { + if (!OutputBuffer::Allocate(fileResponse)) + { + // Cannot allocate an output buffer, try again later + return false; + } + + // To mimic the behaviour of the official RepRapPro firmware: + // If we are emulating RepRap then we print "GCode files:\n" at the start, otherwise we don't. + // If we are emulating Marlin and the code came via the serial/USB interface, then we don't put quotes around the names and we separate them with newline; + // otherwise we put quotes around them and separate them with comma. + if (platform->Emulating() == me || platform->Emulating() == reprapFirmware) + { + fileResponse->copy("GCode files:\n"); + } + + bool encapsulateList = ((&gb != serialGCode && &gb != telnetGCode) || platform->Emulating() != marlin); + FileInfo fileInfo; + if (platform->GetMassStorage()->FindFirst(dir, fileInfo)) + { + // iterate through all entries and append each file name + do { + if (encapsulateList) + { + fileResponse->catf("%c%s%c%c", FILE_LIST_BRACKET, fileInfo.fileName, FILE_LIST_BRACKET, FILE_LIST_SEPARATOR); + } + else + { + fileResponse->catf("%s\n", fileInfo.fileName); + } + } while (platform->GetMassStorage()->FindNext(fileInfo)); + + if (encapsulateList) + { + // remove the last separator + (*fileResponse)[fileResponse->Length() - 1] = 0; + } + } + else + { + fileResponse->cat("NONE\n"); + } + } + + UnlockAll(gb); + HandleReply(gb, false, fileResponse); + return true; + } + + case 21: // Initialise SD card + if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers + { + return false; + } + { + size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0; + result = platform->GetMassStorage()->Mount(card, reply, true); + } + break; + + case 22: // Release SD card + if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers + { + return false; + } + { + size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0; + result = platform->GetMassStorage()->Unmount(card, reply); + } + break; + + case 23: // Set file to print + case 32: // Select file and start SD print + if (fileGCode->OriginalMachineState().fileState.IsLive()) + { + reply.copy("Cannot set file to print, because a file is already being printed"); + error = true; + break; + } + + if (code == 32 && !LockMovementAndWaitForStandstill(gb)) + { + return false; + } + { + const char* filename = gb.GetUnprecedentedString(); + if (filename != nullptr) + { + QueueFileToPrint(filename); + if (fileToPrint.IsLive()) + { + reprap.GetPrintMonitor()->StartingPrint(filename); + if (platform->Emulating() == marlin && (&gb == serialGCode || &gb == telnetGCode)) + { + reply.copy("File opened\nFile selected"); + } + else + { + // Command came from web interface or PanelDue, or not emulating Marlin, so send a nicer response + reply.printf("File %s selected for printing", filename); + } + + if (code == 32) + { + fileGCode->OriginalMachineState().fileState.MoveFrom(fileToPrint); + reprap.GetPrintMonitor()->StartedPrint(); + } + } + else + { + reply.printf("Failed to open file %s", filename); + } + } + } + break; + + case 24: // Print/resume-printing the selected file + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + + if (isPaused) + { + gb.SetState(GCodeState::resuming1); + DoFileMacro(gb, RESUME_G); + } + else if (!fileToPrint.IsLive()) + { + reply.copy("Cannot print, because no file is selected!"); + error = true; + } + else + { + fileGCode->OriginalMachineState().fileState.MoveFrom(fileToPrint); + reprap.GetPrintMonitor()->StartedPrint(); + } + break; + + case 226: // Gcode Initiated Pause + if (&gb == fileGCode) // ignore M226 if it did't come from within a file being printed + { + if (!LockMovement(gb)) // lock movement before calling DoPause + { + return false; + } + DoPause(gb); + } + break; + + case 25: // Pause the print + if (isPaused) + { + reply.copy("Printing is already paused!!"); + error = true; + } + else if (!reprap.GetPrintMonitor()->IsPrinting()) + { + reply.copy("Cannot pause print, because no file is being printed!"); + error = true; + } + else + { + if (!LockMovement(gb)) // lock movement before calling DoPause + { + return false; + } + DoPause(gb); + } + break; + + case 26: // Set SD position + if (gb.Seen('S')) + { + const FilePosition value = gb.GetIValue(); + if (value < 0) + { + reply.copy("SD positions can't be negative!"); + error = true; + } + else if (fileGCode->OriginalMachineState().fileState.IsLive()) + { + if (!fileGCode->OriginalMachineState().fileState.Seek(value)) + { + reply.copy("The specified SD position is invalid!"); + error = true; + } + } + else if (fileToPrint.IsLive()) + { + if (!fileToPrint.Seek(value)) + { + reply.copy("The specified SD position is invalid!"); + error = true; + } + } + else + { + reply.copy("Cannot set SD file position, because no print is in progress!"); + error = true; + } + } + else + { + reply.copy("You must specify the SD position in bytes using the S parameter."); + error = true; + } + break; + + case 27: // Report print status - Deprecated + if (reprap.GetPrintMonitor()->IsPrinting()) + { + // Pronterface keeps sending M27 commands if "Monitor status" is checked, and it specifically expects the following response syntax + FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState; + reply.printf("SD printing byte %lu/%lu", fileBeingPrinted.GetPosition(), fileBeingPrinted.Length()); + } + else + { + reply.copy("Not SD printing."); + } + break; + + case 28: // Write to file + { + const char* str = gb.GetUnprecedentedString(); + if (str != nullptr) + { + bool ok = OpenFileToWrite(gb, platform->GetGCodeDir(), str); + if (ok) + { + reply.printf("Writing to file: %s", str); + } + else + { + reply.printf("Can't open file %s for writing.", str); + error = true; + } + } + } + break; + + case 29: // End of file being written; should be intercepted before getting here + reply.copy("GCode end-of-file being interpreted."); + break; + + case 30: // Delete file + { + const char *filename = gb.GetUnprecedentedString(); + if (filename != nullptr) + { + DeleteFile(filename); + } + } + break; + + // For case 32, see case 24 + + case 36: // Return file information + if (!LockFileSystem(gb)) // getting file info takes several calls and isn't reentrant + { + return false; + } + { + const char* filename = gb.GetUnprecedentedString(true); // get filename, or nullptr if none provided + OutputBuffer *fileInfoResponse; + result = reprap.GetPrintMonitor()->GetFileInfoResponse(filename, fileInfoResponse); + if (result) + { + fileInfoResponse->cat('\n'); + UnlockAll(gb); + HandleReply(gb, false, fileInfoResponse); + return true; + } + } + break; + + case 37: // Simulation mode on/off + if (gb.Seen('S')) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + + bool wasSimulating = (simulationMode != 0); + simulationMode = (uint8_t)gb.GetIValue(); + reprap.GetMove()->Simulate(simulationMode); + + if (simulationMode != 0) + { + simulationTime = 0.0; + if (!wasSimulating) + { + // Starting a new simulation, so save the current position + reprap.GetMove()->GetCurrentUserPosition(simulationRestorePoint.moveCoords, 0, reprap.GetCurrentXAxes()); + simulationRestorePoint.feedRate = gb.MachineState().feedrate; + } + } + else if (wasSimulating) + { + // Ending a simulation, so restore the position + SetPositions(simulationRestorePoint.moveCoords); + for (size_t i = 0; i < DRIVES; ++i) + { + moveBuffer.coords[i] = simulationRestorePoint.moveCoords[i]; + } + gb.MachineState().feedrate = simulationRestorePoint.feedRate; + } + } + else + { + reply.printf("Simulation mode: %s, move time: %.1f sec, other time: %.1f sec", + (simulationMode != 0) ? "on" : "off", reprap.GetMove()->GetSimulationTime(), simulationTime); + } + break; + + case 38: // Report SHA1 of file + if (!LockFileSystem(gb)) // getting file hash takes several calls and isn't reentrant + { + return false; + } + if (fileBeingHashed == nullptr) + { + // See if we can open the file and start hashing + const char* filename = gb.GetUnprecedentedString(true); + if (StartHash(filename)) + { + // Hashing is now in progress... + result = false; + } + else + { + error = true; + reply.printf("Cannot open file: %s", filename); + } + } + else + { + // This can take some time. All the actual heavy lifting is in dedicated methods + result = AdvanceHash(reply); + } + break; + + case 42: // Turn an output pin on or off + if (gb.Seen('P')) + { + const int logicalPin = gb.GetIValue(); + Pin pin; + bool invert; + if (platform->GetFirmwarePin(logicalPin, PinAccess::pwm, pin, invert)) + { + if (gb.Seen('S')) + { + float val = gb.GetFValue(); + if (val > 1.0) + { + val /= 255.0; + } + val = constrain<float>(val, 0.0, 1.0); + if (invert) + { + val = 1.0 - val; + } + Platform::WriteAnalog(pin, val, DefaultPinWritePwmFreq); + } + // Ignore the command if no S parameter provided + } + else + { + reply.printf("Logical pin %d is not available for writing", logicalPin); + error = true; + } + } + break; + + case 80: // ATX power on + platform->SetAtxPower(true); + break; + + case 81: // ATX power off + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + platform->SetAtxPower(false); + break; + + case 82: // Use absolute extruder positioning + if (gb.MachineState().drivesRelative) // don't reset the absolute extruder position if it was already absolute + { + for (size_t extruder = 0; extruder < MaxExtruders; extruder++) + { + lastRawExtruderPosition[extruder] = 0.0; + } + gb.MachineState().drivesRelative = false; + } + break; + + case 83: // Use relative extruder positioning + if (!gb.MachineState().drivesRelative) // don't reset the absolute extruder position if it was already relative + { + for (size_t extruder = 0; extruder < MaxExtruders; extruder++) + { + lastRawExtruderPosition[extruder] = 0.0; + } + gb.MachineState().drivesRelative = true; + } + break; + + // For case 84, see case 18 + + case 85: // Set inactive time + break; + + case 92: // Set/report steps/mm for some axes + { + // Save the current positions as we may need them later + float positionNow[DRIVES]; + Move *move = reprap.GetMove(); + move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes()); + + bool seen = false; + for (size_t axis = 0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + platform->SetDriveStepsPerUnit(axis, gb.GetFValue()); + seen = true; + } + } + + if (gb.Seen(extrudeLetter)) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + seen = true; + float eVals[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetFloatArray(eVals, eCount, true); + + // The user may not have as many extruders as we allow for, so just set the ones for which a value is provided + for (size_t e = 0; e < eCount; e++) + { + platform->SetDriveStepsPerUnit(numAxes + e, eVals[e]); + } + } + + if (seen) + { + // On a delta, if we change the drive steps/mm then we need to recalculate the motor positions + SetPositions(positionNow); + } + else + { + reply.copy("Steps/mm: "); + for (size_t axis = 0; axis < numAxes; ++axis) + { + reply.catf("%c: %.3f, ", axisLetters[axis], platform->DriveStepsPerUnit(axis)); + } + reply.catf("E:"); + char sep = ' '; + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + reply.catf("%c%.3f", sep, platform->DriveStepsPerUnit(extruder + numAxes)); + sep = ':'; + } + } + } + break; + + case 98: // Call Macro/Subprogram + if (gb.Seen('P')) + { + DoFileMacro(gb, gb.GetString()); + } + break; + + case 99: // Return from Macro/Subprogram + FileMacroCyclesReturn(gb); + break; + + case 104: // Deprecated. This sets the active temperature of every heater of the active tool + if (gb.Seen('S')) + { + float temperature = gb.GetFValue(); + Tool* tool; + if (gb.Seen('T')) + { + int toolNumber = gb.GetIValue(); + toolNumber += gb.GetToolNumberAdjust(); + tool = reprap.GetTool(toolNumber); + } + else + { + tool = reprap.GetCurrentOrDefaultTool(); + } + SetToolHeaters(tool, temperature); + } + break; + + case 105: // Get temperatures + { + const int8_t bedHeater = reprap.GetHeat()->GetBedHeater(); + const int8_t chamberHeater = reprap.GetHeat()->GetChamberHeater(); + reply.copy("T:"); + for (int8_t heater = 0; heater < HEATERS; heater++) + { + if (heater != bedHeater && heater != chamberHeater) + { + Heat::HeaterStatus hs = reprap.GetHeat()->GetStatus(heater); + if (hs != Heat::HS_off && hs != Heat::HS_fault) + { + reply.catf("%.1f ", reprap.GetHeat()->GetTemperature(heater)); + } + } + } + if (bedHeater >= 0) + { + reply.catf("B:%.1f", reprap.GetHeat()->GetTemperature(bedHeater)); + } + else + { + // I'm not sure whether Pronterface etc. can handle a missing bed temperature, so return zero + reply.cat("B:0.0"); + } + if (chamberHeater >= 0.0) + { + reply.catf(" C:%.1f", reprap.GetHeat()->GetTemperature(chamberHeater)); + } + } + break; + + case 106: // Set/report fan values + { + bool seenFanNum = false; + int32_t fanNum = 0; // Default to the first fan + gb.TryGetIValue('P', fanNum, seenFanNum); + if (fanNum < 0 || fanNum > (int)NUM_FANS) + { + reply.printf("Fan number %d is invalid, must be between 0 and %u", fanNum, NUM_FANS); + } + else + { + bool seen = false; + Fan& fan = platform->GetFan(fanNum); + + if (gb.Seen('I')) // Invert cooling + { + const int invert = gb.GetIValue(); + if (invert < 0) + { + fan.Disable(); + } + else + { + fan.SetInverted(invert > 0); + } + seen = true; + } + + if (gb.Seen('F')) // Set PWM frequency + { + fan.SetPwmFrequency(gb.GetFValue()); + seen = true; + } + + if (gb.Seen('T')) // Set thermostatic trigger temperature + { + seen = true; + fan.SetTriggerTemperature(gb.GetFValue()); + } + + if (gb.Seen('B')) // Set blip time + { + seen = true; + fan.SetBlipTime(gb.GetFValue()); + } + + if (gb.Seen('L')) // Set minimum speed + { + seen = true; + fan.SetMinValue(gb.GetFValue()); + } + + if (gb.Seen('H')) // Set thermostatically-controller heaters + { + seen = true; + long heaters[HEATERS]; + size_t numH = HEATERS; + gb.GetLongArray(heaters, numH); + // Note that M106 H-1 disables thermostatic mode. The following code implements that automatically. + uint16_t hh = 0; + for (size_t h = 0; h < numH; ++h) + { + const int hnum = heaters[h]; + if (hnum >= 0 && hnum < HEATERS) + { + hh |= (1u << (unsigned int)hnum); + } + } + if (hh != 0) + { + platform->SetFanValue(fanNum, 1.0); // default the fan speed to full for safety + } + fan.SetHeatersMonitored(hh); + } + + if (gb.Seen('S')) // Set new fan value - process this after processing 'H' or it may not be acted on + { + const float f = constrain<float>(gb.GetFValue(), 0.0, 255.0); + if (seen || seenFanNum) + { + platform->SetFanValue(fanNum, f); + } + else + { + // We are processing an M106 S### command with no other recognised parameters and we have a tool selected. + // Apply the fan speed setting to the fans in the fan mapping for the current tool. + lastDefaultFanSpeed = f; + SetMappedFanSpeed(); + } + } + else if (gb.Seen('R')) + { + const int i = gb.GetIValue(); + switch(i) + { + case 0: + case 1: + // Restore fan speed to value when print was paused + platform->SetFanValue(fanNum, pausedFanValues[fanNum]); + break; + case 2: + // Set the speeds of mapped fans to the last known value. Fan number is ignored. + SetMappedFanSpeed(); + break; + default: + break; + } + } + else if (!seen) + { + reply.printf("Fan%i frequency: %dHz, speed: %d%%, min: %d%%, blip: %.2f, inverted: %s", + fanNum, + (int)(fan.GetPwmFrequency()), + (int)(fan.GetValue() * 100.0), + (int)(fan.GetMinValue() * 100.0), + fan.GetBlipTime(), + (fan.GetInverted()) ? "yes" : "no"); + uint16_t hh = fan.GetHeatersMonitored(); + if (hh != 0) + { + reply.catf(", trigger: %dC, heaters:", (int)fan.GetTriggerTemperature()); + for (unsigned int i = 0; i < HEATERS; ++i) + { + if ((hh & (1u << i)) != 0) + { + reply.catf(" %u", i); + } + } + } + } + } + } + break; + + case 107: // Fan off - deprecated + platform->SetFanValue(0, 0.0); //T3P3 as deprecated only applies to fan0 + break; + + case 108: // Cancel waiting for temperature + if (isWaiting) + { + cancelWait = true; + } + break; + + case 109: // Deprecated in RRF, but widely generated by slicers + { + float temperature; + if (gb.Seen('R')) + { + gb.MachineState().waitWhileCooling = true; + temperature = gb.GetFValue(); + } + else if (gb.Seen('S')) + { + gb.MachineState().waitWhileCooling = false; + temperature = gb.GetFValue(); + } + else + { + break; // no target temperature given + } + + Tool *tool; + if (gb.Seen('T')) + { + int toolNumber = gb.GetIValue(); + toolNumber += gb.GetToolNumberAdjust(); + tool = reprap.GetTool(toolNumber); + } + else + { + tool = reprap.GetCurrentOrDefaultTool(); + } + + SetToolHeaters(tool, temperature); + if (tool == nullptr) + { + break; // SetToolHeaters will already have generated an error message + } + + // M109 implies waiting for temperature to be reached, so it doesn't make sense unless the tool has been selected. + // Sadly, many slicers use M109 without selecting the tool first. So we select it here if necessary. + if (tool != reprap.GetCurrentTool()) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + + newToolNumber = tool->Number(); + StartToolChange(gb, true); + } + else + { + gb.SetState(GCodeState::m109ToolChangeComplete); + } + } + break; + + case 110: // Set line numbers - line numbers are dealt with in the GCodeBuffer class + break; + + case 111: // Debug level + if (gb.Seen('S')) + { + bool dbv = (gb.GetIValue() != 0); + if (gb.Seen('P')) + { + reprap.SetDebug(static_cast<Module>(gb.GetIValue()), dbv); + } + else + { + reprap.SetDebug(dbv); + } + } + else + { + reprap.PrintDebug(); + } + break; + + case 112: // Emergency stop - acted upon in Webserver, but also here in case it comes from USB etc. + DoEmergencyStop(); + break; + + case 114: + GetCurrentCoordinates(reply); + break; + + case 115: // Print firmware version or set hardware type + if (gb.Seen('P')) + { + platform->SetBoardType((BoardType)gb.GetIValue()); + } + else + { + reply.printf("FIRMWARE_NAME: %s FIRMWARE_VERSION: %s ELECTRONICS: %s", NAME, VERSION, platform->GetElectronicsString()); +#ifdef DUET_NG + const char* expansionName = DuetExpansion::GetExpansionBoardName(); + if (expansionName != nullptr) + { + reply.catf(" + %s", expansionName); + } +#endif + reply.catf(" FIRMWARE_DATE: %s", DATE); + } + break; + + case 116: // Wait for set temperatures + { + bool seen = false; + if (gb.Seen('P')) + { + // Wait for the heaters associated with the specified tool to be ready + int toolNumber = gb.GetIValue(); + toolNumber += gb.GetToolNumberAdjust(); + if (!cancelWait && !ToolHeatersAtSetTemperatures(reprap.GetTool(toolNumber), true)) + { + isWaiting = true; + return false; + } + seen = true; + } + + if (gb.Seen('H')) + { + // Wait for specified heaters to be ready + long heaters[HEATERS]; + size_t heaterCount = HEATERS; + gb.GetLongArray(heaters, heaterCount); + if (!cancelWait) + { + for (size_t i=0; i<heaterCount; i++) + { + if (!reprap.GetHeat()->HeaterAtSetTemperature(heaters[i], true)) + { + isWaiting = true; + return false; + } + } + } + seen = true; + } + + if (gb.Seen('C')) + { + // Wait for chamber heater to be ready + const int8_t chamberHeater = reprap.GetHeat()->GetChamberHeater(); + if (chamberHeater != -1) + { + if (!cancelWait && !reprap.GetHeat()->HeaterAtSetTemperature(chamberHeater, true)) + { + isWaiting = true; + return false; + } + } + seen = true; + } + + // Wait for all heaters to be ready + if (!seen && !cancelWait && !reprap.GetHeat()->AllHeatersAtSetTemperatures(true)) + { + isWaiting = true; + return false; + } + + // If we get here, there is nothing more to wait for + cancelWait = isWaiting = false; + } + break; + + case 117: // Display message + { + const char *msg = gb.GetUnprecedentedString(true); + reprap.SetMessage((msg == nullptr) ? "" : msg); + } + break; + + case 119: + reply.copy("Endstops - "); + for (size_t axis = 0; axis < numAxes; axis++) + { + reply.catf("%c: %s, ", axisLetters[axis], TranslateEndStopResult(platform->Stopped(axis))); + } + reply.catf("Z probe: %s", TranslateEndStopResult(platform->GetZProbeResult())); + break; + + case 120: + Push(gb); + break; + + case 121: + Pop(gb); + break; + + case 122: + { + int val = (gb.Seen('P')) ? gb.GetIValue() : 0; + if (val == 0) + { + reprap.Diagnostics(gb.GetResponseMessageType()); + } + else + { + platform->DiagnosticTest(val); + } + } + break; + + case 135: // Set PID sample interval + if (gb.Seen('S')) + { + platform->SetHeatSampleTime(gb.GetFValue() * 0.001); // Value is in milliseconds; we want seconds + } + else + { + reply.printf("Heat sample time is %.3f seconds", platform->GetHeatSampleTime()); + } + break; + + case 140: // Set bed temperature + { + int8_t bedHeater; + if (gb.Seen('H')) + { + bedHeater = gb.GetIValue(); + if (bedHeater < 0) + { + // Make sure we stay within reasonable boundaries... + bedHeater = -1; + + // If we're disabling the hot bed, make sure the old heater is turned off + reprap.GetHeat()->SwitchOff(reprap.GetHeat()->GetBedHeater()); + } + else if (bedHeater >= HEATERS) + { + reply.copy("Invalid heater number!"); + error = true; + break; + } + reprap.GetHeat()->SetBedHeater(bedHeater); + + if (bedHeater < 0) + { + // Stop here if the hot bed has been disabled + break; + } + } + else + { + bedHeater = reprap.GetHeat()->GetBedHeater(); + if (bedHeater < 0) + { + reply.copy("Hot bed is not present!"); + error = true; + break; + } + } + + if(gb.Seen('S')) + { + float temperature = gb.GetFValue(); + if (temperature < NEARLY_ABS_ZERO) + { + reprap.GetHeat()->SwitchOff(bedHeater); + } + else + { + reprap.GetHeat()->SetActiveTemperature(bedHeater, temperature); + reprap.GetHeat()->Activate(bedHeater); + } + } + if(gb.Seen('R')) + { + reprap.GetHeat()->SetStandbyTemperature(bedHeater, gb.GetFValue()); + } + } + break; + + case 141: // Chamber temperature + { + bool seen = false; + if (gb.Seen('H')) + { + seen = true; + + int heater = gb.GetIValue(); + if (heater < 0) + { + const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater(); + if (currentHeater != -1) + { + reprap.GetHeat()->SwitchOff(currentHeater); + } + + reprap.GetHeat()->SetChamberHeater(-1); + } + else if (heater < HEATERS) + { + reprap.GetHeat()->SetChamberHeater(heater); + } + else + { + reply.copy("Bad heater number specified!"); + error = true; + } + } + + if (gb.Seen('S')) + { + seen = true; + + const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater(); + if (currentHeater != -1) + { + float temperature = gb.GetFValue(); + + if (temperature < NEARLY_ABS_ZERO) + { + reprap.GetHeat()->SwitchOff(currentHeater); + } + else + { + reprap.GetHeat()->SetActiveTemperature(currentHeater, temperature); + reprap.GetHeat()->Activate(currentHeater); + } + } + else + { + reply.copy("No chamber heater has been set up yet!"); + error = true; + } + } + + if (!seen) + { + const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater(); + if (currentHeater != -1) + { + reply.printf("Chamber heater %d is currently at %.1fC", currentHeater, reprap.GetHeat()->GetTemperature(currentHeater)); + } + else + { + reply.copy("No chamber heater has been configured yet."); + } + } + } + break; + + case 143: // Set temperature limit + { + const int heater = (gb.Seen('H')) ? gb.GetIValue() : 1; // default to extruder 1 if no heater number provided + if (heater < 0 || heater >= HEATERS) + { + reply.copy("Invalid heater number"); + error = true; + } + else if (gb.Seen('S')) + { + const float limit = gb.GetFValue(); + if (limit > BAD_LOW_TEMPERATURE && limit < BAD_ERROR_TEMPERATURE) + { + reprap.GetHeat()->SetTemperatureLimit(heater, limit); + } + else + { + reply.copy("Invalid temperature limit"); + error = true; + } + } + else + { + reply.printf("Temperature limit for heater %d is %.1fC", heater, reprap.GetHeat()->GetTemperatureLimit(heater)); + } + } + break; + + case 144: // Set bed to standby + { + const int8_t bedHeater = reprap.GetHeat()->GetBedHeater(); + if (bedHeater >= 0) + { + reprap.GetHeat()->Standby(bedHeater); + } + } + break; + + case 190: // Set bed temperature and wait + case 191: // Set chamber temperature and wait + { + const int8_t heater = (code == 191) ? reprap.GetHeat()->GetChamberHeater() : reprap.GetHeat()->GetBedHeater(); + if (heater >= 0) + { + float temperature; + bool waitWhenCooling; + if (gb.Seen('R')) + { + waitWhenCooling = true; + temperature = gb.GetFValue(); + } + else if (gb.Seen('S')) + { + waitWhenCooling = false; + temperature = gb.GetFValue(); + } + else + { + break; // no target temperature given + } + + reprap.GetHeat()->SetActiveTemperature(heater, temperature); + reprap.GetHeat()->Activate(heater); + if (cancelWait || reprap.GetHeat()->HeaterAtSetTemperature(heater, waitWhenCooling)) + { + cancelWait = isWaiting = false; + break; + } + // In Marlin emulation mode we should return some sort of (undocumented) message here every second... + isWaiting = true; + return false; + } + } + break; + + case 201: // Set/print axis accelerations + { + bool seen = false; + for (size_t axis = 0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + platform->SetAcceleration(axis, gb.GetFValue() * distanceScale); + seen = true; + } + } + + if (gb.Seen(extrudeLetter)) + { + seen = true; + float eVals[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetFloatArray(eVals, eCount, true); + for (size_t e = 0; e < eCount; e++) + { + platform->SetAcceleration(numAxes + e, eVals[e] * distanceScale); + } + } + + if (gb.Seen('P')) + { + // Set max average printing acceleration + platform->SetMaxAverageAcceleration(gb.GetFValue() * distanceScale); + seen = true; + } + + if (!seen) + { + reply.printf("Accelerations: "); + for (size_t axis = 0; axis < numAxes; ++axis) + { + reply.catf("%c: %.1f, ", axisLetters[axis], platform->Acceleration(axis) / distanceScale); + } + reply.cat("E:"); + char sep = ' '; + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + reply.catf("%c%.1f", sep, platform->Acceleration(extruder + numAxes) / distanceScale); + sep = ':'; + } + reply.catf(", avg. printing: %.1f", platform->GetMaxAverageAcceleration()); + } + } + break; + + case 203: // Set/print maximum feedrates + { + bool seen = false; + for (size_t axis = 0; axis < numAxes; ++axis) + { + if (gb.Seen(axisLetters[axis])) + { + platform->SetMaxFeedrate(axis, gb.GetFValue() * distanceScale * secondsToMinutes); // G Code feedrates are in mm/minute; we need mm/sec + seen = true; + } + } + + if (gb.Seen(extrudeLetter)) + { + seen = true; + float eVals[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetFloatArray(eVals, eCount, true); + for (size_t e = 0; e < eCount; e++) + { + platform->SetMaxFeedrate(numAxes + e, eVals[e] * distanceScale * secondsToMinutes); + } + } + + if (!seen) + { + reply.copy("Maximum feedrates: "); + for (size_t axis = 0; axis < numAxes; ++axis) + { + reply.catf("%c: %.1f, ", axisLetters[axis], platform->MaxFeedrate(axis) / (distanceScale * secondsToMinutes)); + } + reply.cat("E:"); + char sep = ' '; + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + reply.catf("%c%.1f", sep, platform->MaxFeedrate(extruder + numAxes) / (distanceScale * secondsToMinutes)); + sep = ':'; + } + } + } + break; + + case 205: //M205 advanced settings: minimum travel speed S=while printing T=travel only, B=minimum segment time X= maximum xy jerk, Z=maximum Z jerk + // This is superseded in this firmware by M codes for the separate types (e.g. M566). + break; + + case 206: // Offset axes - Deprecated + result = OffsetAxes(gb); + break; + + case 207: // Set firmware retraction details + { + bool seen = false; + if (gb.Seen('S')) + { + retractLength = max<float>(gb.GetFValue(), 0.0); + seen = true; + } + if (gb.Seen('R')) + { + retractExtra = max<float>(gb.GetFValue(), -retractLength); + seen = true; + } + if (gb.Seen('F')) + { + unRetractSpeed = retractSpeed = max<float>(gb.GetFValue(), 60.0); + seen = true; + } + if (gb.Seen('T')) // must do this one after 'F' + { + unRetractSpeed = max<float>(gb.GetFValue(), 60.0); + seen = true; + } + if (gb.Seen('Z')) + { + retractHop = max<float>(gb.GetFValue(), 0.0); + seen = true; + } + if (!seen) + { + reply.printf("Retraction settings: length %.2f/%.2fmm, speed %d/%dmm/min, Z hop %.2fmm", + retractLength, retractLength + retractExtra, (int)retractSpeed, (int)unRetractSpeed, retractHop); + } + } + break; + + case 208: // Set/print maximum axis lengths. If there is an S parameter with value 1 then we set the min value, else we set the max value. + { + bool setMin = (gb.Seen('S') ? (gb.GetIValue() == 1) : false); + bool seen = false; + for (size_t axis = 0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + float value = gb.GetFValue() * distanceScale; + if (setMin) + { + platform->SetAxisMinimum(axis, value); + } + else + { + platform->SetAxisMaximum(axis, value); + } + seen = true; + } + } + + if (!seen) + { + reply.copy("Axis limits "); + char sep = '-'; + for (size_t axis = 0; axis < numAxes; axis++) + { + reply.catf("%c %c: %.1f min, %.1f max", sep, axisLetters[axis], platform->AxisMinimum(axis), + platform->AxisMaximum(axis)); + sep = ','; + } + } + } + break; + + case 210: // Set/print homing feed rates + // This is no longer used, but for backwards compatibility we don't report an error + break; + + case 220: // Set/report speed factor override percentage + if (gb.Seen('S')) + { + float newSpeedFactor = (gb.GetFValue() / 100.0) * secondsToMinutes; // include the conversion from mm/minute to mm/second + if (newSpeedFactor > 0.0) + { + gb.MachineState().feedrate *= newSpeedFactor / speedFactor; + if (segmentsLeft != 0 && !moveBuffer.isFirmwareRetraction) + { + // The last move has not gone yet, so we can update it + moveBuffer.feedRate *= newSpeedFactor / speedFactor; + } + speedFactor = newSpeedFactor; + } + else + { + reply.printf("Invalid speed factor specified."); + error = true; + } + } + else + { + reply.printf("Speed factor override: %.1f%%", speedFactor * minutesToSeconds * 100.0); + } + break; + + case 221: // Set/report extrusion factor override percentage + { + int32_t extruder = 0; + bool dummy; + gb.TryGetIValue('D', extruder, dummy); + + if (extruder >= 0 && extruder < (int32_t)numExtruders) + { + if (gb.Seen('S')) // S parameter sets the override percentage + { + const float extrusionFactor = gb.GetFValue() / 100.0; + if (extrusionFactor >= 0.0) + { + if (segmentsLeft != 0 && !moveBuffer.isFirmwareRetraction) + { + moveBuffer.coords[extruder + numAxes] *= extrusionFactor/extrusionFactors[extruder]; // last move not gone, so update it + } + extrusionFactors[extruder] = extrusionFactor; + } + } + else + { + reply.printf("Extrusion factor override for extruder %d: %.1f%%", extruder, extrusionFactors[extruder] * 100.0); + } + } + } + break; + + // For case 226, see case 25 + + case 280: // Servos + if (gb.Seen('P')) + { + const int servoIndex = gb.GetIValue(); + Pin servoPin; + bool invert; + if (platform->GetFirmwarePin(servoIndex, PinAccess::servo, servoPin, invert)) + { + if (gb.Seen('I') && gb.GetIValue() > 0) + { + invert = !invert; + } + + if (gb.Seen('S')) + { + float angleOrWidth = gb.GetFValue(); + if (angleOrWidth < 0.0) + { + // Disable the servo by setting the pulse width to zero + Platform::WriteAnalog(servoPin, (invert) ? 1.0 : 0.0, ServoRefreshFrequency); + } + else + { + if (angleOrWidth < MinServoPulseWidth) + { + // User gave an angle so convert it to a pulse width in microseconds + angleOrWidth = (min<float>(angleOrWidth, 180.0) * ((MaxServoPulseWidth - MinServoPulseWidth) / 180.0)) + MinServoPulseWidth; + } + else if (angleOrWidth > MaxServoPulseWidth) + { + angleOrWidth = MaxServoPulseWidth; + } + float pwm = angleOrWidth * (ServoRefreshFrequency/1e6); + if (invert) + { + pwm = 1.0 - pwm; + } + Platform::WriteAnalog(servoPin, pwm, ServoRefreshFrequency); + } + } + // We don't currently allow the servo position to be read back + } + else + { + platform->MessageF(GENERIC_MESSAGE, "Error: Invalid servo index %d in M280 command\n", servoIndex); + } + } + break; + + case 300: // Beep + { + const int ms = (gb.Seen('P')) ? gb.GetIValue() : 1000; // time in milliseconds + const int freq = (gb.Seen('S')) ? gb.GetIValue() : 4600; // 4600Hz produces the loudest sound on a PanelDue + reprap.Beep(freq, ms); + } + break; + + case 301: // Set/report hot end PID values + SetPidParameters(gb, 1, reply); + break; + + case 302: // Allow, deny or report cold extrudes + if (gb.Seen('P')) + { + reprap.GetHeat()->AllowColdExtrude(gb.GetIValue() > 0); + } + else + { + reply.printf("Cold extrusion is %s, use M302 P[1/0] to allow/deny it", + reprap.GetHeat()->ColdExtrude() ? "allowed" : "denied"); + } + break; + + case 303: // Run PID tuning + if (gb.Seen('H')) + { + const size_t heater = gb.GetIValue(); + const float temperature = (gb.Seen('S')) ? gb.GetFValue() : 225.0; + const float maxPwm = (gb.Seen('P')) ? gb.GetFValue() : 0.5; + if (heater < HEATERS && maxPwm >= 0.1 && maxPwm <= 1.0 && temperature >= 55.0 && temperature <= reprap.GetHeat()->GetTemperatureLimit(heater)) + { + reprap.GetHeat()->StartAutoTune(heater, temperature, maxPwm, reply); + } + else + { + reply.printf("Bad parameter in M303 command"); + } + } + else + { + reprap.GetHeat()->GetAutoTuneStatus(reply); + } + break; + + case 304: // Set/report heated bed PID values + { + const int8_t bedHeater = reprap.GetHeat()->GetBedHeater(); + if (bedHeater >= 0) + { + SetPidParameters(gb, bedHeater, reply); + } + } + break; + + case 305: // Set/report specific heater parameters + SetHeaterParameters(gb, reply); + break; + + case 307: // Set heater process model parameters + if (gb.Seen('H')) + { + size_t heater = gb.GetIValue(); + if (heater < HEATERS) + { + const FopDt& model = reprap.GetHeat()->GetHeaterModel(heater); + bool seen = false; + float gain = model.GetGain(), + tc = model.GetTimeConstant(), + td = model.GetDeadTime(), + maxPwm = model.GetMaxPwm(); + int32_t dontUsePid = model.UsePid() ? 0 : 1; + + gb.TryGetFValue('A', gain, seen); + gb.TryGetFValue('C', tc, seen); + gb.TryGetFValue('D', td, seen); + gb.TryGetIValue('B', dontUsePid, seen); + gb.TryGetFValue('S', maxPwm, seen); + + if (seen) + { + if (reprap.GetHeat()->SetHeaterModel(heater, gain, tc, td, maxPwm, dontUsePid == 0)) + { + reprap.GetHeat()->UseModel(heater, true); + } + else + { + reply.copy("Error: bad model parameters"); + } + } + else if (!model.IsEnabled()) + { + reply.printf("Heater %u is disabled", heater); + } + else + { + reply.printf("Heater %u model: gain %.1f, time constant %.1f, dead time %.1f, max PWM %.2f, in use: %s, mode: %s", + heater, model.GetGain(), model.GetTimeConstant(), model.GetDeadTime(), model.GetMaxPwm(), + (reprap.GetHeat()->IsModelUsed(heater)) ? "yes" : "no", + (model.UsePid()) ? "PID" : "bang-bang"); + if (model.UsePid()) + { + // When reporting the PID parameters, we scale them by 255 for compatibility with older firmware and other firmware + const PidParams& spParams = model.GetPidParameters(false); + const float scaledSpKp = 255.0 * spParams.kP; + reply.catf("\nSetpoint change: P%.1f, I%.2f, D%.1f", + scaledSpKp, scaledSpKp * spParams.recipTi, scaledSpKp * spParams.tD); + const PidParams& ldParams = model.GetPidParameters(true); + const float scaledLoadKp = 255.0 * ldParams.kP; + reply.catf("\nLoad change: P%.1f, I%.2f, D%.1f", + scaledLoadKp, scaledLoadKp * ldParams.recipTi, scaledLoadKp * ldParams.tD); + } + } + } + } + break; + + case 350: // Set/report microstepping + { + // interp is currently an int not a bool, because we use special values of interp to set the chopper control register + int32_t interp = 0; + bool dummy; + gb.TryGetIValue('I', interp, dummy); + + bool seen = false; + for (size_t axis = 0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + seen = true; + const int microsteps = gb.GetIValue(); + if (ChangeMicrostepping(axis, microsteps, interp)) + { + SetAxisNotHomed(axis); + } + else + { + platform->MessageF(GENERIC_MESSAGE, "Drive %c does not support %dx microstepping%s\n", + axisLetters[axis], microsteps, (interp) ? " with interpolation" : ""); + } + } + } + + if (gb.Seen(extrudeLetter)) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + seen = true; + long eVals[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetLongArray(eVals, eCount); + for (size_t e = 0; e < eCount; e++) + { + if (!ChangeMicrostepping(numAxes + e, (int)eVals[e], interp)) + { + platform->MessageF(GENERIC_MESSAGE, "Drive E%u does not support %dx microstepping%s\n", + e, (int)eVals[e], (interp) ? " with interpolation" : ""); + } + } + } + + if (!seen) + { + reply.copy("Microstepping - "); + for (size_t axis = 0; axis < numAxes; ++axis) + { + bool interp; + const int microsteps = platform->GetMicrostepping(axis, interp); + reply.catf("%c:%d%s, ", axisLetters[axis], microsteps, (interp) ? "(on)" : ""); + } + reply.cat("E"); + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + bool interp; + const int microsteps = platform->GetMicrostepping(extruder + numAxes, interp); + reply.catf(":%d%s", microsteps, (interp) ? "(on)" : ""); + } + } + } + break; + + case 400: // Wait for current moves to finish + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + break; + + case 404: // Filament width and nozzle diameter + { + bool seen = false; + + if (gb.Seen('N')) + { + platform->SetFilamentWidth(gb.GetFValue()); + seen = true; + } + if (gb.Seen('D')) + { + platform->SetNozzleDiameter(gb.GetFValue()); + seen = true; + } + + if (!seen) + { + reply.printf("Filament width: %.2fmm, nozzle diameter: %.2fmm", platform->GetFilamentWidth(), platform->GetNozzleDiameter()); + } + } + break; + + case 408: // Get status in JSON format + { + int type = gb.Seen('S') ? gb.GetIValue() : 0; + int seq = gb.Seen('R') ? gb.GetIValue() : -1; + + OutputBuffer *statusResponse = nullptr; + switch (type) + { + case 0: + case 1: + statusResponse = reprap.GetLegacyStatusResponse(type + 2, seq); + break; + + case 2: + case 3: + case 4: + statusResponse = reprap.GetStatusResponse(type - 1, (&gb == auxGCode) ? ResponseSource::AUX : ResponseSource::Generic); + break; + + case 5: + statusResponse = reprap.GetConfigResponse(); + break; + } + + if (statusResponse != nullptr) + { + UnlockAll(gb); + statusResponse->cat('\n'); + HandleReply(gb, false, statusResponse); + return true; + } + } + break; + + case 500: // Store parameters in EEPROM + reprap.GetPlatform()->WriteNvData(); + break; + + case 501: // Load parameters from EEPROM + reprap.GetPlatform()->ReadNvData(); + if (gb.Seen('S')) + { + reprap.GetPlatform()->SetAutoSave(gb.GetIValue() > 0); + } + break; + + case 502: // Revert to default "factory settings" + reprap.GetPlatform()->ResetNvData(); + break; + + case 503: // List variable settings + { + if (!LockFileSystem(gb)) + { + return false; + } + + // Need a valid output buffer to continue... + OutputBuffer *configResponse; + if (!OutputBuffer::Allocate(configResponse)) + { + // No buffer available, try again later + return false; + } + + // Read the entire file + FileStore * const f = platform->GetFileStore(platform->GetSysDir(), platform->GetConfigFile(), false); + if (f == nullptr) + { + error = true; + reply.copy("Configuration file not found!"); + } + else + { + char fileBuffer[FILE_BUFFER_SIZE]; + size_t bytesRead, + bytesLeftForWriting = OutputBuffer::GetBytesLeft(configResponse); + while ((bytesRead = f->Read(fileBuffer, FILE_BUFFER_SIZE)) > 0 && bytesLeftForWriting > 0) + { + // Don't write more data than we can process + if (bytesRead < bytesLeftForWriting) + { + bytesLeftForWriting -= bytesRead; + } + else + { + bytesRead = bytesLeftForWriting; + bytesLeftForWriting = 0; + } + + // Write it + configResponse->cat(fileBuffer, bytesRead); + } + f->Close(); + + UnlockAll(gb); + HandleReply(gb, false, configResponse); + return true; + } + } + break; + + case 540: // Set/report MAC address + if (gb.Seen('P')) + { + SetMACAddress(gb); + } + else + { + const byte* mac = platform->MACAddress(); + reply.printf("MAC: %x:%x:%x:%x:%x:%x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } + break; + + case 550: // Set/report machine name + if (gb.Seen('P')) + { + reprap.SetName(gb.GetString()); + } + else + { + reply.printf("RepRap name: %s", reprap.GetName()); + } + break; + + case 551: // Set password (no option to report it) + if (gb.Seen('P')) + { + reprap.SetPassword(gb.GetString()); + } + break; + + case 552: // Enable/Disable network and/or Set/Get IP address + { + bool seen = false; + if (gb.Seen('P')) + { + seen = true; + SetEthernetAddress(gb, code); + } + + if (gb.Seen('R')) + { + reprap.GetNetwork()->SetHttpPort(gb.GetIValue()); + seen = true; + } + + // Process this one last in case the IP address is changed and the network enabled in the same command + if (gb.Seen('S')) // Has the user turned the network on or off? + { + seen = true; + if (gb.GetIValue() != 0) + { + reprap.GetNetwork()->Enable(); + } + else + { + reprap.GetNetwork()->Disable(); + } + } + + if (!seen) + { + const byte *config_ip = platform->IPAddress(); + const byte *actual_ip = reprap.GetNetwork()->IPAddress(); + reply.printf("Network is %s, configured IP address: %d.%d.%d.%d, actual IP address: %d.%d.%d.%d, HTTP port: %d", + reprap.GetNetwork()->IsEnabled() ? "enabled" : "disabled", + config_ip[0], config_ip[1], config_ip[2], config_ip[3], actual_ip[0], actual_ip[1], actual_ip[2], actual_ip[3], + reprap.GetNetwork()->GetHttpPort()); + } + } + break; + + case 553: // Set/Get netmask + if (gb.Seen('P')) + { + SetEthernetAddress(gb, code); + } + else + { + const byte *nm = platform->NetMask(); + reply.printf("Net mask: %d.%d.%d.%d ", nm[0], nm[1], nm[2], nm[3]); + } + break; + + case 554: // Set/Get gateway + if (gb.Seen('P')) + { + SetEthernetAddress(gb, code); + } + else + { + const byte *gw = platform->GateWay(); + reply.printf("Gateway: %d.%d.%d.%d ", gw[0], gw[1], gw[2], gw[3]); + } + break; + + case 555: // Set/report firmware type to emulate + if (gb.Seen('P')) + { + platform->SetEmulating((Compatibility) gb.GetIValue()); + } + else + { + reply.copy("Emulating "); + switch (platform->Emulating()) + { + case me: + case reprapFirmware: + reply.cat("RepRap Firmware (i.e. in native mode)"); + break; + + case marlin: + reply.cat("Marlin"); + break; + + case teacup: + reply.cat("Teacup"); + break; + + case sprinter: + reply.cat("Sprinter"); + break; + + case repetier: + reply.cat("Repetier"); + break; + + default: + reply.catf("Unknown: (%d)", platform->Emulating()); + } + } + break; + + case 556: // Axis compensation (we support only X, Y, Z) + if (gb.Seen('S')) + { + float value = gb.GetFValue(); + for (size_t axis = 0; axis <= Z_AXIS; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + reprap.GetMove()->SetAxisCompensation(axis, gb.GetFValue() / value); + } + } + } + else + { + reply.printf("Axis compensations - XY: %.5f, YZ: %.5f, ZX: %.5f", + reprap.GetMove()->AxisCompensation(X_AXIS), reprap.GetMove()->AxisCompensation(Y_AXIS), + reprap.GetMove()->AxisCompensation(Z_AXIS)); + } + break; + + case 557: // Set/report Z probe point coordinates + if (gb.Seen('P')) + { + const int point = gb.GetIValue(); + if (point < 0 || (unsigned int)point >= MaxProbePoints) + { + reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Z probe point index out of range.\n"); + } + else + { + bool seen = false; + if (gb.Seen(axisLetters[X_AXIS])) + { + reprap.GetMove()->SetXBedProbePoint(point, gb.GetFValue()); + seen = true; + } + if (gb.Seen(axisLetters[Y_AXIS])) + { + reprap.GetMove()->SetYBedProbePoint(point, gb.GetFValue()); + seen = true; + } + + if (!seen) + { + reply.printf("Probe point %d - [%.1f, %.1f]", point, reprap.GetMove()->XBedProbePoint(point), reprap.GetMove()->YBedProbePoint(point)); + } + } + } + else + { + LockMovement(gb); // to ensure that probing is not already in progress + error = DefineGrid(gb, reply); + } + break; + + case 558: // Set or report Z probe type and for which axes it is used + { + bool seenAxes = false, seenType = false, seenParam = false; + uint32_t zProbeAxes = platform->GetZProbeAxes(); + for (size_t axis = 0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + if (gb.GetIValue() > 0) + { + zProbeAxes |= (1u << axis); + } + else + { + zProbeAxes &= ~(1u << axis); + } + seenAxes = true; + } + } + if (seenAxes) + { + platform->SetZProbeAxes(zProbeAxes); + } + + // We must get and set the Z probe type first before setting the dive height etc., because different probe types may have different parameters + if (gb.Seen('P')) // probe type + { + platform->SetZProbeType(gb.GetIValue()); + seenType = true; + } + + ZProbeParameters params = platform->GetZProbeParameters(); + gb.TryGetFValue('H', params.diveHeight, seenParam); // dive height + + if (gb.Seen('F')) // feed rate i.e. probing speed + { + params.probeSpeed = gb.GetFValue() * secondsToMinutes; + seenParam = true; + } + + if (gb.Seen('T')) // travel speed to probe point + { + params.travelSpeed = gb.GetFValue() * secondsToMinutes; + seenParam = true; + } + + if (gb.Seen('I')) + { + params.invertReading = (gb.GetIValue() != 0); + seenParam = true; + } + + gb.TryGetFValue('R', params.recoveryTime, seenParam); // Z probe recovery time + gb.TryGetFValue('S', params.extraParam, seenParam); // extra parameter for experimentation + + if (seenParam) + { + platform->SetZProbeParameters(params); + } + + if (!(seenAxes || seenType || seenParam)) + { + reply.printf("Z Probe type %d, invert %s, dive height %.1fmm, probe speed %dmm/min, travel speed %dmm/min, recovery time %.2f sec", + platform->GetZProbeType(), (params.invertReading) ? "yes" : "no", params.diveHeight, + (int)(params.probeSpeed * minutesToSeconds), (int)(params.travelSpeed * minutesToSeconds), params.recoveryTime); + if (platform->GetZProbeType() == ZProbeTypeDelta) + { + reply.catf(", extra parameter %.2f", params.extraParam); + } + reply.cat(", used for axes:"); + for (size_t axis = 0; axis < numAxes; axis++) + { + if ((zProbeAxes & (1u << axis)) != 0) + { + reply.catf(" %c", axisLetters[axis]); + } + } + } + } + break; + + case 559: // Upload config.g or another gcode file to put in the sys directory + { + const char* str = (gb.Seen('P') ? gb.GetString() : platform->GetConfigFile()); + const bool ok = OpenFileToWrite(gb, platform->GetSysDir(), str); + if (ok) + { + reply.printf("Writing to file: %s", str); + } + else + { + reply.printf("Can't open file %s for writing.", str); + error = true; + } + } + break; + + case 560: // Upload reprap.htm or another web interface file + { + const char* str = (gb.Seen('P') ? gb.GetString() : INDEX_PAGE_FILE); + const bool ok = OpenFileToWrite(gb, platform->GetWebDir(), str); + if (ok) + { + reply.printf("Writing to file: %s", str); + } + else + { + reply.printf("Can't open file %s for writing.", str); + error = true; + } + } + break; + + case 561: + reprap.GetMove()->SetIdentityTransform(); + break; + + case 562: // Reset temperature fault - use with great caution + if (gb.Seen('P')) + { + const int heater = gb.GetIValue(); + if (heater >= 0 && heater < HEATERS) + { + reprap.ClearTemperatureFault(heater); + } + else + { + reply.copy("Invalid heater number.\n"); + error = true; + } + } + break; + + case 563: // Define tool + ManageTool(gb, reply); + break; + + case 564: // Think outside the box? + if (gb.Seen('S')) + { + limitAxes = (gb.GetIValue() != 0); + } + else + { + reply.printf("Movement outside the bed is %spermitted", (limitAxes) ? "not " : ""); + } + break; + + case 566: // Set/print maximum jerk speeds + { + bool seen = false; + for (size_t axis = 0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + platform->SetInstantDv(axis, gb.GetFValue() * distanceScale * secondsToMinutes); // G Code feedrates are in mm/minute; we need mm/sec + seen = true; + } + } + + if (gb.Seen(extrudeLetter)) + { + seen = true; + float eVals[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetFloatArray(eVals, eCount, true); + for (size_t e = 0; e < eCount; e++) + { + platform->SetInstantDv(numAxes + e, eVals[e] * distanceScale * secondsToMinutes); + } + } + else if (!seen) + { + reply.copy("Maximum jerk rates: "); + for (size_t axis = 0; axis < numAxes; ++axis) + { + reply.catf("%c: %.1f, ", axisLetters[axis], platform->ConfiguredInstantDv(axis) / (distanceScale * secondsToMinutes)); + } + reply.cat("E:"); + char sep = ' '; + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + reply.catf("%c%.1f", sep, platform->ConfiguredInstantDv(extruder + numAxes) / (distanceScale * secondsToMinutes)); + sep = ':'; + } + } + } + break; + + case 567: // Set/report tool mix ratios + if (gb.Seen('P')) + { + int8_t tNumber = gb.GetIValue(); + Tool* const tool = reprap.GetTool(tNumber); + if (tool != NULL) + { + if (gb.Seen(extrudeLetter)) + { + float eVals[MaxExtruders]; + size_t eCount = tool->DriveCount(); + gb.GetFloatArray(eVals, eCount, false); + if (eCount != tool->DriveCount()) + { + reply.printf("Setting mix ratios - wrong number of E drives: %s", gb.Buffer()); + } + else + { + tool->DefineMix(eVals); + } + } + else + { + reply.printf("Tool %d mix ratios:", tNumber); + char sep = ' '; + for (size_t drive = 0; drive < tool->DriveCount(); drive++) + { + reply.catf("%c%.3f", sep, tool->GetMix()[drive]); + sep = ':'; + } + } + } + } + break; + + case 568: // Turn on/off automatic tool mixing + if (gb.Seen('P')) + { + Tool* const tool = reprap.GetTool(gb.GetIValue()); + if (tool != NULL) + { + if (gb.Seen('S')) + { + tool->SetMixing(gb.GetIValue() != 0); + } + else + { + reply.printf("Tool %d mixing is %s", tool->Number(), (tool->GetMixing()) ? "enabled" : "disabled"); + } + } + } + break; + + case 569: // Set/report axis direction + if (gb.Seen('P')) + { + const size_t drive = gb.GetIValue(); + if (drive < DRIVES) + { + bool seen = false; + if (gb.Seen('S')) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + platform->SetDirectionValue(drive, gb.GetIValue() != 0); + seen = true; + } + if (gb.Seen('R')) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + platform->SetEnableValue(drive, gb.GetIValue() != 0); + seen = true; + } + if (gb.Seen('T')) + { + platform->SetDriverStepTiming(drive, gb.GetFValue()); + seen = true; + } + bool badParameter = false; + for (size_t axis = 0; axis < numAxes; ++axis) + { + if (gb.Seen(axisLetters[axis])) + { + badParameter = true; + } + } + if (gb.Seen(extrudeLetter)) + { + badParameter = true; + } + if (badParameter) + { + platform->Message(GENERIC_MESSAGE, "Error: M569 no longer accepts XYZE parameters; use M584 instead\n"); + } + else if (!seen) + { + reply.printf("A %d sends drive %u forwards, a %d enables it, and the minimum pulse width is %.1f microseconds", + (int)platform->GetDirectionValue(drive), drive, + (int)platform->GetEnableValue(drive), + platform->GetDriverStepTiming(drive)); + } + } + } + break; + + case 570: // Set/report heater timeout + if (gb.Seen('H')) + { + const size_t heater = gb.GetIValue(); + bool seen = false; + if (heater < HEATERS) + { + float maxTempExcursion, maxFaultTime; + reprap.GetHeat()->GetHeaterProtection(heater, maxTempExcursion, maxFaultTime); + gb.TryGetFValue('P', maxFaultTime, seen); + gb.TryGetFValue('T', maxTempExcursion, seen); + if (seen) + { + reprap.GetHeat()->SetHeaterProtection(heater, maxTempExcursion, maxFaultTime); + } + else + { + reply.printf("Heater %u allowed excursion %.1fC, fault trigger time %.1f seconds", heater, maxTempExcursion, maxFaultTime); + } + } + } + else if (gb.Seen('S')) + { + reply.copy("M570 S parameter is no longer required or supported"); + } + break; + + case 571: // Set output on extrude + { + bool seen = false; + if (gb.Seen('P')) + { + const int pwmPin = gb.GetIValue(); + if (!platform->SetExtrusionAncilliaryPwmPin(pwmPin)) + { + reply.printf("Logical pin %d is already in use or not available for use by M571", pwmPin); + break; // don't process 'S' parameter if the pin was wrong + } + seen = true; + } + if (gb.Seen('S')) + { + platform->SetExtrusionAncilliaryPwmValue(gb.GetFValue()); + seen = true; + } + if (!seen) + { + reply.printf("Extrusion ancillary PWM %.3f on pin %u", + platform->GetExtrusionAncilliaryPwmValue(), platform->GetExtrusionAncilliaryPwmPin()); + } + } + break; + + case 572: // Set/report elastic compensation + if (gb.Seen('D')) + { + // New usage: specify the extruder drive using the D parameter + const size_t extruder = gb.GetIValue(); + if (gb.Seen('S')) + { + platform->SetPressureAdvance(extruder, gb.GetFValue()); + } + else + { + reply.printf("Pressure advance for extruder %u is %.3f seconds", extruder, platform->GetPressureAdvance(extruder)); + } + } + break; + + case 573: // Report heater average PWM + if (gb.Seen('P')) + { + const int heater = gb.GetIValue(); + if (heater >= 0 && heater < HEATERS) + { + reply.printf("Average heater %d PWM: %.3f", heater, reprap.GetHeat()->GetAveragePWM(heater)); + } + } + break; + + case 574: // Set endstop configuration + { + bool seen = false; + const bool logicLevel = (gb.Seen('S')) ? (gb.GetIValue() != 0) : true; + for (size_t axis = 0; axis < numAxes; ++axis) + { + if (gb.Seen(axisLetters[axis])) + { + const int ival = gb.GetIValue(); + if (ival >= 0 && ival <= 3) + { + platform->SetEndStopConfiguration(axis, (EndStopType) ival, logicLevel); + seen = true; + } + } + } + if (!seen) + { + reply.copy("Endstop configuration:"); + EndStopType config; + bool logic; + for (size_t axis = 0; axis < numAxes; ++axis) + { + platform->GetEndStopConfiguration(axis, config, logic); + reply.catf(" %c %s (active %s),", axisLetters[axis], + (config == EndStopType::highEndStop) ? "high end" : (config == EndStopType::lowEndStop) ? "low end" : "none", + (config == EndStopType::noEndStop) ? "" : (logic) ? "high" : "low"); + } + } + } + break; + + case 575: // Set communications parameters + if (gb.Seen('P')) + { + size_t chan = gb.GetIValue(); + if (chan < NUM_SERIAL_CHANNELS) + { + bool seen = false; + if (gb.Seen('B')) + { + platform->SetBaudRate(chan, gb.GetIValue()); + seen = true; + } + if (gb.Seen('S')) + { + uint32_t val = gb.GetIValue(); + platform->SetCommsProperties(chan, val); + switch (chan) + { + case 0: + serialGCode->SetCommsProperties(val); + break; + case 1: + auxGCode->SetCommsProperties(val); + break; + default: + break; + } + seen = true; + } + if (!seen) + { + uint32_t cp = platform->GetCommsProperties(chan); + reply.printf("Channel %d: baud rate %d, %s checksum", chan, platform->GetBaudRate(chan), + (cp & 1) ? "requires" : "does not require"); + } + } + } + break; + + case 577: // Wait until endstop is triggered + if (gb.Seen('S')) + { + // Determine trigger type + EndStopHit triggerCondition; + switch (gb.GetIValue()) + { + case 1: + triggerCondition = EndStopHit::lowHit; + break; + case 2: + triggerCondition = EndStopHit::highHit; + break; + case 3: + triggerCondition = EndStopHit::lowNear; + break; + default: + triggerCondition = EndStopHit::noStop; + break; + } + + // Axis endstops + for (size_t axis=0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + if (platform->Stopped(axis) != triggerCondition) + { + result = false; + break; + } + } + } + + // Extruder drives + size_t eDriveCount = MaxExtruders; + long eDrives[MaxExtruders]; + if (gb.Seen(extrudeLetter)) + { + gb.GetLongArray(eDrives, eDriveCount); + for(size_t extruder = 0; extruder < eDriveCount; extruder++) + { + const size_t eDrive = eDrives[extruder]; + if (eDrive >= MaxExtruders) + { + reply.copy("Invalid extruder drive specified!"); + error = result = true; + break; + } + + if (platform->Stopped(eDrive + E0_AXIS) != triggerCondition) + { + result = false; + break; + } + } + } + } + break; + +#if SUPPORT_INKJET + case 578: // Fire Inkjet bits + if (!AllMovesAreFinishedAndMoveBufferIsLoaded()) + { + return false; + } + + if (gb.Seen('S')) // Need to handle the 'P' parameter too; see http://reprap.org/wiki/G-code#M578:_Fire_inkjet_bits + { + platform->Inkjet(gb.GetIValue()); + } + break; +#endif + + case 579: // Scale Cartesian axes (mostly for Delta configurations) + { + bool seen = false; + for (size_t axis = 0; axis < numAxes; axis++) + { + gb.TryGetFValue(axisLetters[axis], axisScaleFactors[axis], seen); + } + + if (!seen) + { + char sep = ':'; + reply.copy("Axis scale factors"); + for(size_t axis = 0; axis < numAxes; axis++) + { + reply.catf("%c %c: %.3f", sep, axisLetters[axis], axisScaleFactors[axis]); + sep = ','; + } + } + } + break; + +#if SUPPORT_ROLAND + case 580: // (De)Select Roland mill + if (gb.Seen('R')) + { + if (gb.GetIValue()) + { + reprap.GetRoland()->Activate(); + if (gb.Seen('P')) + { + result = reprap.GetRoland()->RawWrite(gb.GetString()); + } + } + else + { + result = reprap.GetRoland()->Deactivate(); + } + } + else + { + reply.printf("Roland is %s.", reprap.GetRoland()->Active() ? "active" : "inactive"); + } + break; +#endif + + case 581: // Configure external trigger + case 582: // Check external trigger + if (gb.Seen('T')) + { + unsigned int triggerNumber = gb.GetIValue(); + if (triggerNumber < MaxTriggers) + { + if (code == 582) + { + uint32_t states = platform->GetAllEndstopStates(); + if ((triggers[triggerNumber].rising & states) != 0 || (triggers[triggerNumber].falling & ~states) != 0) + { + triggersPending |= (1u << triggerNumber); + } + } + else + { + bool seen = false; + if (gb.Seen('C')) + { + seen = true; + triggers[triggerNumber].condition = gb.GetIValue(); + } + else if (triggers[triggerNumber].IsUnused()) + { + triggers[triggerNumber].condition = 0; // this is a new trigger, so set no condition + } + if (gb.Seen('S')) + { + seen = true; + int sval = gb.GetIValue(); + TriggerMask triggerMask = 0; + for (size_t axis = 0; axis < numAxes; ++axis) + { + if (gb.Seen(axisLetters[axis])) + { + triggerMask |= (1u << axis); + } + } + if (gb.Seen(extrudeLetter)) + { + long eStops[MaxExtruders]; + size_t numEntries = MaxExtruders; + gb.GetLongArray(eStops, numEntries); + for (size_t i = 0; i < numEntries; ++i) + { + if (eStops[i] >= 0 && (unsigned long)eStops[i] < MaxExtruders) + { + triggerMask |= (1u << (eStops[i] + E0_AXIS)); + } + } + } + switch(sval) + { + case -1: + if (triggerMask == 0) + { + triggers[triggerNumber].rising = triggers[triggerNumber].falling = 0; + } + else + { + triggers[triggerNumber].rising &= (~triggerMask); + triggers[triggerNumber].falling &= (~triggerMask); + } + break; + + case 0: + triggers[triggerNumber].falling |= triggerMask; + break; + + case 1: + triggers[triggerNumber].rising |= triggerMask; + break; + + default: + platform->Message(GENERIC_MESSAGE, "Bad S parameter in M581 command\n"); + } + } + if (!seen) + { + reply.printf("Trigger %u fires on a rising edge on ", triggerNumber); + ListTriggers(reply, triggers[triggerNumber].rising); + reply.cat(" or a falling edge on "); + ListTriggers(reply, triggers[triggerNumber].falling); + reply.cat(" endstop inputs"); + if (triggers[triggerNumber].condition == 1) + { + reply.cat(" when printing from SD card"); + } + } + } + } + else + { + platform->Message(GENERIC_MESSAGE, "Trigger number out of range\n"); + } + } + break; + + case 584: // Set axis/extruder to stepper driver(s) mapping + if (!LockMovementAndWaitForStandstill(gb)) // we also rely on this to retrieve the current motor positions to moveBuffer + { + return false; + } + { + bool seen = false, badDrive = false; + for (size_t drive = 0; drive < MAX_AXES; ++drive) + { + if (gb.Seen(axisLetters[drive])) + { + seen = true; + size_t numValues = MaxDriversPerAxis; + long drivers[MaxDriversPerAxis]; + gb.GetLongArray(drivers, numValues); + + // Check all the driver numbers are in range + bool badAxis = false; + AxisDriversConfig config; + config.numDrivers = numValues; + for (size_t i = 0; i < numValues; ++i) + { + if ((unsigned long)drivers[i] >= DRIVES) + { + badAxis = true; + } + else + { + config.driverNumbers[i] = (uint8_t)drivers[i]; + } + } + if (badAxis) + { + badDrive = true; + } + else + { + while (numAxes <= drive) + { + moveBuffer.coords[numAxes] = 0.0; // user has defined a new axis, so set its position + ++numAxes; + } + SetPositions(moveBuffer.coords); // tell the Move system where any new axes are + platform->SetAxisDriversConfig(drive, config); + if (numAxes + numExtruders > DRIVES) + { + numExtruders = DRIVES - numAxes; // if we added axes, we may have fewer extruders now + } + } + } + } + + if (gb.Seen(extrudeLetter)) + { + seen = true; + size_t numValues = DRIVES - numAxes; + long drivers[MaxExtruders]; + gb.GetLongArray(drivers, numValues); + numExtruders = numValues; + for (size_t i = 0; i < numValues; ++i) + { + if ((unsigned long)drivers[i] >= DRIVES) + { + badDrive = true; + } + else + { + platform->SetExtruderDriver(i, (uint8_t)drivers[i]); + } + } + } + + if (badDrive) + { + platform->Message(GENERIC_MESSAGE, "Error: invalid drive number in M584 command\n"); + } + else if (!seen) + { + reply.copy("Driver assignments:"); + for (size_t drive = 0; drive < numAxes; ++ drive) + { + reply.cat(' '); + const AxisDriversConfig& axisConfig = platform->GetAxisDriversConfig(drive); + char c = axisLetters[drive]; + for (size_t i = 0; i < axisConfig.numDrivers; ++i) + { + reply.catf("%c%u", c, axisConfig.driverNumbers[i]); + c = ':'; + } + } + reply.cat(' '); + char c = extrudeLetter; + for (size_t extruder = 0; extruder < numExtruders; ++extruder) + { + reply.catf("%c%u", c, platform->GetExtruderDriver(extruder)); + c = ':'; + } + } + } + break; + + case 665: // Set delta configuration + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + { + float positionNow[DRIVES]; + Move *move = reprap.GetMove(); + move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes()); // get the current position, we may need it later + DeltaParameters& params = move->AccessDeltaParams(); + const bool wasInDeltaMode = params.IsDeltaMode(); // remember whether we were in delta mode + bool seen = false; + + if (gb.Seen('L')) + { + params.SetDiagonal(gb.GetFValue() * distanceScale); + seen = true; + } + if (gb.Seen('R')) + { + params.SetRadius(gb.GetFValue() * distanceScale); + seen = true; + } + if (gb.Seen('B')) + { + params.SetPrintRadius(gb.GetFValue() * distanceScale); + seen = true; + } + if (gb.Seen('X')) + { + // X tower position correction + params.SetXCorrection(gb.GetFValue()); + seen = true; + } + if (gb.Seen('Y')) + { + // Y tower position correction + params.SetYCorrection(gb.GetFValue()); + seen = true; + } + if (gb.Seen('Z')) + { + // Y tower position correction + params.SetZCorrection(gb.GetFValue()); + seen = true; + } + + // The homed height must be done last, because it gets recalculated when some of the other factors are changed + if (gb.Seen('H')) + { + params.SetHomedHeight(gb.GetFValue() * distanceScale); + seen = true; + } + + if (seen) + { + move->SetCoreXYMode(0); // CoreXYMode needs to be zero when executing special moves on a delta + + // If we have changed between Cartesian and Delta mode, we need to reset the motor coordinates to agree with the XYZ coordinates. + // This normally happens only when we process the M665 command in config.g. Also flag that the machine is not homed. + if (params.IsDeltaMode() != wasInDeltaMode) + { + SetPositions(positionNow); + } + SetAllAxesNotHomed(); + } + else + { + if (params.IsDeltaMode()) + { + reply.printf("Diagonal %.3f, delta radius %.3f, homed height %.3f, bed radius %.1f" + ", X %.3f" DEGREE_SYMBOL ", Y %.3f" DEGREE_SYMBOL ", Z %.3f" DEGREE_SYMBOL, + params.GetDiagonal() / distanceScale, params.GetRadius() / distanceScale, + params.GetHomedHeight() / distanceScale, params.GetPrintRadius() / distanceScale, + params.GetXCorrection(), params.GetYCorrection(), params.GetZCorrection()); + } + else + { + reply.printf("Printer is not in delta mode"); + } + } + } + break; + + case 666: // Set delta endstop adjustments + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + { + DeltaParameters& params = reprap.GetMove()->AccessDeltaParams(); + bool seen = false; + if (gb.Seen('X')) + { + params.SetEndstopAdjustment(X_AXIS, gb.GetFValue()); + seen = true; + } + if (gb.Seen('Y')) + { + params.SetEndstopAdjustment(Y_AXIS, gb.GetFValue()); + seen = true; + } + if (gb.Seen('Z')) + { + params.SetEndstopAdjustment(Z_AXIS, gb.GetFValue()); + seen = true; + } + if (gb.Seen('A')) + { + params.SetXTilt(gb.GetFValue() * 0.01); + seen = true; + } + if (gb.Seen('B')) + { + params.SetYTilt(gb.GetFValue() * 0.01); + seen = true; + } + + if (seen) + { + SetAllAxesNotHomed(); + } + else + { + reply.printf("Endstop adjustments X%.2f Y%.2f Z%.2f, tilt X%.2f%% Y%.2f%%", + params.GetEndstopAdjustment(X_AXIS), params.GetEndstopAdjustment(Y_AXIS), params.GetEndstopAdjustment(Z_AXIS), + params.GetXTilt() * 100.0, params.GetYTilt() * 100.0); + } + } + break; + + case 667: // Set CoreXY mode + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + { + Move* const move = reprap.GetMove(); + bool seen = false; + float positionNow[DRIVES]; + move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes()); // get the current position, we may need it later + if (gb.Seen('S')) + { + move->SetCoreXYMode(gb.GetIValue()); + seen = true; + } + for (size_t axis = 0; axis < numAxes; ++axis) + { + if (gb.Seen(axisLetters[axis])) + { + move->SetCoreAxisFactor(axis, gb.GetFValue()); + seen = true; + } + } + + if (seen) + { + SetPositions(positionNow); + SetAllAxesNotHomed(); + } + else + { + reply.printf("Printer mode is %s with axis factors", move->GetGeometryString()); + for (size_t axis = 0; axis < numAxes; ++axis) + { + reply.catf(" %c:%f", axisLetters[axis], move->GetCoreAxisFactor(axis)); + } + } + } + break; + + case 905: // Set current RTC date and time + { + const time_t now = platform->GetDateTime(); + struct tm * const timeInfo = gmtime(&now); + bool seen = false; + + if (gb.Seen('P')) + { + // Set date + const char * const dateString = gb.GetString(); + if (strptime(dateString, "%Y-%m-%d", timeInfo) != nullptr) + { + if (!platform->SetDate(mktime(timeInfo))) + { + reply.copy("Could not set date"); + error = true; + break; + } + } + else + { + reply.copy("Invalid date format"); + error = true; + break; + } + + seen = true; + } + + if (gb.Seen('S')) + { + // Set time + const char * const timeString = gb.GetString(); + if (strptime(timeString, "%H:%M:%S", timeInfo) != nullptr) + { + if (!platform->SetTime(mktime(timeInfo))) + { + reply.copy("Could not set time"); + error = true; + break; + } + } + else + { + reply.copy("Invalid time format"); + error = true; + break; + } + seen = true; + } + + // TODO: Add correction parameters for SAM4E + + if (!seen) + { + // Report current date and time + reply.printf("Current date and time: %04u-%02u-%02u %02u:%02u:%02u", + timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday, + timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec); + + if (!platform->IsDateTimeSet()) + { + reply.cat("\nWarning: RTC has not been configured yet!"); + } + } + } + break; + + case 906: // Set/report Motor currents + case 913: // Set/report motor current percent + { + bool seen = false; + for (size_t axis = 0; axis < numAxes; axis++) + { + if (gb.Seen(axisLetters[axis])) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + platform->SetMotorCurrent(axis, gb.GetFValue(), code == 913); + seen = true; + } + } + + if (gb.Seen(extrudeLetter)) + { + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + + float eVals[MaxExtruders]; + size_t eCount = numExtruders; + gb.GetFloatArray(eVals, eCount, true); + // 2014-09-29 DC42: we no longer insist that the user supplies values for all possible extruder drives + for (size_t e = 0; e < eCount; e++) + { + platform->SetMotorCurrent(numAxes + e, eVals[e], code == 913); + } + seen = true; + } + + if (code == 906 && gb.Seen('I')) + { + const float idleFactor = gb.GetFValue(); + if (idleFactor >= 0 && idleFactor <= 100.0) + { + platform->SetIdleCurrentFactor(idleFactor/100.0); + seen = true; + } + } + + if (!seen) + { + reply.copy((code == 913) ? "Motor current % of normal - " : "Motor current (mA) - "); + for (size_t axis = 0; axis < numAxes; ++axis) + { + reply.catf("%c:%d, ", axisLetters[axis], (int)platform->GetMotorCurrent(axis, code == 913)); + } + reply.cat("E"); + for (size_t extruder = 0; extruder < numExtruders; extruder++) + { + reply.catf(":%d", (int)platform->GetMotorCurrent(extruder + numAxes, code == 913)); + } + if (code == 906) + { + reply.catf(", idle factor %d%%", (int)(platform->GetIdleCurrentFactor() * 100.0)); + } + } + } + break; + + case 911: // Set power monitor threshold voltages + reply.printf("M911 not implemented yet"); + break; + + case 912: // Set electronics temperature monitor adjustment + // Currently we ignore the P parameter (i.e. temperature measurement channel) + if (gb.Seen('S')) + { + platform->SetMcuTemperatureAdjust(gb.GetFValue()); + } + else + { + reply.printf("MCU temperature calibration adjustment is %.1f" DEGREE_SYMBOL "C", platform->GetMcuTemperatureAdjust()); + } + break; + + // For case 913, see 906 + + case 997: // Perform firmware update + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + reprap.GetHeat()->SwitchOffAll(); // turn all heaters off because the main loop may get suspended + DisableDrives(); // all motors off + + if (firmwareUpdateModuleMap == 0) // have we worked out which modules to update? + { + // Find out which modules we have been asked to update + if (gb.Seen('S')) + { + long modulesToUpdate[3]; + size_t numUpdateModules = ARRAY_SIZE(modulesToUpdate); + gb.GetLongArray(modulesToUpdate, numUpdateModules); + for (size_t i = 0; i < numUpdateModules; ++i) + { + long t = modulesToUpdate[i]; + if (t < 0 || (unsigned long)t >= NumFirmwareUpdateModules) + { + platform->MessageF(GENERIC_MESSAGE, "Invalid module number '%ld'\n", t); + firmwareUpdateModuleMap = 0; + break; + } + firmwareUpdateModuleMap |= (1u << (unsigned int)t); + } + } + else + { + firmwareUpdateModuleMap = (1u << 0); // no modules specified, so update module 0 to match old behaviour + } + + if (firmwareUpdateModuleMap == 0) + { + break; // nothing to update + } + + // Check prerequisites of all modules to be updated, if any are not met then don't update any of them +#ifdef DUET_NG + if (!FirmwareUpdater::CheckFirmwareUpdatePrerequisites(firmwareUpdateModuleMap)) + { + firmwareUpdateModuleMap = 0; + break; + } +#endif + if ((firmwareUpdateModuleMap & 1) != 0 && !platform->CheckFirmwareUpdatePrerequisites()) + { + firmwareUpdateModuleMap = 0; + break; + } + } + + // If we get here then we have the module map, and all prerequisites are satisfied + isFlashing = true; // this tells the web interface and PanelDue that we are about to flash firmware + if (!DoDwellTime(1.0)) // wait a second so all HTTP clients and PanelDue are notified + { + return false; + } + + gb.SetState(GCodeState::flashing1); + break; + + case 998: + // The input handling code replaces the gcode by this when it detects a checksum error. + // Since we have no way of asking for the line to be re-sent, just report an error. + if (gb.Seen('P')) + { + const int val = gb.GetIValue(); + if (val != 0) + { + reply.printf("Checksum error on line %d", val); + } + } + break; + + case 999: + result = DoDwellTime(0.5); // wait half a second to allow the response to be sent back to the web server, otherwise it may retry + if (result) + { + reprap.EmergencyStop(); // this disables heaters and drives - Duet WiFi pre-production boards need drives disabled here + uint16_t reason = (gb.Seen('P') && StringStartsWith(gb.GetString(), "ERASE")) + ? (uint16_t)SoftwareResetReason::erase + : (uint16_t)SoftwareResetReason::user; + platform->SoftwareReset(reason); // doesn't return + } + break; + + default: + error = true; + reply.printf("unsupported command: %s", gb.Buffer()); + } + + if (result && gb.GetState() == GCodeState::normal) + { + UnlockAll(gb); + HandleReply(gb, error, reply.Pointer()); + } + return result; +} + +bool GCodes::HandleTcode(GCodeBuffer& gb, StringRef& reply) +{ + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + + newToolNumber = gb.GetIValue(); + newToolNumber += gb.GetToolNumberAdjust(); + + // TODO for the tool change restore point to be useful, we should undo any X axis mapping and remove any tool offsets + for (size_t drive = 0; drive < DRIVES; ++drive) + { + toolChangeRestorePoint.moveCoords[drive] = moveBuffer.coords[drive]; + } + toolChangeRestorePoint.feedRate = gb.MachineState().feedrate; + + if (simulationMode == 0) // we don't yet simulate any T codes + { + const Tool * const oldTool = reprap.GetCurrentTool(); + // If old and new are the same we no longer follow the sequence. User can deselect and then reselect the tool if he wants the macros run. + if (oldTool == nullptr || oldTool->Number() != newToolNumber) + { + StartToolChange(gb, false); + return true; // proceeding with state machine, so don't unlock or send a reply + } + } + + // If we get here, we have finished + UnlockAll(gb); + HandleReply(gb, false, ""); + return true; +} + +// End |