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

github.com/Duet3D/RepRapFirmware.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Crocker <dcrocker@eschertech.com>2016-12-07 14:18:08 +0300
committerDavid Crocker <dcrocker@eschertech.com>2016-12-07 16:28:17 +0300
commit52b828501751a85848a57af369dee6b965f5e707 (patch)
tree0d1ef673ff89057bbd60f2649623c9f337571908
parentfc954bfa06230ad6c60655e5122abafd11f93dd8 (diff)
Version 1.17dev7
Implemented loading height map from file (G29 S1) Implemented move segmentation when grid levelling is used M571 now accepts a P parameter to define the output pin M558 R parameter is now Z probe recovery time Keep track of time locally instead of using the inaccurate RTC M109 and M104 default to first tool if no tool selected or specified M109 activates the tool if no tool or a different tool was activated Default max temperature excursion increased to 15C Removed PROTO1 confivguration, added RADDS configuration Fixed deadlock in some earlier 1.17dev builds when a macro that does movement is run during a print
-rw-r--r--.cproject116
-rw-r--r--.settings/org.eclipse.cdt.core.prefs8
-rw-r--r--Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.17dev7.binbin0 -> 320124 bytes
-rw-r--r--Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.17dev7.binbin0 -> 269708 bytes
-rw-r--r--src/Configuration.h7
-rw-r--r--src/GCodes/GCodeBuffer.h7
-rw-r--r--src/GCodes/GCodeMachineState.cpp2
-rw-r--r--src/GCodes/GCodeMachineState.h20
-rw-r--r--src/GCodes/GCodes.cpp6605
-rw-r--r--src/GCodes/GCodes.h28
-rw-r--r--src/GCodes/GCodes1.cpp3286
-rw-r--r--src/GCodes/GCodes2.cpp3481
-rw-r--r--src/Heating/Heat.h12
-rw-r--r--src/Movement/DDA.cpp4
-rw-r--r--src/Movement/Grid.cpp222
-rw-r--r--src/Movement/Grid.h63
-rw-r--r--src/Movement/Move.cpp143
-rw-r--r--src/Movement/Move.h15
-rw-r--r--src/Pins.h6
-rw-r--r--src/Platform.cpp154
-rw-r--r--src/Platform.h92
-rw-r--r--src/RADDS/Network.cpp8
-rw-r--r--src/RADDS/Network.h34
-rw-r--r--src/RADDS/Pins_radds.h248
-rw-r--r--src/RADDS/Webserver.h38
-rw-r--r--src/Reprap.cpp44
-rw-r--r--src/Reprap.h4
-rw-r--r--src/Storage/FileStore.cpp34
-rw-r--r--src/Storage/FileStore.h1
29 files changed, 7683 insertions, 6999 deletions
diff --git a/.cproject b/.cproject
index b2516706..18519f03 100644
--- a/.cproject
+++ b/.cproject
@@ -81,7 +81,6 @@
<option id="gnu.cpp.compiler.option.include.paths.1285689288" name="Include paths (-I)" superClass="gnu.cpp.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/cores/arduino}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Flash}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/RTCDue}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/SharedSpi}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Storage}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Wire}&quot;"/>
@@ -114,7 +113,7 @@
</toolChain>
</folderInfo>
<sourceEntries>
- <entry excluding="src/Duet/Lwip/lwip/src/core/ipv6|src/DuetNG|src/Duet/Lwip/lwip/test" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
+ <entry excluding="src/RADDS|src/Duet/Lwip/lwip/src/core/ipv6|src/DuetNG|src/Duet/Lwip/lwip/test" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
</sourceEntries>
</configuration>
</storageModule>
@@ -199,7 +198,6 @@
<option id="gnu.cpp.compiler.option.include.paths.251815634" name="Include paths (-I)" superClass="gnu.cpp.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/cores/arduino}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Flash}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/RTCDue}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/SharedSpi}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Storage}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Wire}&quot;"/>
@@ -235,14 +233,14 @@
</toolChain>
</folderInfo>
<sourceEntries>
- <entry excluding="src/Libraries/MCP4461|src/Duet" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
+ <entry excluding="src/RADDS|src/Libraries/MCP4461|src/Duet" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
</sourceEntries>
</configuration>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
</cconfiguration>
- <cconfiguration id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080">
- <storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080" moduleId="org.eclipse.cdt.core.settings" name="SAM4E_PROTO1">
+ <cconfiguration id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289">
+ <storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289" moduleId="org.eclipse.cdt.core.settings" name="RADDS">
<macros>
<stringMacro name="CoreName" type="VALUE_TEXT" value="CoreNG"/>
</macros>
@@ -257,105 +255,105 @@
</extensions>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
- <configuration artifactExtension="elf" artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.release,org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe" cleanCommand="rm -rf" description="" id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080" name="SAM4E_PROTO1" parent="cdt.managedbuild.config.gnu.cross.exe.release" postannouncebuildStep="Generating binary file" postbuildStep="arm-none-eabi-objcopy -O binary ${workspace_loc:/${ProjName}/${ConfigName}}/${ProjName}.elf ${workspace_loc:/${ProjName}/${ConfigName}}/${ProjName}.bin">
- <folderInfo id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080." name="/" resourcePath="">
- <toolChain id="cdt.managedbuild.toolchain.gnu.cross.exe.release.1721367471" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.exe.release">
- <option id="cdt.managedbuild.option.gnu.cross.path.775185210" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path" value="C:\Arduino-1.5.8\hardware\tools\gcc-arm-none-eabi-4.8.3-2014q1\bin" valueType="string"/>
- <option id="cdt.managedbuild.option.gnu.cross.prefix.662072815" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix" value="arm-none-eabi-" valueType="string"/>
- <targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross.1984540835" isAbstract="false" osList="all" superClass="cdt.managedbuild.targetPlatform.gnu.cross"/>
- <builder buildPath="${workspace_loc:/RepRapFirmware}/Release" id="cdt.managedbuild.builder.gnu.cross.614311341" keepEnvironmentInBuildfile="false" managedBuildOn="true" name="Gnu Make Builder" superClass="cdt.managedbuild.builder.gnu.cross"/>
- <tool id="cdt.managedbuild.tool.gnu.cross.assembler.1598889104" name="Cross GCC Assembler" superClass="cdt.managedbuild.tool.gnu.cross.assembler">
- <inputType id="cdt.managedbuild.tool.gnu.assembler.input.2008444256" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
+ <configuration artifactExtension="elf" artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.release,org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe" cleanCommand="rm -rf" description="" id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289" name="RADDS" parent="cdt.managedbuild.config.gnu.cross.exe.release" postannouncebuildStep="Generating binary file" postbuildStep="arm-none-eabi-objcopy -O binary ${workspace_loc:/${ProjName}/${ConfigName}}/${ProjName}.elf ${workspace_loc:/${ProjName}/${ConfigName}}/${ProjName}.bin">
+ <folderInfo id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289." name="/" resourcePath="">
+ <toolChain id="cdt.managedbuild.toolchain.gnu.cross.exe.release.1973208555" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.exe.release">
+ <option id="cdt.managedbuild.option.gnu.cross.path.2092504710" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path" value="C:\Arduino-1.5.8\hardware\tools\gcc-arm-none-eabi-4.8.3-2014q1\bin" valueType="string"/>
+ <option id="cdt.managedbuild.option.gnu.cross.prefix.1606498191" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix" value="arm-none-eabi-" valueType="string"/>
+ <targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross.342355349" isAbstract="false" osList="all" superClass="cdt.managedbuild.targetPlatform.gnu.cross"/>
+ <builder buildPath="${workspace_loc:/RepRapFirmware}/Release" id="cdt.managedbuild.builder.gnu.cross.1336978387" keepEnvironmentInBuildfile="false" managedBuildOn="true" name="Gnu Make Builder" superClass="cdt.managedbuild.builder.gnu.cross"/>
+ <tool id="cdt.managedbuild.tool.gnu.cross.assembler.863511428" name="Cross GCC Assembler" superClass="cdt.managedbuild.tool.gnu.cross.assembler">
+ <inputType id="cdt.managedbuild.tool.gnu.assembler.input.664007272" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
</tool>
- <tool commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${INPUTS}" id="cdt.managedbuild.tool.gnu.cross.c.compiler.669564787" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler">
- <option defaultValue="gnu.c.optimization.level.most" id="gnu.c.compiler.option.optimization.level.925369674" name="Optimization Level" superClass="gnu.c.compiler.option.optimization.level" useByScannerDiscovery="false" valueType="enumerated"/>
- <option id="gnu.c.compiler.option.debugging.level.1437680267" name="Debug Level" superClass="gnu.c.compiler.option.debugging.level" useByScannerDiscovery="false" value="gnu.c.debugging.level.none" valueType="enumerated"/>
- <option id="gnu.c.compiler.option.misc.verbose.454077281" name="Verbose (-v)" superClass="gnu.c.compiler.option.misc.verbose" useByScannerDiscovery="false" value="false" valueType="boolean"/>
- <option id="gnu.c.compiler.option.misc.other.1262081974" name="Other flags" superClass="gnu.c.compiler.option.misc.other" useByScannerDiscovery="false" value="-c -std=gnu99 -mcpu=cortex-m4 -mthumb -ffunction-sections -fdata-sections -nostdlib --param max-inline-insns-single=500" valueType="string"/>
- <option id="gnu.c.compiler.option.include.paths.1815693236" name="Include paths (-I)" superClass="gnu.c.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
+ <tool commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${INPUTS}" id="cdt.managedbuild.tool.gnu.cross.c.compiler.764246283" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler">
+ <option defaultValue="gnu.c.optimization.level.most" id="gnu.c.compiler.option.optimization.level.1125289372" name="Optimization Level" superClass="gnu.c.compiler.option.optimization.level" useByScannerDiscovery="false" valueType="enumerated"/>
+ <option id="gnu.c.compiler.option.debugging.level.807229803" name="Debug Level" superClass="gnu.c.compiler.option.debugging.level" useByScannerDiscovery="false" value="gnu.c.debugging.level.none" valueType="enumerated"/>
+ <option id="gnu.c.compiler.option.misc.verbose.1747279976" name="Verbose (-v)" superClass="gnu.c.compiler.option.misc.verbose" useByScannerDiscovery="false" value="false" valueType="boolean"/>
+ <option id="gnu.c.compiler.option.misc.other.1771169870" name="Other flags" superClass="gnu.c.compiler.option.misc.other" useByScannerDiscovery="false" value="-c -std=gnu99 -mcpu=cortex-m3 -mthumb -ffunction-sections -fdata-sections -nostdlib --param max-inline-insns-single=500" valueType="string"/>
+ <option id="gnu.c.compiler.option.include.paths.425316569" name="Include paths (-I)" superClass="gnu.c.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/cores/arduino}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Storage}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/common/utils}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/common/services/ioport}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/emac}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/hsmci}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/rstc}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/rtc}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils/cmsis/sam4e/include}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils/cmsis/sam3x/include}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils/header_files}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils/preprocessor}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/thirdparty/CMSIS/Include}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/variants/duetNG}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/variants/duet}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Duet/Lwip}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Duet/Lwip/lwip/src/include}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Duet/EMAC}&quot;"/>
</option>
- <option id="gnu.c.compiler.option.preprocessor.def.symbols.1739570632" name="Defined symbols (-D)" superClass="gnu.c.compiler.option.preprocessor.def.symbols" useByScannerDiscovery="false" valueType="definedSymbols">
- <listOptionValue builtIn="false" value="__SAM4E8E__"/>
- <listOptionValue builtIn="false" value="CORE_NG"/>
- <listOptionValue builtIn="false" value="DUET_NG"/>
+ <option id="gnu.c.compiler.option.preprocessor.def.symbols.2017188375" name="Defined symbols (-D)" superClass="gnu.c.compiler.option.preprocessor.def.symbols" useByScannerDiscovery="false" valueType="definedSymbols">
+ <listOptionValue builtIn="false" value="__SAM3X8E__"/>
+ <listOptionValue builtIn="false" value="__RADDS__"/>
<listOptionValue builtIn="false" value="printf=iprintf"/>
- <listOptionValue builtIn="false" value="PROTOTYPE_1"/>
</option>
- <inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.1283635516" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
+ <inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.110609707" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
</tool>
- <tool id="cdt.managedbuild.tool.gnu.cross.c.linker.316456764" name="Cross GCC Linker" superClass="cdt.managedbuild.tool.gnu.cross.c.linker"/>
- <tool id="cdt.managedbuild.tool.gnu.cross.archiver.81082459" name="Cross GCC Archiver" superClass="cdt.managedbuild.tool.gnu.cross.archiver"/>
- <tool command="gcc" commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${LINK_FLAGS_1} ${workspace_loc:/${CoreName}/SAM4E8E/cores/arduino/syscalls.o} ${INPUTS} ${LINK_FLAGS_2}" id="cdt.managedbuild.tool.gnu.cross.cpp.linker.1781803617" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker">
- <option id="gnu.cpp.link.option.nostdlibs.1188962244" name="No startup or default libs (-nostdlib)" superClass="gnu.cpp.link.option.nostdlibs" value="false" valueType="boolean"/>
- <option id="gnu.cpp.link.option.paths.2059496497" name="Library search path (-L)" superClass="gnu.cpp.link.option.paths" valueType="libPaths">
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/SAM4E_PROTO1/}&quot;"/>
+ <tool id="cdt.managedbuild.tool.gnu.cross.c.linker.1692168928" name="Cross GCC Linker" superClass="cdt.managedbuild.tool.gnu.cross.c.linker"/>
+ <tool id="cdt.managedbuild.tool.gnu.cross.archiver.1755453550" name="Cross GCC Archiver" superClass="cdt.managedbuild.tool.gnu.cross.archiver"/>
+ <tool command="gcc" commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${LINK_FLAGS_1} ${workspace_loc:/${CoreName}/SAM3X8E/cores/arduino/syscalls.o} ${INPUTS} ${LINK_FLAGS_2}" id="cdt.managedbuild.tool.gnu.cross.cpp.linker.1176271302" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker">
+ <option id="gnu.cpp.link.option.nostdlibs.706270025" name="No startup or default libs (-nostdlib)" superClass="gnu.cpp.link.option.nostdlibs" value="false" valueType="boolean"/>
+ <option id="gnu.cpp.link.option.paths.1160723414" name="Library search path (-L)" superClass="gnu.cpp.link.option.paths" valueType="libPaths">
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/SAM3X8E/}&quot;"/>
</option>
- <option id="gnu.cpp.link.option.libs.1868917950" name="Libraries (-l)" superClass="gnu.cpp.link.option.libs" valueType="libs">
+ <option id="gnu.cpp.link.option.libs.1006761104" name="Libraries (-l)" superClass="gnu.cpp.link.option.libs" valueType="libs">
<listOptionValue builtIn="false" value="${CoreName}"/>
</option>
- <option id="gnu.cpp.link.option.flags.748151728" name="Linker flags" superClass="gnu.cpp.link.option.flags" value="-Os -Wl,--gc-sections -Wl,--fatal-warnings -mcpu=cortex-m3 -T${workspace_loc:/${CoreName}/variants/duetNG/linker_scripts/gcc/flash.ld} -Wl,-Map,${workspace_loc:/${ProjName}/${ConfigName}}/${ProjName}.map" valueType="string"/>
- <inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.1649038990" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
+ <option id="gnu.cpp.link.option.flags.827167716" name="Linker flags" superClass="gnu.cpp.link.option.flags" value="-Os -Wl,--gc-sections -Wl,--fatal-warnings -mcpu=cortex-m3 -T${workspace_loc:/${CoreName}/variants/duet/linker_scripts/gcc/flash.ld} -Wl,-Map,${workspace_loc:/${ProjName}/${ConfigName}}/${ProjName}.map" valueType="string"/>
+ <inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.99895855" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
<additionalInput kind="additionalinput" paths="$(LIBS)"/>
</inputType>
</tool>
- <tool command="g++" id="cdt.managedbuild.tool.gnu.cross.cpp.compiler.2138612171" name="Cross G++ Compiler" superClass="cdt.managedbuild.tool.gnu.cross.cpp.compiler">
- <option id="gnu.cpp.compiler.option.optimization.level.1445246033" name="Optimization Level" superClass="gnu.cpp.compiler.option.optimization.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.optimization.level.more" valueType="enumerated"/>
- <option id="gnu.cpp.compiler.option.debugging.level.213346939" name="Debug Level" superClass="gnu.cpp.compiler.option.debugging.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.debugging.level.none" valueType="enumerated"/>
- <option id="gnu.cpp.compiler.option.other.verbose.1378702640" name="Verbose (-v)" superClass="gnu.cpp.compiler.option.other.verbose" useByScannerDiscovery="false" value="false" valueType="boolean"/>
- <option id="gnu.cpp.compiler.option.other.other.1875098822" name="Other flags" superClass="gnu.cpp.compiler.option.other.other" useByScannerDiscovery="false" value="-c -std=gnu++11 -mcpu=cortex-m4 -mthumb -ffunction-sections -fdata-sections -fno-threadsafe-statics -fno-rtti -fno-exceptions -nostdlib --param max-inline-insns-single=500" valueType="string"/>
- <option id="gnu.cpp.compiler.option.include.paths.254420427" name="Include paths (-I)" superClass="gnu.cpp.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
+ <tool command="g++" id="cdt.managedbuild.tool.gnu.cross.cpp.compiler.2077096750" name="Cross G++ Compiler" superClass="cdt.managedbuild.tool.gnu.cross.cpp.compiler">
+ <option id="gnu.cpp.compiler.option.optimization.level.25432549" name="Optimization Level" superClass="gnu.cpp.compiler.option.optimization.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.optimization.level.more" valueType="enumerated"/>
+ <option id="gnu.cpp.compiler.option.debugging.level.1920128035" name="Debug Level" superClass="gnu.cpp.compiler.option.debugging.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.debugging.level.none" valueType="enumerated"/>
+ <option id="gnu.cpp.compiler.option.other.verbose.1825237573" name="Verbose (-v)" superClass="gnu.cpp.compiler.option.other.verbose" useByScannerDiscovery="false" value="false" valueType="boolean"/>
+ <option id="gnu.cpp.compiler.option.other.other.165959132" name="Other flags" superClass="gnu.cpp.compiler.option.other.other" useByScannerDiscovery="false" value="-c -std=gnu++11 -mcpu=cortex-m3 -mthumb -ffunction-sections -fdata-sections -fno-threadsafe-statics -fno-rtti -fno-exceptions -nostdlib --param max-inline-insns-single=500" valueType="string"/>
+ <option id="gnu.cpp.compiler.option.include.paths.603435221" name="Include paths (-I)" superClass="gnu.cpp.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/cores/arduino}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Wire}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Flash}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/SharedSpi}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Storage}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Wire}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/common/utils}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/common/services/clock}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/common/services/ioport}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/dmac}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/efc}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/pdc}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/emac}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/pmc}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/spi}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/twi}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/services/flash_efc}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils/cmsis/sam4e/include}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils/cmsis/sam3x/include}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils/header_files}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils/preprocessor}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/thirdparty/CMSIS/Include}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/variants/duetNG}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/variants/duet}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/DuetNG}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Duet}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Duet/Lwip}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Duet/EMAC}&quot;"/>
</option>
- <option id="gnu.cpp.compiler.option.preprocessor.def.486383061" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false" valueType="definedSymbols">
- <listOptionValue builtIn="false" value="__SAM4E8E__"/>
- <listOptionValue builtIn="false" value="CORE_NG"/>
- <listOptionValue builtIn="false" value="DUET_NG"/>
+ <option id="gnu.cpp.compiler.option.preprocessor.def.1179781669" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false" valueType="definedSymbols">
+ <listOptionValue builtIn="false" value="__SAM3X8E__"/>
+ <listOptionValue builtIn="false" value="__RADDS__"/>
<listOptionValue builtIn="false" value="printf=iprintf"/>
- <listOptionValue builtIn="false" value="PROTOTYPE_1"/>
</option>
- <inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.2012484327" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
+ <inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.1578939493" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
</tool>
</toolChain>
</folderInfo>
<sourceEntries>
- <entry excluding="src/Duet" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
+ <entry excluding="src/Duet|src/Duet/Lwip/lwip/src/core/ipv6|src/DuetNG|src/Duet/Lwip/lwip/test" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
</sourceEntries>
</configuration>
</storageModule>
diff --git a/.settings/org.eclipse.cdt.core.prefs b/.settings/org.eclipse.cdt.core.prefs
index 0d147d57..c941b38c 100644
--- a/.settings/org.eclipse.cdt.core.prefs
+++ b/.settings/org.eclipse.cdt.core.prefs
@@ -1,4 +1,12 @@
eclipse.preferences.version=1
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289/LINK_FLAGS_1/delimiter=;
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289/LINK_FLAGS_1/operation=append
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289/LINK_FLAGS_1/value=-mthumb -Wl,--cref -Wl,--check-sections -Wl,--gc-sections -Wl,--entry\=Reset_Handler -Wl,--unresolved-symbols\=report-all -Wl,--warn-common -Wl,--warn-section-align -Wl,--warn-unresolved-symbols -Wl,--start-group
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289/LINK_FLAGS_2/delimiter=;
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289/LINK_FLAGS_2/operation=append
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289/LINK_FLAGS_2/value=-Wl,--end-group -lm -gcc
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289/append=true
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289/appendContributed=true
environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/LINK_FLAGS_1/delimiter=;
environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/LINK_FLAGS_1/operation=append
environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/LINK_FLAGS_1/value=-mthumb -Wl,--cref -Wl,--check-sections -Wl,--gc-sections -Wl,--entry\=Reset_Handler -Wl,--unresolved-symbols\=report-all -Wl,--warn-common -Wl,--warn-section-align -Wl,--warn-unresolved-symbols -Wl,--start-group
diff --git a/Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.17dev7.bin b/Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.17dev7.bin
new file mode 100644
index 00000000..73e5a4f8
--- /dev/null
+++ b/Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.17dev7.bin
Binary files differ
diff --git a/Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.17dev7.bin b/Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.17dev7.bin
new file mode 100644
index 00000000..e9388c78
--- /dev/null
+++ b/Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.17dev7.bin
Binary files differ
diff --git a/src/Configuration.h b/src/Configuration.h
index 62d7c58c..924e1634 100644
--- a/src/Configuration.h
+++ b/src/Configuration.h
@@ -28,11 +28,11 @@ Licence: GPL
// Firmware name is now defined in the Pins file
#ifndef VERSION
-# define VERSION "1.17dev6"
+# define VERSION "1.17dev7"
#endif
#ifndef DATE
-# define DATE "2016-11-22"
+# define DATE "2016-12-07"
#endif
#define AUTHORS "reprappro, dc42, zpl, t3p3, dnewman"
@@ -105,7 +105,7 @@ const float DefaultMaxHeatingFaultTime = 5.0; // How many seconds we allow a he
const float AllowedTemperatureDerivativeNoise = 0.25; // How much fluctuation in the averaged temperature derivative we allow
const float MaxAmbientTemperature = 45.0; // We expect heaters to cool to this temperature or lower when switched off
const float NormalAmbientTemperature = 25.0; // The ambient temperature we assume - allow for the printer heating its surroundings a little
-const float DefaultMaxTempExcursion = 10.0; // How much error we tolerate when maintaining temperature before deciding that a heater fault has occurred
+const float DefaultMaxTempExcursion = 15.0; // How much error we tolerate when maintaining temperature before deciding that a heater fault has occurred
const float MinimumConnectedTemperature = -5.0; // Temperatures below this we treat as a disconnected thermistor
static_assert(DefaultMaxTempExcursion > TEMPERATURE_CLOSE_ENOUGH, "DefaultMaxTempExcursion is too low");
@@ -173,7 +173,6 @@ const size_t OUTPUT_BUFFER_COUNT = 32; // How many OutputBuffer instances do
const size_t RESERVED_OUTPUT_BUFFERS = 2; // Number of reserved output buffers after long responses. Must be enough for an HTTP header
#endif
-
// Move system
const float DEFAULT_FEEDRATE = 3000.0; // The initial requested feed rate after resetting the printer
diff --git a/src/GCodes/GCodeBuffer.h b/src/GCodes/GCodeBuffer.h
index aa8723c9..b1540871 100644
--- a/src/GCodes/GCodeBuffer.h
+++ b/src/GCodes/GCodeBuffer.h
@@ -48,6 +48,8 @@ public:
bool IsDoingFileMacro() const; // Return true if this source is executing a file macro
GCodeState GetState() const;
void SetState(GCodeState newState);
+ void AdvanceState();
+ const char *GetIdentity() const { return identity; }
private:
@@ -118,4 +120,9 @@ inline void GCodeBuffer::SetState(GCodeState newState)
machineState->state = newState;
}
+inline void GCodeBuffer::AdvanceState()
+{
+ machineState->state = static_cast<GCodeState>(static_cast<uint8_t>(machineState->state) + 1);
+}
+
#endif /* GCODEBUFFER_H_ */
diff --git a/src/GCodes/GCodeMachineState.cpp b/src/GCodes/GCodeMachineState.cpp
index 3cfa1204..98dac7e6 100644
--- a/src/GCodes/GCodeMachineState.cpp
+++ b/src/GCodes/GCodeMachineState.cpp
@@ -12,7 +12,7 @@ unsigned int GCodeMachineState::numAllocated = 0;
// Create a default initialised GCodeMachineState
GCodeMachineState::GCodeMachineState()
- : previous(nullptr), feedrate(DEFAULT_FEEDRATE), fileState(), lockedResources(0), state(GCodeState::normal),
+ : previous(nullptr), feedrate(DEFAULT_FEEDRATE/minutesToSeconds), fileState(), lockedResources(0), state(GCodeState::normal),
drivesRelative(false), axesRelative(false), doingFileMacro(false)
{
}
diff --git a/src/GCodes/GCodeMachineState.h b/src/GCodes/GCodeMachineState.h
index 8424738a..1d05d2fa 100644
--- a/src/GCodes/GCodeMachineState.h
+++ b/src/GCodes/GCodeMachineState.h
@@ -12,6 +12,9 @@
#include "Configuration.h"
#include "Storage/FileData.h"
+const float minutesToSeconds = 60.0;
+const float secondsToMinutes = 1.0/minutesToSeconds;
+
// Enumeration to list all the possible states that the Gcode processing machine may be in
enum class GCodeState : uint8_t
{
@@ -21,9 +24,16 @@ enum class GCodeState : uint8_t
setBed1,
setBed2,
setBed3,
+
+ // These next 3 must be contiguous
toolChange1,
toolChange2,
- toolChange3,
+ toolChangeComplete,
+ // These next 3 must be contiguous
+ m109ToolChange1,
+ m109ToolChange2,
+ m109ToolChangeComplete,
+
pausing1,
pausing2,
resuming1,
@@ -50,9 +60,11 @@ public:
FileData fileState;
uint32_t lockedResources;
GCodeState state;
- bool drivesRelative;
- bool axesRelative;
- bool doingFileMacro;
+ unsigned int
+ drivesRelative : 1,
+ axesRelative : 1,
+ doingFileMacro : 1,
+ waitWhileCooling : 1;
static GCodeMachineState *Allocate()
post(!result.IsLive(); result.state == GCodeState::normal);
diff --git a/src/GCodes/GCodes.cpp b/src/GCodes/GCodes.cpp
deleted file mode 100644
index 37602f94..00000000
--- a/src/GCodes/GCodes.cpp
+++ /dev/null
@@ -1,6605 +0,0 @@
-/****************************************************************************************************
-
- RepRapFirmware - G Codes
-
- This class interprets G Codes from one or more sources, and calls the functions in Move, Heat etc
- that drive the machine to do what the G Codes command.
-
- Most of the functions in here are designed not to wait, and they return a boolean. When you want them to do
- something, you call them. If they return false, the machine can't do what you want yet. So you go away
- and do something else. Then you try again. If they return true, the thing you wanted done has been done.
-
- -----------------------------------------------------------------------------------------------------
-
- Version 0.1
-
- 13 February 2013
-
- Adrian Bowyer
- RepRap Professional Ltd
- http://reprappro.com
-
- Licence: GPL
-
- ****************************************************************************************************/
-
-#include "RepRapFirmware.h"
-
-#ifdef DUET_NG
-#include "FirmwareUpdater.h"
-#endif
-
-#define DEGREE_SYMBOL "\xC2\xB0" // degree-symbol encoding in UTF8
-
-const char GCodes::axisLetters[MAX_AXES] = { 'X', 'Y', 'Z', 'U', 'V', 'W' };
-
-const char* BED_EQUATION_G = "bed.g";
-const char* PAUSE_G = "pause.g";
-const char* RESUME_G = "resume.g";
-const char* CANCEL_G = "cancel.g";
-const char* STOP_G = "stop.g";
-const char* SLEEP_G = "sleep.g";
-const char* homingFileNames[MAX_AXES] = { "homex.g", "homey.g", "homez.g", "homeu.g", "homev.g", "homew.g" };
-const char* HOME_ALL_G = "homeall.g";
-const char* HOME_DELTA_G = "homedelta.g";
-const char* DefaultHeightMapFile = "heightmap.csv";
-
-const size_t gcodeReplyLength = 2048; // long enough to pass back a reasonable number of files in response to M20
-
-const float MinServoPulseWidth = 544.0, MaxServoPulseWidth = 2400.0;
-const uint16_t ServoRefreshFrequency = 50;
-
-void GCodes::RestorePoint::Init()
-{
- for (size_t i = 0; i < DRIVES; ++i)
- {
- moveCoords[i] = 0.0;
- }
- feedRate = DEFAULT_FEEDRATE/minutesToSeconds;
-}
-
-GCodes::GCodes(Platform* p, Webserver* w) :
- platform(p), webserver(w), active(false), isFlashing(false),
- fileBeingHashed(nullptr)
-{
- httpGCode = new GCodeBuffer("http", HTTP_MESSAGE);
- telnetGCode = new GCodeBuffer("telnet", TELNET_MESSAGE);
- fileGCode = new GCodeBuffer("file", GENERIC_MESSAGE);
- serialGCode = new GCodeBuffer("serial", HOST_MESSAGE);
- auxGCode = new GCodeBuffer("aux", AUX_MESSAGE);
- daemonGCode = new GCodeBuffer("daemon", GENERIC_MESSAGE);
-}
-
-void GCodes::Exit()
-{
- platform->Message(HOST_MESSAGE, "GCodes class exited.\n");
- active = false;
-}
-
-void GCodes::Init()
-{
- Reset();
- numAxes = MIN_AXES;
- numExtruders = MaxExtruders;
- distanceScale = 1.0;
- rawExtruderTotal = 0.0;
- for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
- {
- lastRawExtruderPosition[extruder] = 0.0;
- rawExtruderTotalByDrive[extruder] = 0.0;
- }
- eofString = EOF_STRING;
- eofStringCounter = 0;
- eofStringLength = strlen(eofString);
- offSetSet = false;
- zProbesSet = false;
- active = true;
- longWait = platform->Time();
- dwellTime = longWait;
- limitAxes = true;
- for(size_t axis = 0; axis < MAX_AXES; axis++)
- {
- axisScaleFactors[axis] = 1.0;
- }
- SetAllAxesNotHomed();
- for (size_t i = 0; i < NUM_FANS; ++i)
- {
- pausedFanValues[i] = 0.0;
- }
- lastDefaultFanSpeed = 0.0;
-
- retractLength = retractExtra = retractHop = 0.0;
- retractSpeed = unRetractSpeed = 600.0;
-}
-
-// This is called from Init and when doing an emergency stop
-void GCodes::Reset()
-{
- httpGCode->Init();
- telnetGCode->Init();
- fileGCode->Init();
- serialGCode->Init();
- auxGCode->Init();
- auxGCode->SetCommsProperties(1); // by default, we require a checksum on the aux port
- daemonGCode->Init();
-
- nextGcodeSource = 0;
-
- fileToPrint.Close();
- fileBeingWritten = NULL;
- dwellWaiting = false;
- probeCount = 0;
- cannedCycleMoveCount = 0;
- cannedCycleMoveQueued = false;
- speedFactor = 1.0 / minutesToSeconds; // default is just to convert from mm/minute to mm/second
- for (size_t i = 0; i < MaxExtruders; ++i)
- {
- extrusionFactors[i] = 1.0;
- }
- for (size_t i = 0; i < DRIVES; ++i)
- {
- moveBuffer.coords[i] = 0.0;
- }
- moveBuffer.xAxes = DefaultXAxisMapping;
-
- feedRate = DEFAULT_FEEDRATE/minutesToSeconds;
- pauseRestorePoint.Init();
- toolChangeRestorePoint.Init();
-
- ClearMove();
-
- for (size_t i = 0; i < MaxTriggers; ++i)
- {
- triggers[i].Init();
- }
- triggersPending = 0;
-
- simulationMode = 0;
- simulationTime = 0.0;
- isPaused = false;
- filePos = moveBuffer.filePos = noFilePosition;
- lastEndstopStates = platform->GetAllEndstopStates();
- firmwareUpdateModuleMap = 0;
-
- cancelWait = isWaiting = false;
-
- for (size_t i = 0; i < NumResources; ++i)
- {
- resourceOwners[i] = nullptr;
- }
-}
-
-float GCodes::FractionOfFilePrinted() const
-{
- const FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState;
- return (fileBeingPrinted.IsLive()) ? fileBeingPrinted.FractionRead() : -1.0;
-}
-
-// Start running the config file
-// We use triggerCGode as the source to prevent any triggers being executed until we have finished
-bool GCodes::RunConfigFile(const char* fileName)
-{
- return DoFileMacro(*daemonGCode, fileName, false);
-}
-
-// Are we still running the config file?
-bool GCodes::IsRunningConfigFile() const
-{
- return daemonGCode->MachineState().fileState.IsLive();
-}
-
-void GCodes::Spin()
-{
- if (!active)
- {
- return;
- }
-
- CheckTriggers();
-
- // Get the GCodeBuffer that we want to work from
- GCodeBuffer& gb = *(gcodeSources[nextGcodeSource]);
-
- // Set up a buffer for the reply
- char replyBuffer[gcodeReplyLength];
- StringRef reply(replyBuffer, ARRAY_SIZE(replyBuffer));
- reply.Clear();
-
- if (gb.GetState() == GCodeState::normal)
- {
- StartNextGCode(gb, reply);
- }
- else
- {
- // Perform the next operation of the state machine for this gcode source
- bool error = false;
-
- switch (gb.GetState())
- {
- case GCodeState::waitingForMoveToComplete:
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- gb.SetState(GCodeState::normal);
- }
- break;
-
- case GCodeState::homing:
- if (toBeHomed == 0)
- {
- gb.SetState(GCodeState::normal);
- }
- else
- {
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- // Leave the Z axis until all other axes are done
- if ((toBeHomed & (1u << axis)) != 0 && (axis != Z_AXIS || toBeHomed == (1u << Z_AXIS)))
- {
- toBeHomed &= ~(1u << axis);
- DoFileMacro(gb, homingFileNames[axis]);
- break;
- }
- }
- }
- break;
-
- case GCodeState::setBed1:
- reprap.GetMove()->SetIdentityTransform();
- probeCount = 0;
- gb.SetState(GCodeState::setBed2);
- // no break
-
- case GCodeState::setBed2:
- {
- int numProbePoints = reprap.GetMove()->NumberOfXYProbePoints();
- if (DoSingleZProbeAtPoint(gb, probeCount, 0.0))
- {
- probeCount++;
- if (probeCount >= numProbePoints)
- {
- zProbesSet = true;
- reprap.GetMove()->FinishedBedProbing(0, reply);
- gb.SetState(GCodeState::normal);
- }
- }
- }
- break;
-
- case GCodeState::toolChange1: // Release the old tool (if any)
- {
- const Tool *oldTool = reprap.GetCurrentTool();
- if (oldTool != NULL)
- {
- reprap.StandbyTool(oldTool->Number());
- }
- }
- gb.SetState(GCodeState::toolChange2);
- if (reprap.GetTool(newToolNumber) != nullptr && AllAxesAreHomed())
- {
- scratchString.printf("tpre%d.g", newToolNumber);
- DoFileMacro(gb, scratchString.Pointer(), false);
- }
- break;
-
- case GCodeState::toolChange2: // Select the new tool (even if it doesn't exist - that just deselects all tools)
- reprap.SelectTool(newToolNumber);
- gb.SetState(GCodeState::toolChange3);
- if (reprap.GetTool(newToolNumber) != nullptr && AllAxesAreHomed())
- {
- scratchString.printf("tpost%d.g", newToolNumber);
- DoFileMacro(gb, scratchString.Pointer(), false);
- }
- break;
-
- case GCodeState::toolChange3:
- gb.SetState(GCodeState::normal);
- break;
-
- case GCodeState::pausing1:
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- gb.SetState(GCodeState::pausing2);
- DoFileMacro(gb, PAUSE_G);
- }
- break;
-
- case GCodeState::pausing2:
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- reply.copy("Printing paused");
- gb.SetState(GCodeState::normal);
- }
- break;
-
- case GCodeState::resuming1:
- case GCodeState::resuming2:
- // Here when we have just finished running the resume macro file.
- // Move the head back to the paused location
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- float currentZ = moveBuffer.coords[Z_AXIS];
- for (size_t drive = 0; drive < numAxes; ++drive)
- {
- moveBuffer.coords[drive] = pauseRestorePoint.moveCoords[drive];
- }
- for (size_t drive = numAxes; drive < DRIVES; ++drive)
- {
- moveBuffer.coords[drive] = 0.0;
- }
- moveBuffer.feedRate = DEFAULT_FEEDRATE/minutesToSeconds; // ask for a good feed rate, we may have paused during a slow move
- moveBuffer.moveType = 0;
- moveBuffer.endStopsToCheck = 0;
- moveBuffer.usePressureAdvance = false;
- moveBuffer.filePos = noFilePosition;
- if (gb.GetState() == GCodeState::resuming1 && currentZ > pauseRestorePoint.moveCoords[Z_AXIS])
- {
- // First move the head to the correct XY point, then move it down in a separate move
- moveBuffer.coords[Z_AXIS] = currentZ;
- gb.SetState(GCodeState::resuming2);
- }
- else
- {
- // Just move to the saved position in one go
- gb.SetState(GCodeState::resuming3);
- }
- moveAvailable = true;
- }
- break;
-
- case GCodeState::resuming3:
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- for (size_t i = 0; i < NUM_FANS; ++i)
- {
- platform->SetFanValue(i, pausedFanValues[i]);
- }
- for (size_t drive = numAxes; drive < DRIVES; ++drive)
- {
- lastRawExtruderPosition[drive - numAxes] = pauseRestorePoint.moveCoords[drive]; // reset the extruder position in case we are receiving absolute extruder moves
- }
- feedRate = pauseRestorePoint.feedRate;
- isPaused = false;
- reply.copy("Printing resumed");
- gb.SetState(GCodeState::normal);
- }
- break;
-
- case GCodeState::flashing1:
-#ifdef DUET_NG
- // Update additional modules before the main firmware
- if (FirmwareUpdater::IsReady())
- {
- bool updating = false;
- for (unsigned int module = 1; module < NumFirmwareUpdateModules; ++module)
- {
- if ((firmwareUpdateModuleMap & (1u << module)) != 0)
- {
- firmwareUpdateModuleMap &= ~(1u << module);
- FirmwareUpdater::UpdateModule(module);
- updating = true;
- break;
- }
- }
- if (!updating)
- {
- gb.SetState(GCodeState::flashing2);
- }
- }
-#else
- gb.SetState(GCodeState::flashing2);
-#endif
- break;
-
- case GCodeState::flashing2:
- if ((firmwareUpdateModuleMap & 1) != 0)
- {
- // Update main firmware
- firmwareUpdateModuleMap = 0;
- platform->UpdateFirmware();
- // The above call does not return unless an error occurred
- }
- isFlashing = false;
- gb.SetState(GCodeState::normal);
- break;
-
- case GCodeState::stopping: // MO after executing stop.g if present
- case GCodeState::sleeping: // M1 after executing sleep.g if present
- // Deselect the active tool and turn off all heaters, unless parameter Hn was used with n > 0
- if (!gb.Seen('H') || gb.GetIValue() <= 0)
- {
- Tool* tool = reprap.GetCurrentTool();
- if (tool != nullptr)
- {
- reprap.StandbyTool(tool->Number());
- }
- reprap.GetHeat()->SwitchOffAll();
- }
-
- // chrishamm 2014-18-10: Although RRP says M0 is supposed to turn off all drives and heaters,
- // I think M1 is sufficient for this purpose. Leave M0 for a normal reset.
- if (gb.GetState() == GCodeState::sleeping)
- {
- DisableDrives();
- }
- else
- {
- platform->SetDriversIdle();
- }
- gb.SetState(GCodeState::normal);
- break;
-
- case GCodeState::gridProbing1: // ready to move to next grid probe point
- {
- // Move to the current probe point
- const GridDefinition grid = reprap.GetMove()->GetBedProbeGrid();
- const float x = grid.GetXCoordinate(gridXindex);
- const float y = grid.GetYCoordinate(gridYindex);
- if (grid.IsInRadius(x, y) && platform->IsAccessibleProbePoint(x, y))
- {
- moveBuffer.moveType = 0;
- moveBuffer.endStopsToCheck = 0;
- moveBuffer.usePressureAdvance = false;
- moveBuffer.filePos = noFilePosition;
- moveBuffer.coords[X_AXIS] = x - platform->GetZProbeParameters().xOffset;
- moveBuffer.coords[Y_AXIS] = y - platform->GetZProbeParameters().yOffset;
- moveBuffer.coords[Z_AXIS] = platform->GetZProbeDiveHeight();
- moveBuffer.feedRate = platform->GetZProbeTravelSpeed();
- moveBuffer.xAxes = 0;
- moveAvailable = true;
- gb.SetState(GCodeState::gridProbing2);
- }
- else
- {
- gb.SetState(GCodeState::gridProbing4);
- }
- }
- break;
-
- case GCodeState::gridProbing2: // ready to probe the current grid probe point
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- // Probe the bed at the current XY coordinates
- // Check for probe already triggered at start
- if (reprap.GetPlatform()->GetZProbeResult() == EndStopHit::lowHit)
- {
- reply.copy("Z probe already triggered before probing move started");
- error = true;
- gb.SetState(GCodeState::normal);
- break;
- }
-
- zProbeTriggered = false;
- moveBuffer.moveType = 0;
- moveBuffer.endStopsToCheck = ZProbeActive;
- moveBuffer.usePressureAdvance = false;
- moveBuffer.filePos = noFilePosition;
- moveBuffer.coords[Z_AXIS] = -platform->GetZProbeDiveHeight();
- moveBuffer.feedRate = platform->GetZProbeParameters().probeSpeed;
- moveBuffer.xAxes = 0;
- moveAvailable = true;
- gb.SetState(GCodeState::gridProbing3);
- }
- break;
-
- case GCodeState::gridProbing3: // ready to lift the probe after probing the current grid probe point
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- if (!zProbeTriggered)
- {
- reply.copy("Z probe was not triggered during probing move");
- error = true;
- gb.SetState(GCodeState::normal);
- break;
- }
-
- const float heightError = moveBuffer.coords[Z_AXIS] - platform->ZProbeStopHeight();
- reprap.GetMove()->SetGridHeight(gridXindex, gridYindex, heightError);
- ++numPointsProbed;
- heightSum += (double)heightError;
- heightSquaredSum += (double)heightError * (double)heightError;
-
- // Move back up to the dive height
- moveBuffer.moveType = 0;
- moveBuffer.endStopsToCheck = 0;
- moveBuffer.usePressureAdvance = false;
- moveBuffer.filePos = noFilePosition;
- moveBuffer.coords[Z_AXIS] = platform->GetZProbeDiveHeight();
- moveBuffer.feedRate = platform->GetZProbeTravelSpeed();
- moveBuffer.xAxes = 0;
- moveAvailable = true;
- gb.SetState(GCodeState::gridProbing4);
- }
- break;
-
- case GCodeState::gridProbing4: // ready to compute the next probe point
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- const GridDefinition grid = reprap.GetMove()->GetBedProbeGrid();
- if (gridYindex & 1)
- {
- // Odd row, so decreasing X
- if (gridXindex == 0)
- {
- ++gridYindex;
- }
- else
- {
- --gridXindex;
- }
- }
- else
- {
- // Even row, so increasing X
- if (gridXindex + 1 == grid.NumXpoints())
- {
- ++gridYindex;
- }
- else
- {
- ++gridXindex;
- }
- }
- if (gridYindex == grid.NumYpoints())
- {
- // Finished probing the grid
- if (numPointsProbed >= 4)
- {
- error = reprap.GetMove()->SaveHeightMapToFile(heightMapFile, reply);
- const double mean = heightSum/numPointsProbed;
- const double deviation = sqrt(((heightSquaredSum * numPointsProbed) - (heightSum * heightSum)))/numPointsProbed;
- reply.catf(" - %u points probed, mean error %.2f, deviation %.2f", numPointsProbed, mean, deviation);
- reprap.GetMove()->UseHeightMap();
- }
- else
- {
- reply.copy("Too few points probed");
- error = true;
- }
- gb.SetState(GCodeState::normal);
- }
- else
- {
- gb.SetState(GCodeState::gridProbing1);
- }
- }
- break;
-
- default: // should not happen
- platform->Message(GENERIC_MESSAGE, "Error: undefined GCodeState\n");
- gb.SetState(GCodeState::normal);
- break;
- }
-
- if (gb.GetState() == GCodeState::normal)
- {
- // We completed a command, so unlock resources and tell the host about it
- UnlockAll(gb);
- HandleReply(gb, error, reply.Pointer());
- }
- }
-
- // Move on to the next gcode source ready for next time
- ++nextGcodeSource;
- if (nextGcodeSource == ARRAY_SIZE(gcodeSources))
- {
- nextGcodeSource = 0;
- }
-
- platform->ClassReport(longWait);
-}
-
-// Start a new gcode, or continue to execute one that has already been started:
-void GCodes::StartNextGCode(GCodeBuffer& gb, StringRef& reply)
-{
- if (isPaused && &gb == fileGCode)
- {
- // We are paused, so don't process any more gcodes from the file being printed.
- // There is a potential issue here if fileGCode holds any locks, so unlock everything.
- UnlockAll(gb);
- }
- else if (gb.IsReady() || gb.IsExecuting())
- {
- gb.SetFinished(ActOnCode(gb, reply));
- }
- else if (gb.MachineState().fileState.IsLive())
- {
- DoFilePrint(gb, reply);
- }
- else if (&gb == httpGCode)
- {
- // Webserver
- for (unsigned int i = 0; i < 16 && webserver->GCodeAvailable(WebSource::HTTP); ++i)
- {
- const char b = webserver->ReadGCode(WebSource::HTTP);
- if (gb.Put(b))
- {
- // We have a complete gcode
- if (gb.WritingFileDirectory() != nullptr)
- {
- WriteGCodeToFile(gb);
- gb.SetFinished(true);
- }
- else
- {
- gb.SetFinished(ActOnCode(gb, reply));
- }
- break;
- }
- }
- }
- else if (&gb == telnetGCode)
- {
- // Telnet
- for (unsigned int i = 0; i < GCODE_LENGTH && webserver->GCodeAvailable(WebSource::Telnet); ++i)
- {
- char b = webserver->ReadGCode(WebSource::Telnet);
- if (gb.Put(b))
- {
- gb.SetFinished(ActOnCode(gb, reply));
- break;
- }
- }
- }
- else if (&gb == serialGCode)
- {
- // USB interface
- for (unsigned int i = 0; i < 16 && platform->GCodeAvailable(SerialSource::USB); ++i)
- {
- const char b = platform->ReadFromSource(SerialSource::USB);
- // Check the special case of uploading the reprap.htm file
- if (gb.WritingFileDirectory() == platform->GetWebDir())
- {
- WriteHTMLToFile(gb, b);
- }
- else if (gb.Put(b)) // add char to buffer and test whether the gcode is complete
- {
- // We have a complete gcode
- if (gb.WritingFileDirectory() != nullptr)
- {
- WriteGCodeToFile(gb);
- gb.SetFinished(true);
- }
- else
- {
- gb.SetFinished(ActOnCode(gb, reply));
- }
- break;
- }
- }
- }
- else if (&gb == auxGCode)
- {
- // Aux serial port (typically PanelDue)
- for (unsigned int i = 0; i < 16 && platform->GCodeAvailable(SerialSource::AUX); ++i)
- {
- char b = platform->ReadFromSource(SerialSource::AUX);
- if (gb.Put(b)) // add char to buffer and test whether the gcode is complete
- {
- platform->SetAuxDetected();
- gb.SetFinished(ActOnCode(gb, reply));
- break;
- }
- }
- }
-}
-
-void GCodes::DoFilePrint(GCodeBuffer& gb, StringRef& reply)
-{
- FileData& fd = gb.MachineState().fileState;
- for (int i = 0; i < 50 && fd.IsLive(); ++i)
- {
- char b;
- if (fd.Read(b))
- {
- if (gb.StartingNewCode() && &gb == fileGCode && gb.MachineState().previous == nullptr)
- {
- filePos = fd.GetPosition() - 1;
- //debugPrintf("Set file pos %u\n", filePos);
- }
- if (gb.Put(b))
- {
- gb.SetFinished(ActOnCode(gb, reply));
- return;
- }
- }
- else
- {
- // We have reached the end of the file. Check for the last line of gcode not ending in newline.
- if (!gb.StartingNewCode()) // if there is something in the buffer
- {
- if (gb.Put('\n')) // in case there wasn't a newline ending the file
- {
- gb.SetFinished(ActOnCode(gb, reply));
- return;
- }
- }
-
- gb.Init(); // mark buffer as empty
-
- // Don't close the file until all moves have been completed, in case the print gets paused.
- // Also, this keeps the state as 'Printing' until the print really has finished.
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- fd.Close();
- if (gb.MachineState().previous == nullptr)
- {
- // Finished printing SD card file
- reprap.GetPrintMonitor()->StoppedPrint();
- if (platform->Emulating() == marlin)
- {
- // Pronterface expects a "Done printing" message
- HandleReply(gb, false, "Done printing file");
- }
- }
- else
- {
- // Finished a macro
- Pop(gb);
- gb.Init();
- if (gb.GetState() == GCodeState::normal)
- {
- UnlockAll(gb);
- HandleReply(gb, false, "");
- }
- }
- }
- return;
- }
- }
-}
-
-// Check for and execute triggers
-void GCodes::CheckTriggers()
-{
- // Check for endstop state changes that activate new triggers
- const TriggerMask oldEndstopStates = lastEndstopStates;
- lastEndstopStates = platform->GetAllEndstopStates();
- const TriggerMask risen = lastEndstopStates & ~oldEndstopStates,
- fallen = ~lastEndstopStates & oldEndstopStates;
- unsigned int lowestTriggerPending = MaxTriggers;
- for (unsigned int triggerNumber = 0; triggerNumber < MaxTriggers; ++triggerNumber)
- {
- const Trigger& ct = triggers[triggerNumber];
- if ( ((ct.rising & risen) != 0 || (ct.falling & fallen) != 0)
- && (ct.condition == 0 || (ct.condition == 1 && reprap.GetPrintMonitor()->IsPrinting()))
- )
- {
- triggersPending |= (1u << triggerNumber);
- }
- if (triggerNumber < lowestTriggerPending && (triggersPending & (1u << triggerNumber)) != 0)
- {
- lowestTriggerPending = triggerNumber;
- }
- }
-
- // If any triggers are pending, activate the one with the lowest number
- if (lowestTriggerPending == 0)
- {
- triggersPending &= ~(1u << lowestTriggerPending); // clear the trigger
- DoEmergencyStop();
- }
- else if (lowestTriggerPending < MaxTriggers // if a trigger is pending
- && !daemonGCode->MachineState().fileState.IsLive()
- && daemonGCode->GetState() == GCodeState::normal // and we are not already executing a trigger or config.g
- )
- {
- if (lowestTriggerPending == 1)
- {
- if (isPaused || !reprap.GetPrintMonitor()->IsPrinting())
- {
- triggersPending &= ~(1u << lowestTriggerPending); // ignore a pause trigger if we are already paused
- }
- else if (LockMovement(*daemonGCode)) // need to lock movement before executing the pause macro
- {
- triggersPending &= ~(1u << lowestTriggerPending); // clear the trigger
- DoPause(*daemonGCode);
- }
- }
- else
- {
- triggersPending &= ~(1u << lowestTriggerPending); // clear the trigger
- char buffer[25];
- StringRef filename(buffer, ARRAY_SIZE(buffer));
- filename.printf(SYS_DIR "trigger%u.g", lowestTriggerPending);
- DoFileMacro(*daemonGCode, filename.Pointer(), true);
- }
- }
-}
-
-// Execute an emergency stop
-void GCodes::DoEmergencyStop()
-{
- reprap.EmergencyStop();
- Reset();
- platform->Message(GENERIC_MESSAGE, "Emergency Stop! Reset the controller to continue.");
-}
-
-// Pause the print. Before calling this, check that we are doing a file print that isn't already paused and get the movement lock.
-void GCodes::DoPause(GCodeBuffer& gb)
-{
- if (&gb == fileGCode)
- {
- // Pausing a file print because of a command in the file itself
- for (size_t drive = 0; drive < numAxes; ++drive)
- {
- pauseRestorePoint.moveCoords[drive] = moveBuffer.coords[drive];
- }
- for (size_t drive = numAxes; drive < DRIVES; ++drive)
- {
- pauseRestorePoint.moveCoords[drive] = lastRawExtruderPosition[drive - numAxes]; // get current extruder positions into pausedMoveBuffer
- }
- pauseRestorePoint.feedRate = feedRate;
- }
- else
- {
- // Pausing a file print via another input source
- pauseRestorePoint.feedRate = feedRate; // the call to PausePrint may or may not change this
- FilePosition fPos = reprap.GetMove()->PausePrint(pauseRestorePoint.moveCoords, pauseRestorePoint.feedRate, reprap.GetCurrentXAxes());
- // tell Move we wish to pause the current print
- FileData& fdata = fileGCode->MachineState().fileState;
- if (fPos != noFilePosition && fdata.IsLive())
- {
- fdata.Seek(fPos); // replay the abandoned instructions if/when we resume
- }
- if (moveAvailable)
- {
- for (size_t drive = numAxes; drive < DRIVES; ++drive)
- {
- pauseRestorePoint.moveCoords[drive] += moveBuffer.coords[drive]; // add on the extrusion in the move not yet taken
- }
- ClearMove();
- }
-
- for (size_t drive = numAxes; drive < DRIVES; ++drive)
- {
- pauseRestorePoint.moveCoords[drive] = lastRawExtruderPosition[drive - numAxes] - pauseRestorePoint.moveCoords[drive];
- }
-
- //TODO record the virtual extruder positions of mixing tools too. But that's very hard to do unless we store it in the move.
-
- if (reprap.Debug(moduleGcodes))
- {
- platform->MessageF(GENERIC_MESSAGE, "Paused print, file offset=%u\n", fPos);
- }
- }
-
- for (size_t i = 0; i < NUM_FANS; ++i)
- {
- pausedFanValues[i] = platform->GetFanValue(i);
- }
- gb.SetState(GCodeState::pausing1);
- isPaused = true;
-}
-
-void GCodes::Diagnostics(MessageType mtype)
-{
- platform->Message(mtype, "=== GCodes ===\n");
- platform->MessageF(mtype, "Move available? %s\n", moveAvailable ? "yes" : "no");
- platform->MessageF(mtype, "Stack records: %u allocated, %u in use\n", GCodeMachineState::GetNumAllocated(), GCodeMachineState::GetNumInUse());
-
- for (size_t i = 0; i < ARRAY_SIZE(gcodeSources); ++i)
- {
- gcodeSources[i]->Diagnostics(mtype);
- }
-}
-
-// The wait till everything's done function. If you need the machine to
-// be idle before you do something (for example homing an axis, or shutting down) call this
-// until it returns true. As a side-effect it loads moveBuffer with the last position and feedrate for you.
-bool GCodes::AllMovesAreFinishedAndMoveBufferIsLoaded()
-{
- // Last one gone?
- if (moveAvailable)
- {
- return false;
- }
-
- // Wait for all the queued moves to stop so we get the actual last position
- if (!reprap.GetMove()->AllMovesAreFinished())
- {
- return false;
- }
-
- reprap.GetMove()->ResumeMoving();
- reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes());
- return true;
-}
-
-// Save (some of) the state of the machine for recovery in the future.
-bool GCodes::Push(GCodeBuffer& gb)
-{
- bool ok = gb.PushState();
- if (!ok)
- {
- platform->Message(GENERIC_MESSAGE, "Push(): stack overflow!\n");
- }
- return ok;
-}
-
-// Recover a saved state
-void GCodes::Pop(GCodeBuffer& gb)
-{
- if (!gb.PopState())
- {
- platform->Message(GENERIC_MESSAGE, "Pop(): stack underflow!\n");
- }
-}
-
-// Move expects all axis movements to be absolute, and all extruder drive moves to be relative. This function serves that.
-// 'moveType' is the S parameter in the G0 or G1 command, or -1 if we are doing G92.
-// For regular (type 0) moves, we apply limits and do X axis mapping.
-// Returns true if we have a legal move (or G92 argument), false if this gcode should be discarded
-bool GCodes::LoadMoveBufferFromGCode(GCodeBuffer& gb, int moveType)
-{
- // Zero every extruder drive as some drives may not be changed
- for (size_t drive = numAxes; drive < DRIVES; drive++)
- {
- moveBuffer.coords[drive] = 0.0;
- }
-
- // Deal with feed rate
- if (gb.Seen(feedrateLetter))
- {
- feedRate = gb.GetFValue() * distanceScale * speedFactor;
- }
- moveBuffer.feedRate = feedRate;
-
- // First do extrusion, and check, if we are extruding, that we have a tool to extrude with
- Tool* tool = reprap.GetCurrentTool();
- if (gb.Seen(extrudeLetter))
- {
- if (tool == nullptr)
- {
- platform->Message(GENERIC_MESSAGE, "Attempting to extrude with no tool selected.\n");
- return false;
- }
- size_t eMoveCount = tool->DriveCount();
- if (eMoveCount > 0)
- {
- // Set the drive values for this tool.
- // chrishamm-2014-10-03: Do NOT check extruder temperatures here, because we may be executing queued codes like M116
- if (tool->GetMixing())
- {
- const float moveArg = gb.GetFValue() * distanceScale;
- if (moveType == -1) // if doing G92
- {
- tool->virtualExtruderPosition = moveArg;
- }
- else
- {
- const float requestedExtrusionAmount = (gb.MachineState().drivesRelative)
- ? moveArg
- : moveArg - tool->virtualExtruderPosition;
- for (size_t eDrive = 0; eDrive < eMoveCount; eDrive++)
- {
- const int drive = tool->Drive(eDrive);
- const float extrusionAmount = requestedExtrusionAmount * tool->GetMix()[eDrive];
- lastRawExtruderPosition[drive] += extrusionAmount;
- rawExtruderTotalByDrive[drive] += extrusionAmount;
- rawExtruderTotal += extrusionAmount;
- moveBuffer.coords[drive + numAxes] = extrusionAmount * extrusionFactors[drive];
- }
- }
- }
- else
- {
- float eMovement[MaxExtruders];
- size_t mc = eMoveCount;
- gb.GetFloatArray(eMovement, mc, false);
- if (eMoveCount != mc)
- {
- platform->MessageF(GENERIC_MESSAGE, "Wrong number of extruder drives for the selected tool: %s\n", gb.Buffer());
- return false;
- }
-
- for (size_t eDrive = 0; eDrive < eMoveCount; eDrive++)
- {
- const int drive = tool->Drive(eDrive);
- const float moveArg = eMovement[eDrive] * distanceScale;
- if (moveType == -1)
- {
- moveBuffer.coords[drive + numAxes] = moveArg;
- lastRawExtruderPosition[drive] = moveArg;
- }
- else
- {
- const float extrusionAmount = (gb.MachineState().drivesRelative)
- ? moveArg
- : moveArg - lastRawExtruderPosition[drive];
- lastRawExtruderPosition[drive] += extrusionAmount;
- rawExtruderTotalByDrive[drive] += extrusionAmount;
- rawExtruderTotal += extrusionAmount;
- moveBuffer.coords[drive + numAxes] = extrusionAmount * extrusionFactors[drive];
- }
- }
- }
- }
- }
-
- // Now the movement axes
- const Tool *currentTool = reprap.GetCurrentTool();
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- float moveArg = gb.GetFValue() * distanceScale * axisScaleFactors[axis];
- if (moveType == -1) // if doing G92
- {
- SetAxisIsHomed(axis); // doing a G92 defines the absolute axis position
- moveBuffer.coords[axis] = moveArg;
- }
- else if (axis == X_AXIS && moveType == 0 && currentTool != nullptr)
- {
- // Perform X axis mapping
- const uint32_t xMap = currentTool->GetXAxisMap();
- for (size_t mappedAxis = 0; mappedAxis < numAxes; ++mappedAxis)
- {
- if ((xMap & (1u << mappedAxis)) != 0)
- {
- float mappedMoveArg = moveArg;
- if (gb.MachineState().axesRelative)
- {
- mappedMoveArg += moveBuffer.coords[mappedAxis];
- }
- else
- {
- mappedMoveArg -= currentTool->GetOffset()[mappedAxis]; // adjust requested position to compensate for tool offset
- }
- moveBuffer.coords[mappedAxis] = mappedMoveArg;
- }
- }
- }
- else
- {
- if (gb.MachineState().axesRelative)
- {
- moveArg += moveBuffer.coords[axis];
- }
- else if (currentTool != nullptr && moveType == 0)
- {
- moveArg -= currentTool->GetOffset()[axis]; // adjust requested position to compensate for tool offset
- }
- moveBuffer.coords[axis] = moveArg;
- }
- }
- }
-
- // If doing a regular move and applying limits, limit all axes
- if (moveType == 0 && limitAxes
-#if SUPPORT_ROLAND
- && !reprap.GetRoland()->Active()
-#endif
- )
- {
- if (!reprap.GetMove()->IsDeltaMode())
- {
- // Cartesian or CoreXY printer, so limit those axes that have been homed
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (GetAxisIsHomed(axis))
- {
- float& f = moveBuffer.coords[axis];
- if (f < platform->AxisMinimum(axis))
- {
- f = platform->AxisMinimum(axis);
- }
- else if (f > platform->AxisMaximum(axis))
- {
- f = platform->AxisMaximum(axis);
- }
- }
- }
- }
- else if (AllAxesAreHomed()) // this is to allow extruder-only moves before homing
- {
- // If axes have been homed on a delta printer and this isn't a homing move, check for movements outside limits.
- // Skip this check if axes have not been homed, so that extruder-only moved are allowed before homing
- // Constrain the move to be within the build radius
- const float diagonalSquared = fsquare(moveBuffer.coords[X_AXIS]) + fsquare(moveBuffer.coords[Y_AXIS]);
- if (diagonalSquared > reprap.GetMove()->GetDeltaParams().GetPrintRadiusSquared())
- {
- const float factor = sqrtf(reprap.GetMove()->GetDeltaParams().GetPrintRadiusSquared() / diagonalSquared);
- moveBuffer.coords[X_AXIS] *= factor;
- moveBuffer.coords[Y_AXIS] *= factor;
- }
-
- // Constrain the end height of the move to be no greater than the homed height and no lower than -0.2mm
- moveBuffer.coords[Z_AXIS] = max<float>(platform->AxisMinimum(Z_AXIS),
- min<float>(moveBuffer.coords[Z_AXIS], reprap.GetMove()->GetDeltaParams().GetHomedHeight()));
- }
- }
-
- return true;
-}
-
-// This function is called for a G Code that makes a move.
-// If the Move class can't receive the move (i.e. things have to wait), return 0.
-// If we have queued the move and the caller doesn't need to wait for it to complete, return 1.
-// If we need to wait for the move to complete before doing another one (e.g. because endstops are checked in this move), return 2.
-int GCodes::SetUpMove(GCodeBuffer& gb, StringRef& reply)
-{
- // Last one gone yet?
- if (moveAvailable)
- {
- return 0;
- }
-
- // Check to see if the move is a 'homing' move that endstops are checked on.
- moveBuffer.endStopsToCheck = 0;
- moveBuffer.moveType = 0;
- moveBuffer.xAxes = reprap.GetCurrentXAxes();
- if (gb.Seen('S'))
- {
- int ival = gb.GetIValue();
- if (ival == 1 || ival == 2)
- {
- moveBuffer.moveType = ival;
- moveBuffer.xAxes = 0; // don't do bed compensation
- }
-
- if (ival == 1)
- {
- for (size_t i = 0; i < numAxes; ++i)
- {
- if (gb.Seen(axisLetters[i]))
- {
- moveBuffer.endStopsToCheck |= (1u << i);
- }
- }
- }
- else if (ival == 99) // temporary code to log Z probe change positions
- {
- moveBuffer.endStopsToCheck |= LogProbeChanges;
- }
- }
-
- if (reprap.GetMove()->IsDeltaMode())
- {
- // Extra checks to avoid damaging delta printers
- if (moveBuffer.moveType != 0 && !gb.MachineState().axesRelative)
- {
- // We have been asked to do a move without delta mapping on a delta machine, but the move is not relative.
- // This may be damaging and is almost certainly a user mistake, so ignore the move.
- reply.copy("Attempt to move the motors of a delta printer to absolute positions");
- return 1;
- }
-
- if (moveBuffer.moveType == 0 && !AllAxesAreHomed())
- {
- // The user may be attempting to move a delta printer to an XYZ position before homing the axes
- // This may be damaging and is almost certainly a user mistake, so ignore the move. But allow extruder-only moves.
- if (gb.Seen(axisLetters[X_AXIS]) || gb.Seen(axisLetters[Y_AXIS]) || gb.Seen(axisLetters[Z_AXIS]))
- {
- reply.copy("Attempt to move the head of a delta printer before homing the towers");
- return 1;
- }
- }
- }
-
- // Load the last position and feed rate into moveBuffer
-#if SUPPORT_ROLAND
- if (reprap.GetRoland()->Active())
- {
- reprap.GetRoland()->GetCurrentRolandPosition(moveBuffer);
- }
- else
-#endif
- {
- reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, moveBuffer.moveType, reprap.GetCurrentXAxes());
- }
-
- // Load the move buffer with either the absolute movement required or the relative movement required
- float oldCoords[MAX_AXES];
- memcpy(oldCoords, moveBuffer.coords, sizeof(oldCoords));
- moveAvailable = LoadMoveBufferFromGCode(gb, moveBuffer.moveType);
- if (moveAvailable)
- {
- // Flag whether we should use pressure advance, if there is any extrusion in this move.
- // We assume it is a normal printing move needing pressure advance if there is forward extrusion and XY movement.
- // The movement code will only apply pressure advance if there is forward extrusion, so we only need to check for XY movement here.
- moveBuffer.usePressureAdvance = false;
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- if (axis != Z_AXIS && moveBuffer.coords[axis] != oldCoords[axis])
- {
- moveBuffer.usePressureAdvance = true;
- break;
- }
- }
- moveBuffer.filePos = (&gb == fileGCode) ? filePos : noFilePosition;
- //debugPrintf("Queue move pos %u\n", moveFilePos);
- }
- return (moveBuffer.moveType != 0 || moveBuffer.endStopsToCheck != 0) ? 2 : 1;
-}
-
-// The Move class calls this function to find what to do next.
-
-bool GCodes::ReadMove(RawMove& m)
-{
- if (!moveAvailable)
- {
- return false;
- }
-
- m = moveBuffer;
- ClearMove();
- return true;
-}
-
-void GCodes::ClearMove()
-{
- moveAvailable = false;
- moveBuffer.endStopsToCheck = 0;
- moveBuffer.moveType = 0;
- moveBuffer.isFirmwareRetraction = false;
-}
-
-// Run a file macro. Prior to calling this, 'state' must be set to the state we want to enter when the macro has been completed.
-// Return true if the file was found or it wasn't and we were asked to report that fact.
-bool GCodes::DoFileMacro(GCodeBuffer& gb, const char* fileName, bool reportMissing)
-{
- FileStore * const f = platform->GetFileStore(platform->GetSysDir(), fileName, false);
- if (f == nullptr)
- {
- if (reportMissing)
- {
- // Don't use snprintf into scratchString here, because fileName may be aliased to scratchString
- platform->MessageF(GENERIC_MESSAGE, "Macro file %s not found.\n", fileName);
- return true;
- }
- return false;
- }
-
- if (!Push(gb))
- {
- return true;
- }
- gb.MachineState().fileState.Set(f);
- gb.MachineState().doingFileMacro = true;
- gb.SetState(GCodeState::normal);
- gb.Init();
- return true;
-}
-
-void GCodes::FileMacroCyclesReturn(GCodeBuffer& gb)
-{
- if (gb.MachineState().doingFileMacro)
- {
- gb.PopState();
- gb.Init();
- }
-}
-
-// To execute any move, call this until it returns true.
-// There is only one copy of the canned cycle variable so you must acquire the move lock before calling this.
-bool GCodes::DoCannedCycleMove(GCodeBuffer& gb, EndstopChecks ce)
-{
- if (LockMovementAndWaitForStandstill(gb))
- {
- if (cannedCycleMoveQueued) // if the move has already been queued, it must have finished
- {
- Pop(gb);
- cannedCycleMoveQueued = false;
- return true;
- }
-
- // Otherwise, the move has not been queued yet
- if (!Push(gb))
- {
- return true; // stack overflow
- }
-
- for (size_t drive = 0; drive < DRIVES; drive++)
- {
- switch(cannedMoveType[drive])
- {
- case CannedMoveType::none:
- break;
- case CannedMoveType::relative:
- moveBuffer.coords[drive] += cannedMoveCoords[drive];
- break;
- case CannedMoveType::absolute:
- moveBuffer.coords[drive] = cannedMoveCoords[drive];
- break;
- }
- }
- moveBuffer.feedRate = cannedFeedRate;
- moveBuffer.xAxes = 0;
- moveBuffer.endStopsToCheck = ce;
- moveBuffer.filePos = noFilePosition;
- moveBuffer.usePressureAdvance = false;
- moveAvailable = true;
- cannedCycleMoveQueued = true;
- }
- return false;
-}
-
-// This handles G92
-bool GCodes::SetPositions(GCodeBuffer& gb)
-{
- // Don't pause the machine if only extruder drives are being reset (DC, 2015-09-06).
- // This avoids blobs and seams when the gcode uses absolute E coordinates and periodically includes G92 E0.
- bool includingAxes = false;
- for (size_t drive = 0; drive < numAxes; ++drive)
- {
- if (gb.Seen(axisLetters[drive]))
- {
- includingAxes = true;
- break;
- }
- }
-
- if (includingAxes)
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- }
- else if (moveAvailable) // wait for previous move to be taken so that GetCurrentUserPosition returns the correct value
- {
- return false;
- }
-
- reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes()); // make sure move buffer is up to date
- bool ok = LoadMoveBufferFromGCode(gb, -1);
- if (ok && includingAxes)
- {
-#if SUPPORT_ROLAND
- if (reprap.GetRoland()->Active())
- {
- for(size_t axis = 0; axis < AXES; axis++)
- {
- if (!reprap.GetRoland()->ProcessG92(moveBuffer[axis], axis))
- {
- return false;
- }
- }
- }
-#endif
- SetPositions(moveBuffer.coords);
- }
-
- return true;
-}
-
-// Offset the axes by the X, Y, and Z amounts in the M code in gb. Say the machine is at [10, 20, 30] and
-// the offsets specified are [8, 2, -5]. The machine will move to [18, 22, 25] and henceforth consider that point
-// to be [10, 20, 30].
-bool GCodes::OffsetAxes(GCodeBuffer& gb)
-{
- if (!offSetSet)
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- for (size_t drive = 0; drive < DRIVES; drive++)
- {
- if (drive < numAxes)
- {
- record[drive] = moveBuffer.coords[drive];
- if (gb.Seen(axisLetters[drive]))
- {
- cannedMoveCoords[drive] = gb.GetFValue();
- cannedMoveType[drive] = CannedMoveType::relative;
- }
- }
- else
- {
- record[drive] = 0.0;
- }
- cannedMoveType[drive] = CannedMoveType::none;
- }
-
- if (gb.Seen(feedrateLetter)) // Has the user specified a feedrate?
- {
- cannedFeedRate = gb.GetFValue() * distanceScale * SECONDS_TO_MINUTES;
- }
- else
- {
- cannedFeedRate = DEFAULT_FEEDRATE;
- }
-
- offSetSet = true;
- }
-
- if (DoCannedCycleMove(gb, 0))
- {
- // Restore positions
- for (size_t drive = 0; drive < DRIVES; drive++)
- {
- moveBuffer.coords[drive] = record[drive];
- }
- reprap.GetMove()->SetLiveCoordinates(record); // This doesn't transform record
- reprap.GetMove()->SetPositions(record); // This does
- offSetSet = false;
- return true;
- }
-
- return false;
-}
-
-// Home one or more of the axes. Which ones are decided by the
-// booleans homeX, homeY and homeZ.
-// Returns true if completed, false if needs to be called again.
-// 'reply' is only written if there is an error.
-// 'error' is false on entry, gets changed to true if there is an error.
-bool GCodes::DoHome(GCodeBuffer& gb, StringRef& reply, bool& error)
-{
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
-
-#if SUPPORT_ROLAND
- // Deal with a Roland configuration
- if (reprap.GetRoland()->Active())
- {
- bool rolHome = reprap.GetRoland()->ProcessHome();
- if (rolHome)
- {
- for(size_t axis = 0; axis < AXES; axis++)
- {
- axisIsHomed[axis] = true;
- }
- }
- return rolHome;
- }
-#endif
-
- if (reprap.GetMove()->IsDeltaMode())
- {
- SetAllAxesNotHomed();
- DoFileMacro(gb, HOME_DELTA_G);
- }
- else
- {
- toBeHomed = 0;
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- toBeHomed |= (1u << axis);
- SetAxisNotHomed(axis);
- }
- }
-
- if (toBeHomed == 0 || toBeHomed == ((1u << numAxes) - 1))
- {
- // Homing everything
- SetAllAxesNotHomed();
- DoFileMacro(gb, HOME_ALL_G);
- }
- else if ( platform->MustHomeXYBeforeZ()
- && ((toBeHomed & (1u << Z_AXIS)) != 0)
- && ((toBeHomed | axesHomed | (1u << Z_AXIS)) != ((1u << numAxes) - 1))
- )
- {
- // We can only home Z if both X and Y have already been homed or are being homed
- reply.copy("Must home all other axes before homing Z");
- error = true;
- }
- else
- {
- gb.SetState(GCodeState::homing);
- }
- }
- return true;
-}
-
-// This lifts Z a bit, moves to the probe XY coordinates (obtained by a call to GetProbeCoordinates() ),
-// probes the bed height, and records the Z coordinate probed. If you want to program any general
-// internal canned cycle, this shows how to do it.
-// On entry, probePointIndex specifies which of the points this is.
-bool GCodes::DoSingleZProbeAtPoint(GCodeBuffer& gb, int probePointIndex, float heightAdjust)
-{
- reprap.GetMove()->SetIdentityTransform(); // It doesn't matter if these are called repeatedly
-
- for (size_t drive = 0; drive <= DRIVES; drive++)
- {
- cannedMoveType[drive] = CannedMoveType::none;
- }
-
- switch (cannedCycleMoveCount)
- {
- case 0: // Move Z to the dive height. This only does anything on the first move; on all the others Z is already there
- cannedMoveCoords[Z_AXIS] = platform->GetZProbeDiveHeight() + max<float>(platform->ZProbeStopHeight(), 0.0);
- cannedMoveType[Z_AXIS] = CannedMoveType::absolute;
- cannedFeedRate = platform->GetZProbeTravelSpeed();
- if (DoCannedCycleMove(gb, 0))
- {
- cannedCycleMoveCount++;
- }
- return false;
-
- case 1: // Move to the correct XY coordinates
- GetProbeCoordinates(probePointIndex, cannedMoveCoords[X_AXIS], cannedMoveCoords[Y_AXIS], cannedMoveCoords[Z_AXIS]);
- cannedMoveType[X_AXIS] = CannedMoveType::absolute;
- cannedMoveType[Y_AXIS] = CannedMoveType::absolute;
- // NB - we don't use the Z value
- cannedFeedRate = platform->GetZProbeTravelSpeed();
- if (DoCannedCycleMove(gb, 0))
- {
- cannedCycleMoveCount++;
- }
- return false;
-
- case 2: // Probe the bed
- {
- const float height = (GetAxisIsHomed(Z_AXIS))
- ? 2 * platform->GetZProbeDiveHeight() // Z axis has been homed, so no point in going very far
- : 1.1 * platform->AxisTotalLength(Z_AXIS); // Z axis not homed yet, so treat this as a homing move
- switch(DoZProbe(gb, height))
- {
- case 0:
- // Z probe is already triggered at the start of the move, so abandon the probe and record an error
- platform->Message(GENERIC_MESSAGE, "Error: Z probe already triggered at start of probing move\n");
- cannedCycleMoveCount++;
- reprap.GetMove()->SetZBedProbePoint(probePointIndex, platform->GetZProbeDiveHeight(), true, true);
- break;
-
- case 1:
- // Z probe did not trigger
- platform->Message(GENERIC_MESSAGE, "Error: Z probe was not triggered during probing move\n");
- cannedCycleMoveCount++;
- reprap.GetMove()->SetZBedProbePoint(probePointIndex, -(platform->GetZProbeDiveHeight()), true, true);
- break;
-
- case 2:
- // Successful probing
- if (GetAxisIsHomed(Z_AXIS))
- {
- lastProbedZ = moveBuffer.coords[Z_AXIS] - (platform->ZProbeStopHeight() + heightAdjust);
- }
- else
- {
- // The Z axis has not yet been homed, so treat this probe as a homing move.
- moveBuffer.coords[Z_AXIS] = platform->ZProbeStopHeight() + heightAdjust;
- SetPositions(moveBuffer.coords);
- SetAxisIsHomed(Z_AXIS);
- lastProbedZ = 0.0;
- }
- reprap.GetMove()->SetZBedProbePoint(probePointIndex, lastProbedZ, true, false);
- cannedCycleMoveCount++;
- break;
-
- default:
- break;
- }
- }
- return false;
-
- case 3: // Raise the head back up to the dive height
- cannedMoveCoords[Z_AXIS] = platform->GetZProbeDiveHeight() + max<float>(platform->ZProbeStopHeight(), 0.0);
- cannedMoveType[Z_AXIS] = CannedMoveType::absolute;
- cannedFeedRate = platform->GetZProbeTravelSpeed();
- if (DoCannedCycleMove(gb, 0))
- {
- cannedCycleMoveCount = 0;
- return true;
- }
- return false;
-
- default: // should not happen
- cannedCycleMoveCount = 0;
- return true;
- }
-}
-
-// This simply moves down till the Z probe/switch is triggered. Call it repeatedly until it returns true.
-// Called when we do a G30 with no P parameter.
-bool GCodes::DoSingleZProbe(GCodeBuffer& gb, bool reportOnly, float heightAdjust)
-{
- switch (DoZProbe(gb, 1.1 * platform->AxisTotalLength(Z_AXIS)))
- {
- case 0: // failed
- platform->Message(GENERIC_MESSAGE, "Error: Z probe already triggered at start of probing move\n");
- return true;
-
- case 1:
- platform->Message(GENERIC_MESSAGE, "Error: Z probe was not triggered during probing move\n");
- return true;
-
- case 2: // success
- if (!reportOnly)
- {
- moveBuffer.coords[Z_AXIS] = platform->ZProbeStopHeight() + heightAdjust;
- SetPositions(moveBuffer.coords);
- SetAxisIsHomed(Z_AXIS);
- lastProbedZ = 0.0;
- }
- return true;
-
- default: // not finished yet
- return false;
- }
-}
-
-// Do a Z probe cycle up to the maximum specified distance.
-// Returns -1 if not complete yet
-// Returns 0 if Z probe already triggered at start of probing
-// Returns 1 if Z probe didn't trigger
-// Returns 2 if success, with the current position in moveBuffer
-int GCodes::DoZProbe(GCodeBuffer& gb, float distance)
-{
- if (platform->GetZProbeType() == ZProbeTypeDelta)
- {
- const ZProbeParameters& params = platform->GetZProbeParameters();
- return reprap.GetMove()->DoDeltaProbe(params.param1, params.param2, params.probeSpeed, distance);
- }
- else
- {
- // Check for probe already triggered at start
- if (!cannedCycleMoveQueued)
- {
- if (reprap.GetPlatform()->GetZProbeResult() == EndStopHit::lowHit)
- {
- return 0;
- }
- zProbeTriggered = false;
- }
-
- // Do a normal canned cycle Z movement with Z probe enabled
- for (size_t drive = 0; drive <= DRIVES; drive++)
- {
- cannedMoveType[drive] = CannedMoveType::none;
- }
-
- cannedMoveCoords[Z_AXIS] = -distance;
- cannedMoveType[Z_AXIS] = CannedMoveType::relative;
- cannedFeedRate = platform->GetZProbeParameters().probeSpeed;
-
- if (DoCannedCycleMove(gb, ZProbeActive))
- {
- return (zProbeTriggered) ? 2 : 1;
- }
- return -1;
- }
-}
-
-// This is called to execute a G30.
-// It sets wherever we are as the probe point P (probePointIndex)
-// then probes the bed, or gets all its parameters from the arguments.
-// If X or Y are specified, use those; otherwise use the machine's
-// coordinates. If no Z is specified use the machine's coordinates. If it
-// is specified and is greater than SILLY_Z_VALUE (i.e. greater than -9999.0)
-// then that value is used. If it's less than SILLY_Z_VALUE the bed is
-// probed and that value is used.
-// Call this repeatedly until it returns true.
-bool GCodes::SetSingleZProbeAtAPosition(GCodeBuffer& gb, StringRef& reply)
-{
- if (reprap.GetMove()->IsDeltaMode() && !AllAxesAreHomed())
- {
- reply.copy("Must home before bed probing");
- return true;
- }
-
- float heightAdjust = 0.0;
- bool dummy;
- gb.TryGetFValue('H', heightAdjust, dummy);
-
- if (!gb.Seen('P'))
- {
- bool reportOnly = false;
- if (gb.Seen('S') && gb.GetIValue() < 0)
- {
- reportOnly = true;
- }
- return DoSingleZProbe(gb, reportOnly, heightAdjust);
- }
-
- int probePointIndex = gb.GetIValue();
- if (probePointIndex < 0 || (unsigned int)probePointIndex >= MaxProbePoints)
- {
- reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Z probe point index out of range.\n");
- return true;
- }
-
- float x = (gb.Seen(axisLetters[X_AXIS])) ? gb.GetFValue() : moveBuffer.coords[X_AXIS];
- float y = (gb.Seen(axisLetters[Y_AXIS])) ? gb.GetFValue() : moveBuffer.coords[Y_AXIS];
- float z = (gb.Seen(axisLetters[Z_AXIS])) ? gb.GetFValue() : moveBuffer.coords[Z_AXIS];
-
- reprap.GetMove()->SetXBedProbePoint(probePointIndex, x);
- reprap.GetMove()->SetYBedProbePoint(probePointIndex, y);
-
- if (z > SILLY_Z_VALUE)
- {
- reprap.GetMove()->SetZBedProbePoint(probePointIndex, z, false, false);
- if (gb.Seen('S'))
- {
- zProbesSet = true;
- reprap.GetMove()->FinishedBedProbing(gb.GetIValue(), reply);
- }
- return true;
- }
- else
- {
- if (DoSingleZProbeAtPoint(gb, probePointIndex, heightAdjust))
- {
- if (gb.Seen('S'))
- {
- zProbesSet = true;
- int sParam = gb.GetIValue();
- if (sParam == 1)
- {
- // G30 with a silly Z value and S=1 is equivalent to G30 with no parameters in that it sets the current Z height
- // This is useful because it adjusts the XY position to account for the probe offset.
- moveBuffer.coords[Z_AXIS] += lastProbedZ;
- SetPositions(moveBuffer.coords);
- lastProbedZ = 0.0;
- }
- else
- {
- reprap.GetMove()->FinishedBedProbing(sParam, reply);
- }
- }
- return true;
- }
- }
-
- return false;
-}
-
-// This returns the (X, Y) points to probe the bed at probe point count. When probing, it returns false.
-// If called after probing has ended it returns true, and the Z coordinate probed is also returned.
-bool GCodes::GetProbeCoordinates(int count, float& x, float& y, float& z) const
-{
- const ZProbeParameters& rp = platform->GetZProbeParameters();
- x = reprap.GetMove()->XBedProbePoint(count) - rp.xOffset;
- y = reprap.GetMove()->YBedProbePoint(count) - rp.yOffset;
- z = reprap.GetMove()->ZBedProbePoint(count);
- return zProbesSet;
-}
-
-bool GCodes::SetPrintZProbe(GCodeBuffer& gb, StringRef& reply)
-{
- ZProbeParameters params = platform->GetZProbeParameters();
- bool seen = false;
- gb.TryGetFValue(axisLetters[X_AXIS], params.xOffset, seen);
- gb.TryGetFValue(axisLetters[Y_AXIS], params.yOffset, seen);
- gb.TryGetFValue(axisLetters[Z_AXIS], params.height, seen);
- gb.TryGetIValue('P', params.adcValue, seen);
-
- if (gb.Seen('C'))
- {
- params.temperatureCoefficient = gb.GetFValue();
- seen = true;
- if (gb.Seen('S'))
- {
- params.calibTemperature = gb.GetFValue();
- }
- else
- {
- // Use the current bed temperature as the calibration temperature if no value was provided
- params.calibTemperature = platform->GetZProbeTemperature();
- }
- }
-
- if (seen)
- {
- platform->SetZProbeParameters(params);
- }
- else
- {
- const int v0 = platform->ZProbe();
- int v1, v2;
- switch (platform->GetZProbeSecondaryValues(v1, v2))
- {
- case 1:
- reply.printf("%d (%d)", v0, v1);
- break;
- case 2:
- reply.printf("%d (%d, %d)", v0, v1, v2);
- break;
- default:
- reply.printf("%d", v0);
- break;
- }
- }
- return true;
-}
-
-// Define the probing grid, returning true if error
-// Called when we see an M557 command with no P parameter
-bool GCodes::DefineGrid(GCodeBuffer& gb, StringRef &reply)
-{
- bool seenX = false, seenY = false, seenR = false, seenS = false;
- float xValues[2];
- float yValues[2];
-
- if (gb.Seen('X'))
- {
- size_t count = 2;
- gb.GetFloatArray(xValues, count, false);
- if (count == 2)
- {
- seenX = true;
- }
- else
- {
- reply.copy("ERROR: Wrong number of X values in M577, need 2");
- return true;
- }
- }
- if (gb.Seen('Y'))
- {
- size_t count = 2;
- gb.GetFloatArray(yValues, count, false);
- if (count == 2)
- {
- seenY = true;
- }
- else
- {
- reply.copy("ERROR: Wrong number of Y values in M577, need 2");
- return true;
- }
- }
-
- float radius = -1.0;
- gb.TryGetFValue('R', radius, seenR);
- float spacing = DefaultGridSpacing;
- gb.TryGetFValue('S', spacing, seenS);
-
- if (!seenX && !seenY && !seenR && !seenS)
- {
- // Just print the existing grid parameters
- const GridDefinition& grid = reprap.GetMove()->GetBedProbeGrid();
- if (grid.IsValid())
- {
- reply.copy("Grid: ");
- grid.PrintParameters(reply);
- }
- else
- {
- reply.copy("Grid is not defined");
- }
- return false;
- }
-
- if (seenX != seenY)
- {
- reply.copy("ERROR: specify both or neither of X and Y in M577");
- return true;
- }
-
- if (!seenX && !seenR)
- {
- // Must have given just the S parameter
- reply.copy("ERROR: specify at least radius or X,Y ranges in M577");
- return true;
-
- }
-
- if (!seenX)
- {
- if (radius > 0)
- {
- const float effectiveRadius = floor((radius - 0.1)/spacing) * spacing;
- xValues[0] = yValues[0] = -effectiveRadius;
- xValues[1] = yValues[1] = effectiveRadius + 0.1;
- }
- else
- {
- reply.copy("ERROR: M577 radius must be positive unless X and Y are specified");
- return true;
- }
- }
- GridDefinition newGrid(xValues, yValues, radius, spacing); // create a new grid
- if (newGrid.IsValid())
- {
- reprap.GetMove()->SetBedProbeGrid(newGrid);
- return false;
- }
- else
- {
- reply.copy("ERROR: bad grid definition: ");
- newGrid.PrintError(reply);
- return true;
- }
-}
-
-// Start probing the grid, returning true if we didn't because of an error.
-// Prior to calling this the movement system must be locked.
-bool GCodes::ProbeGrid(GCodeBuffer& gb, StringRef& reply)
-{
- long sParam = 0;
- bool dummy;
- gb.TryGetIValue('S', sParam, dummy);
-
- if (gb.Seen('P'))
- {
- heightMapFile = gb.GetString();
- }
- else
- {
- heightMapFile = DefaultHeightMapFile;
- }
-
- switch(sParam)
- {
- case 0: // Probe the bed and save to file
- if (!reprap.GetMove()->GetBedProbeGrid().IsValid())
- {
- reply.copy("No valid grid defined for G29 bed probing");
- return true;
- }
-
- if (!AllAxesAreHomed())
- {
- reply.copy("Must home printer before G29 bed probing");
- return true;
- }
-
- gridXindex = gridYindex = 0;
- numPointsProbed = 0;
- heightSum = heightSquaredSum = 0.0;
-
- reprap.GetMove()->ClearGridHeights();
- reprap.GetMove()->SetIdentityTransform();
- gb.SetState(GCodeState::gridProbing1);
- return false;
-
- case 1: // Load height map from file
- return reprap.GetMove()->LoadHeightMapFromFile(heightMapFile, reply);
-
- case 2: // Clear height map
- reprap.GetMove()->ClearGridHeights();
- return false;
-
- default:
- reply.copy("Invalid S parameter in G29 command");
- return true;
- }
-
-
-}
-
-// Return the current coordinates as a printable string.
-// Coordinates are updated at the end of each movement, so this won't tell you where you are mid-movement.
-void GCodes::GetCurrentCoordinates(StringRef& s) const
-{
- float liveCoordinates[DRIVES];
- reprap.GetMove()->LiveCoordinates(liveCoordinates, reprap.GetCurrentXAxes());
- const Tool *currentTool = reprap.GetCurrentTool();
- if (currentTool != nullptr)
- {
- const float *offset = currentTool->GetOffset();
- for (size_t i = 0; i < numAxes; ++i)
- {
- liveCoordinates[i] += offset[i];
- }
- }
-
- s.Clear();
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- s.catf("%c: %.2f ", axisLetters[axis], liveCoordinates[axis]);
- }
- for (size_t i = numAxes; i < DRIVES; i++)
- {
- s.catf("E%u: %.1f ", i - numAxes, liveCoordinates[i]);
- }
-
- // Print the axis stepper motor positions as Marlin does, as an aid to debugging.
- // Don't bother with the extruder endpoints, they are zero after any non-extruding move.
- s.cat(" Count");
- for (size_t i = 0; i < numAxes; ++i)
- {
- s.catf(" %d", reprap.GetMove()->GetEndPoint(i));
- }
-}
-
-bool GCodes::OpenFileToWrite(GCodeBuffer& gb, const char* directory, const char* fileName)
-{
- fileBeingWritten = platform->GetFileStore(directory, fileName, true);
- eofStringCounter = 0;
- if (fileBeingWritten == NULL)
- {
- platform->MessageF(GENERIC_MESSAGE, "Can't open GCode file \"%s\" for writing.\n", fileName);
- return false;
- }
- else
- {
- gb.SetWritingFileDirectory(directory);
- return true;
- }
-}
-
-void GCodes::WriteHTMLToFile(GCodeBuffer& gb, char b)
-{
- if (fileBeingWritten == NULL)
- {
- platform->Message(GENERIC_MESSAGE, "Attempt to write to a null file.\n");
- return;
- }
-
- if (eofStringCounter != 0 && b != eofString[eofStringCounter])
- {
- fileBeingWritten->Write(eofString);
- eofStringCounter = 0;
- }
-
- if (b == eofString[eofStringCounter])
- {
- eofStringCounter++;
- if (eofStringCounter >= eofStringLength)
- {
- fileBeingWritten->Close();
- fileBeingWritten = NULL;
- gb.SetWritingFileDirectory(NULL);
- const char* r = (platform->Emulating() == marlin) ? "Done saving file." : "";
- HandleReply(gb, false, r);
- return;
- }
- }
- else
- {
- fileBeingWritten->Write(b);
- }
-}
-
-void GCodes::WriteGCodeToFile(GCodeBuffer& gb)
-{
- if (fileBeingWritten == NULL)
- {
- platform->Message(GENERIC_MESSAGE, "Attempt to write to a null file.\n");
- return;
- }
-
- // End of file?
- if (gb.Seen('M'))
- {
- if (gb.GetIValue() == 29)
- {
- fileBeingWritten->Close();
- fileBeingWritten = NULL;
- gb.SetWritingFileDirectory(NULL);
- const char* r = (platform->Emulating() == marlin) ? "Done saving file." : "";
- HandleReply(gb, false, r);
- return;
- }
- }
-
- // Resend request?
- if (gb.Seen('G'))
- {
- if (gb.GetIValue() == 998)
- {
- if (gb.Seen('P'))
- {
- scratchString.printf("%d\n", gb.GetIValue());
- HandleReply(gb, false, scratchString.Pointer());
- return;
- }
- }
- }
-
- fileBeingWritten->Write(gb.Buffer());
- fileBeingWritten->Write('\n');
- HandleReply(gb, false, "");
-}
-
-// Set up a file to print, but don't print it yet.
-void GCodes::QueueFileToPrint(const char* fileName)
-{
- FileStore * const f = platform->GetFileStore(platform->GetGCodeDir(), fileName, false);
- if (f != nullptr)
- {
- // Cancel current print if there is any
- if (!reprap.GetPrintMonitor()->IsPrinting())
- {
- CancelPrint();
- }
-
- fileGCode->SetToolNumberAdjust(0); // clear tool number adjustment
-
- // Reset all extruder positions when starting a new print
- for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
- {
- lastRawExtruderPosition[extruder] = 0.0;
- rawExtruderTotalByDrive[extruder] = 0.0;
- }
- rawExtruderTotal = 0.0;
- reprap.GetMove()->ResetExtruderPositions();
-
- fileToPrint.Set(f);
- }
- else
- {
- platform->MessageF(GENERIC_MESSAGE, "GCode file \"%s\" not found\n", fileName);
- }
-}
-
-void GCodes::DeleteFile(const char* fileName)
-{
- if (!platform->GetMassStorage()->Delete(platform->GetGCodeDir(), fileName))
- {
- platform->MessageF(GENERIC_MESSAGE, "Could not delete file \"%s\"\n", fileName);
- }
-}
-
-// Function to handle dwell delays. Return true for dwell finished, false otherwise.
-bool GCodes::DoDwell(GCodeBuffer& gb)
-{
- float dwell;
- if (gb.Seen('S'))
- {
- dwell = gb.GetFValue();
- }
- else if (gb.Seen('P'))
- {
- dwell = 0.001 * (float) gb.GetIValue(); // P values are in milliseconds; we need seconds
- }
- else
- {
- return true; // No time given - throw it away
- }
-
-#if SUPPORT_ROLAND
- // Deal with a Roland configuration
- if (reprap.GetRoland()->Active())
- {
- return reprap.GetRoland()->ProcessDwell(gb.GetLValue());
- }
-#endif
-
- // Wait for all the queued moves to stop
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
-
- if (simulationMode != 0)
- {
- simulationTime += dwell;
- return true;
- }
- else
- {
- return DoDwellTime(dwell);
- }
-}
-
-bool GCodes::DoDwellTime(float dwell)
-{
- // Are we already in the dwell?
- if (dwellWaiting)
- {
- if (platform->Time() - dwellTime >= 0.0)
- {
- dwellWaiting = false;
- reprap.GetMove()->ResumeMoving();
- return true;
- }
- return false;
- }
-
- // New dwell - set it up
- dwellWaiting = true;
- dwellTime = platform->Time() + dwell;
- return false;
-}
-
-// Set offset, working and standby temperatures for a tool. I.e. handle a G10.
-bool GCodes::SetOrReportOffsets(GCodeBuffer &gb, StringRef& reply)
-{
- if (gb.Seen('P'))
- {
- int8_t toolNumber = gb.GetIValue();
- toolNumber += gb.GetToolNumberAdjust();
- Tool* tool = reprap.GetTool(toolNumber);
- if (tool == NULL)
- {
- reply.printf("Attempt to set/report offsets and temperatures for non-existent tool: %d", toolNumber);
- return true;
- }
-
- // Deal with setting offsets
- float offset[MAX_AXES];
- for (size_t i = 0; i < MAX_AXES; ++i)
- {
- offset[i] = tool->GetOffset()[i];
- }
-
- bool settingOffset = false;
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- gb.TryGetFValue(axisLetters[axis], offset[axis], settingOffset);
- }
- if (settingOffset)
- {
- if (!LockMovement(gb))
- {
- return false;
- }
- tool->SetOffset(offset);
- }
-
- // Deal with setting temperatures
- bool settingTemps = false;
- size_t hCount = tool->HeaterCount();
- float standby[HEATERS];
- float active[HEATERS];
- if (hCount > 0)
- {
- tool->GetVariables(standby, active);
- if (gb.Seen('R'))
- {
- gb.GetFloatArray(standby, hCount, true);
- settingTemps = true;
- }
- if (gb.Seen('S'))
- {
- gb.GetFloatArray(active, hCount, true);
- settingTemps = true;
- }
-
- if (settingTemps && simulationMode == 0)
- {
- tool->SetVariables(standby, active);
- }
- }
-
- if (!settingOffset && !settingTemps)
- {
- // Print offsets and temperatures
- reply.printf("Tool %d offsets:", toolNumber);
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- reply.catf(" %c%.2f", axisLetters[axis], offset[axis]);
- }
- if (hCount != 0)
- {
- reply.cat(", active/standby temperature(s):");
- for (size_t heater = 0; heater < hCount; heater++)
- {
- reply.catf(" %.1f/%.1f", active[heater], standby[heater]);
- }
- }
- }
- }
- return true;
-}
-
-void GCodes::ManageTool(GCodeBuffer& gb, StringRef& reply)
-{
- if (!gb.Seen('P'))
- {
- // DC temporary code to allow tool numbers to be adjusted so that we don't need to edit multi-media files generated by slic3r
- if (gb.Seen('S'))
- {
- int adjust = gb.GetIValue();
- gb.SetToolNumberAdjust(adjust);
- }
- return;
- }
-
- // Check tool number
- bool seen = false;
- const int toolNumber = gb.GetIValue();
- if (toolNumber < 0)
- {
- platform->Message(GENERIC_MESSAGE, "Tool number must be positive!\n");
- return;
- }
-
- // Check drives
- long drives[MaxExtruders]; // There can never be more than we have...
- size_t dCount = numExtruders; // Sets the limit and returns the count
- if (gb.Seen('D'))
- {
- gb.GetLongArray(drives, dCount);
- seen = true;
- }
- else
- {
- dCount = 0;
- }
-
- // Check heaters
- long heaters[HEATERS];
- size_t hCount = HEATERS;
- if (gb.Seen('H'))
- {
- gb.GetLongArray(heaters, hCount);
- seen = true;
- }
- else
- {
- hCount = 0;
- }
-
- // Check X axis mapping
- uint32_t xMap;
- if (gb.Seen('X'))
- {
- long xMapping[MAX_AXES];
- size_t xCount = numAxes;
- gb.GetLongArray(xMapping, xCount);
- xMap = LongArrayToBitMap(xMapping, xCount) & ((1u << numAxes) - 1);
- seen = true;
- }
- else
- {
- xMap = 1; // by default map X axis straight through
- }
-
- // Check for fan mapping
- uint32_t fanMap;
- if (gb.Seen('F'))
- {
- long fanMapping[NUM_FANS];
- size_t fanCount = NUM_FANS;
- gb.GetLongArray(fanMapping, fanCount);
- fanMap = LongArrayToBitMap(fanMapping, fanCount) & ((1u << NUM_FANS) - 1);
- seen = true;
- }
- else
- {
- fanMap = 1; // by default map fan 0 to fan 0
- }
-
- if (seen)
- {
- // Add or delete tool, so start by deleting the old one with this number, if any
- reprap.DeleteTool(reprap.GetTool(toolNumber));
-
- // M563 P# D-1 H-1 removes an existing tool
- if (dCount == 1 && hCount == 1 && drives[0] == -1 && heaters[0] == -1)
- {
- // nothing more to do
- }
- else
- {
- Tool* tool = Tool::Create(toolNumber, drives, dCount, heaters, hCount, xMap, fanMap);
- if (tool != nullptr)
- {
- reprap.AddTool(tool);
- }
- }
- }
- else
- {
- reprap.PrintTool(toolNumber, reply);
- }
-}
-
-// Does what it says.
-void GCodes::DisableDrives()
-{
- for (size_t drive = 0; drive < DRIVES; drive++)
- {
- platform->DisableDrive(drive);
- }
- SetAllAxesNotHomed();
-}
-
-// Does what it says.
-void GCodes::SetEthernetAddress(GCodeBuffer& gb, int mCode)
-{
- byte eth[4];
- const char* ipString = gb.GetString();
- uint8_t sp = 0;
- uint8_t spp = 0;
- uint8_t ipp = 0;
- while (ipString[sp])
- {
- if (ipString[sp] == '.')
- {
- eth[ipp] = atoi(&ipString[spp]);
- ipp++;
- if (ipp > 3)
- {
- platform->MessageF(GENERIC_MESSAGE, "Dud IP address: %s\n", gb.Buffer());
- return;
- }
- sp++;
- spp = sp;
- }
- else
- {
- sp++;
- }
- }
- eth[ipp] = atoi(&ipString[spp]);
- if (ipp == 3)
- {
- switch (mCode)
- {
- case 552:
- platform->SetIPAddress(eth);
- break;
- case 553:
- platform->SetNetMask(eth);
- break;
- case 554:
- platform->SetGateWay(eth);
- break;
-
- default:
- platform->Message(GENERIC_MESSAGE, "Setting ether parameter - dud code.\n");
- }
- }
- else
- {
- platform->MessageF(GENERIC_MESSAGE, "Dud IP address: %s\n", gb.Buffer());
- }
-}
-
-void GCodes::SetMACAddress(GCodeBuffer& gb)
-{
- uint8_t mac[6];
- const char* ipString = gb.GetString();
- uint8_t sp = 0;
- uint8_t spp = 0;
- uint8_t ipp = 0;
- while (ipString[sp])
- {
- if (ipString[sp] == ':')
- {
- mac[ipp] = strtoul(&ipString[spp], NULL, 16);
- ipp++;
- if (ipp > 5)
- {
- platform->MessageF(GENERIC_MESSAGE, "Dud MAC address: %s\n", gb.Buffer());
- return;
- }
- sp++;
- spp = sp;
- }
- else
- {
- sp++;
- }
- }
- mac[ipp] = strtoul(&ipString[spp], NULL, 16);
- if (ipp == 5)
- {
- platform->SetMACAddress(mac);
- }
- else
- {
- platform->MessageF(GENERIC_MESSAGE, "Dud MAC address: %s\n", gb.Buffer());
- }
-}
-
-bool GCodes::ChangeMicrostepping(size_t drive, int microsteps, int mode) const
-{
- bool dummy;
- unsigned int oldSteps = platform->GetMicrostepping(drive, dummy);
- bool success = platform->SetMicrostepping(drive, microsteps, mode);
- if (success && mode <= 1) // modes higher than 1 are used for special functions
- {
- // We changed the microstepping, so adjust the steps/mm to compensate
- float stepsPerMm = platform->DriveStepsPerUnit(drive);
- if (stepsPerMm > 0)
- {
- platform->SetDriveStepsPerUnit(drive, stepsPerMm * (float)microsteps / (float)oldSteps);
- }
- }
- return success;
-}
-
-// Set the speeds of fans mapped for the current tool to lastDefaultFanSpeed
-void GCodes::SetMappedFanSpeed()
-{
- if (reprap.GetCurrentTool() == nullptr)
- {
- platform->SetFanValue(0, lastDefaultFanSpeed);
- }
- else
- {
- const uint32_t fanMap = reprap.GetCurrentTool()->GetFanMapping();
- for (size_t i = 0; i < NUM_FANS; ++i)
- {
- if ((fanMap & (1u << i)) != 0)
- {
- platform->SetFanValue(i, lastDefaultFanSpeed);
- }
- }
- }
-}
-
-// Handle sending a reply back to the appropriate interface(s).
-// Note that 'reply' may be empty. If it isn't, then we need to append newline when sending it.
-// Also, gb may be null if we were executing a trigger macro.
-void GCodes::HandleReply(GCodeBuffer& gb, bool error, const char* reply)
-{
- // Don't report "ok" responses if a (macro) file is being processed
- // Also check that this response was triggered by a gcode
- if ((gb.MachineState().doingFileMacro || &gb == fileGCode) && reply[0] == 0)
- {
- return;
- }
-
- // Second UART device, e.g. dc42's PanelDue. Do NOT use emulation for this one!
- if (&gb == auxGCode)
- {
- platform->AppendAuxReply(reply);
- return;
- }
-
- const Compatibility c = (&gb == serialGCode || &gb == telnetGCode) ? platform->Emulating() : me;
- MessageType type = GENERIC_MESSAGE;
- if (&gb == httpGCode)
- {
- type = HTTP_MESSAGE;
- }
- else if (&gb == telnetGCode)
- {
- type = TELNET_MESSAGE;
- }
- else if (&gb == serialGCode)
- {
- type = HOST_MESSAGE;
- }
-
- const char* response = (gb.Seen('M') && gb.GetIValue() == 998) ? "rs " : "ok";
- const char* emulationType = 0;
-
- switch (c)
- {
- case me:
- case reprapFirmware:
- if (error)
- {
- platform->Message(type, "Error: ");
- }
- platform->Message(type, reply);
- platform->Message(type, "\n");
- return;
-
- case marlin:
- // We don't need to handle M20 here because we always allocate an output buffer for that one
- if (gb.Seen('M') && gb.GetIValue() == 28)
- {
- platform->Message(type, response);
- platform->Message(type, "\n");
- platform->Message(type, reply);
- platform->Message(type, "\n");
- return;
- }
-
- if ((gb.Seen('M') && gb.GetIValue() == 105) || (gb.Seen('M') && gb.GetIValue() == 998))
- {
- platform->Message(type, response);
- platform->Message(type, " ");
- platform->Message(type, reply);
- platform->Message(type, "\n");
- return;
- }
-
- if (reply[0] != 0 && !gb.IsDoingFileMacro())
- {
- platform->Message(type, reply);
- platform->Message(type, "\n");
- platform->Message(type, response);
- platform->Message(type, "\n");
- }
- else if (reply[0] != 0)
- {
- platform->Message(type, reply);
- platform->Message(type, "\n");
- }
- else
- {
- platform->Message(type, response);
- platform->Message(type, "\n");
- }
- return;
-
- case teacup:
- emulationType = "teacup";
- break;
- case sprinter:
- emulationType = "sprinter";
- break;
- case repetier:
- emulationType = "repetier";
- break;
- default:
- emulationType = "unknown";
- }
-
- if (emulationType != 0)
- {
- platform->MessageF(type, "Emulation of %s is not yet supported.\n", emulationType); // don't send this one to the web as well, it concerns only the USB interface
- }
-}
-
-void GCodes::HandleReply(GCodeBuffer& gb, bool error, OutputBuffer *reply)
-{
- // Although unlikely, it's possible that we get a nullptr reply. Don't proceed if this is the case
- if (reply == nullptr)
- {
- return;
- }
-
- // Second UART device, e.g. dc42's PanelDue. Do NOT use emulation for this one!
- if (&gb == auxGCode)
- {
- platform->AppendAuxReply(reply);
- return;
- }
-
- const Compatibility c = (&gb == serialGCode || &gb == telnetGCode) ? platform->Emulating() : me;
- MessageType type = GENERIC_MESSAGE;
- if (&gb == httpGCode)
- {
- type = HTTP_MESSAGE;
- }
- else if (&gb == telnetGCode)
- {
- type = TELNET_MESSAGE;
- }
- else if (&gb == serialGCode)
- {
- type = HOST_MESSAGE;
- }
-
- const char* response = (gb.Seen('M') && gb.GetIValue() == 998) ? "rs " : "ok";
- const char* emulationType = nullptr;
-
- switch (c)
- {
- case me:
- case reprapFirmware:
- if (error)
- {
- platform->Message(type, "Error: ");
- }
- platform->Message(type, reply);
- return;
-
- case marlin:
- if (gb.Seen('M') && gb.GetIValue() == 20)
- {
- platform->Message(type, "Begin file list\n");
- platform->Message(type, reply);
- platform->Message(type, "End file list\n");
- platform->Message(type, response);
- platform->Message(type, "\n");
- return;
- }
-
- if (gb.Seen('M') && gb.GetIValue() == 28)
- {
- platform->Message(type, response);
- platform->Message(type, "\n");
- platform->Message(type, reply);
- return;
- }
-
- if ((gb.Seen('M') && gb.GetIValue() == 105) || (gb.Seen('M') && gb.GetIValue() == 998))
- {
- platform->Message(type, response);
- platform->Message(type, " ");
- platform->Message(type, reply);
- return;
- }
-
- if (reply->Length() != 0 && !gb.IsDoingFileMacro())
- {
- platform->Message(type, reply);
- platform->Message(type, "\n");
- platform->Message(type, response);
- platform->Message(type, "\n");
- }
- else if (reply->Length() != 0)
- {
- platform->Message(type, reply);
- }
- else
- {
- OutputBuffer::ReleaseAll(reply);
- platform->Message(type, response);
- platform->Message(type, "\n");
- }
- return;
-
- case teacup:
- emulationType = "teacup";
- break;
- case sprinter:
- emulationType = "sprinter";
- break;
- case repetier:
- emulationType = "repetier";
- break;
- default:
- emulationType = "unknown";
- }
-
- // If we get here then we didn't handle the message, so release the buffer(s)
- OutputBuffer::ReleaseAll(reply);
- if (emulationType != 0)
- {
- platform->MessageF(type, "Emulation of %s is not yet supported.\n", emulationType); // don't send this one to the web as well, it concerns only the USB interface
- }
-}
-
-// Set PID parameters (M301 or M304 command). 'heater' is the default heater number to use.
-void GCodes::SetPidParameters(GCodeBuffer& gb, int heater, StringRef& reply)
-{
- if (gb.Seen('H'))
- {
- heater = gb.GetIValue();
- }
-
- if (heater >= 0 && heater < HEATERS)
- {
- PidParameters pp = platform->GetPidParameters(heater);
- bool seen = false;
- gb.TryGetFValue('P', pp.kP, seen);
- gb.TryGetFValue('I', pp.kI, seen);
- gb.TryGetFValue('D', pp.kD, seen);
- gb.TryGetFValue('T', pp.kT, seen);
- gb.TryGetFValue('S', pp.kS, seen);
-
- if (seen)
- {
- platform->SetPidParameters(heater, pp);
- reprap.GetHeat()->UseModel(heater, false);
- }
- else
- {
- reply.printf("Heater %d P:%.2f I:%.3f D:%.2f T:%.2f S:%.2f", heater, pp.kP, pp.kI, pp.kD, pp.kT, pp.kS);
- }
- }
-}
-
-void GCodes::SetHeaterParameters(GCodeBuffer& gb, StringRef& reply)
-{
- if (gb.Seen('P'))
- {
- int heater = gb.GetIValue();
- if (heater >= 0 && heater < HEATERS)
- {
- Thermistor& th = platform->GetThermistor(heater);
- bool seen = false;
-
- // We must set the 25C resistance and beta together in order to calculate Rinf. Check for these first.
- float r25 = th.GetR25();
- float beta = th.GetBeta();
- float shC = th.GetShc();
- float seriesR = th.GetSeriesR();
-
- gb.TryGetFValue('T', r25, seen);
- gb.TryGetFValue('B', beta, seen);
- gb.TryGetFValue('C', shC, seen);
- gb.TryGetFValue('R', seriesR, seen);
- if (seen)
- {
- th.SetParameters(r25, beta, shC, seriesR);
- }
-
- if (gb.Seen('L'))
- {
- th.SetLowOffset((int8_t)constrain<int>(gb.GetIValue(), -100, 100));
- seen = true;
- }
- if (gb.Seen('H'))
- {
- th.SetHighOffset((int8_t)constrain<int>(gb.GetIValue(), -100, 100));
- seen = true;
- }
-
- if (gb.Seen('X'))
- {
- int thermistor = gb.GetIValue();
- if ( (0 <= thermistor && thermistor < HEATERS)
- || ((int)FirstThermocoupleChannel <= thermistor && thermistor < (int)(FirstThermocoupleChannel + MaxSpiTempSensors))
- || ((int)FirstRtdChannel <= thermistor && thermistor < (int)(FirstRtdChannel + MaxSpiTempSensors))
- )
- {
- platform->SetThermistorNumber(heater, thermistor);
- }
- else
- {
- platform->MessageF(GENERIC_MESSAGE, "Thermistor number %d is out of range\n", thermistor);
- }
- seen = true;
- }
-
- if (!seen)
- {
- reply.printf("T:%.1f B:%.1f C:%.2e R:%.1f L:%d H:%d X:%d",
- th.GetR25(), th.GetBeta(), th.GetShc(), th.GetSeriesR(),
- th.GetLowOffset(), th.GetHighOffset(), platform->GetThermistorNumber(heater));
- }
- }
- else
- {
- platform->MessageF(GENERIC_MESSAGE, "Heater number %d is out of range\n", heater);
- }
- }
-}
-
-void GCodes::SetToolHeaters(Tool *tool, float temperature)
-{
- if (tool == NULL)
- {
- platform->Message(GENERIC_MESSAGE, "Setting temperature: no tool selected.\n");
- return;
- }
-
- float standby[HEATERS];
- float active[HEATERS];
- tool->GetVariables(standby, active);
- for (size_t h = 0; h < tool->HeaterCount(); h++)
- {
- active[h] = temperature;
- }
- tool->SetVariables(standby, active);
-}
-
-// Retract or un-retract filament, returning true if movement has been queued, false if this needs to be called again
-bool GCodes::RetractFilament(bool retract)
-{
- if (retractLength != 0.0 || retractHop != 0.0 || (!retract && retractExtra != 0.0))
- {
- const Tool *tool = reprap.GetCurrentTool();
- if (tool != nullptr)
- {
- size_t nDrives = tool->DriveCount();
- if (nDrives != 0)
- {
- if (moveAvailable)
- {
- return false;
- }
-
- reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes());
- for (size_t i = numAxes; i < DRIVES; ++i)
- {
- moveBuffer.coords[i] = 0.0;
- }
- // Set the feed rate. If there is any Z hop then we need to pass the Z speed, else we pass the extrusion speed.
- const float speedToUse = (retract) ? retractSpeed : unRetractSpeed;
- moveBuffer.feedRate = (retractHop == 0.0)
- ? speedToUse * secondsToMinutes
- : speedToUse * secondsToMinutes * retractHop/retractLength;
- moveBuffer.coords[Z_AXIS] += (retract) ? retractHop : -retractHop;
- const float lengthToUse = (retract) ? -retractLength : retractLength + retractExtra;
- for (size_t i = 0; i < nDrives; ++i)
- {
- moveBuffer.coords[E0_AXIS + tool->Drive(i)] = lengthToUse;
- }
-
- moveBuffer.isFirmwareRetraction = true;
- moveBuffer.usePressureAdvance = false;
- moveBuffer.filePos = filePos;
- moveBuffer.xAxes = reprap.GetCurrentXAxes();
- moveAvailable = true;
- }
- }
- }
- return true;
-}
-
-// If the code to act on is completed, this returns true,
-// otherwise false. It is called repeatedly for a given
-// code until it returns true for that code.
-bool GCodes::ActOnCode(GCodeBuffer& gb, StringRef& reply)
-{
- // Discard empty buffers right away
- if (gb.IsEmpty())
- {
- return true;
- }
-
- // M-code parameters might contain letters T and G, e.g. in filenames.
- // dc42 assumes that G-and T-code parameters never contain the letter M.
- // Therefore we must check for an M-code first.
- if (gb.Seen('M'))
- {
- return HandleMcode(gb, reply);
- }
- // dc42 doesn't think a G-code parameter ever contains letter T, or a T-code ever contains letter G.
- // So it doesn't matter in which order we look for them.
- if (gb.Seen('G'))
- {
- return HandleGcode(gb, reply);
- }
- if (gb.Seen('T'))
- {
- return HandleTcode(gb, reply);
- }
-
- // An invalid or queued buffer gets discarded
- HandleReply(gb, false, "");
- return true;
-}
-
-bool GCodes::HandleGcode(GCodeBuffer& gb, StringRef& reply)
-{
- bool result = true;
- bool error = false;
-
- int code = gb.GetIValue();
- if (simulationMode != 0 && code != 0 && code != 1 && code != 4 && code != 10 && code != 20 && code != 21 && code != 90 && code != 91 && code != 92)
- {
- return true; // we only simulate some gcodes
- }
-
- switch (code)
- {
- case 0: // There are no rapid moves...
- case 1: // Ordinary move
- if (!LockMovement(gb))
- {
- return false;
- }
- {
- // Check for 'R' parameter here to go back to the coordinates at which the print was paused
- // NOTE: restore point 2 (tool change) won't work when changing tools on dual axis machines because of X axis mapping.
- // We could possibly fix this by saving the virtual X axis position instead of the physical axis positions.
- // However, slicers normally command the tool to the correct place after a tool change, so we don't need this feature anyway.
- int rParam = (gb.Seen('R')) ? gb.GetIValue() : 0;
- RestorePoint *rp = (rParam == 1) ? &pauseRestorePoint : (rParam == 2) ? &toolChangeRestorePoint : nullptr;
- if (rp != nullptr)
- {
- if (moveAvailable)
- {
- return false;
- }
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- float offset = gb.Seen(axisLetters[axis]) ? gb.GetFValue() * distanceScale : 0.0;
- moveBuffer.coords[axis] = rp->moveCoords[axis] + offset;
- }
- // For now we don't handle extrusion at the same time
- for (size_t drive = numAxes; drive < DRIVES; ++drive)
- {
- moveBuffer.coords[drive] = 0.0;
- }
- moveBuffer.feedRate = (gb.Seen(feedrateLetter)) ? gb.GetFValue() : feedRate;
- moveBuffer.filePos = noFilePosition;
- moveBuffer.usePressureAdvance = false;
- moveAvailable = true;
- }
- else
- {
- int res = SetUpMove(gb, reply);
- if (res == 2)
- {
- gb.SetState(GCodeState::waitingForMoveToComplete);
- }
- result = (res != 0);
- }
- }
- break;
-
- case 4: // Dwell
- result = DoDwell(gb);
- break;
-
- case 10: // Set/report offsets and temperatures, or retract
- if (gb.Seen('P'))
- {
- if (!SetOrReportOffsets(gb, reply))
- {
- return false;
- }
- }
- else
- {
- if (!LockMovement(gb))
- {
- return false;
- }
- result = RetractFilament(true);
- }
- break;
-
- case 11: // Un-retract
- if (!LockMovement(gb))
- {
- return false;
- }
- result = RetractFilament(false);
- break;
-
- case 20: // Inches (which century are we living in, here?)
- distanceScale = INCH_TO_MM;
- break;
-
- case 21: // mm
- distanceScale = 1.0;
- break;
-
- case 28: // Home
- result = DoHome(gb, reply, error);
- break;
-
- case 29: // Grid-based bed probing
- if (!LockMovementAndWaitForStandstill(gb)) // do this first to make sure that a new grid isn't being defined
- {
- return false;
- }
- error = ProbeGrid(gb, reply);
- break;
-
- case 30: // Z probe/manually set at a position and set that as point P
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- if (reprap.GetMove()->IsDeltaMode() && !AllAxesAreHomed())
- {
- reply.copy("Must home a delta printer before bed probing");
- error = true;
- }
- else
- {
- result = SetSingleZProbeAtAPosition(gb, reply);
- }
- break;
-
- case 31: // Return the probe value, or set probe variables
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- result = SetPrintZProbe(gb, reply);
- break;
-
- case 32: // Probe Z at multiple positions and generate the bed transform
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
-
- // Try to execute bed.g
- if (!DoFileMacro(gb, BED_EQUATION_G, reprap.GetMove()->IsDeltaMode()))
- {
- // If we get here then we are not on a delta printer and there is no bed.g file
- if (GetAxisIsHomed(X_AXIS) && GetAxisIsHomed(Y_AXIS))
- {
- gb.SetState(GCodeState::setBed1); // no bed.g file, so use the coordinates specified by M557
- }
- else
- {
- // We can only do bed levelling if X and Y have already been homed
- reply.copy("Must home X and Y before bed probing");
- error = true;
- }
- }
- break;
-
- case 90: // Absolute coordinates
- gb.MachineState().axesRelative = false;
- break;
-
- case 91: // Relative coordinates
- gb.MachineState().axesRelative = true; // Axis movements (i.e. X, Y and Z)
- break;
-
- case 92: // Set position
- result = SetPositions(gb);
- break;
-
- default:
- error = true;
- reply.printf("invalid G Code: %s", gb.Buffer());
- }
-
- if (result && gb.GetState() == GCodeState::normal)
- {
- UnlockAll(gb);
- HandleReply(gb, error, reply.Pointer());
- }
- return result;
-}
-
-bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply)
-{
- bool result = true;
- bool error = false;
-
- const int code = gb.GetIValue();
- if (simulationMode != 0 && (code < 20 || code > 37) && code != 0 && code != 1 && code != 82 && code != 83 && code != 105 && code != 111 && code != 112 && code != 122 && code != 408 && code != 999)
- {
- return true; // we don't yet simulate most M codes
- }
-
- switch (code)
- {
- case 0: // Stop
- case 1: // Sleep
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- {
- bool wasPaused = isPaused; // isPaused gets cleared by CancelPrint
- CancelPrint();
- if (wasPaused)
- {
- reply.copy("Print cancelled");
- // If we are cancelling a paused print with M0 and cancel.g exists then run it and do nothing else
- if (code == 0)
- {
- if (DoFileMacro(gb, CANCEL_G, false))
- {
- break;
- }
- }
- }
- }
-
- gb.SetState((code == 0) ? GCodeState::stopping : GCodeState::sleeping);
- DoFileMacro(gb, (code == 0) ? STOP_G : SLEEP_G, false);
- break;
-
-#if SUPPORT_ROLAND
- case 3: // Spin spindle
- if (reprap.GetRoland()->Active())
- {
- if (gb.Seen('S'))
- {
- result = reprap.GetRoland()->ProcessSpindle(gb.GetFValue());
- }
- }
- break;
-#endif
-
- case 18: // Motors off
- case 84:
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- {
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- SetAxisNotHomed(axis);
- platform->DisableDrive(axis);
- seen = true;
- }
- }
-
- if (gb.Seen(extrudeLetter))
- {
- long int eDrive[MaxExtruders];
- size_t eCount = numExtruders;
- gb.GetLongArray(eDrive, eCount);
- for (size_t i = 0; i < eCount; i++)
- {
- seen = true;
- if (eDrive[i] < 0 || (size_t)eDrive[i] >= numExtruders)
- {
- reply.printf("Invalid extruder number specified: %ld", eDrive[i]);
- error = true;
- break;
- }
- platform->DisableDrive(numAxes + eDrive[i]);
- }
- }
-
- if (gb.Seen('S'))
- {
- seen = true;
-
- float idleTimeout = gb.GetFValue();
- if (idleTimeout < 0.0)
- {
- reply.copy("Idle timeouts cannot be negative!");
- error = true;
- }
- else
- {
- reprap.GetMove()->SetIdleTimeout(idleTimeout);
- }
- }
-
- if (!seen)
- {
- DisableDrives();
- }
- }
- break;
-
- case 20: // List files on SD card
- if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers
- {
- return false;
- }
- {
- OutputBuffer *fileResponse;
- const int sparam = (gb.Seen('S')) ? gb.GetIValue() : 0;
- const char* dir = (gb.Seen('P')) ? gb.GetString() : platform->GetGCodeDir();
-
- if (sparam == 2)
- {
- fileResponse = reprap.GetFilesResponse(dir, true); // Send the file list in JSON format
- fileResponse->cat('\n');
- }
- else
- {
- if (!OutputBuffer::Allocate(fileResponse))
- {
- // Cannot allocate an output buffer, try again later
- return false;
- }
-
- // To mimic the behaviour of the official RepRapPro firmware:
- // If we are emulating RepRap then we print "GCode files:\n" at the start, otherwise we don't.
- // If we are emulating Marlin and the code came via the serial/USB interface, then we don't put quotes around the names and we separate them with newline;
- // otherwise we put quotes around them and separate them with comma.
- if (platform->Emulating() == me || platform->Emulating() == reprapFirmware)
- {
- fileResponse->copy("GCode files:\n");
- }
-
- bool encapsulateList = ((&gb != serialGCode && &gb != telnetGCode) || platform->Emulating() != marlin);
- FileInfo fileInfo;
- if (platform->GetMassStorage()->FindFirst(dir, fileInfo))
- {
- // iterate through all entries and append each file name
- do {
- if (encapsulateList)
- {
- fileResponse->catf("%c%s%c%c", FILE_LIST_BRACKET, fileInfo.fileName, FILE_LIST_BRACKET, FILE_LIST_SEPARATOR);
- }
- else
- {
- fileResponse->catf("%s\n", fileInfo.fileName);
- }
- } while (platform->GetMassStorage()->FindNext(fileInfo));
-
- if (encapsulateList)
- {
- // remove the last separator
- (*fileResponse)[fileResponse->Length() - 1] = 0;
- }
- }
- else
- {
- fileResponse->cat("NONE\n");
- }
- }
-
- UnlockAll(gb);
- HandleReply(gb, false, fileResponse);
- return true;
- }
-
- case 21: // Initialise SD card
- if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers
- {
- return false;
- }
- {
- size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0;
- result = platform->GetMassStorage()->Mount(card, reply, true);
- }
- break;
-
- case 22: // Release SD card
- if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers
- {
- return false;
- }
- {
- size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0;
- result = platform->GetMassStorage()->Unmount(card, reply);
- }
- break;
-
- case 23: // Set file to print
- case 32: // Select file and start SD print
- if (fileGCode->OriginalMachineState().fileState.IsLive())
- {
- reply.copy("Cannot set file to print, because a file is already being printed");
- error = true;
- break;
- }
-
- if (code == 32 && !LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- {
- const char* filename = gb.GetUnprecedentedString();
- if (filename != nullptr)
- {
- QueueFileToPrint(filename);
- if (fileToPrint.IsLive())
- {
- reprap.GetPrintMonitor()->StartingPrint(filename);
- if (platform->Emulating() == marlin && (&gb == serialGCode || &gb == telnetGCode))
- {
- reply.copy("File opened\nFile selected");
- }
- else
- {
- // Command came from web interface or PanelDue, or not emulating Marlin, so send a nicer response
- reply.printf("File %s selected for printing", filename);
- }
-
- if (code == 32)
- {
- fileGCode->OriginalMachineState().fileState.MoveFrom(fileToPrint);
- reprap.GetPrintMonitor()->StartedPrint();
- }
- }
- else
- {
- reply.printf("Failed to open file %s", filename);
- }
- }
- }
- break;
-
- case 24: // Print/resume-printing the selected file
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
-
- if (isPaused)
- {
- gb.SetState(GCodeState::resuming1);
- DoFileMacro(gb, RESUME_G);
- }
- else if (!fileToPrint.IsLive())
- {
- reply.copy("Cannot print, because no file is selected!");
- error = true;
- }
- else
- {
- fileGCode->OriginalMachineState().fileState.MoveFrom(fileToPrint);
- reprap.GetPrintMonitor()->StartedPrint();
- }
- break;
-
- case 226: // Gcode Initiated Pause
- if (&gb == fileGCode) // ignore M226 if it did't come from within a file being printed
- {
- DoPause(gb);
- }
- break;
-
- case 25: // Pause the print
- if (isPaused)
- {
- reply.copy("Printing is already paused!!");
- error = true;
- }
- else if (!reprap.GetPrintMonitor()->IsPrinting())
- {
- reply.copy("Cannot pause print, because no file is being printed!");
- error = true;
- }
- else
- {
- if (!LockMovement(gb)) // lock movement before calling DoPause
- {
- return false;
- }
- DoPause(gb);
- }
- break;
-
- case 26: // Set SD position
- if (gb.Seen('S'))
- {
- const FilePosition value = gb.GetIValue();
- if (value < 0)
- {
- reply.copy("SD positions can't be negative!");
- error = true;
- }
- else if (fileGCode->OriginalMachineState().fileState.IsLive())
- {
- if (!fileGCode->OriginalMachineState().fileState.Seek(value))
- {
- reply.copy("The specified SD position is invalid!");
- error = true;
- }
- }
- else if (fileToPrint.IsLive())
- {
- if (!fileToPrint.Seek(value))
- {
- reply.copy("The specified SD position is invalid!");
- error = true;
- }
- }
- else
- {
- reply.copy("Cannot set SD file position, because no print is in progress!");
- error = true;
- }
- }
- else
- {
- reply.copy("You must specify the SD position in bytes using the S parameter.");
- error = true;
- }
- break;
-
- case 27: // Report print status - Deprecated
- if (reprap.GetPrintMonitor()->IsPrinting())
- {
- // Pronterface keeps sending M27 commands if "Monitor status" is checked, and it specifically expects the following response syntax
- FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState;
- reply.printf("SD printing byte %lu/%lu", fileBeingPrinted.GetPosition(), fileBeingPrinted.Length());
- }
- else
- {
- reply.copy("Not SD printing.");
- }
- break;
-
- case 28: // Write to file
- {
- const char* str = gb.GetUnprecedentedString();
- if (str != nullptr)
- {
- bool ok = OpenFileToWrite(gb, platform->GetGCodeDir(), str);
- if (ok)
- {
- reply.printf("Writing to file: %s", str);
- }
- else
- {
- reply.printf("Can't open file %s for writing.", str);
- error = true;
- }
- }
- }
- break;
-
- case 29: // End of file being written; should be intercepted before getting here
- reply.copy("GCode end-of-file being interpreted.");
- break;
-
- case 30: // Delete file
- {
- const char *filename = gb.GetUnprecedentedString();
- if (filename != nullptr)
- {
- DeleteFile(filename);
- }
- }
- break;
-
- // For case 32, see case 24
-
- case 36: // Return file information
- if (!LockFileSystem(gb)) // getting file info takes several calls and isn't reentrant
- {
- return false;
- }
- {
- const char* filename = gb.GetUnprecedentedString(true); // get filename, or nullptr if none provided
- OutputBuffer *fileInfoResponse;
- result = reprap.GetPrintMonitor()->GetFileInfoResponse(filename, fileInfoResponse);
- if (result)
- {
- fileInfoResponse->cat('\n');
- UnlockAll(gb);
- HandleReply(gb, false, fileInfoResponse);
- return true;
- }
- }
- break;
-
- case 37: // Simulation mode on/off
- if (gb.Seen('S'))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
-
- bool wasSimulating = (simulationMode != 0);
- simulationMode = (uint8_t)gb.GetIValue();
- reprap.GetMove()->Simulate(simulationMode);
-
- if (simulationMode != 0)
- {
- simulationTime = 0.0;
- if (!wasSimulating)
- {
- // Starting a new simulation, so save the current position
- reprap.GetMove()->GetCurrentUserPosition(simulationRestorePoint.moveCoords, 0, reprap.GetCurrentXAxes());
- simulationRestorePoint.feedRate = feedRate;
- }
- }
- else if (wasSimulating)
- {
- // Ending a simulation, so restore the position
- SetPositions(simulationRestorePoint.moveCoords);
- for (size_t i = 0; i < DRIVES; ++i)
- {
- moveBuffer.coords[i] = simulationRestorePoint.moveCoords[i];
- }
- feedRate = simulationRestorePoint.feedRate;
- }
- }
- else
- {
- reply.printf("Simulation mode: %s, move time: %.1f sec, other time: %.1f sec",
- (simulationMode != 0) ? "on" : "off", reprap.GetMove()->GetSimulationTime(), simulationTime);
- }
- break;
-
- case 38: // Report SHA1 of file
- if (!LockFileSystem(gb)) // getting file hash takes several calls and isn't reentrant
- {
- return false;
- }
- if (fileBeingHashed == nullptr)
- {
- // See if we can open the file and start hashing
- const char* filename = gb.GetUnprecedentedString(true);
- if (StartHash(filename))
- {
- // Hashing is now in progress...
- result = false;
- }
- else
- {
- error = true;
- reply.printf("Cannot open file: %s", filename);
- }
- }
- else
- {
- // This can take some time. All the actual heavy lifting is in dedicated methods
- result = AdvanceHash(reply);
- }
- break;
-
- case 42: // Turn an output pin on or off
- if (gb.Seen('P'))
- {
- const int logicalPin = gb.GetIValue();
- Pin pin;
- bool invert;
- if (platform->GetFirmwarePin(logicalPin, PinAccess::pwm, pin, invert))
- {
- if (gb.Seen('S'))
- {
- float val = gb.GetFValue();
- if (val > 1.0)
- {
- val /= 255.0;
- }
- val = constrain<float>(val, 0.0, 1.0);
- if (invert)
- {
- val = 1.0 - val;
- }
- Platform::WriteAnalog(pin, val, DefaultPinWritePwmFreq);
- }
- // Ignore the command if no S parameter provided
- }
- else
- {
- reply.printf("Logical pin %d is not available for writing", logicalPin);
- error = true;
- }
- }
- break;
-
- case 80: // ATX power on
- platform->SetAtxPower(true);
- break;
-
- case 81: // ATX power off
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- platform->SetAtxPower(false);
- break;
-
- case 82: // Use absolute extruder positioning
- if (gb.MachineState().drivesRelative) // don't reset the absolute extruder position if it was already absolute
- {
- for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
- {
- lastRawExtruderPosition[extruder] = 0.0;
- }
- gb.MachineState().drivesRelative = false;
- }
- break;
-
- case 83: // Use relative extruder positioning
- if (!gb.MachineState().drivesRelative) // don't reset the absolute extruder position if it was already relative
- {
- for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
- {
- lastRawExtruderPosition[extruder] = 0.0;
- }
- gb.MachineState().drivesRelative = true;
- }
- break;
-
- // For case 84, see case 18
-
- case 85: // Set inactive time
- break;
-
- case 92: // Set/report steps/mm for some axes
- {
- // Save the current positions as we may need them later
- float positionNow[DRIVES];
- Move *move = reprap.GetMove();
- move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes());
-
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- platform->SetDriveStepsPerUnit(axis, gb.GetFValue());
- seen = true;
- }
- }
-
- if (gb.Seen(extrudeLetter))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- seen = true;
- float eVals[MaxExtruders];
- size_t eCount = numExtruders;
- gb.GetFloatArray(eVals, eCount, true);
-
- // The user may not have as many extruders as we allow for, so just set the ones for which a value is provided
- for (size_t e = 0; e < eCount; e++)
- {
- platform->SetDriveStepsPerUnit(numAxes + e, eVals[e]);
- }
- }
-
- if (seen)
- {
- // On a delta, if we change the drive steps/mm then we need to recalculate the motor positions
- SetPositions(positionNow);
- }
- else
- {
- reply.copy("Steps/mm: ");
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- reply.catf("%c: %.3f, ", axisLetters[axis], platform->DriveStepsPerUnit(axis));
- }
- reply.catf("E:");
- char sep = ' ';
- for (size_t extruder = 0; extruder < numExtruders; extruder++)
- {
- reply.catf("%c%.3f", sep, platform->DriveStepsPerUnit(extruder + numAxes));
- sep = ':';
- }
- }
- }
- break;
-
- case 98: // Call Macro/Subprogram
- if (gb.Seen('P'))
- {
- DoFileMacro(gb, gb.GetString());
- }
- break;
-
- case 99: // Return from Macro/Subprogram
- FileMacroCyclesReturn(gb);
- break;
-
- case 104: // Deprecated. This sets the active temperature of every heater of the active tool
- if (gb.Seen('S'))
- {
- float temperature = gb.GetFValue();
- Tool* tool;
- if (gb.Seen('T'))
- {
- int toolNumber = gb.GetIValue();
- toolNumber += gb.GetToolNumberAdjust();
- tool = reprap.GetTool(toolNumber);
- }
- else
- {
- tool = reprap.GetCurrentTool();
- // If no tool is selected, and there is only one tool, set the active temperature for that one
- if (tool == nullptr)
- {
- tool = reprap.GetOnlyTool();
- }
- }
- SetToolHeaters(tool, temperature);
- }
- break;
-
- case 105: // Get temperatures
- {
- const int8_t bedHeater = reprap.GetHeat()->GetBedHeater();
- const int8_t chamberHeater = reprap.GetHeat()->GetChamberHeater();
- reply.copy("T:");
- for (int8_t heater = 0; heater < HEATERS; heater++)
- {
- if (heater != bedHeater && heater != chamberHeater)
- {
- Heat::HeaterStatus hs = reprap.GetHeat()->GetStatus(heater);
- if (hs != Heat::HS_off && hs != Heat::HS_fault)
- {
- reply.catf("%.1f ", reprap.GetHeat()->GetTemperature(heater));
- }
- }
- }
- if (bedHeater >= 0)
- {
- reply.catf("B:%.1f", reprap.GetHeat()->GetTemperature(bedHeater));
- }
- else
- {
- // I'm not sure whether Pronterface etc. can handle a missing bed temperature, so return zero
- reply.cat("B:0.0");
- }
- if (chamberHeater >= 0.0)
- {
- reply.catf(" C:%.1f", reprap.GetHeat()->GetTemperature(chamberHeater));
- }
- }
- break;
-
- case 106: // Set/report fan values
- {
- bool seenFanNum = false;
- int32_t fanNum = 0; // Default to the first fan
- gb.TryGetIValue('P', fanNum, seenFanNum);
- if (fanNum < 0 || fanNum > (int)NUM_FANS)
- {
- reply.printf("Fan number %d is invalid, must be between 0 and %u", fanNum, NUM_FANS);
- }
- else
- {
- bool seen = false;
- Fan& fan = platform->GetFan(fanNum);
-
- if (gb.Seen('I')) // Invert cooling
- {
- const int invert = gb.GetIValue();
- if (invert < 0)
- {
- fan.Disable();
- }
- else
- {
- fan.SetInverted(invert > 0);
- }
- seen = true;
- }
-
- if (gb.Seen('F')) // Set PWM frequency
- {
- fan.SetPwmFrequency(gb.GetFValue());
- seen = true;
- }
-
- if (gb.Seen('T')) // Set thermostatic trigger temperature
- {
- seen = true;
- fan.SetTriggerTemperature(gb.GetFValue());
- }
-
- if (gb.Seen('B')) // Set blip time
- {
- seen = true;
- fan.SetBlipTime(gb.GetFValue());
- }
-
- if (gb.Seen('L')) // Set minimum speed
- {
- seen = true;
- fan.SetMinValue(gb.GetFValue());
- }
-
- if (gb.Seen('H')) // Set thermostatically-controller heaters
- {
- seen = true;
- long heaters[HEATERS];
- size_t numH = HEATERS;
- gb.GetLongArray(heaters, numH);
- // Note that M106 H-1 disables thermostatic mode. The following code implements that automatically.
- uint16_t hh = 0;
- for (size_t h = 0; h < numH; ++h)
- {
- const int hnum = heaters[h];
- if (hnum >= 0 && hnum < HEATERS)
- {
- hh |= (1u << (unsigned int)hnum);
- }
- }
- if (hh != 0)
- {
- platform->SetFanValue(fanNum, 1.0); // default the fan speed to full for safety
- }
- fan.SetHeatersMonitored(hh);
- }
-
- if (gb.Seen('S')) // Set new fan value - process this after processing 'H' or it may not be acted on
- {
- const float f = constrain<float>(gb.GetFValue(), 0.0, 255.0);
- if (seen || seenFanNum)
- {
- platform->SetFanValue(fanNum, f);
- }
- else
- {
- // We are processing an M106 S### command with no other recognised parameters and we have a tool selected.
- // Apply the fan speed setting to the fans in the fan mapping for the current tool.
- lastDefaultFanSpeed = f;
- SetMappedFanSpeed();
- }
- }
- else if (gb.Seen('R'))
- {
- const int i = gb.GetIValue();
- switch(i)
- {
- case 0:
- case 1:
- // Restore fan speed to value when print was paused
- platform->SetFanValue(fanNum, pausedFanValues[fanNum]);
- break;
- case 2:
- // Set the speeds of mapped fans to the last known value. Fan number is ignored.
- SetMappedFanSpeed();
- break;
- default:
- break;
- }
- }
- else if (!seen)
- {
- reply.printf("Fan%i frequency: %dHz, speed: %d%%, min: %d%%, blip: %.2f, inverted: %s",
- fanNum,
- (int)(fan.GetPwmFrequency()),
- (int)(fan.GetValue() * 100.0),
- (int)(fan.GetMinValue() * 100.0),
- fan.GetBlipTime(),
- (fan.GetInverted()) ? "yes" : "no");
- uint16_t hh = fan.GetHeatersMonitored();
- if (hh != 0)
- {
- reply.catf(", trigger: %dC, heaters:", (int)fan.GetTriggerTemperature());
- for (unsigned int i = 0; i < HEATERS; ++i)
- {
- if ((hh & (1u << i)) != 0)
- {
- reply.catf(" %u", i);
- }
- }
- }
- }
- }
- }
- break;
-
- case 107: // Fan off - deprecated
- platform->SetFanValue(0, 0.0); //T3P3 as deprecated only applies to fan0
- break;
-
- case 108: // Cancel waiting for temperature
- if (isWaiting)
- {
- cancelWait = true;
- }
- break;
-
- case 109: // Deprecated in RRF, but widely generated by slicers
- {
- float temperature;
- bool waitWhenCooling;
- if (gb.Seen('R'))
- {
- waitWhenCooling = true;
- temperature = gb.GetFValue();
- }
- else if (gb.Seen('S'))
- {
- waitWhenCooling = false;
- temperature = gb.GetFValue();
- }
- else
- {
- break; // no target temperature given
- }
-
- Tool *tool;
- if (gb.Seen('T'))
- {
- int toolNumber = gb.GetIValue();
- toolNumber += gb.GetToolNumberAdjust();
- tool = reprap.GetTool(toolNumber);
- }
- else
- {
- tool = reprap.GetCurrentTool();
- // If no tool is selected, and there is only one tool, set the active temperature for that one
- if (tool == nullptr)
- {
- tool = reprap.GetOnlyTool();
- }
- }
- SetToolHeaters(tool, temperature);
- if (cancelWait || ToolHeatersAtSetTemperatures(tool, waitWhenCooling))
- {
- cancelWait = isWaiting = false;
- break;
- }
- // In Marlin emulation mode we should return some sort of (undocumented) message here every second...
- isWaiting = true;
- return false;
- }
- break;
-
- case 110: // Set line numbers - line numbers are dealt with in the GCodeBuffer class
- break;
-
- case 111: // Debug level
- if (gb.Seen('S'))
- {
- bool dbv = (gb.GetIValue() != 0);
- if (gb.Seen('P'))
- {
- reprap.SetDebug(static_cast<Module>(gb.GetIValue()), dbv);
- }
- else
- {
- reprap.SetDebug(dbv);
- }
- }
- else
- {
- reprap.PrintDebug();
- }
- break;
-
- case 112: // Emergency stop - acted upon in Webserver, but also here in case it comes from USB etc.
- DoEmergencyStop();
- break;
-
- case 114:
- GetCurrentCoordinates(reply);
- break;
-
- case 115: // Print firmware version or set hardware type
- if (gb.Seen('P'))
- {
- platform->SetBoardType((BoardType)gb.GetIValue());
- }
- else
- {
- reply.printf("FIRMWARE_NAME: %s FIRMWARE_VERSION: %s ELECTRONICS: %s", NAME, VERSION, platform->GetElectronicsString());
-#ifdef DUET_NG
- const char* expansionName = DuetExpansion::GetExpansionBoardName();
- if (expansionName != nullptr)
- {
- reply.catf(" + %s", expansionName);
- }
-#endif
- reply.catf(" FIRMWARE_DATE: %s", DATE);
- }
- break;
-
- case 116: // Wait for set temperatures
- {
- bool seen = false;
- if (gb.Seen('P'))
- {
- // Wait for the heaters associated with the specified tool to be ready
- int toolNumber = gb.GetIValue();
- toolNumber += gb.GetToolNumberAdjust();
- if (!cancelWait && !ToolHeatersAtSetTemperatures(reprap.GetTool(toolNumber), true))
- {
- isWaiting = true;
- return false;
- }
- seen = true;
- }
-
- if (gb.Seen('H'))
- {
- // Wait for specified heaters to be ready
- long heaters[HEATERS];
- size_t heaterCount = HEATERS;
- gb.GetLongArray(heaters, heaterCount);
- if (!cancelWait)
- {
- for (size_t i=0; i<heaterCount; i++)
- {
- if (!reprap.GetHeat()->HeaterAtSetTemperature(heaters[i], true))
- {
- isWaiting = true;
- return false;
- }
- }
- }
- seen = true;
- }
-
- if (gb.Seen('C'))
- {
- // Wait for chamber heater to be ready
- const int8_t chamberHeater = reprap.GetHeat()->GetChamberHeater();
- if (chamberHeater != -1)
- {
- if (!cancelWait && !reprap.GetHeat()->HeaterAtSetTemperature(chamberHeater, true))
- {
- isWaiting = true;
- return false;
- }
- }
- seen = true;
- }
-
- // Wait for all heaters to be ready
- if (!seen && !cancelWait && !reprap.GetHeat()->AllHeatersAtSetTemperatures(true))
- {
- isWaiting = true;
- return false;
- }
-
- // If we get here, there is nothing more to wait for
- cancelWait = isWaiting = false;
- }
- break;
-
- case 117: // Display message
- {
- const char *msg = gb.GetUnprecedentedString(true);
- reprap.SetMessage((msg == nullptr) ? "" : msg);
- }
- break;
-
- case 119:
- reply.copy("Endstops - ");
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- reply.catf("%c: %s, ", axisLetters[axis], TranslateEndStopResult(platform->Stopped(axis)));
- }
- reply.catf("Z probe: %s", TranslateEndStopResult(platform->GetZProbeResult()));
- break;
-
- case 120:
- Push(gb);
- break;
-
- case 121:
- Pop(gb);
- break;
-
- case 122:
- {
- int val = (gb.Seen('P')) ? gb.GetIValue() : 0;
- if (val == 0)
- {
- reprap.Diagnostics(gb.GetResponseMessageType());
- }
- else
- {
- platform->DiagnosticTest(val);
- }
- }
- break;
-
- case 135: // Set PID sample interval
- if (gb.Seen('S'))
- {
- platform->SetHeatSampleTime(gb.GetFValue() * 0.001); // Value is in milliseconds; we want seconds
- }
- else
- {
- reply.printf("Heat sample time is %.3f seconds", platform->GetHeatSampleTime());
- }
- break;
-
- case 140: // Set bed temperature
- {
- int8_t bedHeater;
- if (gb.Seen('H'))
- {
- bedHeater = gb.GetIValue();
- if (bedHeater < 0)
- {
- // Make sure we stay within reasonable boundaries...
- bedHeater = -1;
-
- // If we're disabling the hot bed, make sure the old heater is turned off
- reprap.GetHeat()->SwitchOff(reprap.GetHeat()->GetBedHeater());
- }
- else if (bedHeater >= HEATERS)
- {
- reply.copy("Invalid heater number!");
- error = true;
- break;
- }
- reprap.GetHeat()->SetBedHeater(bedHeater);
-
- if (bedHeater < 0)
- {
- // Stop here if the hot bed has been disabled
- break;
- }
- }
- else
- {
- bedHeater = reprap.GetHeat()->GetBedHeater();
- if (bedHeater < 0)
- {
- reply.copy("Hot bed is not present!");
- error = true;
- break;
- }
- }
-
- if(gb.Seen('S'))
- {
- float temperature = gb.GetFValue();
- if (temperature < NEARLY_ABS_ZERO)
- {
- reprap.GetHeat()->SwitchOff(bedHeater);
- }
- else
- {
- reprap.GetHeat()->SetActiveTemperature(bedHeater, temperature);
- reprap.GetHeat()->Activate(bedHeater);
- }
- }
- if(gb.Seen('R'))
- {
- reprap.GetHeat()->SetStandbyTemperature(bedHeater, gb.GetFValue());
- }
- }
- break;
-
- case 141: // Chamber temperature
- {
- bool seen = false;
- if (gb.Seen('H'))
- {
- seen = true;
-
- int heater = gb.GetIValue();
- if (heater < 0)
- {
- const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater();
- if (currentHeater != -1)
- {
- reprap.GetHeat()->SwitchOff(currentHeater);
- }
-
- reprap.GetHeat()->SetChamberHeater(-1);
- }
- else if (heater < HEATERS)
- {
- reprap.GetHeat()->SetChamberHeater(heater);
- }
- else
- {
- reply.copy("Bad heater number specified!");
- error = true;
- }
- }
-
- if (gb.Seen('S'))
- {
- seen = true;
-
- const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater();
- if (currentHeater != -1)
- {
- float temperature = gb.GetFValue();
-
- if (temperature < NEARLY_ABS_ZERO)
- {
- reprap.GetHeat()->SwitchOff(currentHeater);
- }
- else
- {
- reprap.GetHeat()->SetActiveTemperature(currentHeater, temperature);
- reprap.GetHeat()->Activate(currentHeater);
- }
- }
- else
- {
- reply.copy("No chamber heater has been set up yet!");
- error = true;
- }
- }
-
- if (!seen)
- {
- const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater();
- if (currentHeater != -1)
- {
- reply.printf("Chamber heater %d is currently at %.1fC", currentHeater, reprap.GetHeat()->GetTemperature(currentHeater));
- }
- else
- {
- reply.copy("No chamber heater has been configured yet.");
- }
- }
- }
- break;
-
- case 143: // Set temperature limit
- {
- const int heater = (gb.Seen('H')) ? gb.GetIValue() : 1; // default to extruder 1 if no heater number provided
- if (heater < 0 || heater >= HEATERS)
- {
- reply.copy("Invalid heater number");
- error = true;
- }
- else if (gb.Seen('S'))
- {
- const float limit = gb.GetFValue();
- if (limit > BAD_LOW_TEMPERATURE && limit < BAD_ERROR_TEMPERATURE)
- {
- reprap.GetHeat()->SetTemperatureLimit(heater, limit);
- }
- else
- {
- reply.copy("Invalid temperature limit");
- error = true;
- }
- }
- else
- {
- reply.printf("Temperature limit for heater %d is %.1fC", heater, reprap.GetHeat()->GetTemperatureLimit(heater));
- }
- }
- break;
-
- case 144: // Set bed to standby
- {
- const int8_t bedHeater = reprap.GetHeat()->GetBedHeater();
- if (bedHeater >= 0)
- {
- reprap.GetHeat()->Standby(bedHeater);
- }
- }
- break;
-
- case 190: // Set bed temperature and wait
- case 191: // Set chamber temperature and wait
- {
- const int8_t heater = (code == 191) ? reprap.GetHeat()->GetChamberHeater() : reprap.GetHeat()->GetBedHeater();
- if (heater >= 0)
- {
- float temperature;
- bool waitWhenCooling;
- if (gb.Seen('R'))
- {
- waitWhenCooling = true;
- temperature = gb.GetFValue();
- }
- else if (gb.Seen('S'))
- {
- waitWhenCooling = false;
- temperature = gb.GetFValue();
- }
- else
- {
- break; // no target temperature given
- }
-
- reprap.GetHeat()->SetActiveTemperature(heater, temperature);
- reprap.GetHeat()->Activate(heater);
- if (cancelWait || reprap.GetHeat()->HeaterAtSetTemperature(heater, waitWhenCooling))
- {
- cancelWait = isWaiting = false;
- break;
- }
- // In Marlin emulation mode we should return some sort of (undocumented) message here every second...
- isWaiting = true;
- return false;
- }
- }
- break;
-
- case 201: // Set/print axis accelerations
- {
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- platform->SetAcceleration(axis, gb.GetFValue() * distanceScale);
- seen = true;
- }
- }
-
- if (gb.Seen(extrudeLetter))
- {
- seen = true;
- float eVals[MaxExtruders];
- size_t eCount = numExtruders;
- gb.GetFloatArray(eVals, eCount, true);
- for (size_t e = 0; e < eCount; e++)
- {
- platform->SetAcceleration(numAxes + e, eVals[e] * distanceScale);
- }
- }
-
- if (gb.Seen('P'))
- {
- // Set max average printing acceleration
- platform->SetMaxAverageAcceleration(gb.GetFValue() * distanceScale);
- seen = true;
- }
-
- if (!seen)
- {
- reply.printf("Accelerations: ");
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- reply.catf("%c: %.1f, ", axisLetters[axis], platform->Acceleration(axis) / distanceScale);
- }
- reply.cat("E:");
- char sep = ' ';
- for (size_t extruder = 0; extruder < numExtruders; extruder++)
- {
- reply.catf("%c%.1f", sep, platform->Acceleration(extruder + numAxes) / distanceScale);
- sep = ':';
- }
- reply.catf(", avg. printing: %.1f", platform->GetMaxAverageAcceleration());
- }
- }
- break;
-
- case 203: // Set/print maximum feedrates
- {
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- platform->SetMaxFeedrate(axis, gb.GetFValue() * distanceScale * secondsToMinutes); // G Code feedrates are in mm/minute; we need mm/sec
- seen = true;
- }
- }
-
- if (gb.Seen(extrudeLetter))
- {
- seen = true;
- float eVals[MaxExtruders];
- size_t eCount = numExtruders;
- gb.GetFloatArray(eVals, eCount, true);
- for (size_t e = 0; e < eCount; e++)
- {
- platform->SetMaxFeedrate(numAxes + e, eVals[e] * distanceScale * secondsToMinutes);
- }
- }
-
- if (!seen)
- {
- reply.copy("Maximum feedrates: ");
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- reply.catf("%c: %.1f, ", axisLetters[axis], platform->MaxFeedrate(axis) / (distanceScale * secondsToMinutes));
- }
- reply.cat("E:");
- char sep = ' ';
- for (size_t extruder = 0; extruder < numExtruders; extruder++)
- {
- reply.catf("%c%.1f", sep, platform->MaxFeedrate(extruder + numAxes) / (distanceScale * secondsToMinutes));
- sep = ':';
- }
- }
- }
- break;
-
- case 205: //M205 advanced settings: minimum travel speed S=while printing T=travel only, B=minimum segment time X= maximum xy jerk, Z=maximum Z jerk
- // This is superseded in this firmware by M codes for the separate types (e.g. M566).
- break;
-
- case 206: // Offset axes - Deprecated
- result = OffsetAxes(gb);
- break;
-
- case 207: // Set firmware retraction details
- {
- bool seen = false;
- if (gb.Seen('S'))
- {
- retractLength = max<float>(gb.GetFValue(), 0.0);
- seen = true;
- }
- if (gb.Seen('R'))
- {
- retractExtra = max<float>(gb.GetFValue(), -retractLength);
- seen = true;
- }
- if (gb.Seen('F'))
- {
- unRetractSpeed = retractSpeed = max<float>(gb.GetFValue(), 60.0);
- seen = true;
- }
- if (gb.Seen('T')) // must do this one after 'F'
- {
- unRetractSpeed = max<float>(gb.GetFValue(), 60.0);
- seen = true;
- }
- if (gb.Seen('Z'))
- {
- retractHop = max<float>(gb.GetFValue(), 0.0);
- seen = true;
- }
- if (!seen)
- {
- reply.printf("Retraction settings: length %.2f/%.2fmm, speed %d/%dmm/min, Z hop %.2fmm",
- retractLength, retractLength + retractExtra, (int)retractSpeed, (int)unRetractSpeed, retractHop);
- }
- }
- break;
-
- case 208: // Set/print maximum axis lengths. If there is an S parameter with value 1 then we set the min value, else we set the max value.
- {
- bool setMin = (gb.Seen('S') ? (gb.GetIValue() == 1) : false);
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- float value = gb.GetFValue() * distanceScale;
- if (setMin)
- {
- platform->SetAxisMinimum(axis, value);
- }
- else
- {
- platform->SetAxisMaximum(axis, value);
- }
- seen = true;
- }
- }
-
- if (!seen)
- {
- reply.copy("Axis limits ");
- char sep = '-';
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- reply.catf("%c %c: %.1f min, %.1f max", sep, axisLetters[axis], platform->AxisMinimum(axis),
- platform->AxisMaximum(axis));
- sep = ',';
- }
- }
- }
- break;
-
- case 210: // Set/print homing feed rates
- // This is no longer used, but for backwards compatibility we don't report an error
- break;
-
- case 220: // Set/report speed factor override percentage
- if (gb.Seen('S'))
- {
- float newSpeedFactor = (gb.GetFValue() / 100.0) * secondsToMinutes; // include the conversion from mm/minute to mm/second
- if (newSpeedFactor > 0.0)
- {
- feedRate *= newSpeedFactor / speedFactor;
- if (moveAvailable && !moveBuffer.isFirmwareRetraction)
- {
- // The last move has not gone yet, so we can update it
- moveBuffer.feedRate *= newSpeedFactor / speedFactor;
- }
- speedFactor = newSpeedFactor;
- }
- else
- {
- reply.printf("Invalid speed factor specified.");
- error = true;
- }
- }
- else
- {
- reply.printf("Speed factor override: %.1f%%", speedFactor * minutesToSeconds * 100.0);
- }
- break;
-
- case 221: // Set/report extrusion factor override percentage
- {
- int extruder = 0;
- if (gb.Seen('D')) // D parameter (if present) selects the extruder number
- {
- extruder = gb.GetIValue();
- }
-
- if (gb.Seen('S')) // S parameter sets the override percentage
- {
- float extrusionFactor = gb.GetFValue() / 100.0;
- if (extruder >= 0 && (size_t)extruder < numExtruders && extrusionFactor >= 0.0)
- {
- if (moveAvailable && !moveBuffer.isFirmwareRetraction)
- {
- moveBuffer.coords[extruder + numAxes] *= extrusionFactor/extrusionFactors[extruder]; // last move not gone, so update it
- }
- extrusionFactors[extruder] = extrusionFactor;
- }
- }
- else
- {
- reply.printf("Extrusion factor override for extruder %d: %.1f%%", extruder,
- extrusionFactors[extruder] * 100.0);
- }
- }
- break;
-
- // For case 226, see case 25
-
- case 280: // Servos
- if (gb.Seen('P'))
- {
- const int servoIndex = gb.GetIValue();
- Pin servoPin;
- bool invert;
- if (platform->GetFirmwarePin(servoIndex, PinAccess::servo, servoPin, invert))
- {
- if (gb.Seen('I'))
- {
- if (gb.GetIValue() > 0)
- {
- invert = !invert;
- }
- }
- if (gb.Seen('S'))
- {
- float angleOrWidth = gb.GetFValue();
- if (angleOrWidth < 0.0)
- {
- // Disable the servo by setting the pulse width to zero
- Platform::WriteAnalog(servoPin, (invert) ? 1.0 : 0.0, ServoRefreshFrequency);
- }
- else
- {
- if (angleOrWidth < MinServoPulseWidth)
- {
- // User gave an angle so convert it to a pulse width in microseconds
- angleOrWidth = (min<float>(angleOrWidth, 180.0) * ((MaxServoPulseWidth - MinServoPulseWidth) / 180.0)) + MinServoPulseWidth;
- }
- else if (angleOrWidth > MaxServoPulseWidth)
- {
- angleOrWidth = MaxServoPulseWidth;
- }
- float pwm = angleOrWidth * (ServoRefreshFrequency/1e6);
- if (invert)
- {
- pwm = 1.0 - pwm;
- }
- Platform::WriteAnalog(servoPin, pwm, ServoRefreshFrequency);
- }
- }
- // We don't currently allow the servo position to be read back
- }
- else
- {
- platform->MessageF(GENERIC_MESSAGE, "Error: Invalid servo index %d in M280 command\n", servoIndex);
- }
- }
- break;
-
- case 300: // Beep
- {
- int ms = (gb.Seen('P')) ? gb.GetIValue() : 1000; // time in milliseconds
- int freq = (gb.Seen('S')) ? gb.GetIValue() : 4600; // 4600Hz produces the loudest sound on a PanelDue
- reprap.Beep(freq, ms);
- }
- break;
-
- case 301: // Set/report hot end PID values
- SetPidParameters(gb, 1, reply);
- break;
-
- case 302: // Allow, deny or report cold extrudes
- if (gb.Seen('P'))
- {
- if (gb.GetIValue() > 0)
- {
- reprap.GetHeat()->AllowColdExtrude();
- }
- else
- {
- reprap.GetHeat()->DenyColdExtrude();
- }
- }
- else
- {
- reply.printf("Cold extrusion is %s, use M302 P[1/0] to allow/deny it",
- reprap.GetHeat()->ColdExtrude() ? "allowed" : "denied");
- }
- break;
-
- case 303: // Run PID tuning
- if (gb.Seen('H'))
- {
- const size_t heater = gb.GetIValue();
- const float temperature = (gb.Seen('S')) ? gb.GetFValue() : 225.0;
- const float maxPwm = (gb.Seen('P')) ? gb.GetFValue() : 0.5;
- if (heater < HEATERS && maxPwm >= 0.1 && maxPwm <= 1.0 && temperature >= 55.0 && temperature <= reprap.GetHeat()->GetTemperatureLimit(heater))
- {
- reprap.GetHeat()->StartAutoTune(heater, temperature, maxPwm, reply);
- }
- else
- {
- reply.printf("Bad parameter in M303 command");
- }
- }
- else
- {
- reprap.GetHeat()->GetAutoTuneStatus(reply);
- }
- break;
-
- case 304: // Set/report heated bed PID values
- {
- const int8_t bedHeater = reprap.GetHeat()->GetBedHeater();
- if (bedHeater >= 0)
- {
- SetPidParameters(gb, bedHeater, reply);
- }
- }
- break;
-
- case 305: // Set/report specific heater parameters
- SetHeaterParameters(gb, reply);
- break;
-
- case 307: // Set heater process model parameters
- if (gb.Seen('H'))
- {
- size_t heater = gb.GetIValue();
- if (heater < HEATERS)
- {
- const FopDt& model = reprap.GetHeat()->GetHeaterModel(heater);
- bool seen = false;
- float gain = model.GetGain(), tc = model.GetTimeConstant(), td = model.GetDeadTime(), maxPwm = model.GetMaxPwm();
- int32_t dontUsePid = model.UsePid() ? 0 : 1;
-
- gb.TryGetFValue('A', gain, seen);
- gb.TryGetFValue('C', tc, seen);
- gb.TryGetFValue('D', td, seen);
- gb.TryGetIValue('B', dontUsePid, seen);
- gb.TryGetFValue('S', maxPwm, seen);
-
- if (seen)
- {
- if (reprap.GetHeat()->SetHeaterModel(heater, gain, tc, td, maxPwm, dontUsePid == 0))
- {
- reprap.GetHeat()->UseModel(heater, true);
- }
- else
- {
- reply.copy("Error: bad model parameters");
- }
- }
- else if (!model.IsEnabled())
- {
- reply.printf("Heater %u is disabled", heater);
- }
- else
- {
- reply.printf("Heater %u model: gain %.1f, time constant %.1f, dead time %.1f, max PWM %.2f, in use: %s, mode: %s",
- heater, model.GetGain(), model.GetTimeConstant(), model.GetDeadTime(), model.GetMaxPwm(),
- (reprap.GetHeat()->IsModelUsed(heater)) ? "yes" : "no",
- (model.UsePid()) ? "PID" : "bang-bang");
- if (model.UsePid())
- {
- // When reporting the PID parameters, we scale them by 255 for compatibility with older firmware and other firmware
- const PidParams& spParams = model.GetPidParameters(false);
- const float scaledSpKp = 255.0 * spParams.kP;
- reply.catf("\nSetpoint change: P%.1f, I%.2f, D%.1f",
- scaledSpKp, scaledSpKp * spParams.recipTi, scaledSpKp * spParams.tD);
- const PidParams& ldParams = model.GetPidParameters(true);
- const float scaledLoadKp = 255.0 * ldParams.kP;
- reply.catf("\nLoad change: P%.1f, I%.2f, D%.1f",
- scaledLoadKp, scaledLoadKp * ldParams.recipTi, scaledLoadKp * ldParams.tD);
- }
- }
- }
- }
- break;
-
- case 350: // Set/report microstepping
- {
- // interp is current an int not a bool, because we use special values of interp to set the chopper control register
- int interp = 0;
- if (gb.Seen('I'))
- {
- interp = gb.GetIValue();
- }
-
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- seen = true;
- int microsteps = gb.GetIValue();
- if (ChangeMicrostepping(axis, microsteps, interp))
- {
- SetAxisNotHomed(axis);
- }
- else
- {
- platform->MessageF(GENERIC_MESSAGE, "Drive %c does not support %dx microstepping%s\n",
- axisLetters[axis], microsteps, (interp) ? " with interpolation" : "");
- }
- }
- }
-
- if (gb.Seen(extrudeLetter))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- seen = true;
- long eVals[MaxExtruders];
- size_t eCount = numExtruders;
- gb.GetLongArray(eVals, eCount);
- for (size_t e = 0; e < eCount; e++)
- {
- if (!ChangeMicrostepping(numAxes + e, (int)eVals[e], interp))
- {
- platform->MessageF(GENERIC_MESSAGE, "Drive E%u does not support %dx microstepping%s\n",
- e, (int)eVals[e], (interp) ? " with interpolation" : "");
- }
- }
- }
-
- if (!seen)
- {
- reply.copy("Microstepping - ");
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- bool interp;
- int microsteps = platform->GetMicrostepping(axis, interp);
- reply.catf("%c:%d%s, ", axisLetters[axis], microsteps, (interp) ? "(on)" : "");
- }
- reply.cat("E");
- for (size_t extruder = 0; extruder < numExtruders; extruder++)
- {
- bool interp;
- int microsteps = platform->GetMicrostepping(extruder + numAxes, interp);
- reply.catf(":%d%s", microsteps, (interp) ? "(on)" : "");
- }
- }
- }
- break;
-
- case 400: // Wait for current moves to finish
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- break;
-
- case 404: // Filament width and nozzle diameter
- {
- bool seen = false;
-
- if (gb.Seen('N'))
- {
- platform->SetFilamentWidth(gb.GetFValue());
- seen = true;
- }
- if (gb.Seen('D'))
- {
- platform->SetNozzleDiameter(gb.GetFValue());
- seen = true;
- }
-
- if (!seen)
- {
- reply.printf("Filament width: %.2fmm, nozzle diameter: %.2fmm", platform->GetFilamentWidth(), platform->GetNozzleDiameter());
- }
- }
- break;
-
- case 408: // Get status in JSON format
- {
- int type = gb.Seen('S') ? gb.GetIValue() : 0;
- int seq = gb.Seen('R') ? gb.GetIValue() : -1;
-
- OutputBuffer *statusResponse = nullptr;
- switch (type)
- {
- case 0:
- case 1:
- statusResponse = reprap.GetLegacyStatusResponse(type + 2, seq);
- break;
-
- case 2:
- case 3:
- case 4:
- statusResponse = reprap.GetStatusResponse(type - 1, (&gb == auxGCode) ? ResponseSource::AUX : ResponseSource::Generic);
- break;
-
- case 5:
- statusResponse = reprap.GetConfigResponse();
- break;
- }
-
- if (statusResponse != nullptr)
- {
- UnlockAll(gb);
- statusResponse->cat('\n');
- HandleReply(gb, false, statusResponse);
- return true;
- }
- }
- break;
-
- case 500: // Store parameters in EEPROM
- reprap.GetPlatform()->WriteNvData();
- break;
-
- case 501: // Load parameters from EEPROM
- reprap.GetPlatform()->ReadNvData();
- if (gb.Seen('S'))
- {
- reprap.GetPlatform()->SetAutoSave(gb.GetIValue() > 0);
- }
- break;
-
- case 502: // Revert to default "factory settings"
- reprap.GetPlatform()->ResetNvData();
- break;
-
- case 503: // List variable settings
- {
- if (!LockFileSystem(gb))
- {
- return false;
- }
-
- // Need a valid output buffer to continue...
- OutputBuffer *configResponse;
- if (!OutputBuffer::Allocate(configResponse))
- {
- // No buffer available, try again later
- return false;
- }
-
- // Read the entire file
- FileStore * const f = platform->GetFileStore(platform->GetSysDir(), platform->GetConfigFile(), false);
- if (f == nullptr)
- {
- error = true;
- reply.copy("Configuration file not found!");
- }
- else
- {
- char fileBuffer[FILE_BUFFER_SIZE];
- size_t bytesRead, bytesLeftForWriting = OutputBuffer::GetBytesLeft(configResponse);
- while ((bytesRead = f->Read(fileBuffer, FILE_BUFFER_SIZE)) > 0 && bytesLeftForWriting > 0)
- {
- // Don't write more data than we can process
- if (bytesRead < bytesLeftForWriting)
- {
- bytesLeftForWriting -= bytesRead;
- }
- else
- {
- bytesRead = bytesLeftForWriting;
- bytesLeftForWriting = 0;
- }
-
- // Write it
- configResponse->cat(fileBuffer, bytesRead);
- }
- f->Close();
-
- UnlockAll(gb);
- HandleReply(gb, false, configResponse);
- return true;
- }
- }
- break;
-
- case 540: // Set/report MAC address
- if (gb.Seen('P'))
- {
- SetMACAddress(gb);
- }
- else
- {
- const byte* mac = platform->MACAddress();
- reply.printf("MAC: %x:%x:%x:%x:%x:%x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
- }
- break;
-
- case 550: // Set/report machine name
- if (gb.Seen('P'))
- {
- reprap.SetName(gb.GetString());
- }
- else
- {
- reply.printf("RepRap name: %s", reprap.GetName());
- }
- break;
-
- case 551: // Set password (no option to report it)
- if (gb.Seen('P'))
- {
- reprap.SetPassword(gb.GetString());
- }
- break;
-
- case 552: // Enable/Disable network and/or Set/Get IP address
- {
- bool seen = false;
- if (gb.Seen('P'))
- {
- seen = true;
- SetEthernetAddress(gb, code);
- }
-
- if (gb.Seen('R'))
- {
- reprap.GetNetwork()->SetHttpPort(gb.GetIValue());
- seen = true;
- }
-
- // Process this one last in case the IP address is changed and the network enabled in the same command
- if (gb.Seen('S')) // Has the user turned the network on or off?
- {
- seen = true;
- if (gb.GetIValue() != 0)
- {
- reprap.GetNetwork()->Enable();
- }
- else
- {
- reprap.GetNetwork()->Disable();
- }
- }
-
- if (!seen)
- {
- const byte *config_ip = platform->IPAddress();
- const byte *actual_ip = reprap.GetNetwork()->IPAddress();
- reply.printf("Network is %s, configured IP address: %d.%d.%d.%d, actual IP address: %d.%d.%d.%d, HTTP port: %d",
- reprap.GetNetwork()->IsEnabled() ? "enabled" : "disabled",
- config_ip[0], config_ip[1], config_ip[2], config_ip[3], actual_ip[0], actual_ip[1], actual_ip[2], actual_ip[3],
- reprap.GetNetwork()->GetHttpPort());
- }
- }
- break;
-
- case 553: // Set/Get netmask
- if (gb.Seen('P'))
- {
- SetEthernetAddress(gb, code);
- }
- else
- {
- const byte *nm = platform->NetMask();
- reply.printf("Net mask: %d.%d.%d.%d ", nm[0], nm[1], nm[2], nm[3]);
- }
- break;
-
- case 554: // Set/Get gateway
- if (gb.Seen('P'))
- {
- SetEthernetAddress(gb, code);
- }
- else
- {
- const byte *gw = platform->GateWay();
- reply.printf("Gateway: %d.%d.%d.%d ", gw[0], gw[1], gw[2], gw[3]);
- }
- break;
-
- case 555: // Set/report firmware type to emulate
- if (gb.Seen('P'))
- {
- platform->SetEmulating((Compatibility) gb.GetIValue());
- }
- else
- {
- reply.copy("Emulating ");
- switch (platform->Emulating())
- {
- case me:
- case reprapFirmware:
- reply.cat("RepRap Firmware (i.e. in native mode)");
- break;
-
- case marlin:
- reply.cat("Marlin");
- break;
-
- case teacup:
- reply.cat("Teacup");
- break;
-
- case sprinter:
- reply.cat("Sprinter");
- break;
-
- case repetier:
- reply.cat("Repetier");
- break;
-
- default:
- reply.catf("Unknown: (%d)", platform->Emulating());
- }
- }
- break;
-
- case 556: // Axis compensation (we support only X, Y, Z)
- if (gb.Seen('S'))
- {
- float value = gb.GetFValue();
- for (size_t axis = 0; axis <= Z_AXIS; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- reprap.GetMove()->SetAxisCompensation(axis, gb.GetFValue() / value);
- }
- }
- }
- else
- {
- reply.printf("Axis compensations - XY: %.5f, YZ: %.5f, ZX: %.5f",
- reprap.GetMove()->AxisCompensation(X_AXIS), reprap.GetMove()->AxisCompensation(Y_AXIS),
- reprap.GetMove()->AxisCompensation(Z_AXIS));
- }
- break;
-
- case 557: // Set/report Z probe point coordinates
- if (gb.Seen('P'))
- {
- int point = gb.GetIValue();
- if (point < 0 || (unsigned int)point >= MaxProbePoints)
- {
- reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Z probe point index out of range.\n");
- }
- else
- {
- bool seen = false;
- if (gb.Seen(axisLetters[X_AXIS]))
- {
- reprap.GetMove()->SetXBedProbePoint(point, gb.GetFValue());
- seen = true;
- }
- if (gb.Seen(axisLetters[Y_AXIS]))
- {
- reprap.GetMove()->SetYBedProbePoint(point, gb.GetFValue());
- seen = true;
- }
-
- if (!seen)
- {
- reply.printf("Probe point %d - [%.1f, %.1f]", point, reprap.GetMove()->XBedProbePoint(point), reprap.GetMove()->YBedProbePoint(point));
- }
- }
- }
- else
- {
- LockMovement(gb); // to ensure that probing is not already in progress
- error = DefineGrid(gb, reply);
- }
- break;
-
- case 558: // Set or report Z probe type and for which axes it is used
- {
- bool seenAxes = false, seenType = false, seenParam = false;
- uint32_t zProbeAxes = platform->GetZProbeAxes();
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- if (gb.GetIValue() > 0)
- {
- zProbeAxes |= (1u << axis);
- }
- else
- {
- zProbeAxes &= ~(1u << axis);
- }
- seenAxes = true;
- }
- }
- if (seenAxes)
- {
- platform->SetZProbeAxes(zProbeAxes);
- }
-
- // We must get and set the Z probe type first before setting the dive height etc., because different probe types may have different parameters
- if (gb.Seen('P')) // probe type
- {
- platform->SetZProbeType(gb.GetIValue());
- seenType = true;
- }
-
- ZProbeParameters params = platform->GetZProbeParameters();
- gb.TryGetFValue('H', params.diveHeight, seenParam); // dive height
-
- if (gb.Seen('F')) // feed rate i.e. probing speed
- {
- params.probeSpeed = gb.GetFValue() * secondsToMinutes;
- seenParam = true;
- }
-
- if (gb.Seen('T')) // travel speed to probe point
- {
- params.travelSpeed = gb.GetFValue() * secondsToMinutes;
- seenParam = true;
- }
-
- if (gb.Seen('I'))
- {
- params.invertReading = (gb.GetIValue() != 0);
- seenParam = true;
- }
-
- gb.TryGetFValue('S', params.param1, seenParam); // extra parameter for experimentation
- gb.TryGetFValue('R', params.param2, seenParam); // extra parameter for experimentation
-
- if (seenParam)
- {
- platform->SetZProbeParameters(params);
- }
-
- if (!(seenAxes || seenType || seenParam))
- {
- reply.printf("Z Probe type %d, invert %s, dive height %.1fmm, probe speed %dmm/min, travel speed %dmm/min",
- platform->GetZProbeType(), (params.invertReading) ? "yes" : "no", params.diveHeight,
- (int)(params.probeSpeed * minutesToSeconds), (int)(params.travelSpeed * minutesToSeconds));
- if (platform->GetZProbeType() == ZProbeTypeDelta)
- {
- reply.catf(", parameters %.2f %.2f", params.param1, params.param2);
- }
- reply.cat(", used for axes:");
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if ((zProbeAxes & (1u << axis)) != 0)
- {
- reply.catf(" %c", axisLetters[axis]);
- }
- }
- }
- }
- break;
-
- case 559: // Upload config.g or another gcode file to put in the sys directory
- {
- const char* str = (gb.Seen('P') ? gb.GetString() : platform->GetConfigFile());
- bool ok = OpenFileToWrite(gb, platform->GetSysDir(), str);
- if (ok)
- {
- reply.printf("Writing to file: %s", str);
- }
- else
- {
- reply.printf("Can't open file %s for writing.", str);
- error = true;
- }
- }
- break;
-
- case 560: // Upload reprap.htm or another web interface file
- {
- const char* str = (gb.Seen('P') ? gb.GetString() : INDEX_PAGE_FILE);
- bool ok = OpenFileToWrite(gb, platform->GetWebDir(), str);
- if (ok)
- {
- reply.printf("Writing to file: %s", str);
- }
- else
- {
- reply.printf("Can't open file %s for writing.", str);
- error = true;
- }
- }
- break;
-
- case 561:
- reprap.GetMove()->SetIdentityTransform();
- break;
-
- case 562: // Reset temperature fault - use with great caution
- if (gb.Seen('P'))
- {
- int heater = gb.GetIValue();
- if (heater >= 0 && heater < HEATERS)
- {
- reprap.ClearTemperatureFault(heater);
- }
- else
- {
- reply.copy("Invalid heater number.\n");
- error = true;
- }
- }
- break;
-
- case 563: // Define tool
- ManageTool(gb, reply);
- break;
-
- case 564: // Think outside the box?
- if (gb.Seen('S'))
- {
- limitAxes = (gb.GetIValue() != 0);
- }
- else
- {
- reply.printf("Movement outside the bed is %spermitted", (limitAxes) ? "not " : "");
- }
- break;
-
- case 566: // Set/print maximum jerk speeds
- {
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- platform->SetInstantDv(axis, gb.GetFValue() * distanceScale * secondsToMinutes); // G Code feedrates are in mm/minute; we need mm/sec
- seen = true;
- }
- }
-
- if (gb.Seen(extrudeLetter))
- {
- seen = true;
- float eVals[MaxExtruders];
- size_t eCount = numExtruders;
- gb.GetFloatArray(eVals, eCount, true);
- for (size_t e = 0; e < eCount; e++)
- {
- platform->SetInstantDv(numAxes + e, eVals[e] * distanceScale * secondsToMinutes);
- }
- }
- else if (!seen)
- {
- reply.copy("Maximum jerk rates: ");
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- reply.catf("%c: %.1f, ", axisLetters[axis], platform->ConfiguredInstantDv(axis) / (distanceScale * secondsToMinutes));
- }
- reply.cat("E:");
- char sep = ' ';
- for (size_t extruder = 0; extruder < numExtruders; extruder++)
- {
- reply.catf("%c%.1f", sep, platform->ConfiguredInstantDv(extruder + numAxes) / (distanceScale * secondsToMinutes));
- sep = ':';
- }
- }
- }
- break;
-
- case 567: // Set/report tool mix ratios
- if (gb.Seen('P'))
- {
- int8_t tNumber = gb.GetIValue();
- Tool* tool = reprap.GetTool(tNumber);
- if (tool != NULL)
- {
- if (gb.Seen(extrudeLetter))
- {
- float eVals[MaxExtruders];
- size_t eCount = tool->DriveCount();
- gb.GetFloatArray(eVals, eCount, false);
- if (eCount != tool->DriveCount())
- {
- reply.printf("Setting mix ratios - wrong number of E drives: %s", gb.Buffer());
- }
- else
- {
- tool->DefineMix(eVals);
- }
- }
- else
- {
- reply.printf("Tool %d mix ratios:", tNumber);
- char sep = ' ';
- for (size_t drive = 0; drive < tool->DriveCount(); drive++)
- {
- reply.catf("%c%.3f", sep, tool->GetMix()[drive]);
- sep = ':';
- }
- }
- }
- }
- break;
-
- case 568: // Turn on/off automatic tool mixing
- if (gb.Seen('P'))
- {
- Tool* tool = reprap.GetTool(gb.GetIValue());
- if (tool != NULL)
- {
- if (gb.Seen('S'))
- {
- tool->SetMixing(gb.GetIValue() != 0);
- }
- else
- {
- reply.printf("Tool %d mixing is %s", tool->Number(), (tool->GetMixing()) ? "enabled" : "disabled");
- }
- }
- }
- break;
-
- case 569: // Set/report axis direction
- if (gb.Seen('P'))
- {
- size_t drive = gb.GetIValue();
- if (drive < DRIVES)
- {
- bool seen = false;
- if (gb.Seen('S'))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- platform->SetDirectionValue(drive, gb.GetIValue() != 0);
- seen = true;
- }
- if (gb.Seen('R'))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- platform->SetEnableValue(drive, gb.GetIValue() != 0);
- seen = true;
- }
- if (gb.Seen('T'))
- {
- platform->SetDriverStepTiming(drive, gb.GetFValue());
- seen = true;
- }
- bool badParameter = false;
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- badParameter = true;
- }
- }
- if (gb.Seen(extrudeLetter))
- {
- badParameter = true;
- }
- if (badParameter)
- {
- platform->Message(GENERIC_MESSAGE, "Error: M569 no longer accepts XYZE parameters; use M584 instead\n");
- }
- else if (!seen)
- {
- reply.printf("A %d sends drive %u forwards, a %d enables it, and the minimum pulse width is %.1f microseconds",
- (int)platform->GetDirectionValue(drive), drive,
- (int)platform->GetEnableValue(drive),
- platform->GetDriverStepTiming(drive));
- }
- }
- }
- break;
-
- case 570: // Set/report heater timeout
- if (gb.Seen('H'))
- {
- const size_t heater = gb.GetIValue();
- bool seen = false;
- if (heater < HEATERS)
- {
- float maxTempExcursion, maxFaultTime;
- reprap.GetHeat()->GetHeaterProtection(heater, maxTempExcursion, maxFaultTime);
- gb.TryGetFValue('P', maxFaultTime, seen);
- gb.TryGetFValue('T', maxTempExcursion, seen);
- if (seen)
- {
- reprap.GetHeat()->SetHeaterProtection(heater, maxTempExcursion, maxFaultTime);
- }
- else
- {
- reply.printf("Heater %u allowed excursion %.1fC, fault trigger time %.1f seconds", heater, maxTempExcursion, maxFaultTime);
- }
- }
- }
- else if (gb.Seen('S'))
- {
- reply.copy("M570 S parameter is no longer required or supported");
- }
- break;
-
- case 571: // Set output on extrude
- if (gb.Seen('S'))
- {
- platform->SetExtrusionAncilliaryPWM(gb.GetFValue());
- }
- else
- {
- reply.printf("Extrusion ancillary PWM: %.3f.", platform->GetExtrusionAncilliaryPWM());
- }
- break;
-
- case 572: // Set/report elastic compensation
- if (gb.Seen('D'))
- {
- // New usage: specify the extruder drive using the D parameter
- size_t extruder = gb.GetIValue();
- if (gb.Seen('S'))
- {
- platform->SetPressureAdvance(extruder, gb.GetFValue());
- }
- else
- {
- reply.printf("Pressure advance for extruder %u is %.3f seconds", extruder, platform->GetPressureAdvance(extruder));
- }
- }
- break;
-
- case 573: // Report heater average PWM
- if (gb.Seen('P'))
- {
- int heater = gb.GetIValue();
- if (heater >= 0 && heater < HEATERS)
- {
- reply.printf("Average heater %d PWM: %.3f", heater, reprap.GetHeat()->GetAveragePWM(heater));
- }
- }
- break;
-
- case 574: // Set endstop configuration
- {
- bool seen = false;
- bool logicLevel = (gb.Seen('S')) ? (gb.GetIValue() != 0) : true;
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- int ival = gb.GetIValue();
- if (ival >= 0 && ival <= 3)
- {
- platform->SetEndStopConfiguration(axis, (EndStopType) ival, logicLevel);
- seen = true;
- }
- }
- }
- if (!seen)
- {
- reply.copy("Endstop configuration:");
- EndStopType config;
- bool logic;
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- platform->GetEndStopConfiguration(axis, config, logic);
- reply.catf(" %c %s (active %s),", axisLetters[axis],
- (config == EndStopType::highEndStop) ? "high end" : (config == EndStopType::lowEndStop) ? "low end" : "none",
- (config == EndStopType::noEndStop) ? "" : (logic) ? "high" : "low");
- }
- }
- }
- break;
-
- case 575: // Set communications parameters
- if (gb.Seen('P'))
- {
- size_t chan = gb.GetIValue();
- if (chan < NUM_SERIAL_CHANNELS)
- {
- bool seen = false;
- if (gb.Seen('B'))
- {
- platform->SetBaudRate(chan, gb.GetIValue());
- seen = true;
- }
- if (gb.Seen('S'))
- {
- uint32_t val = gb.GetIValue();
- platform->SetCommsProperties(chan, val);
- switch (chan)
- {
- case 0:
- serialGCode->SetCommsProperties(val);
- break;
- case 1:
- auxGCode->SetCommsProperties(val);
- break;
- default:
- break;
- }
- seen = true;
- }
- if (!seen)
- {
- uint32_t cp = platform->GetCommsProperties(chan);
- reply.printf("Channel %d: baud rate %d, %s checksum", chan, platform->GetBaudRate(chan),
- (cp & 1) ? "requires" : "does not require");
- }
- }
- }
- break;
-
- case 577: // Wait until endstop is triggered
- if (gb.Seen('S'))
- {
- // Determine trigger type
- EndStopHit triggerCondition;
- switch (gb.GetIValue())
- {
- case 1:
- triggerCondition = EndStopHit::lowHit;
- break;
- case 2:
- triggerCondition = EndStopHit::highHit;
- break;
- case 3:
- triggerCondition = EndStopHit::lowNear;
- break;
- default:
- triggerCondition = EndStopHit::noStop;
- break;
- }
-
- // Axis endstops
- for (size_t axis=0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- if (platform->Stopped(axis) != triggerCondition)
- {
- result = false;
- break;
- }
- }
- }
-
- // Extruder drives
- size_t eDriveCount = MaxExtruders;
- long eDrives[MaxExtruders];
- if (gb.Seen(extrudeLetter))
- {
- gb.GetLongArray(eDrives, eDriveCount);
- for(size_t extruder = 0; extruder < eDriveCount; extruder++)
- {
- const size_t eDrive = eDrives[extruder];
- if (eDrive >= MaxExtruders)
- {
- reply.copy("Invalid extruder drive specified!");
- error = result = true;
- break;
- }
-
- if (platform->Stopped(eDrive + E0_AXIS) != triggerCondition)
- {
- result = false;
- break;
- }
- }
- }
- }
- break;
-
-#if SUPPORT_INKJET
- case 578: // Fire Inkjet bits
- if (!AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- return false;
- }
-
- if (gb.Seen('S')) // Need to handle the 'P' parameter too; see http://reprap.org/wiki/G-code#M578:_Fire_inkjet_bits
- {
- platform->Inkjet(gb.GetIValue());
- }
- break;
-#endif
-
- case 579: // Scale Cartesian axes (mostly for Delta configurations)
- {
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- gb.TryGetFValue(axisLetters[axis], axisScaleFactors[axis], seen);
- }
-
- if (!seen)
- {
- char sep = ':';
- reply.copy("Axis scale factors");
- for(size_t axis = 0; axis < numAxes; axis++)
- {
- reply.catf("%c %c: %.3f", sep, axisLetters[axis], axisScaleFactors[axis]);
- sep = ',';
- }
- }
- }
- break;
-
-#if SUPPORT_ROLAND
- case 580: // (De)Select Roland mill
- if (gb.Seen('R'))
- {
- if (gb.GetIValue())
- {
- reprap.GetRoland()->Activate();
- if (gb.Seen('P'))
- {
- result = reprap.GetRoland()->RawWrite(gb.GetString());
- }
- }
- else
- {
- result = reprap.GetRoland()->Deactivate();
- }
- }
- else
- {
- reply.printf("Roland is %s.", reprap.GetRoland()->Active() ? "active" : "inactive");
- }
- break;
-#endif
-
- case 581: // Configure external trigger
- case 582: // Check external trigger
- if (gb.Seen('T'))
- {
- unsigned int triggerNumber = gb.GetIValue();
- if (triggerNumber < MaxTriggers)
- {
- if (code == 582)
- {
- uint32_t states = platform->GetAllEndstopStates();
- if ((triggers[triggerNumber].rising & states) != 0 || (triggers[triggerNumber].falling & ~states) != 0)
- {
- triggersPending |= (1u << triggerNumber);
- }
- }
- else
- {
- bool seen = false;
- if (gb.Seen('C'))
- {
- seen = true;
- triggers[triggerNumber].condition = gb.GetIValue();
- }
- else if (triggers[triggerNumber].IsUnused())
- {
- triggers[triggerNumber].condition = 0; // this is a new trigger, so set no condition
- }
- if (gb.Seen('S'))
- {
- seen = true;
- int sval = gb.GetIValue();
- TriggerMask triggerMask = 0;
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- triggerMask |= (1u << axis);
- }
- }
- if (gb.Seen(extrudeLetter))
- {
- long eStops[MaxExtruders];
- size_t numEntries = MaxExtruders;
- gb.GetLongArray(eStops, numEntries);
- for (size_t i = 0; i < numEntries; ++i)
- {
- if (eStops[i] >= 0 && (unsigned long)eStops[i] < MaxExtruders)
- {
- triggerMask |= (1u << (eStops[i] + E0_AXIS));
- }
- }
- }
- switch(sval)
- {
- case -1:
- if (triggerMask == 0)
- {
- triggers[triggerNumber].rising = triggers[triggerNumber].falling = 0;
- }
- else
- {
- triggers[triggerNumber].rising &= (~triggerMask);
- triggers[triggerNumber].falling &= (~triggerMask);
- }
- break;
-
- case 0:
- triggers[triggerNumber].falling |= triggerMask;
- break;
-
- case 1:
- triggers[triggerNumber].rising |= triggerMask;
- break;
-
- default:
- platform->Message(GENERIC_MESSAGE, "Bad S parameter in M581 command\n");
- }
- }
- if (!seen)
- {
- reply.printf("Trigger %u fires on a rising edge on ", triggerNumber);
- ListTriggers(reply, triggers[triggerNumber].rising);
- reply.cat(" or a falling edge on ");
- ListTriggers(reply, triggers[triggerNumber].falling);
- reply.cat(" endstop inputs");
- if (triggers[triggerNumber].condition == 1)
- {
- reply.cat(" when printing from SD card");
- }
- }
- }
- }
- else
- {
- platform->Message(GENERIC_MESSAGE, "Trigger number out of range\n");
- }
- }
- break;
-
- case 584: // Set axis/extruder to stepper driver(s) mapping
- if (!LockMovementAndWaitForStandstill(gb)) // we also rely on this to retrieve the current motor positions to moveBuffer
- {
- return false;
- }
- {
- bool seen = false, badDrive = false;
- for (size_t drive = 0; drive < MAX_AXES; ++drive)
- {
- if (gb.Seen(axisLetters[drive]))
- {
- seen = true;
- size_t numValues = MaxDriversPerAxis;
- long drivers[MaxDriversPerAxis];
- gb.GetLongArray(drivers, numValues);
-
- // Check all the driver numbers are in range
- bool badAxis = false;
- AxisDriversConfig config;
- config.numDrivers = numValues;
- for (size_t i = 0; i < numValues; ++i)
- {
- if ((unsigned long)drivers[i] >= DRIVES)
- {
- badAxis = true;
- }
- else
- {
- config.driverNumbers[i] = (uint8_t)drivers[i];
- }
- }
- if (badAxis)
- {
- badDrive = true;
- }
- else
- {
- while (numAxes <= drive)
- {
- moveBuffer.coords[numAxes] = 0.0; // user has defined a new axis, so set its position
- ++numAxes;
- }
- SetPositions(moveBuffer.coords); // tell the Move system where any new axes are
- platform->SetAxisDriversConfig(drive, config);
- if (numAxes + numExtruders > DRIVES)
- {
- numExtruders = DRIVES - numAxes; // if we added axes, we may have fewer extruders now
- }
- }
- }
- }
-
- if (gb.Seen(extrudeLetter))
- {
- seen = true;
- size_t numValues = DRIVES - numAxes;
- long drivers[MaxExtruders];
- gb.GetLongArray(drivers, numValues);
- numExtruders = numValues;
- for (size_t i = 0; i < numValues; ++i)
- {
- if ((unsigned long)drivers[i] >= DRIVES)
- {
- badDrive = true;
- }
- else
- {
- platform->SetExtruderDriver(i, (uint8_t)drivers[i]);
- }
- }
- }
-
- if (badDrive)
- {
- platform->Message(GENERIC_MESSAGE, "Error: invalid drive number in M584 command\n");
- }
- else if (!seen)
- {
- reply.copy("Driver assignments:");
- for (size_t drive = 0; drive < numAxes; ++ drive)
- {
- reply.cat(' ');
- const AxisDriversConfig& axisConfig = platform->GetAxisDriversConfig(drive);
- char c = axisLetters[drive];
- for (size_t i = 0; i < axisConfig.numDrivers; ++i)
- {
- reply.catf("%c%u", c, axisConfig.driverNumbers[i]);
- c = ':';
- }
- }
- reply.cat(' ');
- char c = extrudeLetter;
- for (size_t extruder = 0; extruder < numExtruders; ++extruder)
- {
- reply.catf("%c%u", c, platform->GetExtruderDriver(extruder));
- c = ':';
- }
- }
- }
- break;
-
- case 665: // Set delta configuration
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- {
- float positionNow[DRIVES];
- Move *move = reprap.GetMove();
- move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes()); // get the current position, we may need it later
- DeltaParameters& params = move->AccessDeltaParams();
- bool wasInDeltaMode = params.IsDeltaMode(); // remember whether we were in delta mode
- bool seen = false;
-
- if (gb.Seen('L'))
- {
- params.SetDiagonal(gb.GetFValue() * distanceScale);
- seen = true;
- }
- if (gb.Seen('R'))
- {
- params.SetRadius(gb.GetFValue() * distanceScale);
- seen = true;
- }
- if (gb.Seen('B'))
- {
- params.SetPrintRadius(gb.GetFValue() * distanceScale);
- seen = true;
- }
- if (gb.Seen('X'))
- {
- // X tower position correction
- params.SetXCorrection(gb.GetFValue());
- seen = true;
- }
- if (gb.Seen('Y'))
- {
- // Y tower position correction
- params.SetYCorrection(gb.GetFValue());
- seen = true;
- }
- if (gb.Seen('Z'))
- {
- // Y tower position correction
- params.SetZCorrection(gb.GetFValue());
- seen = true;
- }
-
- // The homed height must be done last, because it gets recalculated when some of the other factors are changed
- if (gb.Seen('H'))
- {
- params.SetHomedHeight(gb.GetFValue() * distanceScale);
- seen = true;
- }
-
- if (seen)
- {
- move->SetCoreXYMode(0); // CoreXYMode needs to be zero when executing special moves on a delta
-
- // If we have changed between Cartesian and Delta mode, we need to reset the motor coordinates to agree with the XYZ coordinates.
- // This normally happens only when we process the M665 command in config.g. Also flag that the machine is not homed.
- if (params.IsDeltaMode() != wasInDeltaMode)
- {
- SetPositions(positionNow);
- }
- SetAllAxesNotHomed();
- }
- else
- {
- if (params.IsDeltaMode())
- {
- reply.printf("Diagonal %.3f, delta radius %.3f, homed height %.3f, bed radius %.1f"
- ", X %.3f" DEGREE_SYMBOL ", Y %.3f" DEGREE_SYMBOL ", Z %.3f" DEGREE_SYMBOL,
- params.GetDiagonal() / distanceScale, params.GetRadius() / distanceScale,
- params.GetHomedHeight() / distanceScale, params.GetPrintRadius() / distanceScale,
- params.GetXCorrection(), params.GetYCorrection(), params.GetZCorrection());
- }
- else
- {
- reply.printf("Printer is not in delta mode");
- }
- }
- }
- break;
-
- case 666: // Set delta endstop adjustments
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- {
- DeltaParameters& params = reprap.GetMove()->AccessDeltaParams();
- bool seen = false;
- if (gb.Seen('X'))
- {
- params.SetEndstopAdjustment(X_AXIS, gb.GetFValue());
- seen = true;
- }
- if (gb.Seen('Y'))
- {
- params.SetEndstopAdjustment(Y_AXIS, gb.GetFValue());
- seen = true;
- }
- if (gb.Seen('Z'))
- {
- params.SetEndstopAdjustment(Z_AXIS, gb.GetFValue());
- seen = true;
- }
- if (gb.Seen('A'))
- {
- params.SetXTilt(gb.GetFValue() * 0.01);
- seen = true;
- }
- if (gb.Seen('B'))
- {
- params.SetYTilt(gb.GetFValue() * 0.01);
- seen = true;
- }
-
- if (seen)
- {
- SetAllAxesNotHomed();
- }
- else
- {
- reply.printf("Endstop adjustments X%.2f Y%.2f Z%.2f, tilt X%.2f%% Y%.2f%%",
- params.GetEndstopAdjustment(X_AXIS), params.GetEndstopAdjustment(Y_AXIS), params.GetEndstopAdjustment(Z_AXIS),
- params.GetXTilt() * 100.0, params.GetYTilt() * 100.0);
- }
- }
- break;
-
- case 667: // Set CoreXY mode
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- {
- Move* move = reprap.GetMove();
- bool seen = false;
- float positionNow[DRIVES];
- move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes()); // get the current position, we may need it later
- if (gb.Seen('S'))
- {
- move->SetCoreXYMode(gb.GetIValue());
- seen = true;
- }
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- move->SetCoreAxisFactor(axis, gb.GetFValue());
- seen = true;
- }
- }
-
- if (seen)
- {
- SetPositions(positionNow);
- SetAllAxesNotHomed();
- }
- else
- {
- reply.printf("Printer mode is %s with axis factors", move->GetGeometryString());
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- reply.catf(" %c:%f", axisLetters[axis], move->GetCoreAxisFactor(axis));
- }
- }
- }
- break;
-
- case 905: // Set current RTC date and time
- {
- const time_t now = platform->GetDateTime();
- struct tm * const timeInfo = gmtime(&now);
- bool seen = false;
-
- if (gb.Seen('P'))
- {
- // Set date
- const char * const dateString = gb.GetString();
- if (strptime(dateString, "%Y-%m-%d", timeInfo) != nullptr)
- {
- if (!platform->SetDate(mktime(timeInfo)))
- {
- reply.copy("Could not set date");
- error = true;
- break;
- }
- }
- else
- {
- reply.copy("Invalid date format");
- error = true;
- break;
- }
-
- seen = true;
- }
-
- if (gb.Seen('S'))
- {
- // Set time
- const char * const timeString = gb.GetString();
- if (strptime(timeString, "%H:%M:%S", timeInfo) != nullptr)
- {
- if (!platform->SetTime(mktime(timeInfo)))
- {
- reply.copy("Could not set time");
- error = true;
- break;
- }
- }
- else
- {
- reply.copy("Invalid time format");
- error = true;
- break;
- }
-
- seen = true;
- }
-
- // TODO: Add correction parameters for SAM4E
-
- if (!seen)
- {
- // Report current date and time
- reply.printf("Current date and time: %04u-%02u-%02u %02u:%02u:%02u",
- timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday,
- timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
-
- if (!platform->IsDateTimeSet())
- {
- reply.cat("\nWarning: RTC has not been configured yet!");
- }
- }
- }
- break;
-
- case 906: // Set/report Motor currents
- case 913: // Set/report motor current percent
- {
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- platform->SetMotorCurrent(axis, gb.GetFValue(), code == 913);
- seen = true;
- }
- }
-
- if (gb.Seen(extrudeLetter))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
-
- float eVals[MaxExtruders];
- size_t eCount = numExtruders;
- gb.GetFloatArray(eVals, eCount, true);
- // 2014-09-29 DC42: we no longer insist that the user supplies values for all possible extruder drives
- for (size_t e = 0; e < eCount; e++)
- {
- platform->SetMotorCurrent(numAxes + e, eVals[e], code == 913);
- }
- seen = true;
- }
-
- if (code == 906 && gb.Seen('I'))
- {
- float idleFactor = gb.GetFValue();
- if (idleFactor >= 0 && idleFactor <= 100.0)
- {
- platform->SetIdleCurrentFactor(idleFactor/100.0);
- seen = true;
- }
- }
-
- if (!seen)
- {
- reply.copy((code == 913) ? "Motor current % of normal - " : "Motor current (mA) - ");
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- reply.catf("%c:%d, ", axisLetters[axis], (int)platform->GetMotorCurrent(axis, code == 913));
- }
- reply.cat("E");
- for (size_t extruder = 0; extruder < numExtruders; extruder++)
- {
- reply.catf(":%d", (int)platform->GetMotorCurrent(extruder + numAxes, code == 913));
- }
- if (code == 906)
- {
- reply.catf(", idle factor %d%%", (int)(platform->GetIdleCurrentFactor() * 100.0));
- }
- }
- }
- break;
-
- case 911: // Set power monitor threshold voltages
- reply.printf("M911 not implemented yet");
- break;
-
- case 912: // Set electronics temperature monitor adjustment
- // Currently we ignore the P parameter (i.e. temperature measurement channel)
- if (gb.Seen('S'))
- {
- platform->SetMcuTemperatureAdjust(gb.GetFValue());
- }
- else
- {
- reply.printf("MCU temperature calibration adjustment is %.1f" DEGREE_SYMBOL "C", platform->GetMcuTemperatureAdjust());
- }
- break;
-
- // For case 913, see 906
-
- case 997: // Perform firmware update
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- reprap.GetHeat()->SwitchOffAll(); // turn all heaters off because the main loop may get suspended
- DisableDrives(); // all motors off
-
- if (firmwareUpdateModuleMap == 0) // have we worked out which modules to update?
- {
- // Find out which modules we have been asked to update
- if (gb.Seen('S'))
- {
- long modulesToUpdate[3];
- size_t numUpdateModules = ARRAY_SIZE(modulesToUpdate);
- gb.GetLongArray(modulesToUpdate, numUpdateModules);
- for (size_t i = 0; i < numUpdateModules; ++i)
- {
- long t = modulesToUpdate[i];
- if (t < 0 || (unsigned long)t >= NumFirmwareUpdateModules)
- {
- platform->MessageF(GENERIC_MESSAGE, "Invalid module number '%ld'\n", t);
- firmwareUpdateModuleMap = 0;
- break;
- }
- firmwareUpdateModuleMap |= (1u << (unsigned int)t);
- }
- }
- else
- {
- firmwareUpdateModuleMap = (1u << 0); // no modules specified, so update module 0 to match old behaviour
- }
-
- if (firmwareUpdateModuleMap == 0)
- {
- break; // nothing to update
- }
-
- // Check prerequisites of all modules to be updated, if any are not met then don't update any of them
-#ifdef DUET_NG
- if (!FirmwareUpdater::CheckFirmwareUpdatePrerequisites(firmwareUpdateModuleMap))
- {
- firmwareUpdateModuleMap = 0;
- break;
- }
-#endif
- if ((firmwareUpdateModuleMap & 1) != 0 && !platform->CheckFirmwareUpdatePrerequisites())
- {
- firmwareUpdateModuleMap = 0;
- break;
- }
- }
-
- // If we get here then we have the module map, and all prerequisites are satisfied
- isFlashing = true; // this tells the web interface and PanelDue that we are about to flash firmware
- if (!DoDwellTime(1.0)) // wait a second so all HTTP clients and PanelDue are notified
- {
- return false;
- }
-
- gb.SetState(GCodeState::flashing1);
- break;
-
- case 998:
- // The input handling code replaces the gcode by this when it detects a checksum error.
- // Since we have no way of asking for the line to be re-sent, just report an error.
- if (gb.Seen('P'))
- {
- int val = gb.GetIValue();
- if (val != 0)
- {
- reply.printf("Checksum error on line %d", val);
- }
- }
- break;
-
- case 999:
- result = DoDwellTime(0.5); // wait half a second to allow the response to be sent back to the web server, otherwise it may retry
- if (result)
- {
- reprap.EmergencyStop(); // this disables heaters and drives - Duet WiFi pre-production boards need drives disabled here
- uint16_t reason = (gb.Seen('P') && StringStartsWith(gb.GetString(), "ERASE"))
- ? (uint16_t)SoftwareResetReason::erase
- : (uint16_t)SoftwareResetReason::user;
- platform->SoftwareReset(reason); // doesn't return
- }
- break;
-
- default:
- error = true;
- reply.printf("unsupported command: %s", gb.Buffer());
- }
-
- if (result && gb.GetState() == GCodeState::normal)
- {
- UnlockAll(gb);
- HandleReply(gb, error, reply.Pointer());
- }
- return result;
-}
-
-bool GCodes::HandleTcode(GCodeBuffer& gb, StringRef& reply)
-{
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
-
- newToolNumber = gb.GetIValue();
- newToolNumber += gb.GetToolNumberAdjust();
-
- // TODO for the tool change restore point to be useful, we should undo any X axis mapping and remove any tool offsets
- for (size_t drive = 0; drive < DRIVES; ++drive)
- {
- toolChangeRestorePoint.moveCoords[drive] = moveBuffer.coords[drive];
- }
- toolChangeRestorePoint.feedRate = feedRate;
-
- if (simulationMode == 0) // we don't yet simulate any T codes
- {
- const Tool * const oldTool = reprap.GetCurrentTool();
- // If old and new are the same we no longer follow the sequence. User can deselect and then reselect the tool if he wants the macros run.
- if (oldTool->Number() != newToolNumber)
- {
- gb.SetState(GCodeState::toolChange1);
- if (oldTool != nullptr && AllAxesAreHomed())
- {
- scratchString.printf("tfree%d.g", oldTool->Number());
- DoFileMacro(gb, scratchString.Pointer(), false);
- }
- return true; // proceeding with state machine, so don't unlock or send a reply
- }
- }
-
- // If we get here, we have finished
- UnlockAll(gb);
- HandleReply(gb, false, "");
- return true;
-}
-
-// Return the amount of filament extruded
-float GCodes::GetRawExtruderPosition(size_t extruder) const
-{
- return (extruder < numExtruders) ? lastRawExtruderPosition[extruder] : 0.0;
-}
-
-float GCodes::GetRawExtruderTotalByDrive(size_t extruder) const
-{
- return (extruder < numExtruders) ? rawExtruderTotalByDrive[extruder] : 0.0;
-}
-
-// Cancel the current SD card print.
-// This is called from Pid.cpp when there is a heater fault, and from elsewhere in this module.
-void GCodes::CancelPrint()
-{
- moveAvailable = false;
- isPaused = false;
-
- fileGCode->Init();
- FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState;
- if (fileBeingPrinted.IsLive())
- {
- fileBeingPrinted.Close();
- }
-
- reprap.GetPrintMonitor()->StoppedPrint();
-}
-
-// Return true if all the heaters for the specified tool are at their set temperatures
-bool GCodes::ToolHeatersAtSetTemperatures(const Tool *tool, bool waitWhenCooling) const
-{
- if (tool != NULL)
- {
- for (size_t i = 0; i < tool->HeaterCount(); ++i)
- {
- if (!reprap.GetHeat()->HeaterAtSetTemperature(tool->Heater(i), waitWhenCooling))
- {
- return false;
- }
- }
- }
- return true;
-}
-
-// Set the current position
-void GCodes::SetPositions(float positionNow[DRIVES])
-{
- // Transform the position so that e.g. if the user does G92 Z0,
- // the position we report (which gets inverse-transformed) really is Z=0 afterwards
- reprap.GetMove()->Transform(positionNow, reprap.GetCurrentXAxes());
- reprap.GetMove()->SetLiveCoordinates(positionNow);
- reprap.GetMove()->SetPositions(positionNow);
-}
-
-bool GCodes::IsPaused() const
-{
- return isPaused && !IsPausing() && !IsResuming();
-}
-
-bool GCodes::IsPausing() const
-{
- const GCodeState topState = fileGCode->OriginalMachineState().state;
- return topState == GCodeState::pausing1 || topState == GCodeState::pausing2;
-}
-
-bool GCodes::IsResuming() const
-{
- const GCodeState topState = fileGCode->OriginalMachineState().state;
- return topState == GCodeState::resuming1 || topState == GCodeState::resuming2 || topState == GCodeState::resuming3;
-}
-
-bool GCodes::IsRunning() const
-{
- return !IsPaused() && !IsPausing() && !IsResuming();
-}
-
-const char *GCodes::TranslateEndStopResult(EndStopHit es)
-{
- switch (es)
- {
- case EndStopHit::lowHit:
- return "at min stop";
-
- case EndStopHit::highHit:
- return "at max stop";
-
- case EndStopHit::lowNear:
- return "near min stop";
-
- case EndStopHit::noStop:
- default:
- return "not stopped";
- }
-}
-
-// Append a list of trigger endstops to a message
-void GCodes::ListTriggers(StringRef reply, TriggerMask mask)
-{
- if (mask == 0)
- {
- reply.cat("(none)");
- }
- else
- {
- bool printed = false;
- for (unsigned int i = 0; i < DRIVES; ++i)
- {
- if ((mask & (1u << i)) != 0)
- {
- if (printed)
- {
- reply.cat(' ');
- }
- if (i < numAxes)
- {
- reply.cat(axisLetters[i]);
- }
- else
- {
- reply.catf("E%d", i - numAxes);
- }
- printed = true;
- }
- }
- }
-}
-
-// M38 (SHA1 hash of a file) implementation:
-bool GCodes::StartHash(const char* filename)
-{
- // Get a FileStore object
- fileBeingHashed = platform->GetFileStore(FS_PREFIX, filename, false);
- if (fileBeingHashed == nullptr)
- {
- return false;
- }
-
- // Start hashing
- SHA1Reset(&hash);
- return true;
-}
-
-bool GCodes::AdvanceHash(StringRef &reply)
-{
- // Read and process some more data from the file
- uint32_t buf32[(FILE_BUFFER_SIZE + 3) / 4];
- char *buffer = reinterpret_cast<char *>(buf32);
-
- int bytesRead = fileBeingHashed->Read(buffer, FILE_BUFFER_SIZE);
- if (bytesRead != -1)
- {
- SHA1Input(&hash, reinterpret_cast<const uint8_t *>(buffer), bytesRead);
-
- if (bytesRead != FILE_BUFFER_SIZE)
- {
- // Calculate and report the final result
- SHA1Result(&hash);
- for(size_t i = 0; i < 5; i++)
- {
- reply.catf("%x", hash.Message_Digest[i]);
- }
-
- // Clean up again
- fileBeingHashed->Close();
- fileBeingHashed = nullptr;
- return true;
- }
- return false;
- }
-
- // Something went wrong, we cannot read any more from the file
- fileBeingHashed->Close();
- fileBeingHashed = nullptr;
- return true;
-}
-
-bool GCodes::AllAxesAreHomed() const
-{
- const uint32_t allAxes = (1u << numAxes) - 1;
- return (axesHomed & allAxes) == allAxes;
-}
-
-void GCodes::SetAllAxesNotHomed()
-{
- axesHomed = 0;
-}
-
-// Resource locking/unlocking
-
-// Lock the resource, returning true if success
-bool GCodes::LockResource(const GCodeBuffer& gb, Resource r)
-{
- if (resourceOwners[r] == &gb)
- {
- return true;
- }
- if (resourceOwners[r] == nullptr)
- {
- resourceOwners[r] = &gb;
- gb.MachineState().lockedResources |= (1 << r);
- return true;
- }
- return false;
-}
-
-bool GCodes::LockHeater(const GCodeBuffer& gb, int heater)
-{
- if (heater >= 0 && heater < HEATERS)
- {
- return LockResource(gb, HeaterResourceBase + heater);
- }
- return true;
-}
-
-bool GCodes::LockFan(const GCodeBuffer& gb, int fan)
-{
- if (fan >= 0 && fan < (int)NUM_FANS)
- {
- return LockResource(gb, FanResourceBase + fan);
- }
- return true;
-}
-
-// Lock the unshareable parts of the file system
-bool GCodes::LockFileSystem(const GCodeBuffer &gb)
-{
- return LockResource(gb, FileSystemResource);
-}
-
-// Lock movement
-bool GCodes::LockMovement(const GCodeBuffer& gb)
-{
- return LockResource(gb, MoveResource);
-}
-
-// Lock movement and wait for pending moves to finish
-bool GCodes::LockMovementAndWaitForStandstill(const GCodeBuffer& gb)
-{
- bool b = LockMovement(gb);
- if (b)
- {
- b = AllMovesAreFinishedAndMoveBufferIsLoaded();
- }
- return b;
-}
-
-// Release all locks, except those that were owned when the current macro was started
-void GCodes::UnlockAll(const GCodeBuffer& gb)
-{
- const GCodeMachineState * const mc = gb.MachineState().previous;
- const uint32_t resourcesToKeep = (mc == nullptr) ? 0 : mc->lockedResources;
- for (size_t i = 0; i < NumResources; ++i)
- {
- if (resourceOwners[i] == &gb && ((1 << i) & resourcesToKeep) == 0)
- {
- resourceOwners[i] = nullptr;
- gb.MachineState().lockedResources &= ~(1 << i);
- }
- }
-}
-
-// Convert an array of longs to a bit map
-/*static*/ uint32_t GCodes::LongArrayToBitMap(const long *arr, size_t numEntries)
-{
- uint32_t res = 0;
- for (size_t i = 0; i < numEntries; ++i)
- {
- const long f = arr[i];
- if (f >= 0 && f < 32)
- {
- res |= 1u << (unsigned int)f;
- }
- }
- return res;
-}
-
-// End
diff --git a/src/GCodes/GCodes.h b/src/GCodes/GCodes.h
index c6b6b476..eee3cd72 100644
--- a/src/GCodes/GCodes.h
+++ b/src/GCodes/GCodes.h
@@ -33,9 +33,6 @@ typedef uint16_t EndstopChecks; // must be large enough to hold a bitmap o
const EndstopChecks ZProbeActive = 1 << 15; // must be distinct from 1 << (any drive number)
const EndstopChecks LogProbeChanges = 1 << 14; // must be distinct from 1 << (any drive number)
-const float minutesToSeconds = 60.0;
-const float secondsToMinutes = 1.0/minutesToSeconds;
-
typedef uint16_t TriggerMask;
struct Trigger
@@ -66,6 +63,7 @@ public:
struct RawMove
{
float coords[DRIVES]; // new positions for the axes, amount of movement for the extruders
+ float initialCoords[MAX_AXES]; // the initial positions of the axes
float feedRate; // feed rate of this move
FilePosition filePos; // offset in the file being printed that this move was read from
uint32_t xAxes; // axes that X is mapped to
@@ -156,7 +154,6 @@ private:
void StartNextGCode(GCodeBuffer& gb, StringRef& reply); // Fetch a new or old GCode and process it
void DoFilePrint(GCodeBuffer& gb, StringRef& reply); // Get G Codes from a file and print them
- bool AllMovesAreFinishedAndMoveBufferIsLoaded(); // Wait for move queue to exhaust and the current position is loaded
bool DoFileMacro(GCodeBuffer& gb, const char* fileName, bool reportMissing = true); // Run a GCode macro in a file, optionally report error if not found
bool DoCannedCycleMove(GCodeBuffer& gb, EndstopChecks ce); // Do a move from an internally programmed canned cycle
void FileMacroCyclesReturn(GCodeBuffer& gb); // End a macro
@@ -176,7 +173,7 @@ private:
bool SetPrintZProbe(GCodeBuffer& gb, StringRef& reply); // Either return the probe value, or set its threshold
bool SetOrReportOffsets(GCodeBuffer& gb, StringRef& reply); // Deal with a G10
bool SetPositions(GCodeBuffer& gb); // Deal with a G92
- bool LoadMoveBufferFromGCode(GCodeBuffer& gb, int moveType); // Set up a move for the Move class
+ unsigned int LoadMoveBufferFromGCode(GCodeBuffer& gb, int moveType); // Set up a move for the Move class
bool NoHome() const; // Are we homing and not finished?
bool Push(GCodeBuffer& gb); // Push feedrate etc on the stack
void Pop(GCodeBuffer& gb); // Pop feedrate etc
@@ -185,17 +182,18 @@ private:
void SetMACAddress(GCodeBuffer& gb); // Deals with an M540
void HandleReply(GCodeBuffer& gb, bool error, const char *reply); // Handle G-Code replies
void HandleReply(GCodeBuffer& gb, bool error, OutputBuffer *reply);
- bool OpenFileToWrite(GCodeBuffer& gb, const char* directory, // Start saving GCodes in a file
- const char* fileName);
+ bool OpenFileToWrite(GCodeBuffer& gb, const char* directory, const char* fileName); // Start saving GCodes in a file
void WriteGCodeToFile(GCodeBuffer& gb); // Write this GCode into a file
bool SendConfigToLine(); // Deal with M503
void WriteHTMLToFile(GCodeBuffer& gb, char b); // Save an HTML file (usually to upload a new web interface)
bool OffsetAxes(GCodeBuffer& gb); // Set offsets - deprecated, use G10
- void SetPidParameters(GCodeBuffer& gb, int heater, StringRef& reply); // Set the P/I/D parameters for a heater
- void SetHeaterParameters(GCodeBuffer& gb, StringRef& reply); // Set the thermistor and ADC parameters for a heater
+ void SetPidParameters(GCodeBuffer& gb, int heater, StringRef& reply); // Set the P/I/D parameters for a heater
+ void SetHeaterParameters(GCodeBuffer& gb, StringRef& reply); // Set the thermistor and ADC parameters for a heater
void ManageTool(GCodeBuffer& gb, StringRef& reply); // Create a new tool definition
void SetToolHeaters(Tool *tool, float temperature); // Set all a tool's heaters to the temperature. For M104...
- bool ToolHeatersAtSetTemperatures(const Tool *tool, bool waitWhenCooling) const; // Wait for the heaters associated with the specified tool to reach their set temperatures
+ bool ToolHeatersAtSetTemperatures(const Tool *tool, bool waitWhenCooling) const; // Wait for the heaters associated with the specified tool to reach their set temperatures
+ void StartToolChange(GCodeBuffer& gb, bool inM109); // Begin the tool change sequence
+
void SetAllAxesNotHomed(); // Flag all axes as not homed
void SetPositions(float positionNow[DRIVES]); // Set the current position to be this
const char *TranslateEndStopResult(EndStopHit es); // Translate end stop result to text
@@ -204,11 +202,15 @@ private:
void ListTriggers(StringRef reply, TriggerMask mask); // Append a list of trigger endstops to a message
void CheckTriggers(); // Check for and execute triggers
void DoEmergencyStop(); // Execute an emergency stop
- void DoPause(GCodeBuffer& gb); // Pause the print
+
+ void DoPause(GCodeBuffer& gb) // Pause the print
+ pre(resourceOwners[movementResource] = &gb);
+
void SetMappedFanSpeed(); // Set the speeds of fans mapped for the current tool
bool DefineGrid(GCodeBuffer& gb, StringRef &reply); // Define the probing grid, returning true if error
bool ProbeGrid(GCodeBuffer& gb, StringRef& reply); // Start probing the grid, returning true if we didn't because of an error
+ bool SaveHeightMapToFile(StringRef& reply) const; // Save the height map to file
static uint32_t LongArrayToBitMap(const long *arr, size_t numEntries); // Convert an array of longs to a bit map
@@ -230,9 +232,8 @@ private:
bool active; // Live and running?
bool isPaused; // true if the print has been paused
bool dwellWaiting; // We are in a dwell
- bool moveAvailable; // Have we seen a move G Code and set it up?
+ unsigned int segmentsLeft; // The number of segments left to do in the current move, or 0 if no move available
float dwellTime; // How long a pause for a dwell (seconds)?
- float feedRate; // The feed rate of the last G0/G1 command that had an F parameter
RawMove moveBuffer; // Move details to pass to Move class
RestorePoint simulationRestorePoint; // The position and feed rate when we started a simulation
RestorePoint pauseRestorePoint; // The position and feed rate when we paused the print
@@ -269,6 +270,7 @@ private:
// Z probe
float lastProbedZ; // the last height at which the Z probe stopped
+ uint32_t lastProbedTime; // time in milliseconds that the probe was last triggered
bool zProbesSet; // True if all Z probing is done and we can set the bed equation
volatile bool zProbeTriggered; // Set by the step ISR when a move is aborted because the Z probe is triggered
size_t gridXindex, gridYindex; // Which grid probe point is next
diff --git a/src/GCodes/GCodes1.cpp b/src/GCodes/GCodes1.cpp
new file mode 100644
index 00000000..e5622453
--- /dev/null
+++ b/src/GCodes/GCodes1.cpp
@@ -0,0 +1,3286 @@
+/****************************************************************************************************
+
+ RepRapFirmware - G Codes
+
+ This class interprets G Codes from one or more sources, and calls the functions in Move, Heat etc
+ that drive the machine to do what the G Codes command.
+
+ Most of the functions in here are designed not to wait, and they return a boolean. When you want them to do
+ something, you call them. If they return false, the machine can't do what you want yet. So you go away
+ and do something else. Then you try again. If they return true, the thing you wanted done has been done.
+
+ -----------------------------------------------------------------------------------------------------
+
+ Version 0.1
+
+ 13 February 2013
+
+ Adrian Bowyer
+ RepRap Professional Ltd
+ http://reprappro.com
+
+ Licence: GPL
+
+ ****************************************************************************************************/
+
+#include "RepRapFirmware.h"
+
+#ifdef DUET_NG
+#include "FirmwareUpdater.h"
+#endif
+
+#define DEGREE_SYMBOL "\xC2\xB0" // degree-symbol encoding in UTF8
+
+const char GCodes::axisLetters[MAX_AXES] = { 'X', 'Y', 'Z', 'U', 'V', 'W' };
+
+const char* PAUSE_G = "pause.g";
+const char* HomingFileNames[MAX_AXES] = { "homex.g", "homey.g", "homez.g", "homeu.g", "homev.g", "homew.g" };
+const char* HOME_ALL_G = "homeall.g";
+const char* HOME_DELTA_G = "homedelta.g";
+const char* DefaultHeightMapFile = "heightmap.csv";
+
+const size_t gcodeReplyLength = 2048; // long enough to pass back a reasonable number of files in response to M20
+
+void GCodes::RestorePoint::Init()
+{
+ for (size_t i = 0; i < DRIVES; ++i)
+ {
+ moveCoords[i] = 0.0;
+ }
+ feedRate = DEFAULT_FEEDRATE/minutesToSeconds;
+}
+
+GCodes::GCodes(Platform* p, Webserver* w) :
+ platform(p), webserver(w), active(false), isFlashing(false),
+ fileBeingHashed(nullptr)
+{
+ httpGCode = new GCodeBuffer("http", HTTP_MESSAGE);
+ telnetGCode = new GCodeBuffer("telnet", TELNET_MESSAGE);
+ fileGCode = new GCodeBuffer("file", GENERIC_MESSAGE);
+ serialGCode = new GCodeBuffer("serial", HOST_MESSAGE);
+ auxGCode = new GCodeBuffer("aux", AUX_MESSAGE);
+ daemonGCode = new GCodeBuffer("daemon", GENERIC_MESSAGE);
+}
+
+void GCodes::Exit()
+{
+ platform->Message(HOST_MESSAGE, "GCodes class exited.\n");
+ active = false;
+}
+
+void GCodes::Init()
+{
+ Reset();
+ numAxes = MIN_AXES;
+ numExtruders = MaxExtruders;
+ distanceScale = 1.0;
+ rawExtruderTotal = 0.0;
+ for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
+ {
+ lastRawExtruderPosition[extruder] = 0.0;
+ rawExtruderTotalByDrive[extruder] = 0.0;
+ }
+ eofString = EOF_STRING;
+ eofStringCounter = 0;
+ eofStringLength = strlen(eofString);
+ offSetSet = false;
+ zProbesSet = false;
+ active = true;
+ longWait = platform->Time();
+ dwellTime = longWait;
+ limitAxes = true;
+ for(size_t axis = 0; axis < MAX_AXES; axis++)
+ {
+ axisScaleFactors[axis] = 1.0;
+ }
+ SetAllAxesNotHomed();
+ for (size_t i = 0; i < NUM_FANS; ++i)
+ {
+ pausedFanValues[i] = 0.0;
+ }
+ lastDefaultFanSpeed = 0.0;
+
+ retractLength = retractExtra = retractHop = 0.0;
+ retractSpeed = unRetractSpeed = 600.0;
+}
+
+// This is called from Init and when doing an emergency stop
+void GCodes::Reset()
+{
+ httpGCode->Init();
+ telnetGCode->Init();
+ fileGCode->Init();
+ serialGCode->Init();
+ auxGCode->Init();
+ auxGCode->SetCommsProperties(1); // by default, we require a checksum on the aux port
+ daemonGCode->Init();
+
+ nextGcodeSource = 0;
+
+ fileToPrint.Close();
+ fileBeingWritten = NULL;
+ dwellWaiting = false;
+ probeCount = 0;
+ cannedCycleMoveCount = 0;
+ cannedCycleMoveQueued = false;
+ speedFactor = 1.0 / minutesToSeconds; // default is just to convert from mm/minute to mm/second
+ for (size_t i = 0; i < MaxExtruders; ++i)
+ {
+ extrusionFactors[i] = 1.0;
+ }
+ for (size_t i = 0; i < DRIVES; ++i)
+ {
+ moveBuffer.coords[i] = 0.0;
+ }
+ moveBuffer.xAxes = DefaultXAxisMapping;
+
+ pauseRestorePoint.Init();
+ toolChangeRestorePoint.Init();
+
+ ClearMove();
+
+ for (size_t i = 0; i < MaxTriggers; ++i)
+ {
+ triggers[i].Init();
+ }
+ triggersPending = 0;
+
+ simulationMode = 0;
+ simulationTime = 0.0;
+ isPaused = false;
+ filePos = moveBuffer.filePos = noFilePosition;
+ lastEndstopStates = platform->GetAllEndstopStates();
+ firmwareUpdateModuleMap = 0;
+
+ cancelWait = isWaiting = false;
+
+ for (size_t i = 0; i < NumResources; ++i)
+ {
+ resourceOwners[i] = nullptr;
+ }
+
+ lastProbedTime = millis();
+}
+
+float GCodes::FractionOfFilePrinted() const
+{
+ const FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState;
+ return (fileBeingPrinted.IsLive()) ? fileBeingPrinted.FractionRead() : -1.0;
+}
+
+// Start running the config file
+// We use triggerCGode as the source to prevent any triggers being executed until we have finished
+bool GCodes::RunConfigFile(const char* fileName)
+{
+ return DoFileMacro(*daemonGCode, fileName, false);
+}
+
+// Are we still running the config file?
+bool GCodes::IsRunningConfigFile() const
+{
+ return daemonGCode->MachineState().fileState.IsLive();
+}
+
+void GCodes::Spin()
+{
+ if (!active)
+ {
+ return;
+ }
+
+ CheckTriggers();
+
+ // Get the GCodeBuffer that we want to work from
+ GCodeBuffer& gb = *(gcodeSources[nextGcodeSource]);
+
+ // Set up a buffer for the reply
+ char replyBuffer[gcodeReplyLength];
+ StringRef reply(replyBuffer, ARRAY_SIZE(replyBuffer));
+ reply.Clear();
+
+ if (gb.GetState() == GCodeState::normal)
+ {
+ StartNextGCode(gb, reply);
+ }
+ else
+ {
+ // Perform the next operation of the state machine for this gcode source
+ bool error = false;
+
+ switch (gb.GetState())
+ {
+ case GCodeState::waitingForMoveToComplete:
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ gb.SetState(GCodeState::normal);
+ }
+ break;
+
+ case GCodeState::homing:
+ if (toBeHomed == 0)
+ {
+ gb.SetState(GCodeState::normal);
+ }
+ else
+ {
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ // Leave the Z axis until all other axes are done
+ if ((toBeHomed & (1u << axis)) != 0 && (axis != Z_AXIS || toBeHomed == (1u << Z_AXIS)))
+ {
+ toBeHomed &= ~(1u << axis);
+ DoFileMacro(gb, HomingFileNames[axis]);
+ break;
+ }
+ }
+ }
+ break;
+
+ case GCodeState::setBed1:
+ reprap.GetMove()->SetIdentityTransform();
+ probeCount = 0;
+ gb.SetState(GCodeState::setBed2);
+ // no break
+
+ case GCodeState::setBed2:
+ {
+ int numProbePoints = reprap.GetMove()->NumberOfXYProbePoints();
+ if (DoSingleZProbeAtPoint(gb, probeCount, 0.0))
+ {
+ probeCount++;
+ if (probeCount >= numProbePoints)
+ {
+ zProbesSet = true;
+ reprap.GetMove()->FinishedBedProbing(0, reply);
+ gb.SetState(GCodeState::normal);
+ }
+ }
+ }
+ break;
+
+ case GCodeState::toolChange1: // Release the old tool (if any)
+ case GCodeState::m109ToolChange1: // Release the old tool (if any)
+ {
+ const Tool *oldTool = reprap.GetCurrentTool();
+ if (oldTool != NULL)
+ {
+ reprap.StandbyTool(oldTool->Number());
+ }
+ }
+ gb.AdvanceState();
+ if (reprap.GetTool(newToolNumber) != nullptr && AllAxesAreHomed())
+ {
+ scratchString.printf("tpre%d.g", newToolNumber);
+ DoFileMacro(gb, scratchString.Pointer(), false);
+ }
+ break;
+
+ case GCodeState::toolChange2: // Select the new tool (even if it doesn't exist - that just deselects all tools)
+ case GCodeState::m109ToolChange2: // Select the new tool (even if it doesn't exist - that just deselects all tools)
+ reprap.SelectTool(newToolNumber);
+ gb.AdvanceState();
+ if (reprap.GetTool(newToolNumber) != nullptr && AllAxesAreHomed())
+ {
+ scratchString.printf("tpost%d.g", newToolNumber);
+ DoFileMacro(gb, scratchString.Pointer(), false);
+ }
+ break;
+
+ case GCodeState::toolChangeComplete:
+ gb.SetState(GCodeState::normal);
+ break;
+
+ case GCodeState::m109ToolChangeComplete:
+ if (cancelWait || ToolHeatersAtSetTemperatures(reprap.GetCurrentTool(), gb.MachineState().waitWhileCooling))
+ {
+ cancelWait = isWaiting = false;
+ gb.SetState(GCodeState::normal);
+ }
+ // In Marlin emulation mode we should return some sort of (undocumented) message here every second...
+ isWaiting = true;
+ break;
+
+ case GCodeState::pausing1:
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ gb.SetState(GCodeState::pausing2);
+ DoFileMacro(gb, PAUSE_G);
+ }
+ break;
+
+ case GCodeState::pausing2:
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ reply.copy("Printing paused");
+ gb.SetState(GCodeState::normal);
+ }
+ break;
+
+ case GCodeState::resuming1:
+ case GCodeState::resuming2:
+ // Here when we have just finished running the resume macro file.
+ // Move the head back to the paused location
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ float currentZ = moveBuffer.coords[Z_AXIS];
+ for (size_t drive = 0; drive < numAxes; ++drive)
+ {
+ moveBuffer.coords[drive] = pauseRestorePoint.moveCoords[drive];
+ }
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ moveBuffer.coords[drive] = 0.0;
+ }
+ moveBuffer.feedRate = DEFAULT_FEEDRATE/minutesToSeconds; // ask for a good feed rate, we may have paused during a slow move
+ moveBuffer.moveType = 0;
+ moveBuffer.endStopsToCheck = 0;
+ moveBuffer.usePressureAdvance = false;
+ moveBuffer.filePos = noFilePosition;
+ if (gb.GetState() == GCodeState::resuming1 && currentZ > pauseRestorePoint.moveCoords[Z_AXIS])
+ {
+ // First move the head to the correct XY point, then move it down in a separate move
+ moveBuffer.coords[Z_AXIS] = currentZ;
+ gb.SetState(GCodeState::resuming2);
+ }
+ else
+ {
+ // Just move to the saved position in one go
+ gb.SetState(GCodeState::resuming3);
+ }
+ segmentsLeft = 1;
+ }
+ break;
+
+ case GCodeState::resuming3:
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ for (size_t i = 0; i < NUM_FANS; ++i)
+ {
+ platform->SetFanValue(i, pausedFanValues[i]);
+ }
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ lastRawExtruderPosition[drive - numAxes] = pauseRestorePoint.moveCoords[drive]; // reset the extruder position in case we are receiving absolute extruder moves
+ }
+ fileGCode->MachineState().feedrate = pauseRestorePoint.feedRate;
+ isPaused = false;
+ reply.copy("Printing resumed");
+ gb.SetState(GCodeState::normal);
+ }
+ break;
+
+ case GCodeState::flashing1:
+#ifdef DUET_NG
+ // Update additional modules before the main firmware
+ if (FirmwareUpdater::IsReady())
+ {
+ bool updating = false;
+ for (unsigned int module = 1; module < NumFirmwareUpdateModules; ++module)
+ {
+ if ((firmwareUpdateModuleMap & (1u << module)) != 0)
+ {
+ firmwareUpdateModuleMap &= ~(1u << module);
+ FirmwareUpdater::UpdateModule(module);
+ updating = true;
+ break;
+ }
+ }
+ if (!updating)
+ {
+ gb.SetState(GCodeState::flashing2);
+ }
+ }
+#else
+ gb.SetState(GCodeState::flashing2);
+#endif
+ break;
+
+ case GCodeState::flashing2:
+ if ((firmwareUpdateModuleMap & 1) != 0)
+ {
+ // Update main firmware
+ firmwareUpdateModuleMap = 0;
+ platform->UpdateFirmware();
+ // The above call does not return unless an error occurred
+ }
+ isFlashing = false;
+ gb.SetState(GCodeState::normal);
+ break;
+
+ case GCodeState::stopping: // MO after executing stop.g if present
+ case GCodeState::sleeping: // M1 after executing sleep.g if present
+ // Deselect the active tool and turn off all heaters, unless parameter Hn was used with n > 0
+ if (!gb.Seen('H') || gb.GetIValue() <= 0)
+ {
+ Tool* tool = reprap.GetCurrentTool();
+ if (tool != nullptr)
+ {
+ reprap.StandbyTool(tool->Number());
+ }
+ reprap.GetHeat()->SwitchOffAll();
+ }
+
+ // chrishamm 2014-18-10: Although RRP says M0 is supposed to turn off all drives and heaters,
+ // I think M1 is sufficient for this purpose. Leave M0 for a normal reset.
+ if (gb.GetState() == GCodeState::sleeping)
+ {
+ DisableDrives();
+ }
+ else
+ {
+ platform->SetDriversIdle();
+ }
+ gb.SetState(GCodeState::normal);
+ break;
+
+ case GCodeState::gridProbing1: // ready to move to next grid probe point
+ {
+ // Move to the current probe point
+ const GridDefinition& grid = reprap.GetMove()->AccessBedProbeGrid().GetGrid();
+ const float x = grid.GetXCoordinate(gridXindex);
+ const float y = grid.GetYCoordinate(gridYindex);
+ if (grid.IsInRadius(x, y) && platform->IsAccessibleProbePoint(x, y))
+ {
+ moveBuffer.moveType = 0;
+ moveBuffer.endStopsToCheck = 0;
+ moveBuffer.usePressureAdvance = false;
+ moveBuffer.filePos = noFilePosition;
+ moveBuffer.coords[X_AXIS] = x - platform->GetZProbeParameters().xOffset;
+ moveBuffer.coords[Y_AXIS] = y - platform->GetZProbeParameters().yOffset;
+ moveBuffer.coords[Z_AXIS] = platform->GetZProbeDiveHeight();
+ moveBuffer.feedRate = platform->GetZProbeTravelSpeed();
+ moveBuffer.xAxes = 0;
+ segmentsLeft = 1;
+ gb.SetState(GCodeState::gridProbing2);
+ }
+ else
+ {
+ gb.SetState(GCodeState::gridProbing4);
+ }
+ }
+ break;
+
+ case GCodeState::gridProbing2: // ready to probe the current grid probe point
+ if (LockMovementAndWaitForStandstill(gb)
+ && millis() - lastProbedTime >= (uint32_t)(reprap.GetPlatform()->GetZProbeParameters().recoveryTime * SecondsToMillis)
+ )
+ {
+ // Probe the bed at the current XY coordinates
+ // Check for probe already triggered at start
+ if (reprap.GetPlatform()->GetZProbeResult() == EndStopHit::lowHit)
+ {
+ reply.copy("Z probe already triggered before probing move started");
+ error = true;
+ gb.SetState(GCodeState::normal);
+ break;
+ }
+
+ zProbeTriggered = false;
+ moveBuffer.moveType = 0;
+ moveBuffer.endStopsToCheck = ZProbeActive;
+ moveBuffer.usePressureAdvance = false;
+ moveBuffer.filePos = noFilePosition;
+ moveBuffer.coords[Z_AXIS] = -platform->GetZProbeDiveHeight();
+ moveBuffer.feedRate = platform->GetZProbeParameters().probeSpeed;
+ moveBuffer.xAxes = 0;
+ segmentsLeft = 1;
+ gb.SetState(GCodeState::gridProbing3);
+ }
+ break;
+
+ case GCodeState::gridProbing3: // ready to lift the probe after probing the current grid probe point
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ if (!zProbeTriggered)
+ {
+ reply.copy("Z probe was not triggered during probing move");
+ error = true;
+ gb.SetState(GCodeState::normal);
+ break;
+ }
+
+ lastProbedTime = millis();
+ const float heightError = moveBuffer.coords[Z_AXIS] - platform->ZProbeStopHeight();
+ reprap.GetMove()->AccessBedProbeGrid().SetGridHeight(gridXindex, gridYindex, heightError);
+ ++numPointsProbed;
+ heightSum += (double)heightError;
+ heightSquaredSum += (double)heightError * (double)heightError;
+
+ // Move back up to the dive height
+ moveBuffer.moveType = 0;
+ moveBuffer.endStopsToCheck = 0;
+ moveBuffer.usePressureAdvance = false;
+ moveBuffer.filePos = noFilePosition;
+ moveBuffer.coords[Z_AXIS] = platform->GetZProbeDiveHeight();
+ moveBuffer.feedRate = platform->GetZProbeTravelSpeed();
+ moveBuffer.xAxes = 0;
+ segmentsLeft = 1;
+ gb.SetState(GCodeState::gridProbing4);
+ }
+ break;
+
+ case GCodeState::gridProbing4: // ready to compute the next probe point
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ const GridDefinition& grid = reprap.GetMove()->AccessBedProbeGrid().GetGrid();
+ if (gridYindex & 1)
+ {
+ // Odd row, so decreasing X
+ if (gridXindex == 0)
+ {
+ ++gridYindex;
+ }
+ else
+ {
+ --gridXindex;
+ }
+ }
+ else
+ {
+ // Even row, so increasing X
+ if (gridXindex + 1 == grid.NumXpoints())
+ {
+ ++gridYindex;
+ }
+ else
+ {
+ ++gridXindex;
+ }
+ }
+ if (gridYindex == grid.NumYpoints())
+ {
+ // Finished probing the grid
+ if (numPointsProbed >= 4)
+ {
+ error = SaveHeightMapToFile(reply);
+ const double mean = heightSum/numPointsProbed;
+ const double deviation = sqrt(((heightSquaredSum * numPointsProbed) - (heightSum * heightSum)))/numPointsProbed;
+ reply.catf(" - %u points probed, mean error %.2f, deviation %.2f", numPointsProbed, mean, deviation);
+ reprap.GetMove()->UseHeightMap(true);
+ }
+ else
+ {
+ reply.copy("Too few points probed");
+ error = true;
+ }
+ gb.SetState(GCodeState::normal);
+ }
+ else
+ {
+ gb.SetState(GCodeState::gridProbing1);
+ }
+ }
+ break;
+
+ default: // should not happen
+ platform->Message(GENERIC_MESSAGE, "Error: undefined GCodeState\n");
+ gb.SetState(GCodeState::normal);
+ break;
+ }
+
+ if (gb.GetState() == GCodeState::normal)
+ {
+ // We completed a command, so unlock resources and tell the host about it
+ UnlockAll(gb);
+ HandleReply(gb, error, reply.Pointer());
+ }
+ }
+
+ // Move on to the next gcode source ready for next time
+ ++nextGcodeSource;
+ if (nextGcodeSource == ARRAY_SIZE(gcodeSources))
+ {
+ nextGcodeSource = 0;
+ }
+
+ platform->ClassReport(longWait);
+}
+
+// Start a new gcode, or continue to execute one that has already been started:
+void GCodes::StartNextGCode(GCodeBuffer& gb, StringRef& reply)
+{
+ if (isPaused && &gb == fileGCode)
+ {
+ // We are paused, so don't process any more gcodes from the file being printed.
+ // There is a potential issue here if fileGCode holds any locks, so unlock everything.
+ UnlockAll(gb);
+ }
+ else if (gb.IsReady() || gb.IsExecuting())
+ {
+ gb.SetFinished(ActOnCode(gb, reply));
+ }
+ else if (gb.MachineState().fileState.IsLive())
+ {
+ DoFilePrint(gb, reply);
+ }
+ else if (&gb == httpGCode)
+ {
+ // Webserver
+ for (unsigned int i = 0; i < 16 && webserver->GCodeAvailable(WebSource::HTTP); ++i)
+ {
+ const char b = webserver->ReadGCode(WebSource::HTTP);
+ if (gb.Put(b))
+ {
+ // We have a complete gcode
+ if (gb.WritingFileDirectory() != nullptr)
+ {
+ WriteGCodeToFile(gb);
+ gb.SetFinished(true);
+ }
+ else
+ {
+ gb.SetFinished(ActOnCode(gb, reply));
+ }
+ break;
+ }
+ }
+ }
+ else if (&gb == telnetGCode)
+ {
+ // Telnet
+ for (unsigned int i = 0; i < GCODE_LENGTH && webserver->GCodeAvailable(WebSource::Telnet); ++i)
+ {
+ char b = webserver->ReadGCode(WebSource::Telnet);
+ if (gb.Put(b))
+ {
+ gb.SetFinished(ActOnCode(gb, reply));
+ break;
+ }
+ }
+ }
+ else if (&gb == serialGCode)
+ {
+ // USB interface
+ for (unsigned int i = 0; i < 16 && platform->GCodeAvailable(SerialSource::USB); ++i)
+ {
+ const char b = platform->ReadFromSource(SerialSource::USB);
+ // Check the special case of uploading the reprap.htm file
+ if (gb.WritingFileDirectory() == platform->GetWebDir())
+ {
+ WriteHTMLToFile(gb, b);
+ }
+ else if (gb.Put(b)) // add char to buffer and test whether the gcode is complete
+ {
+ // We have a complete gcode
+ if (gb.WritingFileDirectory() != nullptr)
+ {
+ WriteGCodeToFile(gb);
+ gb.SetFinished(true);
+ }
+ else
+ {
+ gb.SetFinished(ActOnCode(gb, reply));
+ }
+ break;
+ }
+ }
+ }
+ else if (&gb == auxGCode)
+ {
+ // Aux serial port (typically PanelDue)
+ for (unsigned int i = 0; i < 16 && platform->GCodeAvailable(SerialSource::AUX); ++i)
+ {
+ char b = platform->ReadFromSource(SerialSource::AUX);
+ if (gb.Put(b)) // add char to buffer and test whether the gcode is complete
+ {
+ platform->SetAuxDetected();
+ gb.SetFinished(ActOnCode(gb, reply));
+ break;
+ }
+ }
+ }
+}
+
+void GCodes::DoFilePrint(GCodeBuffer& gb, StringRef& reply)
+{
+ FileData& fd = gb.MachineState().fileState;
+ for (int i = 0; i < 50 && fd.IsLive(); ++i)
+ {
+ char b;
+ if (fd.Read(b))
+ {
+ if (gb.StartingNewCode() && &gb == fileGCode && gb.MachineState().previous == nullptr)
+ {
+ filePos = fd.GetPosition() - 1;
+ //debugPrintf("Set file pos %u\n", filePos);
+ }
+ if (gb.Put(b))
+ {
+ gb.SetFinished(ActOnCode(gb, reply));
+ return;
+ }
+ }
+ else
+ {
+ // We have reached the end of the file. Check for the last line of gcode not ending in newline.
+ if (!gb.StartingNewCode()) // if there is something in the buffer
+ {
+ if (gb.Put('\n')) // in case there wasn't a newline ending the file
+ {
+ gb.SetFinished(ActOnCode(gb, reply));
+ return;
+ }
+ }
+
+ gb.Init(); // mark buffer as empty
+
+ // Don't close the file until all moves have been completed, in case the print gets paused.
+ // Also, this keeps the state as 'Printing' until the print really has finished.
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ fd.Close();
+ if (gb.MachineState().previous == nullptr)
+ {
+ // Finished printing SD card file
+ reprap.GetPrintMonitor()->StoppedPrint();
+ if (platform->Emulating() == marlin)
+ {
+ // Pronterface expects a "Done printing" message
+ HandleReply(gb, false, "Done printing file");
+ }
+ }
+ else
+ {
+ // Finished a macro
+ Pop(gb);
+ gb.Init();
+ if (gb.GetState() == GCodeState::normal)
+ {
+ UnlockAll(gb);
+ HandleReply(gb, false, "");
+ }
+ }
+ }
+ return;
+ }
+ }
+}
+
+// Check for and execute triggers
+void GCodes::CheckTriggers()
+{
+ // Check for endstop state changes that activate new triggers
+ const TriggerMask oldEndstopStates = lastEndstopStates;
+ lastEndstopStates = platform->GetAllEndstopStates();
+ const TriggerMask risen = lastEndstopStates & ~oldEndstopStates,
+ fallen = ~lastEndstopStates & oldEndstopStates;
+ unsigned int lowestTriggerPending = MaxTriggers;
+ for (unsigned int triggerNumber = 0; triggerNumber < MaxTriggers; ++triggerNumber)
+ {
+ const Trigger& ct = triggers[triggerNumber];
+ if ( ((ct.rising & risen) != 0 || (ct.falling & fallen) != 0)
+ && (ct.condition == 0 || (ct.condition == 1 && reprap.GetPrintMonitor()->IsPrinting()))
+ )
+ {
+ triggersPending |= (1u << triggerNumber);
+ }
+ if (triggerNumber < lowestTriggerPending && (triggersPending & (1u << triggerNumber)) != 0)
+ {
+ lowestTriggerPending = triggerNumber;
+ }
+ }
+
+ // If any triggers are pending, activate the one with the lowest number
+ if (lowestTriggerPending == 0)
+ {
+ triggersPending &= ~(1u << lowestTriggerPending); // clear the trigger
+ DoEmergencyStop();
+ }
+ else if (lowestTriggerPending < MaxTriggers // if a trigger is pending
+ && !daemonGCode->MachineState().fileState.IsLive()
+ && daemonGCode->GetState() == GCodeState::normal // and we are not already executing a trigger or config.g
+ )
+ {
+ if (lowestTriggerPending == 1)
+ {
+ if (isPaused || !reprap.GetPrintMonitor()->IsPrinting())
+ {
+ triggersPending &= ~(1u << lowestTriggerPending); // ignore a pause trigger if we are already paused
+ }
+ else if (LockMovement(*daemonGCode)) // need to lock movement before executing the pause macro
+ {
+ triggersPending &= ~(1u << lowestTriggerPending); // clear the trigger
+ DoPause(*daemonGCode);
+ }
+ }
+ else
+ {
+ triggersPending &= ~(1u << lowestTriggerPending); // clear the trigger
+ char buffer[25];
+ StringRef filename(buffer, ARRAY_SIZE(buffer));
+ filename.printf(SYS_DIR "trigger%u.g", lowestTriggerPending);
+ DoFileMacro(*daemonGCode, filename.Pointer(), true);
+ }
+ }
+}
+
+// Execute an emergency stop
+void GCodes::DoEmergencyStop()
+{
+ reprap.EmergencyStop();
+ Reset();
+ platform->Message(GENERIC_MESSAGE, "Emergency Stop! Reset the controller to continue.");
+}
+
+// Pause the print. Before calling this, check that we are doing a file print that isn't already paused and get the movement lock.
+void GCodes::DoPause(GCodeBuffer& gb)
+{
+ if (&gb == fileGCode)
+ {
+ // Pausing a file print because of a command in the file itself
+ for (size_t drive = 0; drive < numAxes; ++drive)
+ {
+ pauseRestorePoint.moveCoords[drive] = moveBuffer.coords[drive];
+ }
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ pauseRestorePoint.moveCoords[drive] = lastRawExtruderPosition[drive - numAxes]; // get current extruder positions into pausedMoveBuffer
+ }
+ pauseRestorePoint.feedRate = gb.MachineState().feedrate;
+ }
+ else
+ {
+ // Pausing a file print via another input source
+ pauseRestorePoint.feedRate = fileGCode->MachineState().feedrate; // the call to PausePrint may or may not change this
+ FilePosition fPos = reprap.GetMove()->PausePrint(pauseRestorePoint.moveCoords, pauseRestorePoint.feedRate, reprap.GetCurrentXAxes());
+ // tell Move we wish to pause the current print
+ FileData& fdata = fileGCode->MachineState().fileState;
+ if (fPos != noFilePosition && fdata.IsLive())
+ {
+ fdata.Seek(fPos); // replay the abandoned instructions if/when we resume
+ }
+ if (segmentsLeft != 0)
+ {
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ pauseRestorePoint.moveCoords[drive] += moveBuffer.coords[drive]; // add on the extrusion in the move not yet taken
+ }
+ ClearMove();
+ }
+
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ pauseRestorePoint.moveCoords[drive] = lastRawExtruderPosition[drive - numAxes] - pauseRestorePoint.moveCoords[drive];
+ }
+
+ //TODO record the virtual extruder positions of mixing tools too. But that's very hard to do unless we store it in the move.
+
+ if (reprap.Debug(moduleGcodes))
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Paused print, file offset=%u\n", fPos);
+ }
+ }
+
+ for (size_t i = 0; i < NUM_FANS; ++i)
+ {
+ pausedFanValues[i] = platform->GetFanValue(i);
+ }
+ gb.SetState(GCodeState::pausing1);
+ isPaused = true;
+}
+
+void GCodes::Diagnostics(MessageType mtype)
+{
+ platform->Message(mtype, "=== GCodes ===\n");
+ platform->MessageF(mtype, "Segments left: %u\n", segmentsLeft);
+ platform->MessageF(mtype, "Stack records: %u allocated, %u in use\n", GCodeMachineState::GetNumAllocated(), GCodeMachineState::GetNumInUse());
+ const GCodeBuffer *movementOwner = resourceOwners[MoveResource];
+ platform->MessageF(mtype, "Movement lock held by %s\n", (movementOwner == nullptr) ? "null" : movementOwner->GetIdentity());
+
+ for (size_t i = 0; i < ARRAY_SIZE(gcodeSources); ++i)
+ {
+ gcodeSources[i]->Diagnostics(mtype);
+ }
+}
+
+// Lock movement and wait for pending moves to finish.
+// As a side-effect it loads moveBuffer with the last position and feedrate for you.
+bool GCodes::LockMovementAndWaitForStandstill(const GCodeBuffer& gb)
+{
+ // Lock movement to stop another source adding moves to the queue
+ if (!LockMovement(gb))
+ {
+ return false;
+ }
+
+ // Last one gone?
+ if (segmentsLeft != 0)
+ {
+ return false;
+ }
+
+ // Wait for all the queued moves to stop so we get the actual last position
+ if (!reprap.GetMove()->AllMovesAreFinished())
+ {
+ return false;
+ }
+
+ // Allow movement again
+ reprap.GetMove()->ResumeMoving();
+
+ // Get the current positions. These may not be the same as the ones we remembered from last time if we just did a special move.
+ reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes());
+ memcpy(moveBuffer.initialCoords, moveBuffer.coords, numAxes * sizeof(moveBuffer.initialCoords[0]));
+ return true;
+}
+
+// Save (some of) the state of the machine for recovery in the future.
+bool GCodes::Push(GCodeBuffer& gb)
+{
+ bool ok = gb.PushState();
+ if (!ok)
+ {
+ platform->Message(GENERIC_MESSAGE, "Push(): stack overflow!\n");
+ }
+ return ok;
+}
+
+// Recover a saved state
+void GCodes::Pop(GCodeBuffer& gb)
+{
+ if (!gb.PopState())
+ {
+ platform->Message(GENERIC_MESSAGE, "Pop(): stack underflow!\n");
+ }
+}
+
+// Move expects all axis movements to be absolute, and all extruder drive moves to be relative. This function serves that.
+// 'moveType' is the S parameter in the G0 or G1 command, or -1 if we are doing G92.
+// For regular (type 0) moves, we apply limits and do X axis mapping.
+// Returns the number of segments if we have a legal move (or 1 if we are doing G92), or zero if this gcode should be discarded
+unsigned int GCodes::LoadMoveBufferFromGCode(GCodeBuffer& gb, int moveType)
+{
+ // Zero every extruder drive as some drives may not be changed
+ for (size_t drive = numAxes; drive < DRIVES; drive++)
+ {
+ moveBuffer.coords[drive] = 0.0;
+ }
+
+ // Deal with feed rate
+ if (gb.Seen(feedrateLetter))
+ {
+ gb.MachineState().feedrate = gb.GetFValue() * distanceScale * speedFactor;
+ }
+ moveBuffer.feedRate = gb.MachineState().feedrate;
+
+ // First do extrusion, and check, if we are extruding, that we have a tool to extrude with
+ Tool* tool = reprap.GetCurrentTool();
+ if (gb.Seen(extrudeLetter))
+ {
+ if (tool == nullptr)
+ {
+ platform->Message(GENERIC_MESSAGE, "Attempting to extrude with no tool selected.\n");
+ return 0;
+ }
+ size_t eMoveCount = tool->DriveCount();
+ if (eMoveCount > 0)
+ {
+ // Set the drive values for this tool.
+ // chrishamm-2014-10-03: Do NOT check extruder temperatures here, because we may be executing queued codes like M116
+ if (tool->GetMixing())
+ {
+ const float moveArg = gb.GetFValue() * distanceScale;
+ if (moveType == -1) // if doing G92
+ {
+ tool->virtualExtruderPosition = moveArg;
+ }
+ else
+ {
+ const float requestedExtrusionAmount = (gb.MachineState().drivesRelative)
+ ? moveArg
+ : moveArg - tool->virtualExtruderPosition;
+ for (size_t eDrive = 0; eDrive < eMoveCount; eDrive++)
+ {
+ const int drive = tool->Drive(eDrive);
+ const float extrusionAmount = requestedExtrusionAmount * tool->GetMix()[eDrive];
+ lastRawExtruderPosition[drive] += extrusionAmount;
+ rawExtruderTotalByDrive[drive] += extrusionAmount;
+ rawExtruderTotal += extrusionAmount;
+ moveBuffer.coords[drive + numAxes] = extrusionAmount * extrusionFactors[drive];
+ }
+ }
+ }
+ else
+ {
+ float eMovement[MaxExtruders];
+ size_t mc = eMoveCount;
+ gb.GetFloatArray(eMovement, mc, false);
+ if (eMoveCount != mc)
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Wrong number of extruder drives for the selected tool: %s\n", gb.Buffer());
+ return 0;
+ }
+
+ for (size_t eDrive = 0; eDrive < eMoveCount; eDrive++)
+ {
+ const int drive = tool->Drive(eDrive);
+ const float moveArg = eMovement[eDrive] * distanceScale;
+ if (moveType == -1)
+ {
+ moveBuffer.coords[drive + numAxes] = moveArg;
+ lastRawExtruderPosition[drive] = moveArg;
+ }
+ else
+ {
+ const float extrusionAmount = (gb.MachineState().drivesRelative)
+ ? moveArg
+ : moveArg - lastRawExtruderPosition[drive];
+ lastRawExtruderPosition[drive] += extrusionAmount;
+ rawExtruderTotalByDrive[drive] += extrusionAmount;
+ rawExtruderTotal += extrusionAmount;
+ moveBuffer.coords[drive + numAxes] = extrusionAmount * extrusionFactors[drive];
+ }
+ }
+ }
+ }
+ }
+
+ // Now the movement axes
+ const Tool * const currentTool = reprap.GetCurrentTool();
+ unsigned int numSegments = 1;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ float moveArg = gb.GetFValue() * distanceScale * axisScaleFactors[axis];
+ if (moveType == -1) // if doing G92
+ {
+ SetAxisIsHomed(axis); // doing a G92 defines the absolute axis position
+ moveBuffer.coords[axis] = moveArg;
+ }
+ else if (axis == X_AXIS && moveType == 0 && currentTool != nullptr)
+ {
+ // Perform X axis mapping
+ const uint32_t xMap = currentTool->GetXAxisMap();
+ for (size_t mappedAxis = 0; mappedAxis < numAxes; ++mappedAxis)
+ {
+ if ((xMap & (1u << mappedAxis)) != 0)
+ {
+ float mappedMoveArg = moveArg;
+ if (gb.MachineState().axesRelative)
+ {
+ mappedMoveArg += moveBuffer.coords[mappedAxis];
+ }
+ else
+ {
+ mappedMoveArg -= currentTool->GetOffset()[mappedAxis]; // adjust requested position to compensate for tool offset
+ }
+ if (reprap.GetMove()->UsingHeightMap())
+ {
+ const unsigned int minSegments = reprap.GetMove()->AccessBedProbeGrid().GetMinimumSegments(fabs(mappedMoveArg - moveBuffer.coords[mappedAxis]));
+ if (minSegments > numSegments)
+ {
+ numSegments = minSegments;
+ }
+ }
+ moveBuffer.coords[mappedAxis] = mappedMoveArg;
+ }
+ }
+ }
+ else
+ {
+ if (gb.MachineState().axesRelative)
+ {
+ moveArg += moveBuffer.coords[axis];
+ }
+ else if (currentTool != nullptr && moveType == 0)
+ {
+ moveArg -= currentTool->GetOffset()[axis]; // adjust requested position to compensate for tool offset
+ }
+
+ if (axis < Z_AXIS && moveType == 0 && reprap.GetMove()->UsingHeightMap())
+ {
+ const unsigned int minSegments = reprap.GetMove()->AccessBedProbeGrid().GetMinimumSegments(fabs(moveArg - moveBuffer.coords[axis]));
+ if (minSegments > numSegments)
+ {
+ numSegments = minSegments;
+ }
+ }
+ moveBuffer.coords[axis] = moveArg;
+ }
+ }
+ }
+
+ // If doing a regular move and applying limits, limit all axes
+ if ( moveType == 0
+ && limitAxes
+#if SUPPORT_ROLAND
+ && !reprap.GetRoland()->Active()
+#endif
+ )
+ {
+ if (!reprap.GetMove()->IsDeltaMode())
+ {
+ // Cartesian or CoreXY printer, so limit those axes that have been homed
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (GetAxisIsHomed(axis))
+ {
+ float& f = moveBuffer.coords[axis];
+ if (f < platform->AxisMinimum(axis))
+ {
+ f = platform->AxisMinimum(axis);
+ }
+ else if (f > platform->AxisMaximum(axis))
+ {
+ f = platform->AxisMaximum(axis);
+ }
+ }
+ }
+ }
+ else if (AllAxesAreHomed()) // this is to allow extruder-only moves before homing
+ {
+ // If axes have been homed on a delta printer and this isn't a homing move, check for movements outside limits.
+ // Skip this check if axes have not been homed, so that extruder-only moved are allowed before homing
+ // Constrain the move to be within the build radius
+ const float diagonalSquared = fsquare(moveBuffer.coords[X_AXIS]) + fsquare(moveBuffer.coords[Y_AXIS]);
+ if (diagonalSquared > reprap.GetMove()->GetDeltaParams().GetPrintRadiusSquared())
+ {
+ const float factor = sqrtf(reprap.GetMove()->GetDeltaParams().GetPrintRadiusSquared() / diagonalSquared);
+ moveBuffer.coords[X_AXIS] *= factor;
+ moveBuffer.coords[Y_AXIS] *= factor;
+ }
+
+ // Constrain the end height of the move to be no greater than the homed height and no lower than -0.2mm
+ moveBuffer.coords[Z_AXIS] = max<float>(platform->AxisMinimum(Z_AXIS),
+ min<float>(moveBuffer.coords[Z_AXIS], reprap.GetMove()->GetDeltaParams().GetHomedHeight()));
+ }
+ }
+
+ return numSegments;
+}
+
+// This function is called for a G Code that makes a move.
+// If the Move class can't receive the move (i.e. things have to wait), return 0.
+// If we have queued the move and the caller doesn't need to wait for it to complete, return 1.
+// If we need to wait for the move to complete before doing another one (e.g. because endstops are checked in this move), return 2.
+int GCodes::SetUpMove(GCodeBuffer& gb, StringRef& reply)
+{
+ // Last one gone yet?
+ if (segmentsLeft != 0)
+ {
+ return 0;
+ }
+
+ // Check to see if the move is a 'homing' move that endstops are checked on.
+ moveBuffer.endStopsToCheck = 0;
+ moveBuffer.moveType = 0;
+ moveBuffer.xAxes = reprap.GetCurrentXAxes();
+ if (gb.Seen('S'))
+ {
+ int ival = gb.GetIValue();
+ if (ival == 1 || ival == 2)
+ {
+ moveBuffer.moveType = ival;
+ moveBuffer.xAxes = 0; // don't do bed compensation
+ }
+
+ if (ival == 1)
+ {
+ for (size_t i = 0; i < numAxes; ++i)
+ {
+ if (gb.Seen(axisLetters[i]))
+ {
+ moveBuffer.endStopsToCheck |= (1u << i);
+ }
+ }
+ }
+ else if (ival == 99) // temporary code to log Z probe change positions
+ {
+ moveBuffer.endStopsToCheck |= LogProbeChanges;
+ }
+ }
+
+ if (reprap.GetMove()->IsDeltaMode())
+ {
+ // Extra checks to avoid damaging delta printers
+ if (moveBuffer.moveType != 0 && !gb.MachineState().axesRelative)
+ {
+ // We have been asked to do a move without delta mapping on a delta machine, but the move is not relative.
+ // This may be damaging and is almost certainly a user mistake, so ignore the move.
+ reply.copy("Attempt to move the motors of a delta printer to absolute positions");
+ return 1;
+ }
+
+ if (moveBuffer.moveType == 0 && !AllAxesAreHomed())
+ {
+ // The user may be attempting to move a delta printer to an XYZ position before homing the axes
+ // This may be damaging and is almost certainly a user mistake, so ignore the move. But allow extruder-only moves.
+ if (gb.Seen(axisLetters[X_AXIS]) || gb.Seen(axisLetters[Y_AXIS]) || gb.Seen(axisLetters[Z_AXIS]))
+ {
+ reply.copy("Attempt to move the head of a delta printer before homing the towers");
+ return 1;
+ }
+ }
+ }
+
+ // Load the last position and feed rate into moveBuffer
+#if SUPPORT_ROLAND
+ if (reprap.GetRoland()->Active())
+ {
+ reprap.GetRoland()->GetCurrentRolandPosition(moveBuffer);
+ }
+ else
+#endif
+ {
+ reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, moveBuffer.moveType, reprap.GetCurrentXAxes());
+ }
+
+ // Load the move buffer with either the absolute movement required or the relative movement required
+ float oldCoords[MAX_AXES];
+ memcpy(oldCoords, moveBuffer.coords, sizeof(oldCoords));
+ segmentsLeft = LoadMoveBufferFromGCode(gb, moveBuffer.moveType);
+ if (segmentsLeft != 0)
+ {
+ // Flag whether we should use pressure advance, if there is any extrusion in this move.
+ // We assume it is a normal printing move needing pressure advance if there is forward extrusion and XY movement.
+ // The movement code will only apply pressure advance if there is forward extrusion, so we only need to check for XY movement here.
+ moveBuffer.usePressureAdvance = false;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ if (axis != Z_AXIS && moveBuffer.coords[axis] != oldCoords[axis])
+ {
+ moveBuffer.usePressureAdvance = true;
+ break;
+ }
+ }
+ moveBuffer.filePos = (&gb == fileGCode) ? filePos : noFilePosition;
+ //debugPrintf("Queue move pos %u\n", moveFilePos);
+ }
+ return (moveBuffer.moveType != 0 || moveBuffer.endStopsToCheck != 0) ? 2 : 1;
+}
+
+// The Move class calls this function to find what to do next.
+
+bool GCodes::ReadMove(RawMove& m)
+{
+ if (segmentsLeft == 0)
+ {
+ return false;
+ }
+
+ m = moveBuffer;
+ if (segmentsLeft == 1)
+ {
+ ClearMove();
+ }
+ else
+ {
+ // This move needs to be divided into 2 or more segments
+ // Do the axes
+ for (size_t drive = 0; drive < numAxes; ++drive)
+ {
+ const float movementToDo = (moveBuffer.coords[drive] - moveBuffer.initialCoords[drive])/segmentsLeft;
+ moveBuffer.initialCoords[drive] += movementToDo;
+ m.coords[drive] = moveBuffer.initialCoords[drive];
+ }
+
+ // Do the extruders
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ const float extrusionToDo = moveBuffer.coords[drive]/segmentsLeft;
+ m.coords[drive] = extrusionToDo;
+ moveBuffer.coords[drive] -= extrusionToDo;
+ }
+ --segmentsLeft;
+ }
+ return true;
+}
+
+void GCodes::ClearMove()
+{
+ segmentsLeft = 0;
+ moveBuffer.endStopsToCheck = 0;
+ moveBuffer.moveType = 0;
+ moveBuffer.isFirmwareRetraction = false;
+}
+
+// Run a file macro. Prior to calling this, 'state' must be set to the state we want to enter when the macro has been completed.
+// Return true if the file was found or it wasn't and we were asked to report that fact.
+bool GCodes::DoFileMacro(GCodeBuffer& gb, const char* fileName, bool reportMissing)
+{
+ FileStore * const f = platform->GetFileStore(platform->GetSysDir(), fileName, false);
+ if (f == nullptr)
+ {
+ if (reportMissing)
+ {
+ // Don't use snprintf into scratchString here, because fileName may be aliased to scratchString
+ platform->MessageF(GENERIC_MESSAGE, "Macro file %s not found.\n", fileName);
+ return true;
+ }
+ return false;
+ }
+
+ if (!Push(gb))
+ {
+ return true;
+ }
+ gb.MachineState().fileState.Set(f);
+ gb.MachineState().doingFileMacro = true;
+ gb.SetState(GCodeState::normal);
+ gb.Init();
+ return true;
+}
+
+void GCodes::FileMacroCyclesReturn(GCodeBuffer& gb)
+{
+ if (gb.MachineState().doingFileMacro)
+ {
+ gb.PopState();
+ gb.Init();
+ }
+}
+
+// To execute any move, call this until it returns true.
+// There is only one copy of the canned cycle variable so you must acquire the move lock before calling this.
+bool GCodes::DoCannedCycleMove(GCodeBuffer& gb, EndstopChecks ce)
+{
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ if (cannedCycleMoveQueued) // if the move has already been queued, it must have finished
+ {
+ Pop(gb);
+ cannedCycleMoveQueued = false;
+ return true;
+ }
+
+ // Otherwise, the move has not been queued yet
+ if (!Push(gb))
+ {
+ return true; // stack overflow
+ }
+
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ switch(cannedMoveType[drive])
+ {
+ case CannedMoveType::none:
+ break;
+ case CannedMoveType::relative:
+ moveBuffer.coords[drive] += cannedMoveCoords[drive];
+ break;
+ case CannedMoveType::absolute:
+ moveBuffer.coords[drive] = cannedMoveCoords[drive];
+ break;
+ }
+ }
+ moveBuffer.feedRate = cannedFeedRate;
+ moveBuffer.xAxes = 0;
+ moveBuffer.endStopsToCheck = ce;
+ moveBuffer.filePos = noFilePosition;
+ moveBuffer.usePressureAdvance = false;
+ segmentsLeft = 1;
+ cannedCycleMoveQueued = true;
+ }
+ return false;
+}
+
+// This handles G92
+bool GCodes::SetPositions(GCodeBuffer& gb)
+{
+ // Don't pause the machine if only extruder drives are being reset (DC, 2015-09-06).
+ // This avoids blobs and seams when the gcode uses absolute E coordinates and periodically includes G92 E0.
+ bool includingAxes = false;
+ for (size_t drive = 0; drive < numAxes; ++drive)
+ {
+ if (gb.Seen(axisLetters[drive]))
+ {
+ includingAxes = true;
+ break;
+ }
+ }
+
+ if (includingAxes)
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ }
+ else if (segmentsLeft != 0) // wait for previous move to be taken so that GetCurrentUserPosition returns the correct value
+ {
+ return false;
+ }
+
+ reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes()); // make sure move buffer is up to date
+ bool ok = LoadMoveBufferFromGCode(gb, -1);
+ if (ok && includingAxes)
+ {
+#if SUPPORT_ROLAND
+ if (reprap.GetRoland()->Active())
+ {
+ for(size_t axis = 0; axis < AXES; axis++)
+ {
+ if (!reprap.GetRoland()->ProcessG92(moveBuffer[axis], axis))
+ {
+ return false;
+ }
+ }
+ }
+#endif
+ SetPositions(moveBuffer.coords);
+ }
+
+ return true;
+}
+
+// Offset the axes by the X, Y, and Z amounts in the M code in gb. Say the machine is at [10, 20, 30] and
+// the offsets specified are [8, 2, -5]. The machine will move to [18, 22, 25] and henceforth consider that point
+// to be [10, 20, 30].
+bool GCodes::OffsetAxes(GCodeBuffer& gb)
+{
+ if (!offSetSet)
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ cannedMoveType[drive] = CannedMoveType::none;
+ if (drive < numAxes)
+ {
+ record[drive] = moveBuffer.coords[drive];
+ if (gb.Seen(axisLetters[drive]))
+ {
+ cannedMoveCoords[drive] = gb.GetFValue();
+ cannedMoveType[drive] = CannedMoveType::relative;
+ }
+ }
+ else
+ {
+ record[drive] = 0.0;
+ }
+ }
+
+ if (gb.Seen(feedrateLetter)) // Has the user specified a feedrate?
+ {
+ cannedFeedRate = gb.GetFValue() * distanceScale * SECONDS_TO_MINUTES;
+ }
+ else
+ {
+ cannedFeedRate = DEFAULT_FEEDRATE;
+ }
+
+ offSetSet = true;
+ }
+
+ if (DoCannedCycleMove(gb, 0))
+ {
+ // Restore positions
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ moveBuffer.coords[drive] = record[drive];
+ }
+ reprap.GetMove()->SetLiveCoordinates(record); // This doesn't transform record
+ reprap.GetMove()->SetPositions(record); // This does
+ offSetSet = false;
+ return true;
+ }
+
+ return false;
+}
+
+// Home one or more of the axes. Which ones are decided by the
+// booleans homeX, homeY and homeZ.
+// Returns true if completed, false if needs to be called again.
+// 'reply' is only written if there is an error.
+// 'error' is false on entry, gets changed to true if there is an error.
+bool GCodes::DoHome(GCodeBuffer& gb, StringRef& reply, bool& error)
+{
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+#if SUPPORT_ROLAND
+ // Deal with a Roland configuration
+ if (reprap.GetRoland()->Active())
+ {
+ bool rolHome = reprap.GetRoland()->ProcessHome();
+ if (rolHome)
+ {
+ for(size_t axis = 0; axis < AXES; axis++)
+ {
+ axisIsHomed[axis] = true;
+ }
+ }
+ return rolHome;
+ }
+#endif
+
+ if (reprap.GetMove()->IsDeltaMode())
+ {
+ SetAllAxesNotHomed();
+ DoFileMacro(gb, HOME_DELTA_G);
+ }
+ else
+ {
+ toBeHomed = 0;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ toBeHomed |= (1u << axis);
+ SetAxisNotHomed(axis);
+ }
+ }
+
+ if (toBeHomed == 0 || toBeHomed == ((1u << numAxes) - 1))
+ {
+ // Homing everything
+ SetAllAxesNotHomed();
+ DoFileMacro(gb, HOME_ALL_G);
+ }
+ else if ( platform->MustHomeXYBeforeZ()
+ && ((toBeHomed & (1u << Z_AXIS)) != 0)
+ && ((toBeHomed | axesHomed | (1u << Z_AXIS)) != ((1u << numAxes) - 1))
+ )
+ {
+ // We can only home Z if both X and Y have already been homed or are being homed
+ reply.copy("Must home all other axes before homing Z");
+ error = true;
+ }
+ else
+ {
+ gb.SetState(GCodeState::homing);
+ }
+ }
+ return true;
+}
+
+// This lifts Z a bit, moves to the probe XY coordinates (obtained by a call to GetProbeCoordinates() ),
+// probes the bed height, and records the Z coordinate probed. If you want to program any general
+// internal canned cycle, this shows how to do it.
+// On entry, probePointIndex specifies which of the points this is.
+bool GCodes::DoSingleZProbeAtPoint(GCodeBuffer& gb, int probePointIndex, float heightAdjust)
+{
+ reprap.GetMove()->SetIdentityTransform(); // It doesn't matter if these are called repeatedly
+
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ cannedMoveType[drive] = CannedMoveType::none;
+ }
+
+ switch (cannedCycleMoveCount)
+ {
+ case 0: // Move Z to the dive height. This only does anything on the first move; on all the others Z is already there
+ cannedMoveCoords[Z_AXIS] = platform->GetZProbeDiveHeight() + max<float>(platform->ZProbeStopHeight(), 0.0);
+ cannedMoveType[Z_AXIS] = CannedMoveType::absolute;
+ cannedFeedRate = platform->GetZProbeTravelSpeed();
+ if (DoCannedCycleMove(gb, 0))
+ {
+ cannedCycleMoveCount++;
+ }
+ return false;
+
+ case 1: // Move to the correct XY coordinates
+ GetProbeCoordinates(probePointIndex, cannedMoveCoords[X_AXIS], cannedMoveCoords[Y_AXIS], cannedMoveCoords[Z_AXIS]);
+ cannedMoveType[X_AXIS] = CannedMoveType::absolute;
+ cannedMoveType[Y_AXIS] = CannedMoveType::absolute;
+ // NB - we don't use the Z value
+ cannedFeedRate = platform->GetZProbeTravelSpeed();
+ if (DoCannedCycleMove(gb, 0))
+ {
+ cannedCycleMoveCount++;
+ }
+ return false;
+
+ case 2: // Probe the bed
+ if (millis() - lastProbedTime >= (uint32_t)(platform->GetZProbeParameters().recoveryTime * SecondsToMillis))
+ {
+ const float height = (GetAxisIsHomed(Z_AXIS))
+ ? 2 * platform->GetZProbeDiveHeight() // Z axis has been homed, so no point in going very far
+ : 1.1 * platform->AxisTotalLength(Z_AXIS); // Z axis not homed yet, so treat this as a homing move
+ switch(DoZProbe(gb, height))
+ {
+ case 0:
+ // Z probe is already triggered at the start of the move, so abandon the probe and record an error
+ platform->Message(GENERIC_MESSAGE, "Error: Z probe already triggered at start of probing move\n");
+ cannedCycleMoveCount++;
+ reprap.GetMove()->SetZBedProbePoint(probePointIndex, platform->GetZProbeDiveHeight(), true, true);
+ break;
+
+ case 1:
+ // Z probe did not trigger
+ platform->Message(GENERIC_MESSAGE, "Error: Z probe was not triggered during probing move\n");
+ cannedCycleMoveCount++;
+ reprap.GetMove()->SetZBedProbePoint(probePointIndex, -(platform->GetZProbeDiveHeight()), true, true);
+ break;
+
+ case 2:
+ // Successful probing
+ lastProbedTime = millis();
+ if (GetAxisIsHomed(Z_AXIS))
+ {
+ lastProbedZ = moveBuffer.coords[Z_AXIS] - (platform->ZProbeStopHeight() + heightAdjust);
+ }
+ else
+ {
+ // The Z axis has not yet been homed, so treat this probe as a homing move.
+ moveBuffer.coords[Z_AXIS] = platform->ZProbeStopHeight() + heightAdjust;
+ SetPositions(moveBuffer.coords);
+ SetAxisIsHomed(Z_AXIS);
+ lastProbedZ = 0.0;
+ }
+ reprap.GetMove()->SetZBedProbePoint(probePointIndex, lastProbedZ, true, false);
+ cannedCycleMoveCount++;
+ break;
+
+ default:
+ break;
+ }
+ }
+ return false;
+
+ case 3: // Raise the head back up to the dive height
+ cannedMoveCoords[Z_AXIS] = platform->GetZProbeDiveHeight() + max<float>(platform->ZProbeStopHeight(), 0.0);
+ cannedMoveType[Z_AXIS] = CannedMoveType::absolute;
+ cannedFeedRate = platform->GetZProbeTravelSpeed();
+ if (DoCannedCycleMove(gb, 0))
+ {
+ cannedCycleMoveCount = 0;
+ return true;
+ }
+ return false;
+
+ default: // should not happen
+ cannedCycleMoveCount = 0;
+ return true;
+ }
+}
+
+// This simply moves down till the Z probe/switch is triggered. Call it repeatedly until it returns true.
+// Called when we do a G30 with no P parameter.
+bool GCodes::DoSingleZProbe(GCodeBuffer& gb, bool reportOnly, float heightAdjust)
+{
+ switch (DoZProbe(gb, 1.1 * platform->AxisTotalLength(Z_AXIS)))
+ {
+ case 0: // failed
+ platform->Message(GENERIC_MESSAGE, "Error: Z probe already triggered at start of probing move\n");
+ return true;
+
+ case 1:
+ platform->Message(GENERIC_MESSAGE, "Error: Z probe was not triggered during probing move\n");
+ return true;
+
+ case 2: // success
+ if (!reportOnly)
+ {
+ moveBuffer.coords[Z_AXIS] = platform->ZProbeStopHeight() + heightAdjust;
+ SetPositions(moveBuffer.coords);
+ SetAxisIsHomed(Z_AXIS);
+ lastProbedZ = 0.0;
+ }
+ return true;
+
+ default: // not finished yet
+ return false;
+ }
+}
+
+// Do a Z probe cycle up to the maximum specified distance.
+// Returns -1 if not complete yet
+// Returns 0 if Z probe already triggered at start of probing
+// Returns 1 if Z probe didn't trigger
+// Returns 2 if success, with the current position in moveBuffer
+int GCodes::DoZProbe(GCodeBuffer& gb, float distance)
+{
+ if (platform->GetZProbeType() == ZProbeTypeDelta)
+ {
+ const ZProbeParameters& params = platform->GetZProbeParameters();
+ return reprap.GetMove()->DoDeltaProbe(params.extraParam, 1.0, params.probeSpeed, distance); //TODO second parameter
+ }
+ else
+ {
+ // Check for probe already triggered at start
+ if (!cannedCycleMoveQueued)
+ {
+ if (reprap.GetPlatform()->GetZProbeResult() == EndStopHit::lowHit)
+ {
+ return 0;
+ }
+ zProbeTriggered = false;
+ }
+
+ // Do a normal canned cycle Z movement with Z probe enabled
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ cannedMoveType[drive] = CannedMoveType::none;
+ }
+
+ cannedMoveCoords[Z_AXIS] = -distance;
+ cannedMoveType[Z_AXIS] = CannedMoveType::relative;
+ cannedFeedRate = platform->GetZProbeParameters().probeSpeed;
+
+ if (DoCannedCycleMove(gb, ZProbeActive))
+ {
+ return (zProbeTriggered) ? 2 : 1;
+ }
+ return -1;
+ }
+}
+
+// This is called to execute a G30.
+// It sets wherever we are as the probe point P (probePointIndex)
+// then probes the bed, or gets all its parameters from the arguments.
+// If X or Y are specified, use those; otherwise use the machine's
+// coordinates. If no Z is specified use the machine's coordinates. If it
+// is specified and is greater than SILLY_Z_VALUE (i.e. greater than -9999.0)
+// then that value is used. If it's less than SILLY_Z_VALUE the bed is
+// probed and that value is used.
+// Call this repeatedly until it returns true.
+bool GCodes::SetSingleZProbeAtAPosition(GCodeBuffer& gb, StringRef& reply)
+{
+ if (reprap.GetMove()->IsDeltaMode() && !AllAxesAreHomed())
+ {
+ reply.copy("Must home before bed probing");
+ return true;
+ }
+
+ float heightAdjust = 0.0;
+ bool dummy;
+ gb.TryGetFValue('H', heightAdjust, dummy);
+
+ if (!gb.Seen('P'))
+ {
+ bool reportOnly = false;
+ if (gb.Seen('S') && gb.GetIValue() < 0)
+ {
+ reportOnly = true;
+ }
+ return DoSingleZProbe(gb, reportOnly, heightAdjust);
+ }
+
+ int probePointIndex = gb.GetIValue();
+ if (probePointIndex < 0 || (unsigned int)probePointIndex >= MaxProbePoints)
+ {
+ reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Z probe point index out of range.\n");
+ return true;
+ }
+
+ float x = (gb.Seen(axisLetters[X_AXIS])) ? gb.GetFValue() : moveBuffer.coords[X_AXIS];
+ float y = (gb.Seen(axisLetters[Y_AXIS])) ? gb.GetFValue() : moveBuffer.coords[Y_AXIS];
+ float z = (gb.Seen(axisLetters[Z_AXIS])) ? gb.GetFValue() : moveBuffer.coords[Z_AXIS];
+
+ reprap.GetMove()->SetXBedProbePoint(probePointIndex, x);
+ reprap.GetMove()->SetYBedProbePoint(probePointIndex, y);
+
+ if (z > SILLY_Z_VALUE)
+ {
+ reprap.GetMove()->SetZBedProbePoint(probePointIndex, z, false, false);
+ if (gb.Seen('S'))
+ {
+ zProbesSet = true;
+ reprap.GetMove()->FinishedBedProbing(gb.GetIValue(), reply);
+ }
+ return true;
+ }
+ else
+ {
+ if (DoSingleZProbeAtPoint(gb, probePointIndex, heightAdjust))
+ {
+ if (gb.Seen('S'))
+ {
+ zProbesSet = true;
+ int sParam = gb.GetIValue();
+ if (sParam == 1)
+ {
+ // G30 with a silly Z value and S=1 is equivalent to G30 with no parameters in that it sets the current Z height
+ // This is useful because it adjusts the XY position to account for the probe offset.
+ moveBuffer.coords[Z_AXIS] += lastProbedZ;
+ SetPositions(moveBuffer.coords);
+ lastProbedZ = 0.0;
+ }
+ else
+ {
+ reprap.GetMove()->FinishedBedProbing(sParam, reply);
+ }
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// This returns the (X, Y) points to probe the bed at probe point count. When probing, it returns false.
+// If called after probing has ended it returns true, and the Z coordinate probed is also returned.
+bool GCodes::GetProbeCoordinates(int count, float& x, float& y, float& z) const
+{
+ const ZProbeParameters& rp = platform->GetZProbeParameters();
+ x = reprap.GetMove()->XBedProbePoint(count) - rp.xOffset;
+ y = reprap.GetMove()->YBedProbePoint(count) - rp.yOffset;
+ z = reprap.GetMove()->ZBedProbePoint(count);
+ return zProbesSet;
+}
+
+bool GCodes::SetPrintZProbe(GCodeBuffer& gb, StringRef& reply)
+{
+ ZProbeParameters params = platform->GetZProbeParameters();
+ bool seen = false;
+ gb.TryGetFValue(axisLetters[X_AXIS], params.xOffset, seen);
+ gb.TryGetFValue(axisLetters[Y_AXIS], params.yOffset, seen);
+ gb.TryGetFValue(axisLetters[Z_AXIS], params.height, seen);
+ gb.TryGetIValue('P', params.adcValue, seen);
+
+ if (gb.Seen('C'))
+ {
+ params.temperatureCoefficient = gb.GetFValue();
+ seen = true;
+ if (gb.Seen('S'))
+ {
+ params.calibTemperature = gb.GetFValue();
+ }
+ else
+ {
+ // Use the current bed temperature as the calibration temperature if no value was provided
+ params.calibTemperature = platform->GetZProbeTemperature();
+ }
+ }
+
+ if (seen)
+ {
+ platform->SetZProbeParameters(params);
+ }
+ else
+ {
+ const int v0 = platform->ZProbe();
+ int v1, v2;
+ switch (platform->GetZProbeSecondaryValues(v1, v2))
+ {
+ case 1:
+ reply.printf("%d (%d)", v0, v1);
+ break;
+ case 2:
+ reply.printf("%d (%d, %d)", v0, v1, v2);
+ break;
+ default:
+ reply.printf("%d", v0);
+ break;
+ }
+ }
+ return true;
+}
+
+// Define the probing grid, returning true if error
+// Called when we see an M557 command with no P parameter
+bool GCodes::DefineGrid(GCodeBuffer& gb, StringRef &reply)
+{
+ reprap.GetMove()->UseHeightMap(false);
+
+ bool seenX = false, seenY = false, seenR = false, seenS = false;
+ float xValues[2];
+ float yValues[2];
+
+ if (gb.Seen('X'))
+ {
+ size_t count = 2;
+ gb.GetFloatArray(xValues, count, false);
+ if (count == 2)
+ {
+ seenX = true;
+ }
+ else
+ {
+ reply.copy("ERROR: Wrong number of X values in M577, need 2");
+ return true;
+ }
+ }
+ if (gb.Seen('Y'))
+ {
+ size_t count = 2;
+ gb.GetFloatArray(yValues, count, false);
+ if (count == 2)
+ {
+ seenY = true;
+ }
+ else
+ {
+ reply.copy("ERROR: Wrong number of Y values in M577, need 2");
+ return true;
+ }
+ }
+
+ float radius = -1.0;
+ gb.TryGetFValue('R', radius, seenR);
+ float spacing = DefaultGridSpacing;
+ gb.TryGetFValue('S', spacing, seenS);
+
+ if (!seenX && !seenY && !seenR && !seenS)
+ {
+ // Just print the existing grid parameters
+ const GridDefinition& grid = reprap.GetMove()->AccessBedProbeGrid().GetGrid();
+ if (grid.IsValid())
+ {
+ reply.copy("Grid: ");
+ grid.PrintParameters(reply);
+ }
+ else
+ {
+ reply.copy("Grid is not defined");
+ }
+ return false;
+ }
+
+ if (seenX != seenY)
+ {
+ reply.copy("ERROR: specify both or neither of X and Y in M577");
+ return true;
+ }
+
+ if (!seenX && !seenR)
+ {
+ // Must have given just the S parameter
+ reply.copy("ERROR: specify at least radius or X,Y ranges in M577");
+ return true;
+
+ }
+
+ if (!seenX)
+ {
+ if (radius > 0)
+ {
+ const float effectiveRadius = floor((radius - 0.1)/spacing) * spacing;
+ xValues[0] = yValues[0] = -effectiveRadius;
+ xValues[1] = yValues[1] = effectiveRadius + 0.1;
+ }
+ else
+ {
+ reply.copy("ERROR: M577 radius must be positive unless X and Y are specified");
+ return true;
+ }
+ }
+ GridDefinition newGrid(xValues, yValues, radius, spacing); // create a new grid
+ if (newGrid.IsValid())
+ {
+ reprap.GetMove()->AccessBedProbeGrid().SetGrid(newGrid);
+ return false;
+ }
+ else
+ {
+ reply.copy("ERROR: bad grid definition: ");
+ newGrid.PrintError(reply);
+ return true;
+ }
+}
+
+// Start probing the grid, returning true if we didn't because of an error.
+// Prior to calling this the movement system must be locked.
+bool GCodes::ProbeGrid(GCodeBuffer& gb, StringRef& reply)
+{
+ int32_t sParam = 0;
+ bool dummy;
+ gb.TryGetIValue('S', sParam, dummy);
+
+ if (gb.Seen('P'))
+ {
+ heightMapFile = gb.GetString();
+ }
+ else
+ {
+ heightMapFile = DefaultHeightMapFile;
+ }
+
+ Move * const move = reprap.GetMove();
+ switch(sParam)
+ {
+ case 0: // Probe the bed and save to file
+ if (!move->AccessBedProbeGrid().GetGrid().IsValid())
+ {
+ reply.copy("No valid grid defined for G29 bed probing");
+ return true;
+ }
+
+ if (!AllAxesAreHomed())
+ {
+ reply.copy("Must home printer before G29 bed probing");
+ return true;
+ }
+
+ gridXindex = gridYindex = 0;
+ numPointsProbed = 0;
+ heightSum = heightSquaredSum = 0.0;
+
+ move->AccessBedProbeGrid().ClearGridHeights();
+ move->UseHeightMap(false);
+ move->SetIdentityTransform();
+ gb.SetState(GCodeState::gridProbing1);
+ return false;
+
+ case 1: // Load height map from file
+ {
+ const char* locHeightMapFileName;
+ if (gb.Seen('P'))
+ {
+ locHeightMapFileName = gb.GetString();
+ }
+ else
+ {
+ locHeightMapFileName = DefaultHeightMapFile;
+ }
+ FileStore * const f = platform->GetFileStore(platform->GetSysDir(), locHeightMapFileName, false);
+
+ if (f == nullptr)
+ {
+ reply.printf("Height map file %s not found", locHeightMapFileName);
+ return true;
+ }
+
+ reply.printf("Failed to load height map from file %s: ", heightMapFile); // set up error message to append to
+ const bool err = move->AccessBedProbeGrid().LoadFromFile(f, reply);
+ f->Close();
+ if (err)
+ {
+ move->AccessBedProbeGrid().ClearGridHeights(); // make sure we don't end up with a partial height map
+ }
+ else
+ {
+ reply.Clear(); // wipe the error message
+ }
+
+ move->UseHeightMap(!err);
+ return err;
+ }
+
+ case 2: // Clear height map
+ move->AccessBedProbeGrid().ClearGridHeights();
+ move->UseHeightMap(false);
+ return false;
+
+ default:
+ reply.copy("Invalid S parameter in G29 command");
+ return true;
+ }
+}
+
+// Save the height map and write the success or error message to 'reply', returning true if an error occurred
+bool GCodes::SaveHeightMapToFile(StringRef& reply) const
+{
+ Platform *platform = reprap.GetPlatform();
+ FileStore * const f = platform->GetFileStore(platform->GetSysDir(), heightMapFile, true);
+ bool err;
+ if (f == nullptr)
+ {
+ reply.printf("Failed to create height map file %s", heightMapFile);
+ err = true;
+ }
+ else
+ {
+ err = reprap.GetMove()->AccessBedProbeGrid().SaveToFile(f);
+ f->Close();
+ if (err)
+ {
+ platform->GetMassStorage()->Delete(platform->GetSysDir(), heightMapFile);
+ reply.printf("Failed to save height map to file %s", heightMapFile);
+ }
+ else
+ {
+ reply.printf("Height map saved to file %s", heightMapFile);
+ }
+ }
+ return err;
+}
+
+// Return the current coordinates as a printable string.
+// Coordinates are updated at the end of each movement, so this won't tell you where you are mid-movement.
+void GCodes::GetCurrentCoordinates(StringRef& s) const
+{
+ float liveCoordinates[DRIVES];
+ reprap.GetMove()->LiveCoordinates(liveCoordinates, reprap.GetCurrentXAxes());
+ const Tool *currentTool = reprap.GetCurrentTool();
+ if (currentTool != nullptr)
+ {
+ const float *offset = currentTool->GetOffset();
+ for (size_t i = 0; i < numAxes; ++i)
+ {
+ liveCoordinates[i] += offset[i];
+ }
+ }
+
+ s.Clear();
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ s.catf("%c: %.2f ", axisLetters[axis], liveCoordinates[axis]);
+ }
+ for (size_t i = numAxes; i < DRIVES; i++)
+ {
+ s.catf("E%u: %.1f ", i - numAxes, liveCoordinates[i]);
+ }
+
+ // Print the axis stepper motor positions as Marlin does, as an aid to debugging.
+ // Don't bother with the extruder endpoints, they are zero after any non-extruding move.
+ s.cat(" Count");
+ for (size_t i = 0; i < numAxes; ++i)
+ {
+ s.catf(" %d", reprap.GetMove()->GetEndPoint(i));
+ }
+}
+
+bool GCodes::OpenFileToWrite(GCodeBuffer& gb, const char* directory, const char* fileName)
+{
+ fileBeingWritten = platform->GetFileStore(directory, fileName, true);
+ eofStringCounter = 0;
+ if (fileBeingWritten == NULL)
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Can't open GCode file \"%s\" for writing.\n", fileName);
+ return false;
+ }
+ else
+ {
+ gb.SetWritingFileDirectory(directory);
+ return true;
+ }
+}
+
+void GCodes::WriteHTMLToFile(GCodeBuffer& gb, char b)
+{
+ if (fileBeingWritten == NULL)
+ {
+ platform->Message(GENERIC_MESSAGE, "Attempt to write to a null file.\n");
+ return;
+ }
+
+ if (eofStringCounter != 0 && b != eofString[eofStringCounter])
+ {
+ fileBeingWritten->Write(eofString);
+ eofStringCounter = 0;
+ }
+
+ if (b == eofString[eofStringCounter])
+ {
+ eofStringCounter++;
+ if (eofStringCounter >= eofStringLength)
+ {
+ fileBeingWritten->Close();
+ fileBeingWritten = NULL;
+ gb.SetWritingFileDirectory(NULL);
+ const char* r = (platform->Emulating() == marlin) ? "Done saving file." : "";
+ HandleReply(gb, false, r);
+ return;
+ }
+ }
+ else
+ {
+ fileBeingWritten->Write(b);
+ }
+}
+
+void GCodes::WriteGCodeToFile(GCodeBuffer& gb)
+{
+ if (fileBeingWritten == NULL)
+ {
+ platform->Message(GENERIC_MESSAGE, "Attempt to write to a null file.\n");
+ return;
+ }
+
+ // End of file?
+ if (gb.Seen('M'))
+ {
+ if (gb.GetIValue() == 29)
+ {
+ fileBeingWritten->Close();
+ fileBeingWritten = NULL;
+ gb.SetWritingFileDirectory(NULL);
+ const char* r = (platform->Emulating() == marlin) ? "Done saving file." : "";
+ HandleReply(gb, false, r);
+ return;
+ }
+ }
+
+ // Resend request?
+ if (gb.Seen('G'))
+ {
+ if (gb.GetIValue() == 998)
+ {
+ if (gb.Seen('P'))
+ {
+ scratchString.printf("%d\n", gb.GetIValue());
+ HandleReply(gb, false, scratchString.Pointer());
+ return;
+ }
+ }
+ }
+
+ fileBeingWritten->Write(gb.Buffer());
+ fileBeingWritten->Write('\n');
+ HandleReply(gb, false, "");
+}
+
+// Set up a file to print, but don't print it yet.
+void GCodes::QueueFileToPrint(const char* fileName)
+{
+ FileStore * const f = platform->GetFileStore(platform->GetGCodeDir(), fileName, false);
+ if (f != nullptr)
+ {
+ // Cancel current print if there is any
+ if (!reprap.GetPrintMonitor()->IsPrinting())
+ {
+ CancelPrint();
+ }
+
+ fileGCode->SetToolNumberAdjust(0); // clear tool number adjustment
+
+ // Reset all extruder positions when starting a new print
+ for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
+ {
+ lastRawExtruderPosition[extruder] = 0.0;
+ rawExtruderTotalByDrive[extruder] = 0.0;
+ }
+ rawExtruderTotal = 0.0;
+ reprap.GetMove()->ResetExtruderPositions();
+
+ fileToPrint.Set(f);
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "GCode file \"%s\" not found\n", fileName);
+ }
+}
+
+void GCodes::DeleteFile(const char* fileName)
+{
+ if (!platform->GetMassStorage()->Delete(platform->GetGCodeDir(), fileName))
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Could not delete file \"%s\"\n", fileName);
+ }
+}
+
+// Function to handle dwell delays. Return true for dwell finished, false otherwise.
+bool GCodes::DoDwell(GCodeBuffer& gb)
+{
+ float dwell;
+ if (gb.Seen('S'))
+ {
+ dwell = gb.GetFValue();
+ }
+ else if (gb.Seen('P'))
+ {
+ dwell = 0.001 * (float) gb.GetIValue(); // P values are in milliseconds; we need seconds
+ }
+ else
+ {
+ return true; // No time given - throw it away
+ }
+
+#if SUPPORT_ROLAND
+ // Deal with a Roland configuration
+ if (reprap.GetRoland()->Active())
+ {
+ return reprap.GetRoland()->ProcessDwell(gb.GetLValue());
+ }
+#endif
+
+ // Wait for all the queued moves to stop
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+ if (simulationMode != 0)
+ {
+ simulationTime += dwell;
+ return true;
+ }
+ else
+ {
+ return DoDwellTime(dwell);
+ }
+}
+
+bool GCodes::DoDwellTime(float dwell)
+{
+ // Are we already in the dwell?
+ if (dwellWaiting)
+ {
+ if (platform->Time() - dwellTime >= 0.0)
+ {
+ dwellWaiting = false;
+ reprap.GetMove()->ResumeMoving();
+ return true;
+ }
+ return false;
+ }
+
+ // New dwell - set it up
+ dwellWaiting = true;
+ dwellTime = platform->Time() + dwell;
+ return false;
+}
+
+// Set offset, working and standby temperatures for a tool. I.e. handle a G10.
+bool GCodes::SetOrReportOffsets(GCodeBuffer &gb, StringRef& reply)
+{
+ if (gb.Seen('P'))
+ {
+ int8_t toolNumber = gb.GetIValue();
+ toolNumber += gb.GetToolNumberAdjust();
+ Tool* tool = reprap.GetTool(toolNumber);
+ if (tool == NULL)
+ {
+ reply.printf("Attempt to set/report offsets and temperatures for non-existent tool: %d", toolNumber);
+ return true;
+ }
+
+ // Deal with setting offsets
+ float offset[MAX_AXES];
+ for (size_t i = 0; i < MAX_AXES; ++i)
+ {
+ offset[i] = tool->GetOffset()[i];
+ }
+
+ bool settingOffset = false;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ gb.TryGetFValue(axisLetters[axis], offset[axis], settingOffset);
+ }
+ if (settingOffset)
+ {
+ if (!LockMovement(gb))
+ {
+ return false;
+ }
+ tool->SetOffset(offset);
+ }
+
+ // Deal with setting temperatures
+ bool settingTemps = false;
+ size_t hCount = tool->HeaterCount();
+ float standby[HEATERS];
+ float active[HEATERS];
+ if (hCount > 0)
+ {
+ tool->GetVariables(standby, active);
+ if (gb.Seen('R'))
+ {
+ gb.GetFloatArray(standby, hCount, true);
+ settingTemps = true;
+ }
+ if (gb.Seen('S'))
+ {
+ gb.GetFloatArray(active, hCount, true);
+ settingTemps = true;
+ }
+
+ if (settingTemps && simulationMode == 0)
+ {
+ tool->SetVariables(standby, active);
+ }
+ }
+
+ if (!settingOffset && !settingTemps)
+ {
+ // Print offsets and temperatures
+ reply.printf("Tool %d offsets:", toolNumber);
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ reply.catf(" %c%.2f", axisLetters[axis], offset[axis]);
+ }
+ if (hCount != 0)
+ {
+ reply.cat(", active/standby temperature(s):");
+ for (size_t heater = 0; heater < hCount; heater++)
+ {
+ reply.catf(" %.1f/%.1f", active[heater], standby[heater]);
+ }
+ }
+ }
+ }
+ return true;
+}
+
+void GCodes::ManageTool(GCodeBuffer& gb, StringRef& reply)
+{
+ if (!gb.Seen('P'))
+ {
+ // DC temporary code to allow tool numbers to be adjusted so that we don't need to edit multi-media files generated by slic3r
+ if (gb.Seen('S'))
+ {
+ int adjust = gb.GetIValue();
+ gb.SetToolNumberAdjust(adjust);
+ }
+ return;
+ }
+
+ // Check tool number
+ bool seen = false;
+ const int toolNumber = gb.GetIValue();
+ if (toolNumber < 0)
+ {
+ platform->Message(GENERIC_MESSAGE, "Tool number must be positive!\n");
+ return;
+ }
+
+ // Check drives
+ long drives[MaxExtruders]; // There can never be more than we have...
+ size_t dCount = numExtruders; // Sets the limit and returns the count
+ if (gb.Seen('D'))
+ {
+ gb.GetLongArray(drives, dCount);
+ seen = true;
+ }
+ else
+ {
+ dCount = 0;
+ }
+
+ // Check heaters
+ long heaters[HEATERS];
+ size_t hCount = HEATERS;
+ if (gb.Seen('H'))
+ {
+ gb.GetLongArray(heaters, hCount);
+ seen = true;
+ }
+ else
+ {
+ hCount = 0;
+ }
+
+ // Check X axis mapping
+ uint32_t xMap;
+ if (gb.Seen('X'))
+ {
+ long xMapping[MAX_AXES];
+ size_t xCount = numAxes;
+ gb.GetLongArray(xMapping, xCount);
+ xMap = LongArrayToBitMap(xMapping, xCount) & ((1u << numAxes) - 1);
+ seen = true;
+ }
+ else
+ {
+ xMap = 1; // by default map X axis straight through
+ }
+
+ // Check for fan mapping
+ uint32_t fanMap;
+ if (gb.Seen('F'))
+ {
+ long fanMapping[NUM_FANS];
+ size_t fanCount = NUM_FANS;
+ gb.GetLongArray(fanMapping, fanCount);
+ fanMap = LongArrayToBitMap(fanMapping, fanCount) & ((1u << NUM_FANS) - 1);
+ seen = true;
+ }
+ else
+ {
+ fanMap = 1; // by default map fan 0 to fan 0
+ }
+
+ if (seen)
+ {
+ // Add or delete tool, so start by deleting the old one with this number, if any
+ reprap.DeleteTool(reprap.GetTool(toolNumber));
+
+ // M563 P# D-1 H-1 removes an existing tool
+ if (dCount == 1 && hCount == 1 && drives[0] == -1 && heaters[0] == -1)
+ {
+ // nothing more to do
+ }
+ else
+ {
+ Tool* tool = Tool::Create(toolNumber, drives, dCount, heaters, hCount, xMap, fanMap);
+ if (tool != nullptr)
+ {
+ reprap.AddTool(tool);
+ }
+ }
+ }
+ else
+ {
+ reprap.PrintTool(toolNumber, reply);
+ }
+}
+
+// Does what it says.
+void GCodes::DisableDrives()
+{
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ platform->DisableDrive(drive);
+ }
+ SetAllAxesNotHomed();
+}
+
+// Does what it says.
+void GCodes::SetEthernetAddress(GCodeBuffer& gb, int mCode)
+{
+ byte eth[4];
+ const char* ipString = gb.GetString();
+ uint8_t sp = 0;
+ uint8_t spp = 0;
+ uint8_t ipp = 0;
+ while (ipString[sp])
+ {
+ if (ipString[sp] == '.')
+ {
+ eth[ipp] = atoi(&ipString[spp]);
+ ipp++;
+ if (ipp > 3)
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Dud IP address: %s\n", gb.Buffer());
+ return;
+ }
+ sp++;
+ spp = sp;
+ }
+ else
+ {
+ sp++;
+ }
+ }
+ eth[ipp] = atoi(&ipString[spp]);
+ if (ipp == 3)
+ {
+ switch (mCode)
+ {
+ case 552:
+ platform->SetIPAddress(eth);
+ break;
+ case 553:
+ platform->SetNetMask(eth);
+ break;
+ case 554:
+ platform->SetGateWay(eth);
+ break;
+
+ default:
+ platform->Message(GENERIC_MESSAGE, "Setting ether parameter - dud code.\n");
+ }
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Dud IP address: %s\n", gb.Buffer());
+ }
+}
+
+void GCodes::SetMACAddress(GCodeBuffer& gb)
+{
+ uint8_t mac[6];
+ const char* ipString = gb.GetString();
+ uint8_t sp = 0;
+ uint8_t spp = 0;
+ uint8_t ipp = 0;
+ while (ipString[sp])
+ {
+ if (ipString[sp] == ':')
+ {
+ mac[ipp] = strtoul(&ipString[spp], NULL, 16);
+ ipp++;
+ if (ipp > 5)
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Dud MAC address: %s\n", gb.Buffer());
+ return;
+ }
+ sp++;
+ spp = sp;
+ }
+ else
+ {
+ sp++;
+ }
+ }
+ mac[ipp] = strtoul(&ipString[spp], NULL, 16);
+ if (ipp == 5)
+ {
+ platform->SetMACAddress(mac);
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Dud MAC address: %s\n", gb.Buffer());
+ }
+}
+
+bool GCodes::ChangeMicrostepping(size_t drive, int microsteps, int mode) const
+{
+ bool dummy;
+ unsigned int oldSteps = platform->GetMicrostepping(drive, dummy);
+ bool success = platform->SetMicrostepping(drive, microsteps, mode);
+ if (success && mode <= 1) // modes higher than 1 are used for special functions
+ {
+ // We changed the microstepping, so adjust the steps/mm to compensate
+ float stepsPerMm = platform->DriveStepsPerUnit(drive);
+ if (stepsPerMm > 0)
+ {
+ platform->SetDriveStepsPerUnit(drive, stepsPerMm * (float)microsteps / (float)oldSteps);
+ }
+ }
+ return success;
+}
+
+// Set the speeds of fans mapped for the current tool to lastDefaultFanSpeed
+void GCodes::SetMappedFanSpeed()
+{
+ if (reprap.GetCurrentTool() == nullptr)
+ {
+ platform->SetFanValue(0, lastDefaultFanSpeed);
+ }
+ else
+ {
+ const uint32_t fanMap = reprap.GetCurrentTool()->GetFanMapping();
+ for (size_t i = 0; i < NUM_FANS; ++i)
+ {
+ if ((fanMap & (1u << i)) != 0)
+ {
+ platform->SetFanValue(i, lastDefaultFanSpeed);
+ }
+ }
+ }
+}
+
+// Handle sending a reply back to the appropriate interface(s).
+// Note that 'reply' may be empty. If it isn't, then we need to append newline when sending it.
+// Also, gb may be null if we were executing a trigger macro.
+void GCodes::HandleReply(GCodeBuffer& gb, bool error, const char* reply)
+{
+ // Don't report "ok" responses if a (macro) file is being processed
+ // Also check that this response was triggered by a gcode
+ if ((gb.MachineState().doingFileMacro || &gb == fileGCode) && reply[0] == 0)
+ {
+ return;
+ }
+
+ // Second UART device, e.g. dc42's PanelDue. Do NOT use emulation for this one!
+ if (&gb == auxGCode)
+ {
+ platform->AppendAuxReply(reply);
+ return;
+ }
+
+ const Compatibility c = (&gb == serialGCode || &gb == telnetGCode) ? platform->Emulating() : me;
+ MessageType type = GENERIC_MESSAGE;
+ if (&gb == httpGCode)
+ {
+ type = HTTP_MESSAGE;
+ }
+ else if (&gb == telnetGCode)
+ {
+ type = TELNET_MESSAGE;
+ }
+ else if (&gb == serialGCode)
+ {
+ type = HOST_MESSAGE;
+ }
+
+ const char* response = (gb.Seen('M') && gb.GetIValue() == 998) ? "rs " : "ok";
+ const char* emulationType = 0;
+
+ switch (c)
+ {
+ case me:
+ case reprapFirmware:
+ if (error)
+ {
+ platform->Message(type, "Error: ");
+ }
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ return;
+
+ case marlin:
+ // We don't need to handle M20 here because we always allocate an output buffer for that one
+ if (gb.Seen('M') && gb.GetIValue() == 28)
+ {
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ return;
+ }
+
+ if ((gb.Seen('M') && gb.GetIValue() == 105) || (gb.Seen('M') && gb.GetIValue() == 998))
+ {
+ platform->Message(type, response);
+ platform->Message(type, " ");
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ return;
+ }
+
+ if (reply[0] != 0 && !gb.IsDoingFileMacro())
+ {
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ }
+ else if (reply[0] != 0)
+ {
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ }
+ else
+ {
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ }
+ return;
+
+ case teacup:
+ emulationType = "teacup";
+ break;
+ case sprinter:
+ emulationType = "sprinter";
+ break;
+ case repetier:
+ emulationType = "repetier";
+ break;
+ default:
+ emulationType = "unknown";
+ }
+
+ if (emulationType != 0)
+ {
+ platform->MessageF(type, "Emulation of %s is not yet supported.\n", emulationType); // don't send this one to the web as well, it concerns only the USB interface
+ }
+}
+
+void GCodes::HandleReply(GCodeBuffer& gb, bool error, OutputBuffer *reply)
+{
+ // Although unlikely, it's possible that we get a nullptr reply. Don't proceed if this is the case
+ if (reply == nullptr)
+ {
+ return;
+ }
+
+ // Second UART device, e.g. dc42's PanelDue. Do NOT use emulation for this one!
+ if (&gb == auxGCode)
+ {
+ platform->AppendAuxReply(reply);
+ return;
+ }
+
+ const Compatibility c = (&gb == serialGCode || &gb == telnetGCode) ? platform->Emulating() : me;
+ MessageType type = GENERIC_MESSAGE;
+ if (&gb == httpGCode)
+ {
+ type = HTTP_MESSAGE;
+ }
+ else if (&gb == telnetGCode)
+ {
+ type = TELNET_MESSAGE;
+ }
+ else if (&gb == serialGCode)
+ {
+ type = HOST_MESSAGE;
+ }
+
+ const char* response = (gb.Seen('M') && gb.GetIValue() == 998) ? "rs " : "ok";
+ const char* emulationType = nullptr;
+
+ switch (c)
+ {
+ case me:
+ case reprapFirmware:
+ if (error)
+ {
+ platform->Message(type, "Error: ");
+ }
+ platform->Message(type, reply);
+ return;
+
+ case marlin:
+ if (gb.Seen('M') && gb.GetIValue() == 20)
+ {
+ platform->Message(type, "Begin file list\n");
+ platform->Message(type, reply);
+ platform->Message(type, "End file list\n");
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ return;
+ }
+
+ if (gb.Seen('M') && gb.GetIValue() == 28)
+ {
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ platform->Message(type, reply);
+ return;
+ }
+
+ if ((gb.Seen('M') && gb.GetIValue() == 105) || (gb.Seen('M') && gb.GetIValue() == 998))
+ {
+ platform->Message(type, response);
+ platform->Message(type, " ");
+ platform->Message(type, reply);
+ return;
+ }
+
+ if (reply->Length() != 0 && !gb.IsDoingFileMacro())
+ {
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ }
+ else if (reply->Length() != 0)
+ {
+ platform->Message(type, reply);
+ }
+ else
+ {
+ OutputBuffer::ReleaseAll(reply);
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ }
+ return;
+
+ case teacup:
+ emulationType = "teacup";
+ break;
+ case sprinter:
+ emulationType = "sprinter";
+ break;
+ case repetier:
+ emulationType = "repetier";
+ break;
+ default:
+ emulationType = "unknown";
+ }
+
+ // If we get here then we didn't handle the message, so release the buffer(s)
+ OutputBuffer::ReleaseAll(reply);
+ if (emulationType != 0)
+ {
+ platform->MessageF(type, "Emulation of %s is not yet supported.\n", emulationType); // don't send this one to the web as well, it concerns only the USB interface
+ }
+}
+
+// Set PID parameters (M301 or M304 command). 'heater' is the default heater number to use.
+void GCodes::SetPidParameters(GCodeBuffer& gb, int heater, StringRef& reply)
+{
+ if (gb.Seen('H'))
+ {
+ heater = gb.GetIValue();
+ }
+
+ if (heater >= 0 && heater < HEATERS)
+ {
+ PidParameters pp = platform->GetPidParameters(heater);
+ bool seen = false;
+ gb.TryGetFValue('P', pp.kP, seen);
+ gb.TryGetFValue('I', pp.kI, seen);
+ gb.TryGetFValue('D', pp.kD, seen);
+ gb.TryGetFValue('T', pp.kT, seen);
+ gb.TryGetFValue('S', pp.kS, seen);
+
+ if (seen)
+ {
+ platform->SetPidParameters(heater, pp);
+ reprap.GetHeat()->UseModel(heater, false);
+ }
+ else
+ {
+ reply.printf("Heater %d P:%.2f I:%.3f D:%.2f T:%.2f S:%.2f", heater, pp.kP, pp.kI, pp.kD, pp.kT, pp.kS);
+ }
+ }
+}
+
+void GCodes::SetHeaterParameters(GCodeBuffer& gb, StringRef& reply)
+{
+ if (gb.Seen('P'))
+ {
+ int heater = gb.GetIValue();
+ if (heater >= 0 && heater < HEATERS)
+ {
+ Thermistor& th = platform->GetThermistor(heater);
+ bool seen = false;
+
+ // We must set the 25C resistance and beta together in order to calculate Rinf. Check for these first.
+ float r25 = th.GetR25();
+ float beta = th.GetBeta();
+ float shC = th.GetShc();
+ float seriesR = th.GetSeriesR();
+
+ gb.TryGetFValue('T', r25, seen);
+ gb.TryGetFValue('B', beta, seen);
+ gb.TryGetFValue('C', shC, seen);
+ gb.TryGetFValue('R', seriesR, seen);
+ if (seen)
+ {
+ th.SetParameters(r25, beta, shC, seriesR);
+ }
+
+ if (gb.Seen('L'))
+ {
+ th.SetLowOffset((int8_t)constrain<int>(gb.GetIValue(), -100, 100));
+ seen = true;
+ }
+ if (gb.Seen('H'))
+ {
+ th.SetHighOffset((int8_t)constrain<int>(gb.GetIValue(), -100, 100));
+ seen = true;
+ }
+
+ if (gb.Seen('X'))
+ {
+ int thermistor = gb.GetIValue();
+ if ( (0 <= thermistor && thermistor < HEATERS)
+ || ((int)FirstThermocoupleChannel <= thermistor && thermistor < (int)(FirstThermocoupleChannel + MaxSpiTempSensors))
+ || ((int)FirstRtdChannel <= thermistor && thermistor < (int)(FirstRtdChannel + MaxSpiTempSensors))
+ )
+ {
+ platform->SetThermistorNumber(heater, thermistor);
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Thermistor number %d is out of range\n", thermistor);
+ }
+ seen = true;
+ }
+
+ if (!seen)
+ {
+ reply.printf("T:%.1f B:%.1f C:%.2e R:%.1f L:%d H:%d X:%d",
+ th.GetR25(), th.GetBeta(), th.GetShc(), th.GetSeriesR(),
+ th.GetLowOffset(), th.GetHighOffset(), platform->GetThermistorNumber(heater));
+ }
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Heater number %d is out of range\n", heater);
+ }
+ }
+}
+
+void GCodes::SetToolHeaters(Tool *tool, float temperature)
+{
+ if (tool == NULL)
+ {
+ platform->Message(GENERIC_MESSAGE, "Setting temperature: no tool selected.\n");
+ return;
+ }
+
+ float standby[HEATERS];
+ float active[HEATERS];
+ tool->GetVariables(standby, active);
+ for (size_t h = 0; h < tool->HeaterCount(); h++)
+ {
+ active[h] = temperature;
+ }
+ tool->SetVariables(standby, active);
+}
+
+// Begin the tool change sequence
+void GCodes::StartToolChange(GCodeBuffer& gb, bool inM109)
+{
+ gb.SetState((inM109) ? GCodeState:: m109ToolChange1 : GCodeState::toolChange1);
+ const Tool * const oldTool = reprap.GetCurrentTool();
+ if (oldTool != nullptr && AllAxesAreHomed())
+ {
+ scratchString.printf("tfree%d.g", oldTool->Number());
+ DoFileMacro(gb, scratchString.Pointer(), false);
+ }
+}
+
+// Retract or un-retract filament, returning true if movement has been queued, false if this needs to be called again
+bool GCodes::RetractFilament(bool retract)
+{
+ if (retractLength != 0.0 || retractHop != 0.0 || (!retract && retractExtra != 0.0))
+ {
+ const Tool *tool = reprap.GetCurrentTool();
+ if (tool != nullptr)
+ {
+ size_t nDrives = tool->DriveCount();
+ if (nDrives != 0)
+ {
+ if (segmentsLeft != 0)
+ {
+ return false;
+ }
+
+ reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes());
+ for (size_t i = numAxes; i < DRIVES; ++i)
+ {
+ moveBuffer.coords[i] = 0.0;
+ }
+ // Set the feed rate. If there is any Z hop then we need to pass the Z speed, else we pass the extrusion speed.
+ const float speedToUse = (retract) ? retractSpeed : unRetractSpeed;
+ moveBuffer.feedRate = (retractHop == 0.0)
+ ? speedToUse * secondsToMinutes
+ : speedToUse * secondsToMinutes * retractHop/retractLength;
+ moveBuffer.coords[Z_AXIS] += (retract) ? retractHop : -retractHop;
+ const float lengthToUse = (retract) ? -retractLength : retractLength + retractExtra;
+ for (size_t i = 0; i < nDrives; ++i)
+ {
+ moveBuffer.coords[E0_AXIS + tool->Drive(i)] = lengthToUse;
+ }
+
+ moveBuffer.isFirmwareRetraction = true;
+ moveBuffer.usePressureAdvance = false;
+ moveBuffer.filePos = filePos;
+ moveBuffer.xAxes = reprap.GetCurrentXAxes();
+ segmentsLeft = 1;
+ }
+ }
+ }
+ return true;
+}
+
+// Return the amount of filament extruded
+float GCodes::GetRawExtruderPosition(size_t extruder) const
+{
+ return (extruder < numExtruders) ? lastRawExtruderPosition[extruder] : 0.0;
+}
+
+float GCodes::GetRawExtruderTotalByDrive(size_t extruder) const
+{
+ return (extruder < numExtruders) ? rawExtruderTotalByDrive[extruder] : 0.0;
+}
+
+// Cancel the current SD card print.
+// This is called from Pid.cpp when there is a heater fault, and from elsewhere in this module.
+void GCodes::CancelPrint()
+{
+ segmentsLeft = 0;
+ isPaused = false;
+
+ fileGCode->Init();
+ FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState;
+ if (fileBeingPrinted.IsLive())
+ {
+ fileBeingPrinted.Close();
+ }
+
+ reprap.GetPrintMonitor()->StoppedPrint();
+}
+
+// Return true if all the heaters for the specified tool are at their set temperatures
+bool GCodes::ToolHeatersAtSetTemperatures(const Tool *tool, bool waitWhenCooling) const
+{
+ if (tool != NULL)
+ {
+ for (size_t i = 0; i < tool->HeaterCount(); ++i)
+ {
+ if (!reprap.GetHeat()->HeaterAtSetTemperature(tool->Heater(i), waitWhenCooling))
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+// Set the current position
+void GCodes::SetPositions(float positionNow[DRIVES])
+{
+ // Transform the position so that e.g. if the user does G92 Z0,
+ // the position we report (which gets inverse-transformed) really is Z=0 afterwards
+ reprap.GetMove()->Transform(positionNow, reprap.GetCurrentXAxes());
+ reprap.GetMove()->SetLiveCoordinates(positionNow);
+ reprap.GetMove()->SetPositions(positionNow);
+}
+
+bool GCodes::IsPaused() const
+{
+ return isPaused && !IsPausing() && !IsResuming();
+}
+
+bool GCodes::IsPausing() const
+{
+ const GCodeState topState = fileGCode->OriginalMachineState().state;
+ return topState == GCodeState::pausing1 || topState == GCodeState::pausing2;
+}
+
+bool GCodes::IsResuming() const
+{
+ const GCodeState topState = fileGCode->OriginalMachineState().state;
+ return topState == GCodeState::resuming1 || topState == GCodeState::resuming2 || topState == GCodeState::resuming3;
+}
+
+bool GCodes::IsRunning() const
+{
+ return !IsPaused() && !IsPausing() && !IsResuming();
+}
+
+const char *GCodes::TranslateEndStopResult(EndStopHit es)
+{
+ switch (es)
+ {
+ case EndStopHit::lowHit:
+ return "at min stop";
+
+ case EndStopHit::highHit:
+ return "at max stop";
+
+ case EndStopHit::lowNear:
+ return "near min stop";
+
+ case EndStopHit::noStop:
+ default:
+ return "not stopped";
+ }
+}
+
+// Append a list of trigger endstops to a message
+void GCodes::ListTriggers(StringRef reply, TriggerMask mask)
+{
+ if (mask == 0)
+ {
+ reply.cat("(none)");
+ }
+ else
+ {
+ bool printed = false;
+ for (unsigned int i = 0; i < DRIVES; ++i)
+ {
+ if ((mask & (1u << i)) != 0)
+ {
+ if (printed)
+ {
+ reply.cat(' ');
+ }
+ if (i < numAxes)
+ {
+ reply.cat(axisLetters[i]);
+ }
+ else
+ {
+ reply.catf("E%d", i - numAxes);
+ }
+ printed = true;
+ }
+ }
+ }
+}
+
+// M38 (SHA1 hash of a file) implementation:
+bool GCodes::StartHash(const char* filename)
+{
+ // Get a FileStore object
+ fileBeingHashed = platform->GetFileStore(FS_PREFIX, filename, false);
+ if (fileBeingHashed == nullptr)
+ {
+ return false;
+ }
+
+ // Start hashing
+ SHA1Reset(&hash);
+ return true;
+}
+
+bool GCodes::AdvanceHash(StringRef &reply)
+{
+ // Read and process some more data from the file
+ uint32_t buf32[(FILE_BUFFER_SIZE + 3) / 4];
+ char *buffer = reinterpret_cast<char *>(buf32);
+
+ int bytesRead = fileBeingHashed->Read(buffer, FILE_BUFFER_SIZE);
+ if (bytesRead != -1)
+ {
+ SHA1Input(&hash, reinterpret_cast<const uint8_t *>(buffer), bytesRead);
+
+ if (bytesRead != FILE_BUFFER_SIZE)
+ {
+ // Calculate and report the final result
+ SHA1Result(&hash);
+ for(size_t i = 0; i < 5; i++)
+ {
+ reply.catf("%x", hash.Message_Digest[i]);
+ }
+
+ // Clean up again
+ fileBeingHashed->Close();
+ fileBeingHashed = nullptr;
+ return true;
+ }
+ return false;
+ }
+
+ // Something went wrong, we cannot read any more from the file
+ fileBeingHashed->Close();
+ fileBeingHashed = nullptr;
+ return true;
+}
+
+bool GCodes::AllAxesAreHomed() const
+{
+ const uint32_t allAxes = (1u << numAxes) - 1;
+ return (axesHomed & allAxes) == allAxes;
+}
+
+void GCodes::SetAllAxesNotHomed()
+{
+ axesHomed = 0;
+}
+
+// Resource locking/unlocking
+
+// Lock the resource, returning true if success.
+// Locking the same resource more than once only locks it once, there is no lock count held.
+bool GCodes::LockResource(const GCodeBuffer& gb, Resource r)
+{
+ if (resourceOwners[r] == &gb)
+ {
+ return true;
+ }
+ if (resourceOwners[r] == nullptr)
+ {
+ resourceOwners[r] = &gb;
+ gb.MachineState().lockedResources |= (1u << r);
+ return true;
+ }
+ return false;
+}
+
+bool GCodes::LockHeater(const GCodeBuffer& gb, int heater)
+{
+ if (heater >= 0 && heater < HEATERS)
+ {
+ return LockResource(gb, HeaterResourceBase + heater);
+ }
+ return true;
+}
+
+bool GCodes::LockFan(const GCodeBuffer& gb, int fan)
+{
+ if (fan >= 0 && fan < (int)NUM_FANS)
+ {
+ return LockResource(gb, FanResourceBase + fan);
+ }
+ return true;
+}
+
+// Lock the unshareable parts of the file system
+bool GCodes::LockFileSystem(const GCodeBuffer &gb)
+{
+ return LockResource(gb, FileSystemResource);
+}
+
+// Lock movement
+bool GCodes::LockMovement(const GCodeBuffer& gb)
+{
+ return LockResource(gb, MoveResource);
+}
+
+// Release all locks, except those that were owned when the current macro was started
+void GCodes::UnlockAll(const GCodeBuffer& gb)
+{
+ const GCodeMachineState * const mc = gb.MachineState().previous;
+ const uint32_t resourcesToKeep = (mc == nullptr) ? 0 : mc->lockedResources;
+ for (size_t i = 0; i < NumResources; ++i)
+ {
+ if (resourceOwners[i] == &gb && ((1u << i) & resourcesToKeep) == 0)
+ {
+ resourceOwners[i] = nullptr;
+ gb.MachineState().lockedResources &= ~(1u << i);
+ }
+ }
+}
+
+// Convert an array of longs to a bit map
+/*static*/ uint32_t GCodes::LongArrayToBitMap(const long *arr, size_t numEntries)
+{
+ uint32_t res = 0;
+ for (size_t i = 0; i < numEntries; ++i)
+ {
+ const long f = arr[i];
+ if (f >= 0 && f < 32)
+ {
+ res |= 1u << (unsigned int)f;
+ }
+ }
+ return res;
+}
+
+// End
diff --git a/src/GCodes/GCodes2.cpp b/src/GCodes/GCodes2.cpp
new file mode 100644
index 00000000..5c7ef148
--- /dev/null
+++ b/src/GCodes/GCodes2.cpp
@@ -0,0 +1,3481 @@
+/*
+ * GCodes2.cpp
+ *
+ * Created on: 3 Dec 2016
+ * Author: David
+ *
+ * This file contains the code to see what G, M or T command we have and start processing it.
+ */
+
+#include "RepRapFirmware.h"
+
+#ifdef DUET_NG
+#include "FirmwareUpdater.h"
+#endif
+
+const char* BED_EQUATION_G = "bed.g";
+const char* RESUME_G = "resume.g";
+const char* CANCEL_G = "cancel.g";
+const char* STOP_G = "stop.g";
+const char* SLEEP_G = "sleep.g";
+
+const float MinServoPulseWidth = 544.0, MaxServoPulseWidth = 2400.0;
+const uint16_t ServoRefreshFrequency = 50;
+
+// If the code to act on is completed, this returns true,
+// otherwise false. It is called repeatedly for a given
+// code until it returns true for that code.
+bool GCodes::ActOnCode(GCodeBuffer& gb, StringRef& reply)
+{
+ // Discard empty buffers right away
+ if (gb.IsEmpty())
+ {
+ return true;
+ }
+
+ // M-code parameters might contain letters T and G, e.g. in filenames.
+ // dc42 assumes that G-and T-code parameters never contain the letter M.
+ // Therefore we must check for an M-code first.
+ if (gb.Seen('M'))
+ {
+ return HandleMcode(gb, reply);
+ }
+ // dc42 doesn't think a G-code parameter ever contains letter T, or a T-code ever contains letter G.
+ // So it doesn't matter in which order we look for them.
+ if (gb.Seen('G'))
+ {
+ return HandleGcode(gb, reply);
+ }
+ if (gb.Seen('T'))
+ {
+ return HandleTcode(gb, reply);
+ }
+
+ // An invalid or queued buffer gets discarded
+ HandleReply(gb, false, "");
+ return true;
+}
+
+bool GCodes::HandleGcode(GCodeBuffer& gb, StringRef& reply)
+{
+ bool result = true;
+ bool error = false;
+
+ int code = gb.GetIValue();
+ if (simulationMode != 0 && code != 0 && code != 1 && code != 4 && code != 10 && code != 20 && code != 21 && code != 90 && code != 91 && code != 92)
+ {
+ return true; // we only simulate some gcodes
+ }
+
+ switch (code)
+ {
+ case 0: // There are no rapid moves...
+ case 1: // Ordinary move
+ if (!LockMovement(gb))
+ {
+ return false;
+ }
+ {
+ // Check for 'R' parameter here to go back to the coordinates at which the print was paused
+ // NOTE: restore point 2 (tool change) won't work when changing tools on dual axis machines because of X axis mapping.
+ // We could possibly fix this by saving the virtual X axis position instead of the physical axis positions.
+ // However, slicers normally command the tool to the correct place after a tool change, so we don't need this feature anyway.
+ int rParam = (gb.Seen('R')) ? gb.GetIValue() : 0;
+ RestorePoint *rp = (rParam == 1) ? &pauseRestorePoint : (rParam == 2) ? &toolChangeRestorePoint : nullptr;
+ if (rp != nullptr)
+ {
+ if (segmentsLeft != 0)
+ {
+ return false;
+ }
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ float offset = gb.Seen(axisLetters[axis]) ? gb.GetFValue() * distanceScale : 0.0;
+ moveBuffer.coords[axis] = rp->moveCoords[axis] + offset;
+ }
+ // For now we don't handle extrusion at the same time
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ moveBuffer.coords[drive] = 0.0;
+ }
+ moveBuffer.feedRate = (gb.Seen(feedrateLetter)) ? gb.GetFValue() : gb.MachineState().feedrate;
+ moveBuffer.filePos = noFilePosition;
+ moveBuffer.usePressureAdvance = false;
+ segmentsLeft = 1;
+ }
+ else
+ {
+ int res = SetUpMove(gb, reply);
+ if (res == 2)
+ {
+ gb.SetState(GCodeState::waitingForMoveToComplete);
+ }
+ result = (res != 0);
+ }
+ }
+ break;
+
+ case 4: // Dwell
+ result = DoDwell(gb);
+ break;
+
+ case 10: // Set/report offsets and temperatures, or retract
+ if (gb.Seen('P'))
+ {
+ if (!SetOrReportOffsets(gb, reply))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (!LockMovement(gb))
+ {
+ return false;
+ }
+ result = RetractFilament(true);
+ }
+ break;
+
+ case 11: // Un-retract
+ if (!LockMovement(gb))
+ {
+ return false;
+ }
+ result = RetractFilament(false);
+ break;
+
+ case 20: // Inches (which century are we living in, here?)
+ distanceScale = INCH_TO_MM;
+ break;
+
+ case 21: // mm
+ distanceScale = 1.0;
+ break;
+
+ case 28: // Home
+ result = DoHome(gb, reply, error);
+ break;
+
+ case 29: // Grid-based bed probing
+ if (!LockMovementAndWaitForStandstill(gb)) // do this first to make sure that a new grid isn't being defined
+ {
+ return false;
+ }
+ error = ProbeGrid(gb, reply);
+ break;
+
+ case 30: // Z probe/manually set at a position and set that as point P
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ if (reprap.GetMove()->IsDeltaMode() && !AllAxesAreHomed())
+ {
+ reply.copy("Must home a delta printer before bed probing");
+ error = true;
+ }
+ else
+ {
+ result = SetSingleZProbeAtAPosition(gb, reply);
+ }
+ break;
+
+ case 31: // Return the probe value, or set probe variables
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ result = SetPrintZProbe(gb, reply);
+ break;
+
+ case 32: // Probe Z at multiple positions and generate the bed transform
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+ // Try to execute bed.g
+ if (!DoFileMacro(gb, BED_EQUATION_G, reprap.GetMove()->IsDeltaMode()))
+ {
+ // If we get here then we are not on a delta printer and there is no bed.g file
+ if (GetAxisIsHomed(X_AXIS) && GetAxisIsHomed(Y_AXIS))
+ {
+ gb.SetState(GCodeState::setBed1); // no bed.g file, so use the coordinates specified by M557
+ }
+ else
+ {
+ // We can only do bed levelling if X and Y have already been homed
+ reply.copy("Must home X and Y before bed probing");
+ error = true;
+ }
+ }
+ break;
+
+ case 90: // Absolute coordinates
+ gb.MachineState().axesRelative = false;
+ break;
+
+ case 91: // Relative coordinates
+ gb.MachineState().axesRelative = true; // Axis movements (i.e. X, Y and Z)
+ break;
+
+ case 92: // Set position
+ result = SetPositions(gb);
+ break;
+
+ default:
+ error = true;
+ reply.printf("invalid G Code: %s", gb.Buffer());
+ }
+
+ if (result && gb.GetState() == GCodeState::normal)
+ {
+ UnlockAll(gb);
+ HandleReply(gb, error, reply.Pointer());
+ }
+ return result;
+}
+
+bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply)
+{
+ bool result = true;
+ bool error = false;
+
+ const int code = gb.GetIValue();
+ if (simulationMode != 0 && (code < 20 || code > 37) && code != 0 && code != 1 && code != 82 && code != 83 && code != 105 && code != 111 && code != 112 && code != 122 && code != 408 && code != 999)
+ {
+ return true; // we don't yet simulate most M codes
+ }
+
+ switch (code)
+ {
+ case 0: // Stop
+ case 1: // Sleep
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ {
+ bool wasPaused = isPaused; // isPaused gets cleared by CancelPrint
+ CancelPrint();
+ if (wasPaused)
+ {
+ reply.copy("Print cancelled");
+ // If we are cancelling a paused print with M0 and cancel.g exists then run it and do nothing else
+ if (code == 0)
+ {
+ if (DoFileMacro(gb, CANCEL_G, false))
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ gb.SetState((code == 0) ? GCodeState::stopping : GCodeState::sleeping);
+ DoFileMacro(gb, (code == 0) ? STOP_G : SLEEP_G, false);
+ break;
+
+#if SUPPORT_ROLAND
+ case 3: // Spin spindle
+ if (reprap.GetRoland()->Active())
+ {
+ if (gb.Seen('S'))
+ {
+ result = reprap.GetRoland()->ProcessSpindle(gb.GetFValue());
+ }
+ }
+ break;
+#endif
+
+ case 18: // Motors off
+ case 84:
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ {
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ SetAxisNotHomed(axis);
+ platform->DisableDrive(axis);
+ seen = true;
+ }
+ }
+
+ if (gb.Seen(extrudeLetter))
+ {
+ long int eDrive[MaxExtruders];
+ size_t eCount = numExtruders;
+ gb.GetLongArray(eDrive, eCount);
+ for (size_t i = 0; i < eCount; i++)
+ {
+ seen = true;
+ if (eDrive[i] < 0 || (size_t)eDrive[i] >= numExtruders)
+ {
+ reply.printf("Invalid extruder number specified: %ld", eDrive[i]);
+ error = true;
+ break;
+ }
+ platform->DisableDrive(numAxes + eDrive[i]);
+ }
+ }
+
+ if (gb.Seen('S'))
+ {
+ seen = true;
+
+ float idleTimeout = gb.GetFValue();
+ if (idleTimeout < 0.0)
+ {
+ reply.copy("Idle timeouts cannot be negative!");
+ error = true;
+ }
+ else
+ {
+ reprap.GetMove()->SetIdleTimeout(idleTimeout);
+ }
+ }
+
+ if (!seen)
+ {
+ DisableDrives();
+ }
+ }
+ break;
+
+ case 20: // List files on SD card
+ if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers
+ {
+ return false;
+ }
+ {
+ OutputBuffer *fileResponse;
+ const int sparam = (gb.Seen('S')) ? gb.GetIValue() : 0;
+ const char* dir = (gb.Seen('P')) ? gb.GetString() : platform->GetGCodeDir();
+
+ if (sparam == 2)
+ {
+ fileResponse = reprap.GetFilesResponse(dir, true); // Send the file list in JSON format
+ fileResponse->cat('\n');
+ }
+ else
+ {
+ if (!OutputBuffer::Allocate(fileResponse))
+ {
+ // Cannot allocate an output buffer, try again later
+ return false;
+ }
+
+ // To mimic the behaviour of the official RepRapPro firmware:
+ // If we are emulating RepRap then we print "GCode files:\n" at the start, otherwise we don't.
+ // If we are emulating Marlin and the code came via the serial/USB interface, then we don't put quotes around the names and we separate them with newline;
+ // otherwise we put quotes around them and separate them with comma.
+ if (platform->Emulating() == me || platform->Emulating() == reprapFirmware)
+ {
+ fileResponse->copy("GCode files:\n");
+ }
+
+ bool encapsulateList = ((&gb != serialGCode && &gb != telnetGCode) || platform->Emulating() != marlin);
+ FileInfo fileInfo;
+ if (platform->GetMassStorage()->FindFirst(dir, fileInfo))
+ {
+ // iterate through all entries and append each file name
+ do {
+ if (encapsulateList)
+ {
+ fileResponse->catf("%c%s%c%c", FILE_LIST_BRACKET, fileInfo.fileName, FILE_LIST_BRACKET, FILE_LIST_SEPARATOR);
+ }
+ else
+ {
+ fileResponse->catf("%s\n", fileInfo.fileName);
+ }
+ } while (platform->GetMassStorage()->FindNext(fileInfo));
+
+ if (encapsulateList)
+ {
+ // remove the last separator
+ (*fileResponse)[fileResponse->Length() - 1] = 0;
+ }
+ }
+ else
+ {
+ fileResponse->cat("NONE\n");
+ }
+ }
+
+ UnlockAll(gb);
+ HandleReply(gb, false, fileResponse);
+ return true;
+ }
+
+ case 21: // Initialise SD card
+ if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers
+ {
+ return false;
+ }
+ {
+ size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0;
+ result = platform->GetMassStorage()->Mount(card, reply, true);
+ }
+ break;
+
+ case 22: // Release SD card
+ if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers
+ {
+ return false;
+ }
+ {
+ size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0;
+ result = platform->GetMassStorage()->Unmount(card, reply);
+ }
+ break;
+
+ case 23: // Set file to print
+ case 32: // Select file and start SD print
+ if (fileGCode->OriginalMachineState().fileState.IsLive())
+ {
+ reply.copy("Cannot set file to print, because a file is already being printed");
+ error = true;
+ break;
+ }
+
+ if (code == 32 && !LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ {
+ const char* filename = gb.GetUnprecedentedString();
+ if (filename != nullptr)
+ {
+ QueueFileToPrint(filename);
+ if (fileToPrint.IsLive())
+ {
+ reprap.GetPrintMonitor()->StartingPrint(filename);
+ if (platform->Emulating() == marlin && (&gb == serialGCode || &gb == telnetGCode))
+ {
+ reply.copy("File opened\nFile selected");
+ }
+ else
+ {
+ // Command came from web interface or PanelDue, or not emulating Marlin, so send a nicer response
+ reply.printf("File %s selected for printing", filename);
+ }
+
+ if (code == 32)
+ {
+ fileGCode->OriginalMachineState().fileState.MoveFrom(fileToPrint);
+ reprap.GetPrintMonitor()->StartedPrint();
+ }
+ }
+ else
+ {
+ reply.printf("Failed to open file %s", filename);
+ }
+ }
+ }
+ break;
+
+ case 24: // Print/resume-printing the selected file
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+ if (isPaused)
+ {
+ gb.SetState(GCodeState::resuming1);
+ DoFileMacro(gb, RESUME_G);
+ }
+ else if (!fileToPrint.IsLive())
+ {
+ reply.copy("Cannot print, because no file is selected!");
+ error = true;
+ }
+ else
+ {
+ fileGCode->OriginalMachineState().fileState.MoveFrom(fileToPrint);
+ reprap.GetPrintMonitor()->StartedPrint();
+ }
+ break;
+
+ case 226: // Gcode Initiated Pause
+ if (&gb == fileGCode) // ignore M226 if it did't come from within a file being printed
+ {
+ if (!LockMovement(gb)) // lock movement before calling DoPause
+ {
+ return false;
+ }
+ DoPause(gb);
+ }
+ break;
+
+ case 25: // Pause the print
+ if (isPaused)
+ {
+ reply.copy("Printing is already paused!!");
+ error = true;
+ }
+ else if (!reprap.GetPrintMonitor()->IsPrinting())
+ {
+ reply.copy("Cannot pause print, because no file is being printed!");
+ error = true;
+ }
+ else
+ {
+ if (!LockMovement(gb)) // lock movement before calling DoPause
+ {
+ return false;
+ }
+ DoPause(gb);
+ }
+ break;
+
+ case 26: // Set SD position
+ if (gb.Seen('S'))
+ {
+ const FilePosition value = gb.GetIValue();
+ if (value < 0)
+ {
+ reply.copy("SD positions can't be negative!");
+ error = true;
+ }
+ else if (fileGCode->OriginalMachineState().fileState.IsLive())
+ {
+ if (!fileGCode->OriginalMachineState().fileState.Seek(value))
+ {
+ reply.copy("The specified SD position is invalid!");
+ error = true;
+ }
+ }
+ else if (fileToPrint.IsLive())
+ {
+ if (!fileToPrint.Seek(value))
+ {
+ reply.copy("The specified SD position is invalid!");
+ error = true;
+ }
+ }
+ else
+ {
+ reply.copy("Cannot set SD file position, because no print is in progress!");
+ error = true;
+ }
+ }
+ else
+ {
+ reply.copy("You must specify the SD position in bytes using the S parameter.");
+ error = true;
+ }
+ break;
+
+ case 27: // Report print status - Deprecated
+ if (reprap.GetPrintMonitor()->IsPrinting())
+ {
+ // Pronterface keeps sending M27 commands if "Monitor status" is checked, and it specifically expects the following response syntax
+ FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState;
+ reply.printf("SD printing byte %lu/%lu", fileBeingPrinted.GetPosition(), fileBeingPrinted.Length());
+ }
+ else
+ {
+ reply.copy("Not SD printing.");
+ }
+ break;
+
+ case 28: // Write to file
+ {
+ const char* str = gb.GetUnprecedentedString();
+ if (str != nullptr)
+ {
+ bool ok = OpenFileToWrite(gb, platform->GetGCodeDir(), str);
+ if (ok)
+ {
+ reply.printf("Writing to file: %s", str);
+ }
+ else
+ {
+ reply.printf("Can't open file %s for writing.", str);
+ error = true;
+ }
+ }
+ }
+ break;
+
+ case 29: // End of file being written; should be intercepted before getting here
+ reply.copy("GCode end-of-file being interpreted.");
+ break;
+
+ case 30: // Delete file
+ {
+ const char *filename = gb.GetUnprecedentedString();
+ if (filename != nullptr)
+ {
+ DeleteFile(filename);
+ }
+ }
+ break;
+
+ // For case 32, see case 24
+
+ case 36: // Return file information
+ if (!LockFileSystem(gb)) // getting file info takes several calls and isn't reentrant
+ {
+ return false;
+ }
+ {
+ const char* filename = gb.GetUnprecedentedString(true); // get filename, or nullptr if none provided
+ OutputBuffer *fileInfoResponse;
+ result = reprap.GetPrintMonitor()->GetFileInfoResponse(filename, fileInfoResponse);
+ if (result)
+ {
+ fileInfoResponse->cat('\n');
+ UnlockAll(gb);
+ HandleReply(gb, false, fileInfoResponse);
+ return true;
+ }
+ }
+ break;
+
+ case 37: // Simulation mode on/off
+ if (gb.Seen('S'))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+ bool wasSimulating = (simulationMode != 0);
+ simulationMode = (uint8_t)gb.GetIValue();
+ reprap.GetMove()->Simulate(simulationMode);
+
+ if (simulationMode != 0)
+ {
+ simulationTime = 0.0;
+ if (!wasSimulating)
+ {
+ // Starting a new simulation, so save the current position
+ reprap.GetMove()->GetCurrentUserPosition(simulationRestorePoint.moveCoords, 0, reprap.GetCurrentXAxes());
+ simulationRestorePoint.feedRate = gb.MachineState().feedrate;
+ }
+ }
+ else if (wasSimulating)
+ {
+ // Ending a simulation, so restore the position
+ SetPositions(simulationRestorePoint.moveCoords);
+ for (size_t i = 0; i < DRIVES; ++i)
+ {
+ moveBuffer.coords[i] = simulationRestorePoint.moveCoords[i];
+ }
+ gb.MachineState().feedrate = simulationRestorePoint.feedRate;
+ }
+ }
+ else
+ {
+ reply.printf("Simulation mode: %s, move time: %.1f sec, other time: %.1f sec",
+ (simulationMode != 0) ? "on" : "off", reprap.GetMove()->GetSimulationTime(), simulationTime);
+ }
+ break;
+
+ case 38: // Report SHA1 of file
+ if (!LockFileSystem(gb)) // getting file hash takes several calls and isn't reentrant
+ {
+ return false;
+ }
+ if (fileBeingHashed == nullptr)
+ {
+ // See if we can open the file and start hashing
+ const char* filename = gb.GetUnprecedentedString(true);
+ if (StartHash(filename))
+ {
+ // Hashing is now in progress...
+ result = false;
+ }
+ else
+ {
+ error = true;
+ reply.printf("Cannot open file: %s", filename);
+ }
+ }
+ else
+ {
+ // This can take some time. All the actual heavy lifting is in dedicated methods
+ result = AdvanceHash(reply);
+ }
+ break;
+
+ case 42: // Turn an output pin on or off
+ if (gb.Seen('P'))
+ {
+ const int logicalPin = gb.GetIValue();
+ Pin pin;
+ bool invert;
+ if (platform->GetFirmwarePin(logicalPin, PinAccess::pwm, pin, invert))
+ {
+ if (gb.Seen('S'))
+ {
+ float val = gb.GetFValue();
+ if (val > 1.0)
+ {
+ val /= 255.0;
+ }
+ val = constrain<float>(val, 0.0, 1.0);
+ if (invert)
+ {
+ val = 1.0 - val;
+ }
+ Platform::WriteAnalog(pin, val, DefaultPinWritePwmFreq);
+ }
+ // Ignore the command if no S parameter provided
+ }
+ else
+ {
+ reply.printf("Logical pin %d is not available for writing", logicalPin);
+ error = true;
+ }
+ }
+ break;
+
+ case 80: // ATX power on
+ platform->SetAtxPower(true);
+ break;
+
+ case 81: // ATX power off
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ platform->SetAtxPower(false);
+ break;
+
+ case 82: // Use absolute extruder positioning
+ if (gb.MachineState().drivesRelative) // don't reset the absolute extruder position if it was already absolute
+ {
+ for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
+ {
+ lastRawExtruderPosition[extruder] = 0.0;
+ }
+ gb.MachineState().drivesRelative = false;
+ }
+ break;
+
+ case 83: // Use relative extruder positioning
+ if (!gb.MachineState().drivesRelative) // don't reset the absolute extruder position if it was already relative
+ {
+ for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
+ {
+ lastRawExtruderPosition[extruder] = 0.0;
+ }
+ gb.MachineState().drivesRelative = true;
+ }
+ break;
+
+ // For case 84, see case 18
+
+ case 85: // Set inactive time
+ break;
+
+ case 92: // Set/report steps/mm for some axes
+ {
+ // Save the current positions as we may need them later
+ float positionNow[DRIVES];
+ Move *move = reprap.GetMove();
+ move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes());
+
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ platform->SetDriveStepsPerUnit(axis, gb.GetFValue());
+ seen = true;
+ }
+ }
+
+ if (gb.Seen(extrudeLetter))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ seen = true;
+ float eVals[MaxExtruders];
+ size_t eCount = numExtruders;
+ gb.GetFloatArray(eVals, eCount, true);
+
+ // The user may not have as many extruders as we allow for, so just set the ones for which a value is provided
+ for (size_t e = 0; e < eCount; e++)
+ {
+ platform->SetDriveStepsPerUnit(numAxes + e, eVals[e]);
+ }
+ }
+
+ if (seen)
+ {
+ // On a delta, if we change the drive steps/mm then we need to recalculate the motor positions
+ SetPositions(positionNow);
+ }
+ else
+ {
+ reply.copy("Steps/mm: ");
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ reply.catf("%c: %.3f, ", axisLetters[axis], platform->DriveStepsPerUnit(axis));
+ }
+ reply.catf("E:");
+ char sep = ' ';
+ for (size_t extruder = 0; extruder < numExtruders; extruder++)
+ {
+ reply.catf("%c%.3f", sep, platform->DriveStepsPerUnit(extruder + numAxes));
+ sep = ':';
+ }
+ }
+ }
+ break;
+
+ case 98: // Call Macro/Subprogram
+ if (gb.Seen('P'))
+ {
+ DoFileMacro(gb, gb.GetString());
+ }
+ break;
+
+ case 99: // Return from Macro/Subprogram
+ FileMacroCyclesReturn(gb);
+ break;
+
+ case 104: // Deprecated. This sets the active temperature of every heater of the active tool
+ if (gb.Seen('S'))
+ {
+ float temperature = gb.GetFValue();
+ Tool* tool;
+ if (gb.Seen('T'))
+ {
+ int toolNumber = gb.GetIValue();
+ toolNumber += gb.GetToolNumberAdjust();
+ tool = reprap.GetTool(toolNumber);
+ }
+ else
+ {
+ tool = reprap.GetCurrentOrDefaultTool();
+ }
+ SetToolHeaters(tool, temperature);
+ }
+ break;
+
+ case 105: // Get temperatures
+ {
+ const int8_t bedHeater = reprap.GetHeat()->GetBedHeater();
+ const int8_t chamberHeater = reprap.GetHeat()->GetChamberHeater();
+ reply.copy("T:");
+ for (int8_t heater = 0; heater < HEATERS; heater++)
+ {
+ if (heater != bedHeater && heater != chamberHeater)
+ {
+ Heat::HeaterStatus hs = reprap.GetHeat()->GetStatus(heater);
+ if (hs != Heat::HS_off && hs != Heat::HS_fault)
+ {
+ reply.catf("%.1f ", reprap.GetHeat()->GetTemperature(heater));
+ }
+ }
+ }
+ if (bedHeater >= 0)
+ {
+ reply.catf("B:%.1f", reprap.GetHeat()->GetTemperature(bedHeater));
+ }
+ else
+ {
+ // I'm not sure whether Pronterface etc. can handle a missing bed temperature, so return zero
+ reply.cat("B:0.0");
+ }
+ if (chamberHeater >= 0.0)
+ {
+ reply.catf(" C:%.1f", reprap.GetHeat()->GetTemperature(chamberHeater));
+ }
+ }
+ break;
+
+ case 106: // Set/report fan values
+ {
+ bool seenFanNum = false;
+ int32_t fanNum = 0; // Default to the first fan
+ gb.TryGetIValue('P', fanNum, seenFanNum);
+ if (fanNum < 0 || fanNum > (int)NUM_FANS)
+ {
+ reply.printf("Fan number %d is invalid, must be between 0 and %u", fanNum, NUM_FANS);
+ }
+ else
+ {
+ bool seen = false;
+ Fan& fan = platform->GetFan(fanNum);
+
+ if (gb.Seen('I')) // Invert cooling
+ {
+ const int invert = gb.GetIValue();
+ if (invert < 0)
+ {
+ fan.Disable();
+ }
+ else
+ {
+ fan.SetInverted(invert > 0);
+ }
+ seen = true;
+ }
+
+ if (gb.Seen('F')) // Set PWM frequency
+ {
+ fan.SetPwmFrequency(gb.GetFValue());
+ seen = true;
+ }
+
+ if (gb.Seen('T')) // Set thermostatic trigger temperature
+ {
+ seen = true;
+ fan.SetTriggerTemperature(gb.GetFValue());
+ }
+
+ if (gb.Seen('B')) // Set blip time
+ {
+ seen = true;
+ fan.SetBlipTime(gb.GetFValue());
+ }
+
+ if (gb.Seen('L')) // Set minimum speed
+ {
+ seen = true;
+ fan.SetMinValue(gb.GetFValue());
+ }
+
+ if (gb.Seen('H')) // Set thermostatically-controller heaters
+ {
+ seen = true;
+ long heaters[HEATERS];
+ size_t numH = HEATERS;
+ gb.GetLongArray(heaters, numH);
+ // Note that M106 H-1 disables thermostatic mode. The following code implements that automatically.
+ uint16_t hh = 0;
+ for (size_t h = 0; h < numH; ++h)
+ {
+ const int hnum = heaters[h];
+ if (hnum >= 0 && hnum < HEATERS)
+ {
+ hh |= (1u << (unsigned int)hnum);
+ }
+ }
+ if (hh != 0)
+ {
+ platform->SetFanValue(fanNum, 1.0); // default the fan speed to full for safety
+ }
+ fan.SetHeatersMonitored(hh);
+ }
+
+ if (gb.Seen('S')) // Set new fan value - process this after processing 'H' or it may not be acted on
+ {
+ const float f = constrain<float>(gb.GetFValue(), 0.0, 255.0);
+ if (seen || seenFanNum)
+ {
+ platform->SetFanValue(fanNum, f);
+ }
+ else
+ {
+ // We are processing an M106 S### command with no other recognised parameters and we have a tool selected.
+ // Apply the fan speed setting to the fans in the fan mapping for the current tool.
+ lastDefaultFanSpeed = f;
+ SetMappedFanSpeed();
+ }
+ }
+ else if (gb.Seen('R'))
+ {
+ const int i = gb.GetIValue();
+ switch(i)
+ {
+ case 0:
+ case 1:
+ // Restore fan speed to value when print was paused
+ platform->SetFanValue(fanNum, pausedFanValues[fanNum]);
+ break;
+ case 2:
+ // Set the speeds of mapped fans to the last known value. Fan number is ignored.
+ SetMappedFanSpeed();
+ break;
+ default:
+ break;
+ }
+ }
+ else if (!seen)
+ {
+ reply.printf("Fan%i frequency: %dHz, speed: %d%%, min: %d%%, blip: %.2f, inverted: %s",
+ fanNum,
+ (int)(fan.GetPwmFrequency()),
+ (int)(fan.GetValue() * 100.0),
+ (int)(fan.GetMinValue() * 100.0),
+ fan.GetBlipTime(),
+ (fan.GetInverted()) ? "yes" : "no");
+ uint16_t hh = fan.GetHeatersMonitored();
+ if (hh != 0)
+ {
+ reply.catf(", trigger: %dC, heaters:", (int)fan.GetTriggerTemperature());
+ for (unsigned int i = 0; i < HEATERS; ++i)
+ {
+ if ((hh & (1u << i)) != 0)
+ {
+ reply.catf(" %u", i);
+ }
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case 107: // Fan off - deprecated
+ platform->SetFanValue(0, 0.0); //T3P3 as deprecated only applies to fan0
+ break;
+
+ case 108: // Cancel waiting for temperature
+ if (isWaiting)
+ {
+ cancelWait = true;
+ }
+ break;
+
+ case 109: // Deprecated in RRF, but widely generated by slicers
+ {
+ float temperature;
+ if (gb.Seen('R'))
+ {
+ gb.MachineState().waitWhileCooling = true;
+ temperature = gb.GetFValue();
+ }
+ else if (gb.Seen('S'))
+ {
+ gb.MachineState().waitWhileCooling = false;
+ temperature = gb.GetFValue();
+ }
+ else
+ {
+ break; // no target temperature given
+ }
+
+ Tool *tool;
+ if (gb.Seen('T'))
+ {
+ int toolNumber = gb.GetIValue();
+ toolNumber += gb.GetToolNumberAdjust();
+ tool = reprap.GetTool(toolNumber);
+ }
+ else
+ {
+ tool = reprap.GetCurrentOrDefaultTool();
+ }
+
+ SetToolHeaters(tool, temperature);
+ if (tool == nullptr)
+ {
+ break; // SetToolHeaters will already have generated an error message
+ }
+
+ // M109 implies waiting for temperature to be reached, so it doesn't make sense unless the tool has been selected.
+ // Sadly, many slicers use M109 without selecting the tool first. So we select it here if necessary.
+ if (tool != reprap.GetCurrentTool())
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+ newToolNumber = tool->Number();
+ StartToolChange(gb, true);
+ }
+ else
+ {
+ gb.SetState(GCodeState::m109ToolChangeComplete);
+ }
+ }
+ break;
+
+ case 110: // Set line numbers - line numbers are dealt with in the GCodeBuffer class
+ break;
+
+ case 111: // Debug level
+ if (gb.Seen('S'))
+ {
+ bool dbv = (gb.GetIValue() != 0);
+ if (gb.Seen('P'))
+ {
+ reprap.SetDebug(static_cast<Module>(gb.GetIValue()), dbv);
+ }
+ else
+ {
+ reprap.SetDebug(dbv);
+ }
+ }
+ else
+ {
+ reprap.PrintDebug();
+ }
+ break;
+
+ case 112: // Emergency stop - acted upon in Webserver, but also here in case it comes from USB etc.
+ DoEmergencyStop();
+ break;
+
+ case 114:
+ GetCurrentCoordinates(reply);
+ break;
+
+ case 115: // Print firmware version or set hardware type
+ if (gb.Seen('P'))
+ {
+ platform->SetBoardType((BoardType)gb.GetIValue());
+ }
+ else
+ {
+ reply.printf("FIRMWARE_NAME: %s FIRMWARE_VERSION: %s ELECTRONICS: %s", NAME, VERSION, platform->GetElectronicsString());
+#ifdef DUET_NG
+ const char* expansionName = DuetExpansion::GetExpansionBoardName();
+ if (expansionName != nullptr)
+ {
+ reply.catf(" + %s", expansionName);
+ }
+#endif
+ reply.catf(" FIRMWARE_DATE: %s", DATE);
+ }
+ break;
+
+ case 116: // Wait for set temperatures
+ {
+ bool seen = false;
+ if (gb.Seen('P'))
+ {
+ // Wait for the heaters associated with the specified tool to be ready
+ int toolNumber = gb.GetIValue();
+ toolNumber += gb.GetToolNumberAdjust();
+ if (!cancelWait && !ToolHeatersAtSetTemperatures(reprap.GetTool(toolNumber), true))
+ {
+ isWaiting = true;
+ return false;
+ }
+ seen = true;
+ }
+
+ if (gb.Seen('H'))
+ {
+ // Wait for specified heaters to be ready
+ long heaters[HEATERS];
+ size_t heaterCount = HEATERS;
+ gb.GetLongArray(heaters, heaterCount);
+ if (!cancelWait)
+ {
+ for (size_t i=0; i<heaterCount; i++)
+ {
+ if (!reprap.GetHeat()->HeaterAtSetTemperature(heaters[i], true))
+ {
+ isWaiting = true;
+ return false;
+ }
+ }
+ }
+ seen = true;
+ }
+
+ if (gb.Seen('C'))
+ {
+ // Wait for chamber heater to be ready
+ const int8_t chamberHeater = reprap.GetHeat()->GetChamberHeater();
+ if (chamberHeater != -1)
+ {
+ if (!cancelWait && !reprap.GetHeat()->HeaterAtSetTemperature(chamberHeater, true))
+ {
+ isWaiting = true;
+ return false;
+ }
+ }
+ seen = true;
+ }
+
+ // Wait for all heaters to be ready
+ if (!seen && !cancelWait && !reprap.GetHeat()->AllHeatersAtSetTemperatures(true))
+ {
+ isWaiting = true;
+ return false;
+ }
+
+ // If we get here, there is nothing more to wait for
+ cancelWait = isWaiting = false;
+ }
+ break;
+
+ case 117: // Display message
+ {
+ const char *msg = gb.GetUnprecedentedString(true);
+ reprap.SetMessage((msg == nullptr) ? "" : msg);
+ }
+ break;
+
+ case 119:
+ reply.copy("Endstops - ");
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ reply.catf("%c: %s, ", axisLetters[axis], TranslateEndStopResult(platform->Stopped(axis)));
+ }
+ reply.catf("Z probe: %s", TranslateEndStopResult(platform->GetZProbeResult()));
+ break;
+
+ case 120:
+ Push(gb);
+ break;
+
+ case 121:
+ Pop(gb);
+ break;
+
+ case 122:
+ {
+ int val = (gb.Seen('P')) ? gb.GetIValue() : 0;
+ if (val == 0)
+ {
+ reprap.Diagnostics(gb.GetResponseMessageType());
+ }
+ else
+ {
+ platform->DiagnosticTest(val);
+ }
+ }
+ break;
+
+ case 135: // Set PID sample interval
+ if (gb.Seen('S'))
+ {
+ platform->SetHeatSampleTime(gb.GetFValue() * 0.001); // Value is in milliseconds; we want seconds
+ }
+ else
+ {
+ reply.printf("Heat sample time is %.3f seconds", platform->GetHeatSampleTime());
+ }
+ break;
+
+ case 140: // Set bed temperature
+ {
+ int8_t bedHeater;
+ if (gb.Seen('H'))
+ {
+ bedHeater = gb.GetIValue();
+ if (bedHeater < 0)
+ {
+ // Make sure we stay within reasonable boundaries...
+ bedHeater = -1;
+
+ // If we're disabling the hot bed, make sure the old heater is turned off
+ reprap.GetHeat()->SwitchOff(reprap.GetHeat()->GetBedHeater());
+ }
+ else if (bedHeater >= HEATERS)
+ {
+ reply.copy("Invalid heater number!");
+ error = true;
+ break;
+ }
+ reprap.GetHeat()->SetBedHeater(bedHeater);
+
+ if (bedHeater < 0)
+ {
+ // Stop here if the hot bed has been disabled
+ break;
+ }
+ }
+ else
+ {
+ bedHeater = reprap.GetHeat()->GetBedHeater();
+ if (bedHeater < 0)
+ {
+ reply.copy("Hot bed is not present!");
+ error = true;
+ break;
+ }
+ }
+
+ if(gb.Seen('S'))
+ {
+ float temperature = gb.GetFValue();
+ if (temperature < NEARLY_ABS_ZERO)
+ {
+ reprap.GetHeat()->SwitchOff(bedHeater);
+ }
+ else
+ {
+ reprap.GetHeat()->SetActiveTemperature(bedHeater, temperature);
+ reprap.GetHeat()->Activate(bedHeater);
+ }
+ }
+ if(gb.Seen('R'))
+ {
+ reprap.GetHeat()->SetStandbyTemperature(bedHeater, gb.GetFValue());
+ }
+ }
+ break;
+
+ case 141: // Chamber temperature
+ {
+ bool seen = false;
+ if (gb.Seen('H'))
+ {
+ seen = true;
+
+ int heater = gb.GetIValue();
+ if (heater < 0)
+ {
+ const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater();
+ if (currentHeater != -1)
+ {
+ reprap.GetHeat()->SwitchOff(currentHeater);
+ }
+
+ reprap.GetHeat()->SetChamberHeater(-1);
+ }
+ else if (heater < HEATERS)
+ {
+ reprap.GetHeat()->SetChamberHeater(heater);
+ }
+ else
+ {
+ reply.copy("Bad heater number specified!");
+ error = true;
+ }
+ }
+
+ if (gb.Seen('S'))
+ {
+ seen = true;
+
+ const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater();
+ if (currentHeater != -1)
+ {
+ float temperature = gb.GetFValue();
+
+ if (temperature < NEARLY_ABS_ZERO)
+ {
+ reprap.GetHeat()->SwitchOff(currentHeater);
+ }
+ else
+ {
+ reprap.GetHeat()->SetActiveTemperature(currentHeater, temperature);
+ reprap.GetHeat()->Activate(currentHeater);
+ }
+ }
+ else
+ {
+ reply.copy("No chamber heater has been set up yet!");
+ error = true;
+ }
+ }
+
+ if (!seen)
+ {
+ const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater();
+ if (currentHeater != -1)
+ {
+ reply.printf("Chamber heater %d is currently at %.1fC", currentHeater, reprap.GetHeat()->GetTemperature(currentHeater));
+ }
+ else
+ {
+ reply.copy("No chamber heater has been configured yet.");
+ }
+ }
+ }
+ break;
+
+ case 143: // Set temperature limit
+ {
+ const int heater = (gb.Seen('H')) ? gb.GetIValue() : 1; // default to extruder 1 if no heater number provided
+ if (heater < 0 || heater >= HEATERS)
+ {
+ reply.copy("Invalid heater number");
+ error = true;
+ }
+ else if (gb.Seen('S'))
+ {
+ const float limit = gb.GetFValue();
+ if (limit > BAD_LOW_TEMPERATURE && limit < BAD_ERROR_TEMPERATURE)
+ {
+ reprap.GetHeat()->SetTemperatureLimit(heater, limit);
+ }
+ else
+ {
+ reply.copy("Invalid temperature limit");
+ error = true;
+ }
+ }
+ else
+ {
+ reply.printf("Temperature limit for heater %d is %.1fC", heater, reprap.GetHeat()->GetTemperatureLimit(heater));
+ }
+ }
+ break;
+
+ case 144: // Set bed to standby
+ {
+ const int8_t bedHeater = reprap.GetHeat()->GetBedHeater();
+ if (bedHeater >= 0)
+ {
+ reprap.GetHeat()->Standby(bedHeater);
+ }
+ }
+ break;
+
+ case 190: // Set bed temperature and wait
+ case 191: // Set chamber temperature and wait
+ {
+ const int8_t heater = (code == 191) ? reprap.GetHeat()->GetChamberHeater() : reprap.GetHeat()->GetBedHeater();
+ if (heater >= 0)
+ {
+ float temperature;
+ bool waitWhenCooling;
+ if (gb.Seen('R'))
+ {
+ waitWhenCooling = true;
+ temperature = gb.GetFValue();
+ }
+ else if (gb.Seen('S'))
+ {
+ waitWhenCooling = false;
+ temperature = gb.GetFValue();
+ }
+ else
+ {
+ break; // no target temperature given
+ }
+
+ reprap.GetHeat()->SetActiveTemperature(heater, temperature);
+ reprap.GetHeat()->Activate(heater);
+ if (cancelWait || reprap.GetHeat()->HeaterAtSetTemperature(heater, waitWhenCooling))
+ {
+ cancelWait = isWaiting = false;
+ break;
+ }
+ // In Marlin emulation mode we should return some sort of (undocumented) message here every second...
+ isWaiting = true;
+ return false;
+ }
+ }
+ break;
+
+ case 201: // Set/print axis accelerations
+ {
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ platform->SetAcceleration(axis, gb.GetFValue() * distanceScale);
+ seen = true;
+ }
+ }
+
+ if (gb.Seen(extrudeLetter))
+ {
+ seen = true;
+ float eVals[MaxExtruders];
+ size_t eCount = numExtruders;
+ gb.GetFloatArray(eVals, eCount, true);
+ for (size_t e = 0; e < eCount; e++)
+ {
+ platform->SetAcceleration(numAxes + e, eVals[e] * distanceScale);
+ }
+ }
+
+ if (gb.Seen('P'))
+ {
+ // Set max average printing acceleration
+ platform->SetMaxAverageAcceleration(gb.GetFValue() * distanceScale);
+ seen = true;
+ }
+
+ if (!seen)
+ {
+ reply.printf("Accelerations: ");
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ reply.catf("%c: %.1f, ", axisLetters[axis], platform->Acceleration(axis) / distanceScale);
+ }
+ reply.cat("E:");
+ char sep = ' ';
+ for (size_t extruder = 0; extruder < numExtruders; extruder++)
+ {
+ reply.catf("%c%.1f", sep, platform->Acceleration(extruder + numAxes) / distanceScale);
+ sep = ':';
+ }
+ reply.catf(", avg. printing: %.1f", platform->GetMaxAverageAcceleration());
+ }
+ }
+ break;
+
+ case 203: // Set/print maximum feedrates
+ {
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ platform->SetMaxFeedrate(axis, gb.GetFValue() * distanceScale * secondsToMinutes); // G Code feedrates are in mm/minute; we need mm/sec
+ seen = true;
+ }
+ }
+
+ if (gb.Seen(extrudeLetter))
+ {
+ seen = true;
+ float eVals[MaxExtruders];
+ size_t eCount = numExtruders;
+ gb.GetFloatArray(eVals, eCount, true);
+ for (size_t e = 0; e < eCount; e++)
+ {
+ platform->SetMaxFeedrate(numAxes + e, eVals[e] * distanceScale * secondsToMinutes);
+ }
+ }
+
+ if (!seen)
+ {
+ reply.copy("Maximum feedrates: ");
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ reply.catf("%c: %.1f, ", axisLetters[axis], platform->MaxFeedrate(axis) / (distanceScale * secondsToMinutes));
+ }
+ reply.cat("E:");
+ char sep = ' ';
+ for (size_t extruder = 0; extruder < numExtruders; extruder++)
+ {
+ reply.catf("%c%.1f", sep, platform->MaxFeedrate(extruder + numAxes) / (distanceScale * secondsToMinutes));
+ sep = ':';
+ }
+ }
+ }
+ break;
+
+ case 205: //M205 advanced settings: minimum travel speed S=while printing T=travel only, B=minimum segment time X= maximum xy jerk, Z=maximum Z jerk
+ // This is superseded in this firmware by M codes for the separate types (e.g. M566).
+ break;
+
+ case 206: // Offset axes - Deprecated
+ result = OffsetAxes(gb);
+ break;
+
+ case 207: // Set firmware retraction details
+ {
+ bool seen = false;
+ if (gb.Seen('S'))
+ {
+ retractLength = max<float>(gb.GetFValue(), 0.0);
+ seen = true;
+ }
+ if (gb.Seen('R'))
+ {
+ retractExtra = max<float>(gb.GetFValue(), -retractLength);
+ seen = true;
+ }
+ if (gb.Seen('F'))
+ {
+ unRetractSpeed = retractSpeed = max<float>(gb.GetFValue(), 60.0);
+ seen = true;
+ }
+ if (gb.Seen('T')) // must do this one after 'F'
+ {
+ unRetractSpeed = max<float>(gb.GetFValue(), 60.0);
+ seen = true;
+ }
+ if (gb.Seen('Z'))
+ {
+ retractHop = max<float>(gb.GetFValue(), 0.0);
+ seen = true;
+ }
+ if (!seen)
+ {
+ reply.printf("Retraction settings: length %.2f/%.2fmm, speed %d/%dmm/min, Z hop %.2fmm",
+ retractLength, retractLength + retractExtra, (int)retractSpeed, (int)unRetractSpeed, retractHop);
+ }
+ }
+ break;
+
+ case 208: // Set/print maximum axis lengths. If there is an S parameter with value 1 then we set the min value, else we set the max value.
+ {
+ bool setMin = (gb.Seen('S') ? (gb.GetIValue() == 1) : false);
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ float value = gb.GetFValue() * distanceScale;
+ if (setMin)
+ {
+ platform->SetAxisMinimum(axis, value);
+ }
+ else
+ {
+ platform->SetAxisMaximum(axis, value);
+ }
+ seen = true;
+ }
+ }
+
+ if (!seen)
+ {
+ reply.copy("Axis limits ");
+ char sep = '-';
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ reply.catf("%c %c: %.1f min, %.1f max", sep, axisLetters[axis], platform->AxisMinimum(axis),
+ platform->AxisMaximum(axis));
+ sep = ',';
+ }
+ }
+ }
+ break;
+
+ case 210: // Set/print homing feed rates
+ // This is no longer used, but for backwards compatibility we don't report an error
+ break;
+
+ case 220: // Set/report speed factor override percentage
+ if (gb.Seen('S'))
+ {
+ float newSpeedFactor = (gb.GetFValue() / 100.0) * secondsToMinutes; // include the conversion from mm/minute to mm/second
+ if (newSpeedFactor > 0.0)
+ {
+ gb.MachineState().feedrate *= newSpeedFactor / speedFactor;
+ if (segmentsLeft != 0 && !moveBuffer.isFirmwareRetraction)
+ {
+ // The last move has not gone yet, so we can update it
+ moveBuffer.feedRate *= newSpeedFactor / speedFactor;
+ }
+ speedFactor = newSpeedFactor;
+ }
+ else
+ {
+ reply.printf("Invalid speed factor specified.");
+ error = true;
+ }
+ }
+ else
+ {
+ reply.printf("Speed factor override: %.1f%%", speedFactor * minutesToSeconds * 100.0);
+ }
+ break;
+
+ case 221: // Set/report extrusion factor override percentage
+ {
+ int32_t extruder = 0;
+ bool dummy;
+ gb.TryGetIValue('D', extruder, dummy);
+
+ if (extruder >= 0 && extruder < (int32_t)numExtruders)
+ {
+ if (gb.Seen('S')) // S parameter sets the override percentage
+ {
+ const float extrusionFactor = gb.GetFValue() / 100.0;
+ if (extrusionFactor >= 0.0)
+ {
+ if (segmentsLeft != 0 && !moveBuffer.isFirmwareRetraction)
+ {
+ moveBuffer.coords[extruder + numAxes] *= extrusionFactor/extrusionFactors[extruder]; // last move not gone, so update it
+ }
+ extrusionFactors[extruder] = extrusionFactor;
+ }
+ }
+ else
+ {
+ reply.printf("Extrusion factor override for extruder %d: %.1f%%", extruder, extrusionFactors[extruder] * 100.0);
+ }
+ }
+ }
+ break;
+
+ // For case 226, see case 25
+
+ case 280: // Servos
+ if (gb.Seen('P'))
+ {
+ const int servoIndex = gb.GetIValue();
+ Pin servoPin;
+ bool invert;
+ if (platform->GetFirmwarePin(servoIndex, PinAccess::servo, servoPin, invert))
+ {
+ if (gb.Seen('I') && gb.GetIValue() > 0)
+ {
+ invert = !invert;
+ }
+
+ if (gb.Seen('S'))
+ {
+ float angleOrWidth = gb.GetFValue();
+ if (angleOrWidth < 0.0)
+ {
+ // Disable the servo by setting the pulse width to zero
+ Platform::WriteAnalog(servoPin, (invert) ? 1.0 : 0.0, ServoRefreshFrequency);
+ }
+ else
+ {
+ if (angleOrWidth < MinServoPulseWidth)
+ {
+ // User gave an angle so convert it to a pulse width in microseconds
+ angleOrWidth = (min<float>(angleOrWidth, 180.0) * ((MaxServoPulseWidth - MinServoPulseWidth) / 180.0)) + MinServoPulseWidth;
+ }
+ else if (angleOrWidth > MaxServoPulseWidth)
+ {
+ angleOrWidth = MaxServoPulseWidth;
+ }
+ float pwm = angleOrWidth * (ServoRefreshFrequency/1e6);
+ if (invert)
+ {
+ pwm = 1.0 - pwm;
+ }
+ Platform::WriteAnalog(servoPin, pwm, ServoRefreshFrequency);
+ }
+ }
+ // We don't currently allow the servo position to be read back
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Error: Invalid servo index %d in M280 command\n", servoIndex);
+ }
+ }
+ break;
+
+ case 300: // Beep
+ {
+ const int ms = (gb.Seen('P')) ? gb.GetIValue() : 1000; // time in milliseconds
+ const int freq = (gb.Seen('S')) ? gb.GetIValue() : 4600; // 4600Hz produces the loudest sound on a PanelDue
+ reprap.Beep(freq, ms);
+ }
+ break;
+
+ case 301: // Set/report hot end PID values
+ SetPidParameters(gb, 1, reply);
+ break;
+
+ case 302: // Allow, deny or report cold extrudes
+ if (gb.Seen('P'))
+ {
+ reprap.GetHeat()->AllowColdExtrude(gb.GetIValue() > 0);
+ }
+ else
+ {
+ reply.printf("Cold extrusion is %s, use M302 P[1/0] to allow/deny it",
+ reprap.GetHeat()->ColdExtrude() ? "allowed" : "denied");
+ }
+ break;
+
+ case 303: // Run PID tuning
+ if (gb.Seen('H'))
+ {
+ const size_t heater = gb.GetIValue();
+ const float temperature = (gb.Seen('S')) ? gb.GetFValue() : 225.0;
+ const float maxPwm = (gb.Seen('P')) ? gb.GetFValue() : 0.5;
+ if (heater < HEATERS && maxPwm >= 0.1 && maxPwm <= 1.0 && temperature >= 55.0 && temperature <= reprap.GetHeat()->GetTemperatureLimit(heater))
+ {
+ reprap.GetHeat()->StartAutoTune(heater, temperature, maxPwm, reply);
+ }
+ else
+ {
+ reply.printf("Bad parameter in M303 command");
+ }
+ }
+ else
+ {
+ reprap.GetHeat()->GetAutoTuneStatus(reply);
+ }
+ break;
+
+ case 304: // Set/report heated bed PID values
+ {
+ const int8_t bedHeater = reprap.GetHeat()->GetBedHeater();
+ if (bedHeater >= 0)
+ {
+ SetPidParameters(gb, bedHeater, reply);
+ }
+ }
+ break;
+
+ case 305: // Set/report specific heater parameters
+ SetHeaterParameters(gb, reply);
+ break;
+
+ case 307: // Set heater process model parameters
+ if (gb.Seen('H'))
+ {
+ size_t heater = gb.GetIValue();
+ if (heater < HEATERS)
+ {
+ const FopDt& model = reprap.GetHeat()->GetHeaterModel(heater);
+ bool seen = false;
+ float gain = model.GetGain(),
+ tc = model.GetTimeConstant(),
+ td = model.GetDeadTime(),
+ maxPwm = model.GetMaxPwm();
+ int32_t dontUsePid = model.UsePid() ? 0 : 1;
+
+ gb.TryGetFValue('A', gain, seen);
+ gb.TryGetFValue('C', tc, seen);
+ gb.TryGetFValue('D', td, seen);
+ gb.TryGetIValue('B', dontUsePid, seen);
+ gb.TryGetFValue('S', maxPwm, seen);
+
+ if (seen)
+ {
+ if (reprap.GetHeat()->SetHeaterModel(heater, gain, tc, td, maxPwm, dontUsePid == 0))
+ {
+ reprap.GetHeat()->UseModel(heater, true);
+ }
+ else
+ {
+ reply.copy("Error: bad model parameters");
+ }
+ }
+ else if (!model.IsEnabled())
+ {
+ reply.printf("Heater %u is disabled", heater);
+ }
+ else
+ {
+ reply.printf("Heater %u model: gain %.1f, time constant %.1f, dead time %.1f, max PWM %.2f, in use: %s, mode: %s",
+ heater, model.GetGain(), model.GetTimeConstant(), model.GetDeadTime(), model.GetMaxPwm(),
+ (reprap.GetHeat()->IsModelUsed(heater)) ? "yes" : "no",
+ (model.UsePid()) ? "PID" : "bang-bang");
+ if (model.UsePid())
+ {
+ // When reporting the PID parameters, we scale them by 255 for compatibility with older firmware and other firmware
+ const PidParams& spParams = model.GetPidParameters(false);
+ const float scaledSpKp = 255.0 * spParams.kP;
+ reply.catf("\nSetpoint change: P%.1f, I%.2f, D%.1f",
+ scaledSpKp, scaledSpKp * spParams.recipTi, scaledSpKp * spParams.tD);
+ const PidParams& ldParams = model.GetPidParameters(true);
+ const float scaledLoadKp = 255.0 * ldParams.kP;
+ reply.catf("\nLoad change: P%.1f, I%.2f, D%.1f",
+ scaledLoadKp, scaledLoadKp * ldParams.recipTi, scaledLoadKp * ldParams.tD);
+ }
+ }
+ }
+ }
+ break;
+
+ case 350: // Set/report microstepping
+ {
+ // interp is currently an int not a bool, because we use special values of interp to set the chopper control register
+ int32_t interp = 0;
+ bool dummy;
+ gb.TryGetIValue('I', interp, dummy);
+
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ seen = true;
+ const int microsteps = gb.GetIValue();
+ if (ChangeMicrostepping(axis, microsteps, interp))
+ {
+ SetAxisNotHomed(axis);
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Drive %c does not support %dx microstepping%s\n",
+ axisLetters[axis], microsteps, (interp) ? " with interpolation" : "");
+ }
+ }
+ }
+
+ if (gb.Seen(extrudeLetter))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ seen = true;
+ long eVals[MaxExtruders];
+ size_t eCount = numExtruders;
+ gb.GetLongArray(eVals, eCount);
+ for (size_t e = 0; e < eCount; e++)
+ {
+ if (!ChangeMicrostepping(numAxes + e, (int)eVals[e], interp))
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Drive E%u does not support %dx microstepping%s\n",
+ e, (int)eVals[e], (interp) ? " with interpolation" : "");
+ }
+ }
+ }
+
+ if (!seen)
+ {
+ reply.copy("Microstepping - ");
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ bool interp;
+ const int microsteps = platform->GetMicrostepping(axis, interp);
+ reply.catf("%c:%d%s, ", axisLetters[axis], microsteps, (interp) ? "(on)" : "");
+ }
+ reply.cat("E");
+ for (size_t extruder = 0; extruder < numExtruders; extruder++)
+ {
+ bool interp;
+ const int microsteps = platform->GetMicrostepping(extruder + numAxes, interp);
+ reply.catf(":%d%s", microsteps, (interp) ? "(on)" : "");
+ }
+ }
+ }
+ break;
+
+ case 400: // Wait for current moves to finish
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ break;
+
+ case 404: // Filament width and nozzle diameter
+ {
+ bool seen = false;
+
+ if (gb.Seen('N'))
+ {
+ platform->SetFilamentWidth(gb.GetFValue());
+ seen = true;
+ }
+ if (gb.Seen('D'))
+ {
+ platform->SetNozzleDiameter(gb.GetFValue());
+ seen = true;
+ }
+
+ if (!seen)
+ {
+ reply.printf("Filament width: %.2fmm, nozzle diameter: %.2fmm", platform->GetFilamentWidth(), platform->GetNozzleDiameter());
+ }
+ }
+ break;
+
+ case 408: // Get status in JSON format
+ {
+ int type = gb.Seen('S') ? gb.GetIValue() : 0;
+ int seq = gb.Seen('R') ? gb.GetIValue() : -1;
+
+ OutputBuffer *statusResponse = nullptr;
+ switch (type)
+ {
+ case 0:
+ case 1:
+ statusResponse = reprap.GetLegacyStatusResponse(type + 2, seq);
+ break;
+
+ case 2:
+ case 3:
+ case 4:
+ statusResponse = reprap.GetStatusResponse(type - 1, (&gb == auxGCode) ? ResponseSource::AUX : ResponseSource::Generic);
+ break;
+
+ case 5:
+ statusResponse = reprap.GetConfigResponse();
+ break;
+ }
+
+ if (statusResponse != nullptr)
+ {
+ UnlockAll(gb);
+ statusResponse->cat('\n');
+ HandleReply(gb, false, statusResponse);
+ return true;
+ }
+ }
+ break;
+
+ case 500: // Store parameters in EEPROM
+ reprap.GetPlatform()->WriteNvData();
+ break;
+
+ case 501: // Load parameters from EEPROM
+ reprap.GetPlatform()->ReadNvData();
+ if (gb.Seen('S'))
+ {
+ reprap.GetPlatform()->SetAutoSave(gb.GetIValue() > 0);
+ }
+ break;
+
+ case 502: // Revert to default "factory settings"
+ reprap.GetPlatform()->ResetNvData();
+ break;
+
+ case 503: // List variable settings
+ {
+ if (!LockFileSystem(gb))
+ {
+ return false;
+ }
+
+ // Need a valid output buffer to continue...
+ OutputBuffer *configResponse;
+ if (!OutputBuffer::Allocate(configResponse))
+ {
+ // No buffer available, try again later
+ return false;
+ }
+
+ // Read the entire file
+ FileStore * const f = platform->GetFileStore(platform->GetSysDir(), platform->GetConfigFile(), false);
+ if (f == nullptr)
+ {
+ error = true;
+ reply.copy("Configuration file not found!");
+ }
+ else
+ {
+ char fileBuffer[FILE_BUFFER_SIZE];
+ size_t bytesRead,
+ bytesLeftForWriting = OutputBuffer::GetBytesLeft(configResponse);
+ while ((bytesRead = f->Read(fileBuffer, FILE_BUFFER_SIZE)) > 0 && bytesLeftForWriting > 0)
+ {
+ // Don't write more data than we can process
+ if (bytesRead < bytesLeftForWriting)
+ {
+ bytesLeftForWriting -= bytesRead;
+ }
+ else
+ {
+ bytesRead = bytesLeftForWriting;
+ bytesLeftForWriting = 0;
+ }
+
+ // Write it
+ configResponse->cat(fileBuffer, bytesRead);
+ }
+ f->Close();
+
+ UnlockAll(gb);
+ HandleReply(gb, false, configResponse);
+ return true;
+ }
+ }
+ break;
+
+ case 540: // Set/report MAC address
+ if (gb.Seen('P'))
+ {
+ SetMACAddress(gb);
+ }
+ else
+ {
+ const byte* mac = platform->MACAddress();
+ reply.printf("MAC: %x:%x:%x:%x:%x:%x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+ }
+ break;
+
+ case 550: // Set/report machine name
+ if (gb.Seen('P'))
+ {
+ reprap.SetName(gb.GetString());
+ }
+ else
+ {
+ reply.printf("RepRap name: %s", reprap.GetName());
+ }
+ break;
+
+ case 551: // Set password (no option to report it)
+ if (gb.Seen('P'))
+ {
+ reprap.SetPassword(gb.GetString());
+ }
+ break;
+
+ case 552: // Enable/Disable network and/or Set/Get IP address
+ {
+ bool seen = false;
+ if (gb.Seen('P'))
+ {
+ seen = true;
+ SetEthernetAddress(gb, code);
+ }
+
+ if (gb.Seen('R'))
+ {
+ reprap.GetNetwork()->SetHttpPort(gb.GetIValue());
+ seen = true;
+ }
+
+ // Process this one last in case the IP address is changed and the network enabled in the same command
+ if (gb.Seen('S')) // Has the user turned the network on or off?
+ {
+ seen = true;
+ if (gb.GetIValue() != 0)
+ {
+ reprap.GetNetwork()->Enable();
+ }
+ else
+ {
+ reprap.GetNetwork()->Disable();
+ }
+ }
+
+ if (!seen)
+ {
+ const byte *config_ip = platform->IPAddress();
+ const byte *actual_ip = reprap.GetNetwork()->IPAddress();
+ reply.printf("Network is %s, configured IP address: %d.%d.%d.%d, actual IP address: %d.%d.%d.%d, HTTP port: %d",
+ reprap.GetNetwork()->IsEnabled() ? "enabled" : "disabled",
+ config_ip[0], config_ip[1], config_ip[2], config_ip[3], actual_ip[0], actual_ip[1], actual_ip[2], actual_ip[3],
+ reprap.GetNetwork()->GetHttpPort());
+ }
+ }
+ break;
+
+ case 553: // Set/Get netmask
+ if (gb.Seen('P'))
+ {
+ SetEthernetAddress(gb, code);
+ }
+ else
+ {
+ const byte *nm = platform->NetMask();
+ reply.printf("Net mask: %d.%d.%d.%d ", nm[0], nm[1], nm[2], nm[3]);
+ }
+ break;
+
+ case 554: // Set/Get gateway
+ if (gb.Seen('P'))
+ {
+ SetEthernetAddress(gb, code);
+ }
+ else
+ {
+ const byte *gw = platform->GateWay();
+ reply.printf("Gateway: %d.%d.%d.%d ", gw[0], gw[1], gw[2], gw[3]);
+ }
+ break;
+
+ case 555: // Set/report firmware type to emulate
+ if (gb.Seen('P'))
+ {
+ platform->SetEmulating((Compatibility) gb.GetIValue());
+ }
+ else
+ {
+ reply.copy("Emulating ");
+ switch (platform->Emulating())
+ {
+ case me:
+ case reprapFirmware:
+ reply.cat("RepRap Firmware (i.e. in native mode)");
+ break;
+
+ case marlin:
+ reply.cat("Marlin");
+ break;
+
+ case teacup:
+ reply.cat("Teacup");
+ break;
+
+ case sprinter:
+ reply.cat("Sprinter");
+ break;
+
+ case repetier:
+ reply.cat("Repetier");
+ break;
+
+ default:
+ reply.catf("Unknown: (%d)", platform->Emulating());
+ }
+ }
+ break;
+
+ case 556: // Axis compensation (we support only X, Y, Z)
+ if (gb.Seen('S'))
+ {
+ float value = gb.GetFValue();
+ for (size_t axis = 0; axis <= Z_AXIS; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ reprap.GetMove()->SetAxisCompensation(axis, gb.GetFValue() / value);
+ }
+ }
+ }
+ else
+ {
+ reply.printf("Axis compensations - XY: %.5f, YZ: %.5f, ZX: %.5f",
+ reprap.GetMove()->AxisCompensation(X_AXIS), reprap.GetMove()->AxisCompensation(Y_AXIS),
+ reprap.GetMove()->AxisCompensation(Z_AXIS));
+ }
+ break;
+
+ case 557: // Set/report Z probe point coordinates
+ if (gb.Seen('P'))
+ {
+ const int point = gb.GetIValue();
+ if (point < 0 || (unsigned int)point >= MaxProbePoints)
+ {
+ reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Z probe point index out of range.\n");
+ }
+ else
+ {
+ bool seen = false;
+ if (gb.Seen(axisLetters[X_AXIS]))
+ {
+ reprap.GetMove()->SetXBedProbePoint(point, gb.GetFValue());
+ seen = true;
+ }
+ if (gb.Seen(axisLetters[Y_AXIS]))
+ {
+ reprap.GetMove()->SetYBedProbePoint(point, gb.GetFValue());
+ seen = true;
+ }
+
+ if (!seen)
+ {
+ reply.printf("Probe point %d - [%.1f, %.1f]", point, reprap.GetMove()->XBedProbePoint(point), reprap.GetMove()->YBedProbePoint(point));
+ }
+ }
+ }
+ else
+ {
+ LockMovement(gb); // to ensure that probing is not already in progress
+ error = DefineGrid(gb, reply);
+ }
+ break;
+
+ case 558: // Set or report Z probe type and for which axes it is used
+ {
+ bool seenAxes = false, seenType = false, seenParam = false;
+ uint32_t zProbeAxes = platform->GetZProbeAxes();
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ if (gb.GetIValue() > 0)
+ {
+ zProbeAxes |= (1u << axis);
+ }
+ else
+ {
+ zProbeAxes &= ~(1u << axis);
+ }
+ seenAxes = true;
+ }
+ }
+ if (seenAxes)
+ {
+ platform->SetZProbeAxes(zProbeAxes);
+ }
+
+ // We must get and set the Z probe type first before setting the dive height etc., because different probe types may have different parameters
+ if (gb.Seen('P')) // probe type
+ {
+ platform->SetZProbeType(gb.GetIValue());
+ seenType = true;
+ }
+
+ ZProbeParameters params = platform->GetZProbeParameters();
+ gb.TryGetFValue('H', params.diveHeight, seenParam); // dive height
+
+ if (gb.Seen('F')) // feed rate i.e. probing speed
+ {
+ params.probeSpeed = gb.GetFValue() * secondsToMinutes;
+ seenParam = true;
+ }
+
+ if (gb.Seen('T')) // travel speed to probe point
+ {
+ params.travelSpeed = gb.GetFValue() * secondsToMinutes;
+ seenParam = true;
+ }
+
+ if (gb.Seen('I'))
+ {
+ params.invertReading = (gb.GetIValue() != 0);
+ seenParam = true;
+ }
+
+ gb.TryGetFValue('R', params.recoveryTime, seenParam); // Z probe recovery time
+ gb.TryGetFValue('S', params.extraParam, seenParam); // extra parameter for experimentation
+
+ if (seenParam)
+ {
+ platform->SetZProbeParameters(params);
+ }
+
+ if (!(seenAxes || seenType || seenParam))
+ {
+ reply.printf("Z Probe type %d, invert %s, dive height %.1fmm, probe speed %dmm/min, travel speed %dmm/min, recovery time %.2f sec",
+ platform->GetZProbeType(), (params.invertReading) ? "yes" : "no", params.diveHeight,
+ (int)(params.probeSpeed * minutesToSeconds), (int)(params.travelSpeed * minutesToSeconds), params.recoveryTime);
+ if (platform->GetZProbeType() == ZProbeTypeDelta)
+ {
+ reply.catf(", extra parameter %.2f", params.extraParam);
+ }
+ reply.cat(", used for axes:");
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if ((zProbeAxes & (1u << axis)) != 0)
+ {
+ reply.catf(" %c", axisLetters[axis]);
+ }
+ }
+ }
+ }
+ break;
+
+ case 559: // Upload config.g or another gcode file to put in the sys directory
+ {
+ const char* str = (gb.Seen('P') ? gb.GetString() : platform->GetConfigFile());
+ const bool ok = OpenFileToWrite(gb, platform->GetSysDir(), str);
+ if (ok)
+ {
+ reply.printf("Writing to file: %s", str);
+ }
+ else
+ {
+ reply.printf("Can't open file %s for writing.", str);
+ error = true;
+ }
+ }
+ break;
+
+ case 560: // Upload reprap.htm or another web interface file
+ {
+ const char* str = (gb.Seen('P') ? gb.GetString() : INDEX_PAGE_FILE);
+ const bool ok = OpenFileToWrite(gb, platform->GetWebDir(), str);
+ if (ok)
+ {
+ reply.printf("Writing to file: %s", str);
+ }
+ else
+ {
+ reply.printf("Can't open file %s for writing.", str);
+ error = true;
+ }
+ }
+ break;
+
+ case 561:
+ reprap.GetMove()->SetIdentityTransform();
+ break;
+
+ case 562: // Reset temperature fault - use with great caution
+ if (gb.Seen('P'))
+ {
+ const int heater = gb.GetIValue();
+ if (heater >= 0 && heater < HEATERS)
+ {
+ reprap.ClearTemperatureFault(heater);
+ }
+ else
+ {
+ reply.copy("Invalid heater number.\n");
+ error = true;
+ }
+ }
+ break;
+
+ case 563: // Define tool
+ ManageTool(gb, reply);
+ break;
+
+ case 564: // Think outside the box?
+ if (gb.Seen('S'))
+ {
+ limitAxes = (gb.GetIValue() != 0);
+ }
+ else
+ {
+ reply.printf("Movement outside the bed is %spermitted", (limitAxes) ? "not " : "");
+ }
+ break;
+
+ case 566: // Set/print maximum jerk speeds
+ {
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ platform->SetInstantDv(axis, gb.GetFValue() * distanceScale * secondsToMinutes); // G Code feedrates are in mm/minute; we need mm/sec
+ seen = true;
+ }
+ }
+
+ if (gb.Seen(extrudeLetter))
+ {
+ seen = true;
+ float eVals[MaxExtruders];
+ size_t eCount = numExtruders;
+ gb.GetFloatArray(eVals, eCount, true);
+ for (size_t e = 0; e < eCount; e++)
+ {
+ platform->SetInstantDv(numAxes + e, eVals[e] * distanceScale * secondsToMinutes);
+ }
+ }
+ else if (!seen)
+ {
+ reply.copy("Maximum jerk rates: ");
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ reply.catf("%c: %.1f, ", axisLetters[axis], platform->ConfiguredInstantDv(axis) / (distanceScale * secondsToMinutes));
+ }
+ reply.cat("E:");
+ char sep = ' ';
+ for (size_t extruder = 0; extruder < numExtruders; extruder++)
+ {
+ reply.catf("%c%.1f", sep, platform->ConfiguredInstantDv(extruder + numAxes) / (distanceScale * secondsToMinutes));
+ sep = ':';
+ }
+ }
+ }
+ break;
+
+ case 567: // Set/report tool mix ratios
+ if (gb.Seen('P'))
+ {
+ int8_t tNumber = gb.GetIValue();
+ Tool* const tool = reprap.GetTool(tNumber);
+ if (tool != NULL)
+ {
+ if (gb.Seen(extrudeLetter))
+ {
+ float eVals[MaxExtruders];
+ size_t eCount = tool->DriveCount();
+ gb.GetFloatArray(eVals, eCount, false);
+ if (eCount != tool->DriveCount())
+ {
+ reply.printf("Setting mix ratios - wrong number of E drives: %s", gb.Buffer());
+ }
+ else
+ {
+ tool->DefineMix(eVals);
+ }
+ }
+ else
+ {
+ reply.printf("Tool %d mix ratios:", tNumber);
+ char sep = ' ';
+ for (size_t drive = 0; drive < tool->DriveCount(); drive++)
+ {
+ reply.catf("%c%.3f", sep, tool->GetMix()[drive]);
+ sep = ':';
+ }
+ }
+ }
+ }
+ break;
+
+ case 568: // Turn on/off automatic tool mixing
+ if (gb.Seen('P'))
+ {
+ Tool* const tool = reprap.GetTool(gb.GetIValue());
+ if (tool != NULL)
+ {
+ if (gb.Seen('S'))
+ {
+ tool->SetMixing(gb.GetIValue() != 0);
+ }
+ else
+ {
+ reply.printf("Tool %d mixing is %s", tool->Number(), (tool->GetMixing()) ? "enabled" : "disabled");
+ }
+ }
+ }
+ break;
+
+ case 569: // Set/report axis direction
+ if (gb.Seen('P'))
+ {
+ const size_t drive = gb.GetIValue();
+ if (drive < DRIVES)
+ {
+ bool seen = false;
+ if (gb.Seen('S'))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ platform->SetDirectionValue(drive, gb.GetIValue() != 0);
+ seen = true;
+ }
+ if (gb.Seen('R'))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ platform->SetEnableValue(drive, gb.GetIValue() != 0);
+ seen = true;
+ }
+ if (gb.Seen('T'))
+ {
+ platform->SetDriverStepTiming(drive, gb.GetFValue());
+ seen = true;
+ }
+ bool badParameter = false;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ badParameter = true;
+ }
+ }
+ if (gb.Seen(extrudeLetter))
+ {
+ badParameter = true;
+ }
+ if (badParameter)
+ {
+ platform->Message(GENERIC_MESSAGE, "Error: M569 no longer accepts XYZE parameters; use M584 instead\n");
+ }
+ else if (!seen)
+ {
+ reply.printf("A %d sends drive %u forwards, a %d enables it, and the minimum pulse width is %.1f microseconds",
+ (int)platform->GetDirectionValue(drive), drive,
+ (int)platform->GetEnableValue(drive),
+ platform->GetDriverStepTiming(drive));
+ }
+ }
+ }
+ break;
+
+ case 570: // Set/report heater timeout
+ if (gb.Seen('H'))
+ {
+ const size_t heater = gb.GetIValue();
+ bool seen = false;
+ if (heater < HEATERS)
+ {
+ float maxTempExcursion, maxFaultTime;
+ reprap.GetHeat()->GetHeaterProtection(heater, maxTempExcursion, maxFaultTime);
+ gb.TryGetFValue('P', maxFaultTime, seen);
+ gb.TryGetFValue('T', maxTempExcursion, seen);
+ if (seen)
+ {
+ reprap.GetHeat()->SetHeaterProtection(heater, maxTempExcursion, maxFaultTime);
+ }
+ else
+ {
+ reply.printf("Heater %u allowed excursion %.1fC, fault trigger time %.1f seconds", heater, maxTempExcursion, maxFaultTime);
+ }
+ }
+ }
+ else if (gb.Seen('S'))
+ {
+ reply.copy("M570 S parameter is no longer required or supported");
+ }
+ break;
+
+ case 571: // Set output on extrude
+ {
+ bool seen = false;
+ if (gb.Seen('P'))
+ {
+ const int pwmPin = gb.GetIValue();
+ if (!platform->SetExtrusionAncilliaryPwmPin(pwmPin))
+ {
+ reply.printf("Logical pin %d is already in use or not available for use by M571", pwmPin);
+ break; // don't process 'S' parameter if the pin was wrong
+ }
+ seen = true;
+ }
+ if (gb.Seen('S'))
+ {
+ platform->SetExtrusionAncilliaryPwmValue(gb.GetFValue());
+ seen = true;
+ }
+ if (!seen)
+ {
+ reply.printf("Extrusion ancillary PWM %.3f on pin %u",
+ platform->GetExtrusionAncilliaryPwmValue(), platform->GetExtrusionAncilliaryPwmPin());
+ }
+ }
+ break;
+
+ case 572: // Set/report elastic compensation
+ if (gb.Seen('D'))
+ {
+ // New usage: specify the extruder drive using the D parameter
+ const size_t extruder = gb.GetIValue();
+ if (gb.Seen('S'))
+ {
+ platform->SetPressureAdvance(extruder, gb.GetFValue());
+ }
+ else
+ {
+ reply.printf("Pressure advance for extruder %u is %.3f seconds", extruder, platform->GetPressureAdvance(extruder));
+ }
+ }
+ break;
+
+ case 573: // Report heater average PWM
+ if (gb.Seen('P'))
+ {
+ const int heater = gb.GetIValue();
+ if (heater >= 0 && heater < HEATERS)
+ {
+ reply.printf("Average heater %d PWM: %.3f", heater, reprap.GetHeat()->GetAveragePWM(heater));
+ }
+ }
+ break;
+
+ case 574: // Set endstop configuration
+ {
+ bool seen = false;
+ const bool logicLevel = (gb.Seen('S')) ? (gb.GetIValue() != 0) : true;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ const int ival = gb.GetIValue();
+ if (ival >= 0 && ival <= 3)
+ {
+ platform->SetEndStopConfiguration(axis, (EndStopType) ival, logicLevel);
+ seen = true;
+ }
+ }
+ }
+ if (!seen)
+ {
+ reply.copy("Endstop configuration:");
+ EndStopType config;
+ bool logic;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ platform->GetEndStopConfiguration(axis, config, logic);
+ reply.catf(" %c %s (active %s),", axisLetters[axis],
+ (config == EndStopType::highEndStop) ? "high end" : (config == EndStopType::lowEndStop) ? "low end" : "none",
+ (config == EndStopType::noEndStop) ? "" : (logic) ? "high" : "low");
+ }
+ }
+ }
+ break;
+
+ case 575: // Set communications parameters
+ if (gb.Seen('P'))
+ {
+ size_t chan = gb.GetIValue();
+ if (chan < NUM_SERIAL_CHANNELS)
+ {
+ bool seen = false;
+ if (gb.Seen('B'))
+ {
+ platform->SetBaudRate(chan, gb.GetIValue());
+ seen = true;
+ }
+ if (gb.Seen('S'))
+ {
+ uint32_t val = gb.GetIValue();
+ platform->SetCommsProperties(chan, val);
+ switch (chan)
+ {
+ case 0:
+ serialGCode->SetCommsProperties(val);
+ break;
+ case 1:
+ auxGCode->SetCommsProperties(val);
+ break;
+ default:
+ break;
+ }
+ seen = true;
+ }
+ if (!seen)
+ {
+ uint32_t cp = platform->GetCommsProperties(chan);
+ reply.printf("Channel %d: baud rate %d, %s checksum", chan, platform->GetBaudRate(chan),
+ (cp & 1) ? "requires" : "does not require");
+ }
+ }
+ }
+ break;
+
+ case 577: // Wait until endstop is triggered
+ if (gb.Seen('S'))
+ {
+ // Determine trigger type
+ EndStopHit triggerCondition;
+ switch (gb.GetIValue())
+ {
+ case 1:
+ triggerCondition = EndStopHit::lowHit;
+ break;
+ case 2:
+ triggerCondition = EndStopHit::highHit;
+ break;
+ case 3:
+ triggerCondition = EndStopHit::lowNear;
+ break;
+ default:
+ triggerCondition = EndStopHit::noStop;
+ break;
+ }
+
+ // Axis endstops
+ for (size_t axis=0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ if (platform->Stopped(axis) != triggerCondition)
+ {
+ result = false;
+ break;
+ }
+ }
+ }
+
+ // Extruder drives
+ size_t eDriveCount = MaxExtruders;
+ long eDrives[MaxExtruders];
+ if (gb.Seen(extrudeLetter))
+ {
+ gb.GetLongArray(eDrives, eDriveCount);
+ for(size_t extruder = 0; extruder < eDriveCount; extruder++)
+ {
+ const size_t eDrive = eDrives[extruder];
+ if (eDrive >= MaxExtruders)
+ {
+ reply.copy("Invalid extruder drive specified!");
+ error = result = true;
+ break;
+ }
+
+ if (platform->Stopped(eDrive + E0_AXIS) != triggerCondition)
+ {
+ result = false;
+ break;
+ }
+ }
+ }
+ }
+ break;
+
+#if SUPPORT_INKJET
+ case 578: // Fire Inkjet bits
+ if (!AllMovesAreFinishedAndMoveBufferIsLoaded())
+ {
+ return false;
+ }
+
+ if (gb.Seen('S')) // Need to handle the 'P' parameter too; see http://reprap.org/wiki/G-code#M578:_Fire_inkjet_bits
+ {
+ platform->Inkjet(gb.GetIValue());
+ }
+ break;
+#endif
+
+ case 579: // Scale Cartesian axes (mostly for Delta configurations)
+ {
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ gb.TryGetFValue(axisLetters[axis], axisScaleFactors[axis], seen);
+ }
+
+ if (!seen)
+ {
+ char sep = ':';
+ reply.copy("Axis scale factors");
+ for(size_t axis = 0; axis < numAxes; axis++)
+ {
+ reply.catf("%c %c: %.3f", sep, axisLetters[axis], axisScaleFactors[axis]);
+ sep = ',';
+ }
+ }
+ }
+ break;
+
+#if SUPPORT_ROLAND
+ case 580: // (De)Select Roland mill
+ if (gb.Seen('R'))
+ {
+ if (gb.GetIValue())
+ {
+ reprap.GetRoland()->Activate();
+ if (gb.Seen('P'))
+ {
+ result = reprap.GetRoland()->RawWrite(gb.GetString());
+ }
+ }
+ else
+ {
+ result = reprap.GetRoland()->Deactivate();
+ }
+ }
+ else
+ {
+ reply.printf("Roland is %s.", reprap.GetRoland()->Active() ? "active" : "inactive");
+ }
+ break;
+#endif
+
+ case 581: // Configure external trigger
+ case 582: // Check external trigger
+ if (gb.Seen('T'))
+ {
+ unsigned int triggerNumber = gb.GetIValue();
+ if (triggerNumber < MaxTriggers)
+ {
+ if (code == 582)
+ {
+ uint32_t states = platform->GetAllEndstopStates();
+ if ((triggers[triggerNumber].rising & states) != 0 || (triggers[triggerNumber].falling & ~states) != 0)
+ {
+ triggersPending |= (1u << triggerNumber);
+ }
+ }
+ else
+ {
+ bool seen = false;
+ if (gb.Seen('C'))
+ {
+ seen = true;
+ triggers[triggerNumber].condition = gb.GetIValue();
+ }
+ else if (triggers[triggerNumber].IsUnused())
+ {
+ triggers[triggerNumber].condition = 0; // this is a new trigger, so set no condition
+ }
+ if (gb.Seen('S'))
+ {
+ seen = true;
+ int sval = gb.GetIValue();
+ TriggerMask triggerMask = 0;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ triggerMask |= (1u << axis);
+ }
+ }
+ if (gb.Seen(extrudeLetter))
+ {
+ long eStops[MaxExtruders];
+ size_t numEntries = MaxExtruders;
+ gb.GetLongArray(eStops, numEntries);
+ for (size_t i = 0; i < numEntries; ++i)
+ {
+ if (eStops[i] >= 0 && (unsigned long)eStops[i] < MaxExtruders)
+ {
+ triggerMask |= (1u << (eStops[i] + E0_AXIS));
+ }
+ }
+ }
+ switch(sval)
+ {
+ case -1:
+ if (triggerMask == 0)
+ {
+ triggers[triggerNumber].rising = triggers[triggerNumber].falling = 0;
+ }
+ else
+ {
+ triggers[triggerNumber].rising &= (~triggerMask);
+ triggers[triggerNumber].falling &= (~triggerMask);
+ }
+ break;
+
+ case 0:
+ triggers[triggerNumber].falling |= triggerMask;
+ break;
+
+ case 1:
+ triggers[triggerNumber].rising |= triggerMask;
+ break;
+
+ default:
+ platform->Message(GENERIC_MESSAGE, "Bad S parameter in M581 command\n");
+ }
+ }
+ if (!seen)
+ {
+ reply.printf("Trigger %u fires on a rising edge on ", triggerNumber);
+ ListTriggers(reply, triggers[triggerNumber].rising);
+ reply.cat(" or a falling edge on ");
+ ListTriggers(reply, triggers[triggerNumber].falling);
+ reply.cat(" endstop inputs");
+ if (triggers[triggerNumber].condition == 1)
+ {
+ reply.cat(" when printing from SD card");
+ }
+ }
+ }
+ }
+ else
+ {
+ platform->Message(GENERIC_MESSAGE, "Trigger number out of range\n");
+ }
+ }
+ break;
+
+ case 584: // Set axis/extruder to stepper driver(s) mapping
+ if (!LockMovementAndWaitForStandstill(gb)) // we also rely on this to retrieve the current motor positions to moveBuffer
+ {
+ return false;
+ }
+ {
+ bool seen = false, badDrive = false;
+ for (size_t drive = 0; drive < MAX_AXES; ++drive)
+ {
+ if (gb.Seen(axisLetters[drive]))
+ {
+ seen = true;
+ size_t numValues = MaxDriversPerAxis;
+ long drivers[MaxDriversPerAxis];
+ gb.GetLongArray(drivers, numValues);
+
+ // Check all the driver numbers are in range
+ bool badAxis = false;
+ AxisDriversConfig config;
+ config.numDrivers = numValues;
+ for (size_t i = 0; i < numValues; ++i)
+ {
+ if ((unsigned long)drivers[i] >= DRIVES)
+ {
+ badAxis = true;
+ }
+ else
+ {
+ config.driverNumbers[i] = (uint8_t)drivers[i];
+ }
+ }
+ if (badAxis)
+ {
+ badDrive = true;
+ }
+ else
+ {
+ while (numAxes <= drive)
+ {
+ moveBuffer.coords[numAxes] = 0.0; // user has defined a new axis, so set its position
+ ++numAxes;
+ }
+ SetPositions(moveBuffer.coords); // tell the Move system where any new axes are
+ platform->SetAxisDriversConfig(drive, config);
+ if (numAxes + numExtruders > DRIVES)
+ {
+ numExtruders = DRIVES - numAxes; // if we added axes, we may have fewer extruders now
+ }
+ }
+ }
+ }
+
+ if (gb.Seen(extrudeLetter))
+ {
+ seen = true;
+ size_t numValues = DRIVES - numAxes;
+ long drivers[MaxExtruders];
+ gb.GetLongArray(drivers, numValues);
+ numExtruders = numValues;
+ for (size_t i = 0; i < numValues; ++i)
+ {
+ if ((unsigned long)drivers[i] >= DRIVES)
+ {
+ badDrive = true;
+ }
+ else
+ {
+ platform->SetExtruderDriver(i, (uint8_t)drivers[i]);
+ }
+ }
+ }
+
+ if (badDrive)
+ {
+ platform->Message(GENERIC_MESSAGE, "Error: invalid drive number in M584 command\n");
+ }
+ else if (!seen)
+ {
+ reply.copy("Driver assignments:");
+ for (size_t drive = 0; drive < numAxes; ++ drive)
+ {
+ reply.cat(' ');
+ const AxisDriversConfig& axisConfig = platform->GetAxisDriversConfig(drive);
+ char c = axisLetters[drive];
+ for (size_t i = 0; i < axisConfig.numDrivers; ++i)
+ {
+ reply.catf("%c%u", c, axisConfig.driverNumbers[i]);
+ c = ':';
+ }
+ }
+ reply.cat(' ');
+ char c = extrudeLetter;
+ for (size_t extruder = 0; extruder < numExtruders; ++extruder)
+ {
+ reply.catf("%c%u", c, platform->GetExtruderDriver(extruder));
+ c = ':';
+ }
+ }
+ }
+ break;
+
+ case 665: // Set delta configuration
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ {
+ float positionNow[DRIVES];
+ Move *move = reprap.GetMove();
+ move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes()); // get the current position, we may need it later
+ DeltaParameters& params = move->AccessDeltaParams();
+ const bool wasInDeltaMode = params.IsDeltaMode(); // remember whether we were in delta mode
+ bool seen = false;
+
+ if (gb.Seen('L'))
+ {
+ params.SetDiagonal(gb.GetFValue() * distanceScale);
+ seen = true;
+ }
+ if (gb.Seen('R'))
+ {
+ params.SetRadius(gb.GetFValue() * distanceScale);
+ seen = true;
+ }
+ if (gb.Seen('B'))
+ {
+ params.SetPrintRadius(gb.GetFValue() * distanceScale);
+ seen = true;
+ }
+ if (gb.Seen('X'))
+ {
+ // X tower position correction
+ params.SetXCorrection(gb.GetFValue());
+ seen = true;
+ }
+ if (gb.Seen('Y'))
+ {
+ // Y tower position correction
+ params.SetYCorrection(gb.GetFValue());
+ seen = true;
+ }
+ if (gb.Seen('Z'))
+ {
+ // Y tower position correction
+ params.SetZCorrection(gb.GetFValue());
+ seen = true;
+ }
+
+ // The homed height must be done last, because it gets recalculated when some of the other factors are changed
+ if (gb.Seen('H'))
+ {
+ params.SetHomedHeight(gb.GetFValue() * distanceScale);
+ seen = true;
+ }
+
+ if (seen)
+ {
+ move->SetCoreXYMode(0); // CoreXYMode needs to be zero when executing special moves on a delta
+
+ // If we have changed between Cartesian and Delta mode, we need to reset the motor coordinates to agree with the XYZ coordinates.
+ // This normally happens only when we process the M665 command in config.g. Also flag that the machine is not homed.
+ if (params.IsDeltaMode() != wasInDeltaMode)
+ {
+ SetPositions(positionNow);
+ }
+ SetAllAxesNotHomed();
+ }
+ else
+ {
+ if (params.IsDeltaMode())
+ {
+ reply.printf("Diagonal %.3f, delta radius %.3f, homed height %.3f, bed radius %.1f"
+ ", X %.3f" DEGREE_SYMBOL ", Y %.3f" DEGREE_SYMBOL ", Z %.3f" DEGREE_SYMBOL,
+ params.GetDiagonal() / distanceScale, params.GetRadius() / distanceScale,
+ params.GetHomedHeight() / distanceScale, params.GetPrintRadius() / distanceScale,
+ params.GetXCorrection(), params.GetYCorrection(), params.GetZCorrection());
+ }
+ else
+ {
+ reply.printf("Printer is not in delta mode");
+ }
+ }
+ }
+ break;
+
+ case 666: // Set delta endstop adjustments
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ {
+ DeltaParameters& params = reprap.GetMove()->AccessDeltaParams();
+ bool seen = false;
+ if (gb.Seen('X'))
+ {
+ params.SetEndstopAdjustment(X_AXIS, gb.GetFValue());
+ seen = true;
+ }
+ if (gb.Seen('Y'))
+ {
+ params.SetEndstopAdjustment(Y_AXIS, gb.GetFValue());
+ seen = true;
+ }
+ if (gb.Seen('Z'))
+ {
+ params.SetEndstopAdjustment(Z_AXIS, gb.GetFValue());
+ seen = true;
+ }
+ if (gb.Seen('A'))
+ {
+ params.SetXTilt(gb.GetFValue() * 0.01);
+ seen = true;
+ }
+ if (gb.Seen('B'))
+ {
+ params.SetYTilt(gb.GetFValue() * 0.01);
+ seen = true;
+ }
+
+ if (seen)
+ {
+ SetAllAxesNotHomed();
+ }
+ else
+ {
+ reply.printf("Endstop adjustments X%.2f Y%.2f Z%.2f, tilt X%.2f%% Y%.2f%%",
+ params.GetEndstopAdjustment(X_AXIS), params.GetEndstopAdjustment(Y_AXIS), params.GetEndstopAdjustment(Z_AXIS),
+ params.GetXTilt() * 100.0, params.GetYTilt() * 100.0);
+ }
+ }
+ break;
+
+ case 667: // Set CoreXY mode
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ {
+ Move* const move = reprap.GetMove();
+ bool seen = false;
+ float positionNow[DRIVES];
+ move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes()); // get the current position, we may need it later
+ if (gb.Seen('S'))
+ {
+ move->SetCoreXYMode(gb.GetIValue());
+ seen = true;
+ }
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ move->SetCoreAxisFactor(axis, gb.GetFValue());
+ seen = true;
+ }
+ }
+
+ if (seen)
+ {
+ SetPositions(positionNow);
+ SetAllAxesNotHomed();
+ }
+ else
+ {
+ reply.printf("Printer mode is %s with axis factors", move->GetGeometryString());
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ reply.catf(" %c:%f", axisLetters[axis], move->GetCoreAxisFactor(axis));
+ }
+ }
+ }
+ break;
+
+ case 905: // Set current RTC date and time
+ {
+ const time_t now = platform->GetDateTime();
+ struct tm * const timeInfo = gmtime(&now);
+ bool seen = false;
+
+ if (gb.Seen('P'))
+ {
+ // Set date
+ const char * const dateString = gb.GetString();
+ if (strptime(dateString, "%Y-%m-%d", timeInfo) != nullptr)
+ {
+ if (!platform->SetDate(mktime(timeInfo)))
+ {
+ reply.copy("Could not set date");
+ error = true;
+ break;
+ }
+ }
+ else
+ {
+ reply.copy("Invalid date format");
+ error = true;
+ break;
+ }
+
+ seen = true;
+ }
+
+ if (gb.Seen('S'))
+ {
+ // Set time
+ const char * const timeString = gb.GetString();
+ if (strptime(timeString, "%H:%M:%S", timeInfo) != nullptr)
+ {
+ if (!platform->SetTime(mktime(timeInfo)))
+ {
+ reply.copy("Could not set time");
+ error = true;
+ break;
+ }
+ }
+ else
+ {
+ reply.copy("Invalid time format");
+ error = true;
+ break;
+ }
+ seen = true;
+ }
+
+ // TODO: Add correction parameters for SAM4E
+
+ if (!seen)
+ {
+ // Report current date and time
+ reply.printf("Current date and time: %04u-%02u-%02u %02u:%02u:%02u",
+ timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday,
+ timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
+
+ if (!platform->IsDateTimeSet())
+ {
+ reply.cat("\nWarning: RTC has not been configured yet!");
+ }
+ }
+ }
+ break;
+
+ case 906: // Set/report Motor currents
+ case 913: // Set/report motor current percent
+ {
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ platform->SetMotorCurrent(axis, gb.GetFValue(), code == 913);
+ seen = true;
+ }
+ }
+
+ if (gb.Seen(extrudeLetter))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+ float eVals[MaxExtruders];
+ size_t eCount = numExtruders;
+ gb.GetFloatArray(eVals, eCount, true);
+ // 2014-09-29 DC42: we no longer insist that the user supplies values for all possible extruder drives
+ for (size_t e = 0; e < eCount; e++)
+ {
+ platform->SetMotorCurrent(numAxes + e, eVals[e], code == 913);
+ }
+ seen = true;
+ }
+
+ if (code == 906 && gb.Seen('I'))
+ {
+ const float idleFactor = gb.GetFValue();
+ if (idleFactor >= 0 && idleFactor <= 100.0)
+ {
+ platform->SetIdleCurrentFactor(idleFactor/100.0);
+ seen = true;
+ }
+ }
+
+ if (!seen)
+ {
+ reply.copy((code == 913) ? "Motor current % of normal - " : "Motor current (mA) - ");
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ reply.catf("%c:%d, ", axisLetters[axis], (int)platform->GetMotorCurrent(axis, code == 913));
+ }
+ reply.cat("E");
+ for (size_t extruder = 0; extruder < numExtruders; extruder++)
+ {
+ reply.catf(":%d", (int)platform->GetMotorCurrent(extruder + numAxes, code == 913));
+ }
+ if (code == 906)
+ {
+ reply.catf(", idle factor %d%%", (int)(platform->GetIdleCurrentFactor() * 100.0));
+ }
+ }
+ }
+ break;
+
+ case 911: // Set power monitor threshold voltages
+ reply.printf("M911 not implemented yet");
+ break;
+
+ case 912: // Set electronics temperature monitor adjustment
+ // Currently we ignore the P parameter (i.e. temperature measurement channel)
+ if (gb.Seen('S'))
+ {
+ platform->SetMcuTemperatureAdjust(gb.GetFValue());
+ }
+ else
+ {
+ reply.printf("MCU temperature calibration adjustment is %.1f" DEGREE_SYMBOL "C", platform->GetMcuTemperatureAdjust());
+ }
+ break;
+
+ // For case 913, see 906
+
+ case 997: // Perform firmware update
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ reprap.GetHeat()->SwitchOffAll(); // turn all heaters off because the main loop may get suspended
+ DisableDrives(); // all motors off
+
+ if (firmwareUpdateModuleMap == 0) // have we worked out which modules to update?
+ {
+ // Find out which modules we have been asked to update
+ if (gb.Seen('S'))
+ {
+ long modulesToUpdate[3];
+ size_t numUpdateModules = ARRAY_SIZE(modulesToUpdate);
+ gb.GetLongArray(modulesToUpdate, numUpdateModules);
+ for (size_t i = 0; i < numUpdateModules; ++i)
+ {
+ long t = modulesToUpdate[i];
+ if (t < 0 || (unsigned long)t >= NumFirmwareUpdateModules)
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Invalid module number '%ld'\n", t);
+ firmwareUpdateModuleMap = 0;
+ break;
+ }
+ firmwareUpdateModuleMap |= (1u << (unsigned int)t);
+ }
+ }
+ else
+ {
+ firmwareUpdateModuleMap = (1u << 0); // no modules specified, so update module 0 to match old behaviour
+ }
+
+ if (firmwareUpdateModuleMap == 0)
+ {
+ break; // nothing to update
+ }
+
+ // Check prerequisites of all modules to be updated, if any are not met then don't update any of them
+#ifdef DUET_NG
+ if (!FirmwareUpdater::CheckFirmwareUpdatePrerequisites(firmwareUpdateModuleMap))
+ {
+ firmwareUpdateModuleMap = 0;
+ break;
+ }
+#endif
+ if ((firmwareUpdateModuleMap & 1) != 0 && !platform->CheckFirmwareUpdatePrerequisites())
+ {
+ firmwareUpdateModuleMap = 0;
+ break;
+ }
+ }
+
+ // If we get here then we have the module map, and all prerequisites are satisfied
+ isFlashing = true; // this tells the web interface and PanelDue that we are about to flash firmware
+ if (!DoDwellTime(1.0)) // wait a second so all HTTP clients and PanelDue are notified
+ {
+ return false;
+ }
+
+ gb.SetState(GCodeState::flashing1);
+ break;
+
+ case 998:
+ // The input handling code replaces the gcode by this when it detects a checksum error.
+ // Since we have no way of asking for the line to be re-sent, just report an error.
+ if (gb.Seen('P'))
+ {
+ const int val = gb.GetIValue();
+ if (val != 0)
+ {
+ reply.printf("Checksum error on line %d", val);
+ }
+ }
+ break;
+
+ case 999:
+ result = DoDwellTime(0.5); // wait half a second to allow the response to be sent back to the web server, otherwise it may retry
+ if (result)
+ {
+ reprap.EmergencyStop(); // this disables heaters and drives - Duet WiFi pre-production boards need drives disabled here
+ uint16_t reason = (gb.Seen('P') && StringStartsWith(gb.GetString(), "ERASE"))
+ ? (uint16_t)SoftwareResetReason::erase
+ : (uint16_t)SoftwareResetReason::user;
+ platform->SoftwareReset(reason); // doesn't return
+ }
+ break;
+
+ default:
+ error = true;
+ reply.printf("unsupported command: %s", gb.Buffer());
+ }
+
+ if (result && gb.GetState() == GCodeState::normal)
+ {
+ UnlockAll(gb);
+ HandleReply(gb, error, reply.Pointer());
+ }
+ return result;
+}
+
+bool GCodes::HandleTcode(GCodeBuffer& gb, StringRef& reply)
+{
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+ newToolNumber = gb.GetIValue();
+ newToolNumber += gb.GetToolNumberAdjust();
+
+ // TODO for the tool change restore point to be useful, we should undo any X axis mapping and remove any tool offsets
+ for (size_t drive = 0; drive < DRIVES; ++drive)
+ {
+ toolChangeRestorePoint.moveCoords[drive] = moveBuffer.coords[drive];
+ }
+ toolChangeRestorePoint.feedRate = gb.MachineState().feedrate;
+
+ if (simulationMode == 0) // we don't yet simulate any T codes
+ {
+ const Tool * const oldTool = reprap.GetCurrentTool();
+ // If old and new are the same we no longer follow the sequence. User can deselect and then reselect the tool if he wants the macros run.
+ if (oldTool == nullptr || oldTool->Number() != newToolNumber)
+ {
+ StartToolChange(gb, false);
+ return true; // proceeding with state machine, so don't unlock or send a reply
+ }
+ }
+
+ // If we get here, we have finished
+ UnlockAll(gb);
+ HandleReply(gb, false, "");
+ return true;
+}
+
+// End
diff --git a/src/Heating/Heat.h b/src/Heating/Heat.h
index 6290cd16..ab74e6a9 100644
--- a/src/Heating/Heat.h
+++ b/src/Heating/Heat.h
@@ -39,8 +39,7 @@ public:
void Exit(); // Shut everything down
bool ColdExtrude() const; // Is cold extrusion allowed?
- void AllowColdExtrude(); // Allow cold extrusion
- void DenyColdExtrude(); // Deny cold extrusion
+ void AllowColdExtrude(bool b); // Allow or deny cold extrusion
int8_t GetBedHeater() const // Get hot bed heater number
post(-1 <= result; result < HEATERS);
@@ -132,14 +131,9 @@ inline bool Heat::ColdExtrude() const
return coldExtrude;
}
-inline void Heat::AllowColdExtrude()
+inline void Heat::AllowColdExtrude(bool b)
{
- coldExtrude = true;
-}
-
-inline void Heat::DenyColdExtrude()
-{
- coldExtrude = false;
+ coldExtrude = b;
}
inline int8_t Heat::GetBedHeater() const
diff --git a/src/Movement/DDA.cpp b/src/Movement/DDA.cpp
index 0eeea2b6..a42ac642 100644
--- a/src/Movement/DDA.cpp
+++ b/src/Movement/DDA.cpp
@@ -161,8 +161,8 @@ void DDA::Init()
bool DDA::Init(const GCodes::RawMove &nextMove, bool doMotorMapping)
{
// 1. Compute the new endpoints and the movement vector
- const int32_t *positionNow = prev->DriveCoordinates();
- const Move *move = reprap.GetMove();
+ const int32_t * const positionNow = prev->DriveCoordinates();
+ const Move * const move = reprap.GetMove();
if (doMotorMapping)
{
move->MotorTransform(nextMove.coords, endPoint); // transform the axis coordinates if on a delta or CoreXY printer
diff --git a/src/Movement/Grid.cpp b/src/Movement/Grid.cpp
index ba194537..3cd91e49 100644
--- a/src/Movement/Grid.cpp
+++ b/src/Movement/Grid.cpp
@@ -9,12 +9,11 @@
#include "RepRapFirmware.h"
#include <cmath>
-// Increase the version number in the following string whenever we change the format of the height map file.
-const char *HeightMapComment = "RepRapFirmware height map file v1";
+const char *GridDefinition::HeightMapLabelLine = "xmin,xmax,ymin,ymax,radius,spacing,xnum,ynum";
// Initialise the grid to be invalid
GridDefinition::GridDefinition()
- : xMin(0.0), xMax(-1.0), yMin(0.0), yMax(-1.0), radius(-1.0), spacing(DefaultGridSpacing), gridHeights(nullptr),
+ : xMin(0.0), xMax(-1.0), yMin(0.0), yMax(-1.0), radius(-1.0), spacing(DefaultGridSpacing),
numX(0), numY(0), recipSpacing(1.0/spacing), isValid(false)
{
}
@@ -24,13 +23,13 @@ GridDefinition::GridDefinition(const float xRange[2], const float yRange[2], flo
{
numX = (xMax - xMin >= MinRange && spacing >= MinSpacing) ? (uint32_t)((xMax - xMin) * recipSpacing) + 1 : 0;
numY = (yMax - yMin >= MinRange && spacing >= MinSpacing) ? (uint32_t)((yMax - yMin) * recipSpacing) + 1 : 0;
- isValid = NumPoints() != 0 && NumPoints() <= MaxGridProbePoints && (radius < 0.0 || radius >= 1.0);
+ CheckValidity();
+
}
-void GridDefinition::SetStorage(const float *heightStorage, const uint32_t *heightSetStorage)
+void GridDefinition::CheckValidity()
{
- gridHeights = heightStorage;
- gridHeightSet = heightSetStorage;
+ isValid = NumPoints() != 0 && NumPoints() <= MaxGridProbePoints && (radius < 0.0 || radius >= 1.0);
}
float GridDefinition::GetXCoordinate(unsigned int xIndex) const
@@ -49,27 +48,54 @@ bool GridDefinition::IsInRadius(float x, float y) const
}
// Append the grid parameters to the end of a string
-void GridDefinition::PrintParameters(StringRef& r) const
+void GridDefinition::PrintParameters(StringRef& s) const
+{
+ s.catf("X%.1f:%.1f, Y%.1f:%.1f, radius %.1f, spacing %.1f, %d points", xMin, xMax, yMin, yMax, radius, spacing, NumPoints());
+}
+
+// Write the parameter label line to a string
+void GridDefinition::WriteHeadingAndParameters(StringRef& s) const
+{
+ s.printf("%s\n%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%u,%u\n", HeightMapLabelLine, xMin, xMax, yMin, yMax, radius, spacing, numX, numY);
+}
+
+// Check the parameter label line returning true if correct
+/*static*/ bool GridDefinition::CheckHeading(const StringRef& s)
{
- r.catf("X%.1f:%.1f, Y%.1f:%.1f, radius %.1f, spacing %.1f, %d points", xMin, xMax, yMin, yMax, radius, spacing, NumPoints());
+ return StringStartsWith(s.Pointer(), HeightMapLabelLine);
}
-// Print what is wrong with the grid
+// Read the grid parameters from a string returning true if success
+bool GridDefinition::ReadParameters(const StringRef& s)
+{
+ bool ok = (sscanf(s.Pointer(), "%f,%f,%f,%f,%f,%f,%lu,%lu", &xMin, &xMax, &yMin, &yMax, &radius, &spacing, &numX, &numY) == 8);
+ if (ok)
+ {
+ CheckValidity();
+ }
+ else
+ {
+ isValid = false;
+ }
+ return ok;
+}
+
+// Print what is wrong with the grid, appending it to the existing string
void GridDefinition::PrintError(StringRef& r) const
{
if (spacing < MinSpacing)
{
r.cat("Spacing too small");
}
- else if (NumXpoints() == 0)
+ else if (numX == 0)
{
r.cat("X range too small");
}
- else if (NumYpoints() == 0)
+ else if (numY == 0)
{
r.cat("Y range too small");
}
- else if (NumPoints() > MaxGridProbePoints)
+ else if (numX > MaxGridProbePoints || numY > MaxGridProbePoints || NumPoints() > MaxGridProbePoints) // check X and Y individually in case X*Y overflows
{
r.catf("Too many grid points (maximum %d, needed %d)", MaxGridProbePoints, NumPoints());
}
@@ -80,8 +106,44 @@ void GridDefinition::PrintError(StringRef& r) const
}
}
+// Increase the version number in the following string whenever we change the format of the height map file.
+const char *HeightMap::HeightMapComment = "RepRapFirmware height map file v1";
+
+HeightMap::HeightMap(float *heightStorage) : gridHeights(heightStorage) { }
+
+void HeightMap::SetGrid(const GridDefinition& gd)
+{
+ def = gd;
+ ClearGridHeights();
+}
+
+void HeightMap::ClearGridHeights()
+{
+ for (size_t i = 0; i < MaxGridProbePoints/32; ++i)
+ {
+ gridHeightSet[i] = 0;
+ }
+}
+
+// Set the height of a grid point
+void HeightMap::SetGridHeight(size_t xIndex, size_t yIndex, float height)
+{
+ size_t index = yIndex * def.numX + xIndex;
+ if (index < MaxGridProbePoints)
+ {
+ gridHeights[index] = height;
+ gridHeightSet[index/32] |= 1u << (index & 31u);
+ }
+}
+
+// Return the minimum number of segments for a move by this X or Y amount
+unsigned int HeightMap::GetMinimumSegments(float distance) const
+{
+ return (distance > 0.0) ? (unsigned int)(distance * def.recipSpacing + 0.4) : 1;
+}
+
// Save the grid to file returning true if an error occurred
-bool GridDefinition::SaveToFile(FileStore *f) const
+bool HeightMap::SaveToFile(FileStore *f) const
{
char bufferSpace[500];
StringRef buf(bufferSpace, ARRAY_SIZE(bufferSpace));
@@ -93,7 +155,7 @@ bool GridDefinition::SaveToFile(FileStore *f) const
time_t timeNow = reprap.GetPlatform()->GetDateTime();
const struct tm * const timeInfo = gmtime(&timeNow);
buf.catf(" generated at %04u-%02u-%02u %02u:%02u",
- timeInfo->tm_year, timeInfo->tm_mon, timeInfo->tm_mday, timeInfo->tm_hour, timeInfo->tm_min);
+ timeInfo->tm_year + 1900, timeInfo->tm_mon, timeInfo->tm_mday, timeInfo->tm_hour, timeInfo->tm_min);
}
buf.cat('\n');
if (!f->Write(buf.Pointer()))
@@ -102,10 +164,7 @@ bool GridDefinition::SaveToFile(FileStore *f) const
}
// Write the grid parameters
- buf.printf("xmin,xmax,ymin,ymax,radius,spacing,xnum,ynum\n"
- "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%u,%u\n",
- xMin, xMax, yMin, yMax, radius, spacing, numX, numY
- );
+ def.WriteHeadingAndParameters(buf);
if (!f->Write(buf.Pointer()))
{
return true;
@@ -113,10 +172,10 @@ bool GridDefinition::SaveToFile(FileStore *f) const
// Write the grid heights
uint32_t index = 0;
- for (uint32_t i = 0; i < numY; ++i)
+ for (uint32_t i = 0; i < def.numY; ++i)
{
buf.Clear();
- for (uint32_t j = 0; j < numX; ++j)
+ for (uint32_t j = 0; j < def.numX; ++j)
{
if (j != 0)
{
@@ -142,20 +201,93 @@ bool GridDefinition::SaveToFile(FileStore *f) const
return false;
}
-// Load the grid from file returning true if an error occurred
-bool GridDefinition::LoadFromFile(FileStore *f)
+// Load the grid from file, returning true if an error occurred with the error reason appended to the buffer
+bool HeightMap::LoadFromFile(FileStore *f, StringRef& r)
{
- //TODO
- return true;
+ const size_t MaxLineLength = 200; // maximum length of a line in the height map file
+ const char* const readFailureText = "failed to read line from file";
+ char buffer[MaxLineLength + 1];
+ StringRef s(buffer, ARRAY_SIZE(buffer));
+
+ ClearGridHeights();
+ GridDefinition newGrid;
+
+ if (f->ReadLine(buffer, sizeof(buffer)) <= 0)
+ {
+ r.cat(readFailureText);
+ }
+ else if (!StringStartsWith(buffer, HeightMapComment)) // check the version line is as expected
+ {
+ r.cat("bad header line or wrong version header");
+ }
+ else if (f->ReadLine(buffer, sizeof(buffer)) <= 0)
+ {
+ r.cat(readFailureText);
+ }
+ else if (!GridDefinition::CheckHeading(s)) // check the label line is as expected
+ {
+ r.cat("bad label line");
+ }
+ else if (f->ReadLine(buffer, sizeof(buffer)) <= 0) // read the height map parameters
+ {
+ r.cat(readFailureText);
+ }
+ else if (!newGrid.ReadParameters(s))
+ {
+ r.cat("failed to parse grid parameters");
+ }
+ else if (!newGrid.IsValid())
+ {
+ r.cat("invalid grid");
+ }
+ else
+ {
+ SetGrid(newGrid);
+ for (uint32_t row = 0; row < def.numY; ++row) // read the grid a row at a time
+ {
+ if (f->ReadLine(buffer, sizeof(buffer)) <= 0)
+ {
+ r.cat(readFailureText);
+ return true; // failed to read a line
+ }
+ const char *p = buffer;
+ for (uint32_t col = 0; col < def.numX; ++col)
+ {
+ if (*p == '0' && (p[1] == ',' || p[1] == 0))
+ {
+ // Values of 0 with no decimal places in un-probed values, so leave the point set as not valid
+ ++p;
+ }
+ else
+ {
+ char* np = nullptr;
+ const float f = strtod(p, &np);
+ if (np == p)
+ {
+ r.catf("number expected at line %u column %d", row + 3, (p - buffer) + 1);
+ return true; // failed to read a number
+ }
+ SetGridHeight(col, row, f);
+ p = np;
+ }
+ if (*p == ',')
+ {
+ ++p;
+ }
+ }
+ }
+ return false; // success!
+ }
+ return true; // an error occurred
}
// Compute the height error at the specified point
-float GridDefinition::ComputeHeightError(float x, float y) const
+float HeightMap::ComputeHeightError(float x, float y) const
{
- const float xf = (x - xMin) * recipSpacing;
+ const float xf = (x - def.xMin) * def.recipSpacing;
const float xFloor = floor(xf);
const int32_t xIndex = (int32_t)xFloor;
- const float yf = (y - yMin) * recipSpacing;
+ const float yf = (y - def.yMin) * def.recipSpacing;
const float yFloor = floor(yf);
const int32_t yIndex = (int32_t)yFloor;
@@ -166,29 +298,29 @@ float GridDefinition::ComputeHeightError(float x, float y) const
// We are off the bottom left corner of the grid
return GetHeightError(0, 0);
}
- else if (yIndex >= (int)NumYpoints())
+ else if (yIndex >= (int)def.numY)
{
- return GetHeightError(0, NumYpoints());
+ return GetHeightError(0, def.numY);
}
else
{
return InterpolateY(0, yIndex, yf - yFloor);
}
}
- else if (xIndex >= (int)NumXpoints())
+ else if (xIndex >= (int)def.numX)
{
if (yIndex < 0)
{
// We are off the bottom left corner of the grid
- return GetHeightError(NumXpoints(), 0);
+ return GetHeightError(def.numX, 0);
}
- else if (yIndex >= (int)NumYpoints())
+ else if (yIndex >= (int)def.numY)
{
- return GetHeightError(NumXpoints(), NumYpoints());
+ return GetHeightError(def.numX, def.numY);
}
else
{
- return InterpolateY(NumXpoints(), yIndex, yf - yFloor);
+ return InterpolateY(def.numX, yIndex, yf - yFloor);
}
}
else
@@ -198,9 +330,9 @@ float GridDefinition::ComputeHeightError(float x, float y) const
// We are off the bottom left corner of the grid
return InterpolateX(xIndex, 0, xf - xFloor);
}
- else if (yIndex >= (int)NumYpoints())
+ else if (yIndex >= (int)def.numY)
{
- return InterpolateX(xIndex, NumYpoints(), xf - xFloor);
+ return InterpolateX(xIndex, def.numY, xf - xFloor);
}
else
{
@@ -209,25 +341,25 @@ float GridDefinition::ComputeHeightError(float x, float y) const
}
}
-float GridDefinition::GetHeightError(uint32_t xIndex, uint32_t yIndex) const
+float HeightMap::GetHeightError(uint32_t xIndex, uint32_t yIndex) const
{
const uint32_t index = GetMapIndex(xIndex, yIndex);
return (IsHeightSet(index)) ? gridHeights[index] : 0.0;
}
-float GridDefinition::InterpolateX(uint32_t xIndex, uint32_t yIndex, float xFrac) const
+float HeightMap::InterpolateX(uint32_t xIndex, uint32_t yIndex, float xFrac) const
{
const uint32_t index1 = GetMapIndex(xIndex, yIndex);
return Interpolate2(index1, index1 + 1, xFrac);
}
-float GridDefinition::InterpolateY(uint32_t xIndex, uint32_t yIndex, float yFrac) const
+float HeightMap::InterpolateY(uint32_t xIndex, uint32_t yIndex, float yFrac) const
{
const uint32_t index1 = GetMapIndex(xIndex, yIndex);
- return Interpolate2(index1, index1 + numX, yFrac);
+ return Interpolate2(index1, index1 + def.numX, yFrac);
}
-float GridDefinition::Interpolate2(uint32_t index1, uint32_t index2, float frac) const
+float HeightMap::Interpolate2(uint32_t index1, uint32_t index2, float frac) const
{
const bool b1 = IsHeightSet(index1);
const bool b2 = IsHeightSet(index2);
@@ -237,11 +369,11 @@ float GridDefinition::Interpolate2(uint32_t index1, uint32_t index2, float frac)
: 0.0;
}
-float GridDefinition::InterpolateXY(uint32_t xIndex, uint32_t yIndex, float xFrac, float yFrac) const
+float HeightMap::InterpolateXY(uint32_t xIndex, uint32_t yIndex, float xFrac, float yFrac) const
{
const uint32_t indexX0Y0 = GetMapIndex(xIndex, yIndex); // (X0,Y0)
const uint32_t indexX1Y0 = indexX0Y0 + 1; // (X1,Y0)
- const uint32_t indexX0Y1 = indexX0Y0 + numX; // (X0 Y1)
+ const uint32_t indexX0Y1 = indexX0Y0 + def.numX; // (X0 Y1)
const uint32_t indexX1Y1 = indexX0Y1 + 1; // (X1,Y1)
const unsigned int cc = ((unsigned int)IsHeightSet(indexX0Y0) << 0)
+ ((unsigned int)IsHeightSet(indexX1Y0) << 1)
@@ -291,7 +423,7 @@ float GridDefinition::InterpolateXY(uint32_t xIndex, uint32_t yIndex, float xFra
}
}
-float GridDefinition::InterpolateCorner(uint32_t cornerIndex, uint32_t indexX, uint32_t indexY, float xFrac, float yFrac) const
+float HeightMap::InterpolateCorner(uint32_t cornerIndex, uint32_t indexX, uint32_t indexY, float xFrac, float yFrac) const
{
return ((xFrac * gridHeights[indexX]) + (yFrac * gridHeights[indexY]) + ((2.0 - xFrac - yFrac) * gridHeights[cornerIndex]))/2;
}
diff --git a/src/Movement/Grid.h b/src/Movement/Grid.h
index 9146d394..cb73b21f 100644
--- a/src/Movement/Grid.h
+++ b/src/Movement/Grid.h
@@ -11,6 +11,7 @@
#include <cstdint>
#include "ecv.h"
#include "Libraries/General/StringRef.h"
+#include "Configuration.h"
class FileStore;
@@ -18,9 +19,10 @@ class FileStore;
class GridDefinition
{
public:
+ friend class HeightMap;
+
GridDefinition();
GridDefinition(const float xRange[2], const float yRange[2], float pRadius, float pSpacing);
- void SetStorage(const float *heightStorage, const uint32_t *heightSetStorage);
uint32_t NumXpoints() const { return numX; }
uint32_t NumYpoints() const { return numY; }
@@ -31,35 +33,62 @@ public:
bool IsValid() const { return isValid; }
void PrintParameters(StringRef& r) const;
+ void WriteHeadingAndParameters(StringRef& r) const;
+ static bool CheckHeading(const StringRef& s);
+ bool ReadParameters(const StringRef& s);
void PrintError(StringRef& r) const
pre(!IsValid());
- bool SaveToFile(FileStore *f) const // Save the grid to file returning true if an error occurred
- pre(IsValid());
-
- bool LoadFromFile(FileStore *f); // Load the grid from file returning true if an error occurred
-
- float ComputeHeightError(float x, float y) const // Compute the height error at the specified point
- pre(IsValid(); gridHeights != nullptr; gridHeights.upb >= NumPoints());
-
private:
- static constexpr float MinSpacing = 0.1; // The minimum point spacing allowed
- static constexpr float MinRange = 1.0; // The minimum X and Y range allowed
+ void CheckValidity();
+
+ static constexpr float MinSpacing = 0.1; // The minimum point spacing allowed
+ static constexpr float MinRange = 1.0; // The minimum X and Y range allowed
+ static const char *HeightMapLabelLine; // The line we write to the height map file listing the parameter names
// Primary parameters
- float xMin, xMax, yMin, yMax; // The edges of the grid for G29 probing
- float radius; // The grid radius to probe
- float spacing; // The spacing of the grid probe points
- const float *gridHeights; // The map of grid heights
- const uint32_t *gridHeightSet; // Bitmap of which heights are set
+ float xMin, xMax, yMin, yMax; // The edges of the grid for G29 probing
+ float radius; // The grid radius to probe
+ float spacing; // The spacing of the grid probe points
// Derived parameters
uint32_t numX, numY;
float recipSpacing;
bool isValid;
- uint32_t GetMapIndex(uint32_t xIndex, uint32_t yIndex) const { return (yIndex * numX) + xIndex; }
+};
+
+// Class to represent the height map
+class HeightMap
+{
+public:
+ HeightMap(float *heightStorage);
+
+ const GridDefinition& GetGrid() const { return def; }
+ void SetGrid(const GridDefinition& gd);
+
+ float ComputeHeightError(float x, float y) const // Compute the height error at the specified point
+ pre(IsValid(); gridHeights != nullptr; gridHeights.upb >= NumPoints());
+
+ void ClearGridHeights(); // Clear all grid height corrections
+ void SetGridHeight(size_t xIndex, size_t yIndex, float height); // Set the height of a grid point
+
+ bool SaveToFile(FileStore *f) const // Save the grid to file returning true if an error occurred
+ pre(IsValid());
+
+ bool LoadFromFile(FileStore *f, StringRef& r); // Load the grid from file returning true if an error occurred
+
+ unsigned int GetMinimumSegments(float distance) const; // Return the minimum number of segments for a move by this X or Y amount
+
+private:
+ static const char *HeightMapComment; // The start of the comment we write at the start of the height map file
+
+ GridDefinition def;
+ float *gridHeights; // The map of grid heights, must have at least MaxGridProbePoints entries
+ uint32_t gridHeightSet[MaxGridProbePoints/32]; // Bitmap of which heights are set
+
+ uint32_t GetMapIndex(uint32_t xIndex, uint32_t yIndex) const { return (yIndex * def.NumXpoints()) + xIndex; }
bool IsHeightSet(uint32_t index) const { return (gridHeightSet[index/32] & (1 << (index & 31))) != 0; }
float GetHeightError(uint32_t xIndex, uint32_t yIndex) const;
float InterpolateX(uint32_t xIndex, uint32_t yIndex, float xFrac) const;
diff --git a/src/Movement/Move.cpp b/src/Movement/Move.cpp
index 0d7ccf78..e9ed842d 100644
--- a/src/Movement/Move.cpp
+++ b/src/Movement/Move.cpp
@@ -7,7 +7,7 @@
#include "RepRapFirmware.h"
-Move::Move(Platform* p, GCodes* g) : currentDda(NULL)
+Move::Move(Platform* p, GCodes* g) : currentDda(NULL), grid(zBedProbePoints)
{
active = false;
@@ -66,18 +66,26 @@ void Move::Init()
SetPositions(move);
// Set up default bed probe points. This is only a guess, because we don't know the bed size yet.
- for (size_t point = 0; point < MaxProbePoints; point++)
+ for (size_t point = 0; point < ARRAY_SIZE(zBedProbePoints); point++)
{
if (point < 4)
{
- xBedProbePoints[point] = (0.3 + 0.6*(float)(point%2))*reprap.GetPlatform()->AxisMaximum(X_AXIS);
- yBedProbePoints[point] = (0.0 + 0.9*(float)(point/2))*reprap.GetPlatform()->AxisMaximum(Y_AXIS);
+ xBedProbePoints[point] = (0.1 + 0.8 * (float)(point%2)) * reprap.GetPlatform()->AxisMaximum(X_AXIS);
+ yBedProbePoints[point] = (0.1 + 0.8 * (float)(point/2)) * reprap.GetPlatform()->AxisMaximum(Y_AXIS);
+ }
+ else if (point == 4)
+ {
+ xBedProbePoints[point] = 0.5 * reprap.GetPlatform()->AxisMaximum(X_AXIS);
+ yBedProbePoints[point] = 0.5 * reprap.GetPlatform()->AxisMaximum(Y_AXIS);
}
zBedProbePoints[point] = 0.0;
- probePointSet[point] = unset;
+ if (point < ARRAY_SIZE(probePointSet))
+ {
+ probePointSet[point] = unset;
+ }
}
- xRectangle = 1.0/(0.8*reprap.GetPlatform()->AxisMaximum(X_AXIS));
+ xRectangle = 1.0/(0.8 * reprap.GetPlatform()->AxisMaximum(X_AXIS));
yRectangle = xRectangle;
longWait = reprap.GetPlatform()->Time();
@@ -174,38 +182,6 @@ void Move::Spin()
if (simulationMode < 2) // in simulation mode 2 and higher, we don't process incoming moves beyond this point
{
-#if 0 //*** This code is not finished yet ***
- // If we are doing bed compensation and the move crosses a compensation boundary by a significant amount,
- // segment it so that we can apply proper bed compensation
- // Issues here:
- // 1. Are there enough DDAs? need to make nextMove static and remember whether we have the remains of a move in there.
- // 2. Pause/restart: if we restart a segmented move when we have already executed part of it, we will extrude too much.
- // Perhaps remember how much of the last move we executed? Or always insist on completing all the segments in a move?
- bool isSegmented;
- do
- {
- GCodes::RawMove tempMove = nextMove;
- isSegmented = SegmentMove(tempMove);
- if (isSegmented)
- {
- // Extruder moves are relative, so we need to adjust the extrusion amounts in the original move
- for (size_t drive = AXES; drive < DRIVES; ++drive)
- {
- nextMove.coords[drive] -= tempMove.coords[drive];
- }
- }
- bool doMotorMapping = (moveType == 0) || (moveType == 1 && !IsDeltaMode());
- if (doMotorMapping)
- {
- Transform(tempMove);
- }
- if (ddaRingAddPointer->Init(tempMove.coords, nextMove.feedRate, nextMove.endStopsToCheck, doMotorMapping, nextMove.filePos))
- {
- ddaRingAddPointer = ddaRingAddPointer->GetNext();
- idleCount = 0;
- }
- } while (isSegmented);
-#else // Use old code
bool doMotorMapping = (nextMove.moveType == 0) || (nextMove.moveType == 1 && !IsDeltaMode());
if (doMotorMapping)
{
@@ -216,7 +192,6 @@ void Move::Spin()
ddaRingAddPointer = ddaRingAddPointer->GetNext();
idleCount = 0;
}
-#endif
}
}
else
@@ -432,6 +407,14 @@ void Move::Diagnostics(MessageType mtype)
numLookaheadUnderruns = numPrepareUnderruns = 0;
longestGcodeWaitInterval = 0;
+ // Show the current probe position heights
+ p->Message(mtype, "Bed probe heights:");
+ for (size_t i = 0; i < MaxProbePoints; ++i)
+ {
+ p->MessageF(mtype, " %.3f", ZBedProbePoint(i));
+ }
+ p->Message(mtype, "\n");
+
#if DDA_LOG_PROBE_CHANGES
// Temporary code to print Z probe trigger positions
p->Message(mtype, "Probe change coordinates:");
@@ -1529,88 +1512,6 @@ size_t Move::NumberOfXYProbePoints() const
return MaxProbePoints;
}
-// Set a new grid
-void Move::SetBedProbeGrid(const GridDefinition& newGrid)
-{
- useGridHeights = false;
- grid = newGrid;
- grid.SetStorage(zBedProbePoints, gridHeightSet);
-}
-
-void Move::ClearGridHeights()
-{
- useGridHeights = false;
- for (size_t i = 0; i < ARRAY_SIZE(gridHeightSet); ++i)
- {
- gridHeightSet[i] = 0;
- }
-}
-
-// Set the height of a grid point
-void Move::SetGridHeight(size_t xIndex, size_t yIndex, float height)
-{
- size_t index = yIndex * grid.NumXpoints() + xIndex;
- if (index < MaxGridProbePoints)
- {
- zBedProbePoints[index] = height;
- gridHeightSet[index/32] |= 1u << (index & 31u);
- }
-}
-
-// Load the height map
-bool Move::LoadHeightMapFromFile(const char *fname, StringRef& reply)
-{
- Platform *platform = reprap.GetPlatform();
- FileStore * const f = platform->GetFileStore(platform->GetSysDir(), fname, false);
- bool err;
- if (f == nullptr)
- {
- reply.printf("Height map file %s not found", fname);
- err = true;
- }
- else
- {
- //TODO
- err = grid.LoadFromFile(f);
- f->Close();
- }
-
- if (err)
- {
- ClearGridHeights(); // make sure we don't end up with a partial height map
- }
- return err;
-}
-
-// Save the height map and write the success or error message to 'reply'
-// Returning true if an error occurred
-bool Move::SaveHeightMapToFile(const char *fname, StringRef& reply) const
-{
- Platform *platform = reprap.GetPlatform();
- FileStore * const f = platform->GetFileStore(platform->GetSysDir(), fname, true);
- bool err;
- if (f == nullptr)
- {
- reply.printf("Failed to create height map file %s", fname);
- err = true;
- }
- else
- {
- err = grid.SaveToFile(f);
- f->Close();
- if (err)
- {
- platform->GetMassStorage()->Delete(platform->GetSysDir(), fname);
- reply.printf("Failed to save height map to file %s", fname);
- }
- else
- {
- reply.printf("Height map saved to file %s", fname);
- }
- }
- return err;
-}
-
// Enter or leave simulation mode
void Move::Simulate(uint8_t simMode)
{
diff --git a/src/Movement/Move.h b/src/Movement/Move.h
index 401f0c1d..698fae2f 100644
--- a/src/Movement/Move.h
+++ b/src/Movement/Move.h
@@ -113,16 +113,10 @@ public:
bool IsExtruding() const; // Is filament being extruded?
- const GridDefinition& GetBedProbeGrid() const { return grid; } // Access the bed probing grid
+ HeightMap& AccessBedProbeGrid() { return grid; } // Access the bed probing grid
- void SetBedProbeGrid(const GridDefinition& newGrid) // Set a new grid
- pre(newGrid.IsValid());
-
- void ClearGridHeights(); // Clear all grid height corrections
- void SetGridHeight(size_t xIndex, size_t yIndex, float height); // Set the height of a grid point
- void UseHeightMap() { useGridHeights = true; } // Start using the height map
- bool LoadHeightMapFromFile(const char *fname, StringRef& reply); // Load the height map and *append* any error message to 'reply'
- bool SaveHeightMapToFile(const char *fname, StringRef& reply) const; // Save the height map
+ void UseHeightMap(bool b) { useGridHeights = b; } // Start or stop using the height map
+ bool UsingHeightMap() const { return useGridHeights; } // Are we doing grid bed compensation?
private:
@@ -183,8 +177,7 @@ private:
int numBedCompensationPoints; // The number of points we are actually using for bed compensation, 0 means identity bed transform
float xRectangle, yRectangle; // The side lengths of the rectangle used for second-degree bed compensation
- GridDefinition grid; // Grid definition for G29 bed probing. The probe heights are stored in zBedProbePoints, see above.
- uint32_t gridHeightSet[MaxGridProbePoints/32]; // Bitmap of which points have been probed
+ HeightMap grid; // Grid definition and height map for G29 bed probing. The probe heights are stored in zBedProbePoints, see above.
bool useGridHeights; // True if the zBedProbePoints came from valid bed probing and relate to the current grid
float idleTimeout; // How long we wait with no activity before we reduce motor currents to idle
diff --git a/src/Pins.h b/src/Pins.h
index de9738e0..46d06333 100644
--- a/src/Pins.h
+++ b/src/Pins.h
@@ -5,7 +5,11 @@
#if !defined(PLATFORM)
# if defined(__SAM3X8E__)
-# define PLATFORM Duet
+# if defined(__RADDS__)
+# define PLATFORM RADDS
+# else
+# define PLATFORM Duet
+# endif
# elif defined(__SAM4E8E__)
# define PLATFORM DuetNG
# else
diff --git a/src/Platform.cpp b/src/Platform.cpp
index 72ddd3f4..3fb43cf9 100644
--- a/src/Platform.cpp
+++ b/src/Platform.cpp
@@ -21,7 +21,6 @@
#include "RepRapFirmware.h"
#include "DueFlashStorage.h"
-#include "RTCDue.h"
#include "sam/drivers/tc/tc.h"
#include "sam/drivers/hsmci/hsmci.h"
@@ -54,6 +53,12 @@ static volatile uint32_t fanInterval = 0; // written by ISR, read outside the
const float minStepPulseTiming = 0.2; // we assume that we always generate step high and low times at least this wide without special action
+const int Heater0LogicalPin = 0;
+const int Fan0LogicalPin = 20;
+const int EndstopXLogicalPin = 40;
+const int Special0LogicalPin = 60;
+const int DueX5Gpio0LogicalPin = 100;
+
//#define MOVE_DEBUG
#ifdef MOVE_DEBUG
@@ -157,8 +162,7 @@ void Platform::Init()
SetBoardType(BoardType::Auto);
// Real-time clock
-
- RTCDue::Init();
+ realTime = 0;
// Comms
@@ -202,22 +206,12 @@ void Platform::Init()
fileStructureInitialised = true;
-#if !defined(DUET_NG)
+#if !defined(DUET_NG) && !defined(__RADDS__)
mcpDuet.begin(); // only call begin once in the entire execution, this begins the I2C comms on that channel for all objects
mcpExpansion.setMCP4461Address(0x2E); // not required for mcpDuet, as this uses the default address
#endif
- // Directories
-
- sysDir = SYS_DIR;
- macroDir = MACRO_DIR;
- webDir = WEB_DIR;
- gcodeDir = GCODE_DIR;
- configFile = CONFIG_FILE;
- defaultFile = DEFAULT_FILE;
-
// DRIVES
-
ARRAY_INIT(endStopPins, END_STOP_PINS);
ARRAY_INIT(maxFeedrates, MAX_FEEDRATES);
ARRAY_INIT(accelerations, ACCELERATIONS);
@@ -236,13 +230,11 @@ void Platform::Init()
maxAverageAcceleration = 10000.0; // high enough to have no effect until it is changed
// Z PROBE
-
zProbePin = Z_PROBE_PIN;
zProbeAdcChannel = PinToAdcChannel(zProbePin);
InitZProbe(); // this also sets up zProbeModulationPin
// AXES
-
ARRAY_INIT(axisMaxima, AXIS_MAXIMA);
ARRAY_INIT(axisMinima, AXIS_MINIMA);
@@ -334,7 +326,11 @@ void Platform::Init()
TMC2660::Init(ENABLE_PINS, numTMC2660Drivers);
#endif
- extrusionAncilliaryPWM = 0.0;
+ extrusionAncilliaryPwmValue = 0.0;
+ extrusionAncilliaryPwmLogicalPin = -1;
+ extrusionAncilliaryPwmFirmwarePin = NoPin;
+ extrusionAncilliaryPwmInvert = false;
+ SetExtrusionAncilliaryPwmPin(Fan0LogicalPin);
ARRAY_INIT(tempSensePins, TEMP_SENSE_PINS);
ARRAY_INIT(heatOnPins, HEAT_ON_PINS);
@@ -1204,6 +1200,16 @@ void Platform::Spin()
TMC2660::SetDriversPowered(driversPowered);
#endif
+ // Update the time
+ if (realTime != 0)
+ {
+ if (millis() - timeLastUpdatedMillis >= 1000)
+ {
+ ++realTime; // this assumes that time_t is a seconds-since-epoch counter, which is not guaranteed by the C standard
+ timeLastUpdatedMillis += 1000;
+ }
+ }
+
ClassReport(longWait);
}
@@ -1372,7 +1378,7 @@ void Platform::Diagnostics(MessageType mtype)
(unsigned int)(now/3600), (unsigned int)((now % 3600)/60), (unsigned int)(now % 60),
resetReasons[(REG_RSTC_SR & RSTC_SR_RSTTYP_Msk) >> RSTC_SR_RSTTYP_Pos]);
- // Show the error code stored at the last software reset
+ // Show the reset code stored at the last software reset
{
SoftwareResetData temp;
temp.magic = 0;
@@ -1387,14 +1393,6 @@ void Platform::Diagnostics(MessageType mtype)
// Show the current error codes
MessageF(mtype, "Error status: %u\n", errorCodeBits);
- // Show the current probe position heights
- Message(mtype, "Bed probe heights:");
- for (size_t i = 0; i < MaxProbePoints; ++i)
- {
- MessageF(mtype, " %.3f", reprap.GetMove()->ZBedProbePoint(i));
- }
- Message(mtype, "\n");
-
// Show the number of free entries in the file table
unsigned int numFreeFiles = 0;
for (size_t i = 0; i < MAX_FILES; i++)
@@ -1449,11 +1447,13 @@ void Platform::Diagnostics(MessageType mtype)
#endif
// Show current RTC time
- const time_t timeNow = RTCDue::GetDateTime();
- const struct tm * const timeInfo = gmtime(&timeNow);
- MessageF(mtype, "Current date and time: %04u-%02u-%02u %02u:%02u:%02u\n",
- timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday,
- timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
+ struct tm timeInfo;
+ if (gmtime_r(&realTime, &timeInfo) != nullptr)
+ {
+ MessageF(mtype, "Current date and time: %04u-%02u-%02u %02u:%02u:%02u\n",
+ timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday,
+ timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
+ }
// Debug
//MessageF(mtype, "TC_FMR = %08x, PWM_FPE = %08x, PWM_FSR = %08x\n", TC2->TC_FMR, PWM->PWM_FPE, PWM->PWM_FSR);
@@ -2608,50 +2608,50 @@ bool Platform::GetFirmwarePin(int logicalPin, PinAccess access, Pin& firmwarePin
{
// Pin number out of range, so nothing to do here
}
- else if (logicalPin < HEATERS) // pins 0-9 correspond to heater channels
+ else if (logicalPin >= Heater0LogicalPin && logicalPin < Heater0LogicalPin + HEATERS) // pins 0-9 correspond to heater channels
{
// For safety, we don't allow a heater channel to be used for servos until the heater has been disabled
- if (!reprap.GetHeat()->IsHeaterEnabled(logicalPin))
+ if (!reprap.GetHeat()->IsHeaterEnabled(logicalPin - Heater0LogicalPin))
{
- firmwarePin = heatOnPins[logicalPin];
+ firmwarePin = heatOnPins[logicalPin - Heater0LogicalPin];
invert = !HEAT_ON;
}
}
- else if (logicalPin >= 20 && logicalPin < 20 + (int)NUM_FANS) // pins 100-107 correspond to fan channels
+ else if (logicalPin >= Fan0LogicalPin && logicalPin < Fan0LogicalPin + (int)NUM_FANS) // pins 20- correspond to fan channels
{
// Don't allow a fan channel to be used unless the fan has been disabled
- if (!fans[logicalPin - 20].IsEnabled()
+ if (!fans[logicalPin - Fan0LogicalPin].IsEnabled()
#ifdef DUET_NG
// Fan pins on the DueX2/DueX5 cannot be used to control servos because the frequency is not well-defined.
- && (logicalPin <= 22 || access != PinAccess::servo)
+ && (logicalPin <= Fan0LogicalPin + 2 || access != PinAccess::servo)
#endif
)
{
- firmwarePin = COOLING_FAN_PINS[logicalPin - 20];
+ firmwarePin = COOLING_FAN_PINS[logicalPin - Fan0LogicalPin];
}
}
- else if (logicalPin >= 40 && logicalPin < 40 + (int)ARRAY_SIZE(endStopPins)) // pins 40-49 correspond to endstop pins
+ else if (logicalPin >= EndstopXLogicalPin && logicalPin < EndstopXLogicalPin + (int)ARRAY_SIZE(endStopPins)) // pins 40-49 correspond to endstop pins
{
if (access == PinAccess::read
#ifdef DUET_NG
// Endstop pins on the DueX2/DueX5 can be used as digital outputs too
- || (access == PinAccess::write && logicalPin >= 45)
+ || (access == PinAccess::write && logicalPin >= EndstopXLogicalPin + 5)
#endif
)
{
- firmwarePin = endStopPins[logicalPin - 40];
+ firmwarePin = endStopPins[logicalPin - EndstopXLogicalPin];
}
}
- else if (logicalPin >= 60 && logicalPin < 60 + (int)ARRAY_SIZE(SpecialPinMap))
+ else if (logicalPin >= Special0LogicalPin && logicalPin < Special0LogicalPin + (int)ARRAY_SIZE(SpecialPinMap))
{
- firmwarePin = SpecialPinMap[logicalPin - 60];
+ firmwarePin = SpecialPinMap[logicalPin - Special0LogicalPin];
}
#ifdef DUET_NG
- else if (logicalPin >= 100 && logicalPin < 100 + (int)ARRAY_SIZE(DueX5GpioPinMap)) // Pins 100-103 are the GPIO pins on the DueX2/X5
+ else if (logicalPin >= DueX5Gpio0LogicalPin && logicalPin < DueX5Gpio0LogicalPin + (int)ARRAY_SIZE(DueX5GpioPinMap)) // Pins 100-103 are the GPIO pins on the DueX2/X5
{
if (access != PinAccess::servo)
{
- firmwarePin = DueX5GpioPinMap[logicalPin - 100];
+ firmwarePin = DueX5GpioPinMap[logicalPin - DueX5Gpio0LogicalPin];
}
}
#endif
@@ -2682,6 +2682,11 @@ bool Platform::GetFirmwarePin(int logicalPin, PinAccess access, Pin& firmwarePin
return false;
}
+bool Platform::SetExtrusionAncilliaryPwmPin(int logicalPin)
+{
+ return GetFirmwarePin(logicalPin, PinAccess::pwm, extrusionAncilliaryPwmFirmwarePin, extrusionAncilliaryPwmInvert);
+}
+
#if SUPPORT_INKJET
// Fire the inkjet (if any) in the given pattern
@@ -2791,27 +2796,76 @@ void Platform::GetPowerVoltages(float& minV, float& currV, float& maxV) const
bool Platform::IsDateTimeSet() const
{
- return RTCDue::IsDateTimeSet();
+ return realTime != 0;
}
time_t Platform::GetDateTime() const
{
- return RTCDue::GetDateTime();
+ return realTime;
}
bool Platform::SetDateTime(time_t time)
{
- return RTCDue::SetDateTime(time);
+ struct tm brokenDateTime;
+ const bool ok = (gmtime_r(&time, &brokenDateTime) != nullptr);
+ if (ok)
+ {
+ realTime = time;
+ timeLastUpdatedMillis = millis();
+ }
+ return ok;
}
bool Platform::SetDate(time_t date)
{
- return RTCDue::SetDate(date);
+ // Check the validity of the date passed in
+ struct tm brokenNewDate;
+ const bool ok = (gmtime_r(&date, &brokenNewDate) != nullptr);
+ if (ok)
+ {
+ struct tm brokenTimeNow;
+ if (realTime == 0 || gmtime_r(&realTime, &brokenTimeNow) == nullptr)
+ {
+ // We didn't have a valid date/time set, so set the date and time to the value passed in
+ realTime = date;
+ timeLastUpdatedMillis = millis();
+ }
+ else
+ {
+ // Merge the existing time into the date passed in
+ brokenNewDate.tm_hour = brokenTimeNow.tm_hour;
+ brokenNewDate.tm_min = brokenTimeNow.tm_min;
+ brokenNewDate.tm_sec = brokenTimeNow.tm_sec;
+ realTime = mktime(&brokenNewDate);
+ }
+ }
+ return ok;
}
bool Platform::SetTime(time_t time)
{
- return RTCDue::SetTime(time);
+ // Check the validity of the date passed in
+ struct tm brokenNewTime;
+ const bool ok = (gmtime_r(&time, &brokenNewTime) != nullptr);
+ if (ok)
+ {
+ struct tm brokenTimeNow;
+ if (realTime == 0 || gmtime_r(&realTime, &brokenTimeNow) == nullptr)
+ {
+ // We didn't have a valid date/time set, so set the date and time to the value passed in
+ realTime = time;
+ }
+ else
+ {
+ // Merge the new time into the current date/time
+ brokenTimeNow.tm_hour = brokenNewTime.tm_hour;
+ brokenTimeNow.tm_min = brokenNewTime.tm_min;
+ brokenTimeNow.tm_sec = brokenNewTime.tm_sec;
+ realTime = mktime(&brokenTimeNow);
+ }
+ timeLastUpdatedMillis = millis();
+ }
+ return ok;
}
// Pragma pop_options is not supported on this platform, so we put this time-critical code right at the end of the file
diff --git a/src/Platform.h b/src/Platform.h
index 036084f9..8c9af3cb 100644
--- a/src/Platform.h
+++ b/src/Platform.h
@@ -236,7 +236,8 @@ struct ZProbeParameters
float diveHeight; // the dive height we use when probing
float probeSpeed; // the initial speed of probing
float travelSpeed; // the speed at which we travel to the probe point
- float param1, param2; // extra parameters used by some types of probe e.g. Delta probe
+ float recoveryTime; // Z probe recovery time
+ float extraParam; // extra parameters used by some types of probe e.g. Delta probe
bool invertReading; // true if we need to invert the reading
void Init(float h)
@@ -249,7 +250,7 @@ struct ZProbeParameters
diveHeight = DEFAULT_Z_DIVE;
probeSpeed = DEFAULT_PROBE_SPEED;
travelSpeed = DEFAULT_TRAVEL_SPEED;
- param1 = param2 = 0.0;
+ recoveryTime = extraParam = 0.0;
invertReading = false;
}
@@ -269,8 +270,8 @@ struct ZProbeParameters
&& diveHeight == other.diveHeight
&& probeSpeed == other.probeSpeed
&& travelSpeed == other.travelSpeed
- && param1 == other.param1
- && param2 == other.param2
+ && recoveryTime == other.recoveryTime
+ && extraParam == other.extraParam
&& invertReading == other.invertReading;
}
@@ -527,8 +528,13 @@ public:
bool IsAccessibleProbePoint(float x, float y) const;
float GetPressureAdvance(size_t drive) const;
void SetPressureAdvance(size_t extruder, float factor);
- void SetEndStopConfiguration(size_t axis, EndStopType endstopType, bool logicLevel);
- void GetEndStopConfiguration(size_t axis, EndStopType& endstopType, bool& logicLevel) const;
+
+ void SetEndStopConfiguration(size_t axis, EndStopType endstopType, bool logicLevel)
+ pre(axis < MAX_AXES);
+
+ void GetEndStopConfiguration(size_t axis, EndStopType& endstopType, bool& logicLevel) const
+ pre(axis < MAX_AXES);
+
uint32_t GetAllEndstopStates() const;
void SetAxisDriversConfig(size_t drive, const AxisDriversConfig& config);
const AxisDriversConfig& GetAxisDriversConfig(size_t drive) const
@@ -559,8 +565,10 @@ public:
void SetZProbeParameters(const struct ZProbeParameters& params);
bool MustHomeXYBeforeZ() const;
- void SetExtrusionAncilliaryPWM(float v);
- float GetExtrusionAncilliaryPWM() const;
+ void SetExtrusionAncilliaryPwmValue(float v);
+ float GetExtrusionAncilliaryPwmValue() const;
+ bool SetExtrusionAncilliaryPwmPin(int logicalPin);
+ int GetExtrusionAncilliaryPwmPin() const { return extrusionAncilliaryPwmLogicalPin; }
void ExtrudeOn();
void ExtrudeOff();
@@ -766,7 +774,7 @@ private:
#if defined(DUET_NG)
size_t numTMC2660Drivers; // the number of TMC2660 drivers we have, the remaining are simple enable/step/dir drivers
-#else
+#elif !defined(__RADDS__)
// Digipots
MCP4461 mcpDuet;
MCP4461 mcpExpansion;
@@ -784,7 +792,10 @@ private:
volatile ZProbeAveragingFilter zProbeOffFilter; // Z probe readings we took with the IR turned off
volatile ThermistorAveragingFilter thermistorFilters[HEATERS]; // bed and extruder thermistor readings
- float extrusionAncilliaryPWM;
+ float extrusionAncilliaryPwmValue;
+ int extrusionAncilliaryPwmLogicalPin;
+ Pin extrusionAncilliaryPwmFirmwarePin;
+ bool extrusionAncilliaryPwmInvert;
void InitZProbe();
uint16_t GetRawZProbeReading() const;
@@ -831,12 +842,6 @@ private:
MassStorage* massStorage;
FileStore* files[MAX_FILES];
bool fileStructureInitialised;
- const char* webDir;
- const char* gcodeDir;
- const char* sysDir;
- const char* macroDir;
- const char* configFile;
- const char* defaultFile;
// Data used by the tick interrupt handler
@@ -896,6 +901,10 @@ private:
bool driversPowered;
#endif
+ // RTC
+ time_t realTime; // the current date/time, or zero if never set
+ uint32_t timeLastUpdatedMillis; // the milliseconds counter when we last incremented the time
+
// Direct pin manipulation
int8_t logicalPinModes[HighestLogicalPin + 1]; // what mode each logical pin is set to - would ideally be class PinMode not int8_t
};
@@ -968,51 +977,50 @@ private:
inline const char* Platform::GetWebDir() const
{
- return webDir;
+ return WEB_DIR;
}
// Where the gcodes are
inline const char* Platform::GetGCodeDir() const
{
- return gcodeDir;
+ return GCODE_DIR;
}
// Where the system files are
inline const char* Platform::GetSysDir() const
{
- return sysDir;
+ return SYS_DIR;
}
inline const char* Platform::GetMacroDir() const
{
- return macroDir;
+ return MACRO_DIR;
}
inline const char* Platform::GetConfigFile() const
{
- return configFile;
+ return CONFIG_FILE;
}
inline const char* Platform::GetDefaultFile() const
{
- return defaultFile;
+ return DEFAULT_FILE;
}
-
//*****************************************************************************************************************
// Drive the RepRap machine - Movement
inline float Platform::DriveStepsPerUnit(size_t drive) const
{
- return driveStepsPerUnit[drive];
+ return driveStepsPerUnit[drive];
}
inline void Platform::SetDriveStepsPerUnit(size_t drive, float value)
{
- driveStepsPerUnit[drive] = value;
+ driveStepsPerUnit[drive] = value;
}
inline float Platform::Acceleration(size_t drive) const
@@ -1032,7 +1040,7 @@ inline void Platform::SetAcceleration(size_t drive, float value)
inline float Platform::MaxFeedrate(size_t drive) const
{
- return maxFeedrates[drive];
+ return maxFeedrates[drive];
}
inline const float* Platform::MaxFeedrates() const
@@ -1067,7 +1075,7 @@ inline bool Platform::GetDirectionValue(size_t drive) const
inline void Platform::SetDriverDirection(uint8_t driver, bool direction)
{
- bool d = (direction == FORWARDS) ? directions[driver] : !directions[driver];
+ const bool d = (direction == FORWARDS) ? directions[driver] : !directions[driver];
digitalWrite(DIRECTION_PINS[driver], d);
}
@@ -1107,14 +1115,14 @@ inline float Platform::AxisTotalLength(size_t axis) const
return axisMaxima[axis] - axisMinima[axis];
}
-inline void Platform::SetExtrusionAncilliaryPWM(float v)
+inline void Platform::SetExtrusionAncilliaryPwmValue(float v)
{
- extrusionAncilliaryPWM = v;
+ extrusionAncilliaryPwmValue = v;
}
-inline float Platform::GetExtrusionAncilliaryPWM() const
+inline float Platform::GetExtrusionAncilliaryPwmValue() const
{
- return extrusionAncilliaryPWM;
+ return extrusionAncilliaryPwmValue;
}
// For the Duet we use the fan output for this
@@ -1123,9 +1131,9 @@ inline float Platform::GetExtrusionAncilliaryPWM() const
// Caution: this is often called from an ISR, or with interrupts disabled!
inline void Platform::ExtrudeOn()
{
- if (extrusionAncilliaryPWM > 0.0)
+ if (extrusionAncilliaryPwmValue > 0.0)
{
- SetFanValue(0,extrusionAncilliaryPWM);
+ WriteAnalog(extrusionAncilliaryPwmFirmwarePin, extrusionAncilliaryPwmValue, DefaultPinWritePwmFreq);
}
}
@@ -1134,9 +1142,9 @@ inline void Platform::ExtrudeOn()
// Caution: this is often called from an ISR, or with interrupts disabled!
inline void Platform::ExtrudeOff()
{
- if (extrusionAncilliaryPWM > 0.0)
+ if (extrusionAncilliaryPwmValue > 0.0)
{
- SetFanValue(0,0.0);
+ WriteAnalog(extrusionAncilliaryPwmFirmwarePin, 0.0, DefaultPinWritePwmFreq);
}
}
@@ -1204,14 +1212,12 @@ inline float Platform::GetPressureAdvance(size_t extruder) const
}
inline void Platform::SetEndStopConfiguration(size_t axis, EndStopType esType, bool logicLevel)
-pre(axis < MAX_AXES)
{
endStopType[axis] = esType;
endStopLogicLevel[axis] = logicLevel;
}
inline void Platform::GetEndStopConfiguration(size_t axis, EndStopType& esType, bool& logicLevel) const
-pre(axis < MAX_AXES)
{
esType = endStopType[axis];
logicLevel = endStopLogicLevel[axis];
@@ -1325,7 +1331,7 @@ inline float Platform::AdcReadingToPowerVoltage(uint16_t adcVal)
#if defined(DUET_NG)
return pinDesc.ulPin;
#elif defined(__RADDS__)
-# error needs writing
+ return (pinDesc.pPort == PIOC) ? pinDesc.ulPin << 1 : pinDesc.ulPin;
#else
return (pinDesc.pPort == PIOA) ? pinDesc.ulPin << 1 : pinDesc.ulPin;
#endif
@@ -1339,7 +1345,10 @@ inline float Platform::AdcReadingToPowerVoltage(uint16_t adcVal)
#if defined(DUET_NG)
PIOD->PIO_ODSR = driverMap; // on Duet WiFi all step pins are on port D
#elif defined(__RADDS__)
-# error need to write this
+ PIOA->PIO_ODSR = driverMap;
+ PIOB->PIO_ODSR = driverMap;
+ PIOD->PIO_ODSR = driverMap;
+ PIOC->PIO_ODSR = driverMap >> 1; // do this last, it means the processor doesn't need to preserve the register containing driverMap
#else // Duet
PIOD->PIO_ODSR = driverMap;
PIOC->PIO_ODSR = driverMap;
@@ -1355,7 +1364,10 @@ inline float Platform::AdcReadingToPowerVoltage(uint16_t adcVal)
#if defined(DUET_NG)
PIOD->PIO_ODSR = 0; // on Duet WiFi all step pins are on port D
#elif defined(__RADDS__)
-# error need to write this
+ PIOD->PIO_ODSR = 0;
+ PIOC->PIO_ODSR = 0;
+ PIOB->PIO_ODSR = 0;
+ PIOA->PIO_ODSR = 0;
#else // Duet
PIOD->PIO_ODSR = 0;
PIOC->PIO_ODSR = 0;
diff --git a/src/RADDS/Network.cpp b/src/RADDS/Network.cpp
new file mode 100644
index 00000000..901f3b45
--- /dev/null
+++ b/src/RADDS/Network.cpp
@@ -0,0 +1,8 @@
+#include "Network.h"
+
+static const uint8_t dummy_ipv4[4] = { 0, 0, 0, 0 };
+
+const uint8_t *Network::IPAddress() const
+{
+ return dummy_ipv4;
+}
diff --git a/src/RADDS/Network.h b/src/RADDS/Network.h
new file mode 100644
index 00000000..0cc3db50
--- /dev/null
+++ b/src/RADDS/Network.h
@@ -0,0 +1,34 @@
+#ifndef NETWORK_H
+#define NETWORK_H
+
+#include <inttypes.h>
+#include "Platform.h"
+
+const uint8_t MAC_ADDRESS[6] = { 0, 0, 0, 0, 0, 0 };
+const uint8_t IP_ADDRESS[4] = { 0, 0, 0, 0 };
+const uint8_t NET_MASK[4] = { 0, 0, 0, 0 };
+const uint8_t GATE_WAY[4] = { 0, 0, 0, 0 };
+
+// The main network class that drives the network.
+class Network
+{
+public:
+ Network(Platform* p) { };
+ void Init() const { };
+ void Activate() const { };
+ void Disable() const { };
+ void Enable() const { };
+ void Exit() const { }
+ void Spin() const { };
+ void Interrupt() const { };
+ void Diagnostics(MessageType mtype) const { };
+
+ boolean IsEnabled() const { return false; }
+ boolean InLwip() const { return false; }
+ void SetHostname(const char *name) const { };
+ void SetHttpPort(uint16_t port) const { };
+ uint16_t GetHttpPort() const { return (uint16_t)0; }
+ const uint8_t *IPAddress() const;
+};
+
+#endif
diff --git a/src/RADDS/Pins_radds.h b/src/RADDS/Pins_radds.h
new file mode 100644
index 00000000..09beff9c
--- /dev/null
+++ b/src/RADDS/Pins_radds.h
@@ -0,0 +1,248 @@
+#ifndef PINS_DUET_H__
+#define PINS_DUET_H__
+
+#define NAME "RepRapFirmware for Duet"
+
+const size_t NumFirmwareUpdateModules = 1;
+#define IAP_UPDATE_FILE "iapradds.bin"
+#define IAP_FIRMWARE_FILE "RepRapFirmware.bin"
+
+// Default board type
+#define DEFAULT_BOARD_TYPE BoardType::RADDS_15
+#define ELECTRONICS "RADDS (+ Extension)"
+
+#define SUPPORT_INKJET 0 // set nonzero to support inkjet control
+#define SUPPORT_ROLAND 0 // set nonzero to support Roland mill
+
+// The physical capabilities of the machine
+
+// The number of drives in the machine, including X, Y, and Z plus extruder drives
+const size_t DRIVES = 8;
+
+// Initialization macro used in statements needing to initialize values in arrays of size DRIVES. E.g.,
+// max_feed_rates[DRIVES] = {DRIVES_(1, 1, 1, 1, 1, 1, 1, 1, 1)}
+#define DRIVES_(a,b,c,d,e,f,g,h,i,j) { a,b,c,d,e,f,g,h }
+const size_t MaxDriversPerAxis = 4; // The maximum number of stepper drivers assigned to one axis
+
+// The number of heaters in the machine
+// 0 is the heated bed even if there isn't one.
+const int8_t HEATERS = 4;
+
+// Initialization macro used in statements needing to initialize values in arrays of size HEATERS. E.g.,
+// defaultPidKis[HEATERS] = {HEATERS_(5.0, 0.1, 0.1, 0.1, 0.1, 0.1)};
+#define HEATERS_(a,b,c,d,e,f,g,h) { a,b,c,d }
+
+const size_t MAX_AXES = 6; // FIXME The maximum number of movement axes in the machine, usually just X, Y and Z, <= DRIVES
+const size_t MIN_AXES = 3; // The minimum and default number of axes
+const size_t DELTA_AXES = 3; // The number of axis involved in delta movement
+const size_t CART_AXES = 3; // The number of Cartesian axes
+const size_t MaxExtruders = DRIVES - MIN_AXES; // The maximum number of extruders
+
+const size_t NUM_SERIAL_CHANNELS = 2;
+// Use TX0/RX0 for the auxiliary serial line
+#define SERIAL_MAIN_DEVICE SerialUSB
+#define SERIAL_AUX_DEVICE Serial1
+
+// The numbers of entries in each array must correspond with the values of DRIVES, AXES, or HEATERS. Set values to -1 to flag unavailability.
+// DRIVES
+// X Y Z E1 E2 E3 E4 E5
+const Pin ENABLE_PINS[DRIVES] = { 26, 22, 15, 62, 65, 49, 37, 31 };
+const bool ENABLE_VALUES[DRIVES] = { false, false, false, false, false, false, false, false };
+// A15 D04 B25 A02 B19 C12 C03 D06
+const Pin STEP_PINS[DRIVES] = { 24, 17, 2, 61, 64, 51, 35, 29 };
+const Pin DIRECTION_PINS[DRIVES] = { 23, 16, 3, 60, 63, 53, 33, 27 };
+
+// Endstops
+// E Stops not currently used
+// Note: RepRapFirmware only as a single endstop per axis
+// gcode defines if it is a max ("high end") or min ("low end")
+// endstop. gcode also sets if it is active HIGH or LOW
+//
+// 28 = RADDS X min
+// 30 = RADDS Y min
+// 32 = RADDS Z min
+// 39 = RADDS PWM3
+//
+// This leaves 34, 36, and 38 as spare pins (X, Y, Z max)
+
+const Pin END_STOP_PINS[DRIVES] = { 28, 30, 32, 39, NoPin, NoPin, NoPin, NoPin };
+
+// HEATERS - The bed is assumed to be the at index 0
+
+// 0 for inverted heater (e.g. Duet v0.6)
+// 1 for not (e.g. Duet v0.4; RADDS)
+const bool HEAT_ON = true;
+
+// Analogue pin numbers
+const Pin TEMP_SENSE_PINS[HEATERS] = HEATERS_(4, 0, 1, 2, e, f, g, h);
+
+// Heater outputs
+// Bed PMW: D7 has hardware PWM so bed has PWM
+// h0, h1 PMW: D13 & D12 are on TIOB0 & B8 which are both TC B channels, so they get PWM
+// h2 bang-bang: D11 is on TIOA8 which is a TC A channel shared with h1, it gets bang-bang control
+
+const Pin HEAT_ON_PINS[HEATERS] = HEATERS_(7, 13, 12, 11, e, f, g, h); // bed, h0, h1, h2
+
+// Default thermistor betas
+const float BED_R25 = 10000.0;
+const float BED_BETA = 4066.0;
+const float EXT_R25 = 100000.0;
+const float EXT_BETA = 4066.0;
+
+// Thermistor series resistor value in Ohms
+const float THERMISTOR_SERIES_RS = 4700.0;
+
+const size_t MaxSpiTempSensors = 2;
+
+// Digital pins the 31855s have their select lines tied to
+const Pin SpiTempSensorCsPins[MaxSpiTempSensors] = { 38, 36 };
+
+// Digital pin number that controls the ATX power on/off
+const Pin ATX_POWER_PIN = 40;
+
+// Z Probe pin
+// Must be an ADC capable pin. Can be any of the ARM's A/D capable
+// pins even a non-Arduino pin.
+const Pin Z_PROBE_PIN = 5; // RADDS "ADC" pin
+
+// Digital pin number to turn the IR LED on (high) or off (low)
+// channel 1: Z_PROBE_MOD_PIN07
+// channel 0: Z_PROBE_MOD_PIN
+// Make channel 0 == channel 1 for RADDS. Difference in channels
+// is an artifact of difference in different versions of Duet electronics
+//
+// D34 -- unused X-max on RADDS
+const Pin Z_PROBE_MOD_PIN = 34;
+const Pin Z_PROBE_MOD_PIN07 = 34;
+
+// Use a PWM capable pin
+// Firmware uses SamNonDue so feel free to use a non-Arduino pin
+const size_t NUM_FANS = 2;
+const Pin COOLING_FAN_PINS[NUM_FANS] = { 9, 8 }; // Fan 0, Fan 1
+
+// Firmware will attach a FALLING interrupt to this pin
+// see FanInterrupt() in Platform.cpp
+//
+// D25 -- Unused GPIO on AUX1
+const Pin COOLING_FAN_RPM_PIN = 25;
+
+// SD cards
+const size_t NumSdCards = 1;
+const Pin SdCardDetectPins[NumSdCards] = {NoPin};
+const Pin SdWriteProtectPins[NumSdCards] = { NoPin};
+const Pin SdSpiCSPins[1] = {zz}; //FIXME
+
+// Definition of which pins we allow to be controlled using M42
+//
+// Spare pins on the Arduino Due are
+//
+// D5 / TIOA6 / C.25
+// D6 / PWML7 / C.24
+// ### Removed: now E0_AXIS endstop D39 / PWMH2 / C.7
+// D58 / AD3 / A.6
+// D59 / AD2 / A.4
+// D66 / DAC0 / B.15
+// D67 / DAC1 / B.16
+// D68 / CANRX0 / A.1
+// D69 / CANTX0 / A.0
+// D70 / SDA1 / A.17
+// D71 / SCL1 / A.18
+// D72 / RX LED / C.30
+// D73 / TX LED / A.21
+
+const size_t NUM_PINS_ALLOWED = 80;
+#define PINS_ALLOWED { \
+ /* pins 00-07 */ 0b01100000, \
+ /* pins 08-15 */ 0, \
+ /* pins 16-23 */ 0, \
+ /* pins 24-31 */ 0, \
+ /* pins 32-39 */ 0b00000000, \
+ /* pins 40-47 */ 0, \
+ /* pins 48-55 */ 0, \
+ /* pins 56-63 */ 0b00001100, \
+ /* pins 64-71 */ 0b11111100, \
+ /* pins 72-79 */ 0b00000011 \
+}
+
+
+// SAM3X Flash locations (may be expanded in the future)
+const uint32_t IAP_FLASH_START = 0x000F0000;
+const uint32_t IAP_FLASH_END = 0x000FFBFF; // don't touch the last 1KB, it's used for NvData
+
+// Timer allocation
+#define NETWORK_TC (TC1)
+#define NETWORK_TC_CHAN (1)
+#define NETWORK_TC_IRQN TC4_IRQn
+#define NETWORK_TC_HANDLER TC4_Handler
+
+#define STEP_TC (TC1)
+#define STEP_TC_CHAN (0)
+#define STEP_TC_IRQN TC3_IRQn
+#define STEP_TC_HANDLER TC3_Handler
+
+// Hardware SPI support for SD cards
+
+// SD select
+#define SD_SS 4
+#define SD_DETECT_PIN 14 // card detect switch; needs pullup asserted
+#define SD_DETECT_VAL 0 // detect switch active low
+#define SD_DETECT_PIO_ID ID_PIOD
+
+#ifdef LCD_UI
+
+// Hardware I2C support for LCD
+#define TWI_ID ID_TWI1
+
+#define FEATURE_CONTROLLER 7
+#define UI_PAGES_DURATION 4000
+#define UI_ANIMATION 0
+#define UI_SPEEDDEPENDENT_POSITIONING 0
+#define UI_DISABLE_AUTO_PAGESWITCH 1
+#define UI_AUTORETURN_TO_MENU_AFTER 30000
+#define UI_ENCODER_SPEED 1
+#define UI_KEY_BOUNCETIME 10
+#define UI_KEY_FIRST_REPEAT 500
+#define UI_KEY_REDUCE_REPEAT 50
+#define UI_KEY_MIN_REPEAT 50
+#define FEATURE_BEEPER 1
+#define UI_START_SCREEN_DELAY 1000
+
+#define CASE_LIGHTS_PIN -1
+#define SPI_PIN 77
+#define SPI_CHAN 0
+#define UI_HAS_KEYS 1
+#define UI_HAS_BACK_KEY 1
+#define UI_DISPLAY_TYPE 1
+#define UI_DISPLAY_CHARSET 1
+#define BEEPER_TYPE 1
+#define UI_COLS 20
+#define UI_ROWS 4
+#define BEEPER_PIN 41
+#define UI_DISPLAY_RS_PIN 42
+#define UI_DISPLAY_RW_PIN -1
+#define UI_DISPLAY_ENABLE_PIN 43
+#define UI_DISPLAY_D0_PIN 44
+#define UI_DISPLAY_D1_PIN 45
+#define UI_DISPLAY_D2_PIN 46
+#define UI_DISPLAY_D3_PIN 47
+#define UI_DISPLAY_D4_PIN 44
+#define UI_DISPLAY_D5_PIN 45
+#define UI_DISPLAY_D6_PIN 46
+#define UI_DISPLAY_D7_PIN 47
+#define UI_ENCODER_A 52
+#define UI_ENCODER_B 50
+#define UI_ENCODER_CLICK 48
+#define UI_RESET_PIN -1
+#define UI_DELAYPERCHAR 40
+#define UI_INVERT_MENU_DIRECTION 0
+#define UI_BUTTON_BACK 71
+
+// Beeper sound definitions for short beeps during key actions and longer
+// beeps for important actions. Parameters are the delay in microseconds
+// followed by the number of repetitions. Values must be in range 1..255
+#define BEEPER_SHORT_SEQUENCE 2,2
+#define BEEPER_LONG_SEQUENCE 8,8
+
+#endif // LCD_UI
+
+#endif
diff --git a/src/RADDS/Webserver.h b/src/RADDS/Webserver.h
new file mode 100644
index 00000000..0c8accb0
--- /dev/null
+++ b/src/RADDS/Webserver.h
@@ -0,0 +1,38 @@
+#ifndef WEBSERVER_H
+#define WEBSERVER_H
+
+#include <inttypes.h>
+#include "OutputMemory.h"
+
+// List of protocols that can execute G-Codes
+enum class WebSource
+{
+ HTTP,
+ Telnet
+};
+
+class Webserver
+{
+public:
+ Webserver(Platform* p, Network *n) { };
+ void Init() const { };
+ void Spin() const { };
+ void Exit() const { };
+ void Diagnostics(MessageType mtype) const { };
+
+ bool GCodeAvailable(const WebSource source) const { return false; }
+ char ReadGCode(const WebSource source) const { return '\0'; }
+ uint32_t GetReplySeq() const { return (uint32_t)0; }
+ uint16_t GetGCodeBufferSpace(const WebSource source) const { return 0; }
+
+ void HandleGCodeReply(const WebSource source, OutputBuffer *reply) const;
+ void HandleGCodeReply(const WebSource source, const char *reply) const { };
+};
+
+inline void Webserver::HandleGCodeReply(const WebSource source, OutputBuffer *reply) const
+{
+ if (reply != (OutputBuffer *)0)
+ OutputBuffer::ReleaseAll(reply);
+}
+
+#endif
diff --git a/src/Reprap.cpp b/src/Reprap.cpp
index d3cd3288..aa372a27 100644
--- a/src/Reprap.cpp
+++ b/src/Reprap.cpp
@@ -166,8 +166,8 @@ void RepRap::Spin()
// Keep track of the loop time
- float t = platform->Time();
- float dt = t - lastTime;
+ const float t = platform->Time();
+ const float dt = t - lastTime;
if(dt < fastLoop)
{
fastLoop = dt;
@@ -279,6 +279,7 @@ void RepRap::PrintDebug()
// Add a tool.
// Prior to calling this, delete any existing tool with the same number
+// The tool list is maintained in tool number order.
void RepRap::AddTool(Tool* tool)
{
Tool** t = &toolList;
@@ -401,9 +402,12 @@ Tool* RepRap::GetTool(int toolNumber) const
return nullptr; // Not an error
}
-Tool* RepRap::GetOnlyTool() const
+// Get the current tool, or failing that the default tool. May return nullptr if we can't
+// Called when a M104 or M109 command doesn't specify a tool number.
+Tool* RepRap::GetCurrentOrDefaultTool() const
{
- return (toolList != nullptr && toolList->Next() == nullptr) ? toolList : nullptr;
+ // If a tool is already selected, use that one, else use the lowest-numbered tool which is the one at the start of the tool list
+ return (currentTool != nullptr) ? currentTool : toolList;
}
void RepRap::SetToolVariables(int toolNumber, const float* standbyTemperatures, const float* activeTemperatures)
@@ -449,6 +453,16 @@ bool RepRap::IsHeaterAssignedToTool(int8_t heater) const
return false;
}
+unsigned int RepRap::GetNumberOfContiguousTools() const
+{
+ unsigned int numTools = 0;
+ while (GetTool(numTools) != nullptr)
+ {
+ ++numTools;
+ }
+ return numTools;
+}
+
void RepRap::Tick()
{
if (active)
@@ -561,7 +575,7 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source)
}
// Current tool number
- int toolNumber = (currentTool == nullptr) ? -1 : currentTool->Number();
+ const int toolNumber = (currentTool == nullptr) ? -1 : currentTool->Number();
response->catf("]},\"currentTool\":%d", toolNumber);
// Output - only reported once
@@ -633,7 +647,7 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source)
response->cat(",\"sensors\":{");
// Probe
- int v0 = platform->ZProbe();
+ const int v0 = platform->ZProbe();
int v1, v2;
switch (platform->GetZProbeSecondaryValues(v1, v2))
{
@@ -1117,7 +1131,7 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq)
const size_t numAxes = reprap.GetGCodes()->GetNumAxes();
float liveCoordinates[DRIVES];
reprap.GetMove()->LiveCoordinates(liveCoordinates, GetCurrentXAxes());
- const Tool* currentTool = reprap.GetCurrentTool();
+ const Tool* const currentTool = reprap.GetCurrentTool();
if (currentTool != nullptr)
{
const float *offset = currentTool->GetOffset();
@@ -1155,11 +1169,11 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq)
response->cat((ch == '[') ? "[]" : "]");
// Send the current tool number
- int toolNumber = (currentTool == nullptr) ? 0 : currentTool->Number();
+ const int toolNumber = (currentTool == nullptr) ? 0 : currentTool->Number();
response->catf(",\"tool\":%d", toolNumber);
// Send the Z probe value
- int v0 = platform->ZProbe();
+ const int v0 = platform->ZProbe();
int v1, v2;
switch (platform->GetZProbeSecondaryValues(v1, v2))
{
@@ -1218,24 +1232,24 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq)
}
else if (type == 3)
{
- // Add the static fields. For now this is just geometry and the machine name, but other fields could be added e.g. axis lengths.
- response->catf(",\"geometry\":\"%s\",\"axes\":%u,\"volumes\":%u,\"myName\":", move->GetGeometryString(), numAxes, NumSdCards);
+ // Add the static fields
+ response->catf(",\"geometry\":\"%s\",\"axes\":%u,\"volumes\":%u,\"numTools\":%u,\"myName\":",
+ move->GetGeometryString(), numAxes, NumSdCards, GetNumberOfContiguousTools());
response->EncodeString(myName, ARRAY_SIZE(myName), false);
}
- int auxSeq = (int)platform->GetAuxSeq();
- if (type < 2 || (seq != -1 && (int)auxSeq != seq))
+ const int auxSeq = (int)platform->GetAuxSeq();
+ if (type < 2 || (seq != -1 && auxSeq != seq))
{
// Send the response to the last command. Do this last because it can be long and may need to be truncated.
- response->catf(",\"seq\":%u,\"resp\":", auxSeq); // send the response sequence number
+ response->catf(",\"seq\":%d,\"resp\":", auxSeq); // send the response sequence number
// Send the JSON response
response->EncodeReply(platform->GetAuxGCodeReply(), true); // also releases the OutputBuffer chain
}
response->cat("}");
-
return response;
}
diff --git a/src/Reprap.h b/src/Reprap.h
index f7408bdd..8b80d527 100644
--- a/src/Reprap.h
+++ b/src/Reprap.h
@@ -58,12 +58,12 @@ public:
void StandbyTool(int toolNumber);
Tool* GetCurrentTool() const;
Tool* GetTool(int toolNumber) const;
- Tool* GetOnlyTool() const;
+ Tool* GetCurrentOrDefaultTool() const;
uint32_t GetCurrentXAxes() const; // Get the current axes used as X axes
- //Tool* GetToolByDrive(int driveNumber);
void SetToolVariables(int toolNumber, const float* standbyTemperatures, const float* activeTemperatures);
bool ToolWarningsAllowed();
bool IsHeaterAssignedToTool(int8_t heater) const;
+ unsigned int GetNumberOfContiguousTools() const;
unsigned int GetProhibitedExtruderMovements(unsigned int extrusions, unsigned int retractions);
void PrintTool(int toolNumber, StringRef& reply) const;
diff --git a/src/Storage/FileStore.cpp b/src/Storage/FileStore.cpp
index e62c8f1d..680032f8 100644
--- a/src/Storage/FileStore.cpp
+++ b/src/Storage/FileStore.cpp
@@ -265,6 +265,40 @@ int FileStore::Read(char* extBuf, size_t nBytes)
return (int)bytes_read;
}
+// As Read but stop after '\n' or '\r\n' and null-terminate the string.
+// If the next line is too long to fit in the buffer then the line will be split.
+int FileStore::ReadLine(char* buf, size_t nBytes)
+{
+ const FilePosition lineStart = Position();
+ const int r = Read(buf, nBytes);
+ if (r < 0)
+ {
+ return r;
+ }
+
+ int i = 0;
+ while (i < r && buf[i] != '\r' && buf[i] != '\n')
+ {
+ ++i;
+ }
+
+ if (i + 1 < r && buf[i] == '\r' && buf[i + 1] == '\n') // if stopped at CRLF (Windows-style line end)
+ {
+ Seek(lineStart + i + 2); // seek to just after the CRLF
+ }
+ else if (i < r) // if stopped at CR or LF
+ {
+ Seek(lineStart + i + 1); // seek to just after the CR or LF
+ }
+ else if (i == (int)nBytes)
+ {
+ --i; // make room for the null terminator
+ Seek(lineStart + i);
+ }
+ buf[i] = 0;
+ return i;
+}
+
bool FileStore::WriteBuffer()
{
if (bufferPointer != 0)
diff --git a/src/Storage/FileStore.h b/src/Storage/FileStore.h
index 23e4e97d..e998e3e8 100644
--- a/src/Storage/FileStore.h
+++ b/src/Storage/FileStore.h
@@ -28,6 +28,7 @@ public:
uint8_t Status(); // Returns OR of IOStatus
bool Read(char& b); // Read 1 byte
int Read(char* buf, size_t nBytes); // Read a block of nBytes length
+ int ReadLine(char* buf, size_t nBytes); // As Read but stop after '\n' or '\r\n' and null-terminate
bool Write(char b); // Write 1 byte
bool Write(const char *s, size_t len); // Write a block of len bytes
bool Write(const char* s); // Write a string